deploy.sh 1.0.0 → 3.0.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.
- package/.claude/settings.local.json +36 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +105 -0
- package/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +56 -0
- package/.github/workflows/ci.yml +29 -0
- package/.github/workflows/pages.yml +48 -0
- package/.oxfmtrc.json +7 -0
- package/.oxlintrc.json +11 -0
- package/LICENSE +183 -183
- package/README.md +99 -11
- package/app/actions/deployments.ts +82 -0
- package/app/actions/metrics.ts +13 -0
- package/app/root.tsx +60 -0
- package/app/routes/dashboard/detail/history.tsx +73 -0
- package/app/routes/dashboard/detail/layout.tsx +125 -0
- package/app/routes/dashboard/detail/logs.tsx +85 -0
- package/app/routes/dashboard/detail/overview.tsx +119 -0
- package/app/routes/dashboard/detail/requests.tsx +163 -0
- package/app/routes/dashboard/detail/resources.tsx +268 -0
- package/app/routes/dashboard/detail/shared.tsx +59 -0
- package/app/routes/dashboard/index.tsx +360 -0
- package/app/routes/dashboard/layout.tsx +30 -0
- package/app/routes/docs/architecture.tsx +155 -0
- package/app/routes/docs/cli.tsx +122 -0
- package/app/routes/docs/deploying.tsx +105 -0
- package/app/routes/docs/index.tsx +104 -0
- package/app/routes/docs/layout.tsx +58 -0
- package/app/routes/home.tsx +134 -0
- package/app/routes/root.client.tsx +46 -0
- package/app/routes.ts +21 -0
- package/app/styles.css +15 -0
- package/app/theme.css +134 -0
- package/bin/deploy.js +360 -110
- package/docs-site/404.html +33 -0
- package/docs-site/home.tsx +130 -0
- package/docs-site/index.html +35 -0
- package/docs-site/layout.tsx +57 -0
- package/docs-site/main.tsx +41 -0
- package/docs-site/shell.tsx +34 -0
- package/docs-site/styles.css +4 -0
- package/drizzle.config.js +8 -0
- package/examples/docker/Dockerfile +5 -0
- package/examples/docker/server.js +18 -0
- package/examples/node/package.json +7 -0
- package/examples/node/pnpm-lock.yaml +9 -0
- package/examples/node/server.js +12 -0
- package/examples/static/index.html +48 -0
- package/package.json +41 -55
- package/public/favicon.ico +0 -0
- package/react-router-vite/entry.browser.tsx +49 -0
- package/react-router-vite/entry.rsc.single.tsx +7 -0
- package/react-router-vite/entry.rsc.tsx +36 -0
- package/react-router-vite/entry.ssr.tsx +29 -0
- package/react-router-vite/plugin.ts +114 -0
- package/react-router-vite/types.d.ts +11 -0
- package/react-router.config.ts +5 -0
- package/server/api.test.ts +344 -0
- package/server/api.ts +445 -0
- package/server/docker.ts +268 -0
- package/server/index.ts +17 -0
- package/server/metrics-collector.ts +29 -0
- package/server/schema.ts +56 -0
- package/server/store.test.ts +278 -0
- package/server/store.ts +398 -0
- package/tsconfig.json +21 -0
- package/vite.config.ts +45 -0
- package/vite.docs.config.ts +31 -0
- package/.eslintignore +0 -5
- package/.eslintrc +0 -23
- package/.travis.yml +0 -9
- package/.tryitout +0 -48
- package/CHANGELOG.md +0 -56
- package/bin/deploy-delete.js +0 -11
- package/bin/deploy-deploy.js +0 -31
- package/bin/deploy-list.js +0 -32
- package/bin/deploy-login.js +0 -39
- package/bin/deploy-logout.js +0 -12
- package/bin/deploy-logs.js +0 -19
- package/bin/deploy-open.js +0 -19
- package/bin/deploy-register.js +0 -40
- package/bin/deploy-server.js +0 -5
- package/bin/deploy-whoami.js +0 -12
- package/docs/code/CLI.html +0 -2901
- package/docs/code/Deployment.html +0 -2469
- package/docs/code/Request.html +0 -906
- package/docs/code/User.html +0 -1219
- package/docs/code/classifier.js.html +0 -121
- package/docs/code/deploy.js.html +0 -122
- package/docs/code/fonts/OpenSans-Bold-webfont.eot +0 -0
- package/docs/code/fonts/OpenSans-Bold-webfont.svg +0 -1830
- package/docs/code/fonts/OpenSans-Bold-webfont.woff +0 -0
- package/docs/code/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
- package/docs/code/fonts/OpenSans-BoldItalic-webfont.svg +0 -1830
- package/docs/code/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
- package/docs/code/fonts/OpenSans-Italic-webfont.eot +0 -0
- package/docs/code/fonts/OpenSans-Italic-webfont.svg +0 -1830
- package/docs/code/fonts/OpenSans-Italic-webfont.woff +0 -0
- package/docs/code/fonts/OpenSans-Light-webfont.eot +0 -0
- package/docs/code/fonts/OpenSans-Light-webfont.svg +0 -1831
- package/docs/code/fonts/OpenSans-Light-webfont.woff +0 -0
- package/docs/code/fonts/OpenSans-LightItalic-webfont.eot +0 -0
- package/docs/code/fonts/OpenSans-LightItalic-webfont.svg +0 -1835
- package/docs/code/fonts/OpenSans-LightItalic-webfont.woff +0 -0
- package/docs/code/fonts/OpenSans-Regular-webfont.eot +0 -0
- package/docs/code/fonts/OpenSans-Regular-webfont.svg +0 -1831
- package/docs/code/fonts/OpenSans-Regular-webfont.woff +0 -0
- package/docs/code/fonts/OpenSans-Semibold-webfont.eot +0 -0
- package/docs/code/fonts/OpenSans-Semibold-webfont.svg +0 -1830
- package/docs/code/fonts/OpenSans-Semibold-webfont.ttf +0 -0
- package/docs/code/fonts/OpenSans-Semibold-webfont.woff +0 -0
- package/docs/code/fonts/OpenSans-SemiboldItalic-webfont.eot +0 -0
- package/docs/code/fonts/OpenSans-SemiboldItalic-webfont.svg +0 -1830
- package/docs/code/fonts/OpenSans-SemiboldItalic-webfont.ttf +0 -0
- package/docs/code/fonts/OpenSans-SemiboldItalic-webfont.woff +0 -0
- package/docs/code/helpers_cli.js.html +0 -315
- package/docs/code/helpers_util.js.html +0 -194
- package/docs/code/index.html +0 -66
- package/docs/code/models_deployment.js.html +0 -515
- package/docs/code/models_request.js.html +0 -158
- package/docs/code/models_user.js.html +0 -198
- package/docs/code/module-lib_classifier.html +0 -246
- package/docs/code/module-lib_deploy.html +0 -350
- package/docs/code/module-lib_helpers_util.html +0 -707
- package/docs/code/scripts/linenumber.js +0 -25
- package/docs/code/scripts/prettify/Apache-License-2.0.txt +0 -202
- package/docs/code/scripts/prettify/lang-css.js +0 -2
- package/docs/code/scripts/prettify/prettify.js +0 -28
- package/docs/code/styles/jsdoc-default.css +0 -692
- package/docs/code/styles/prettify-jsdoc.css +0 -111
- package/docs/code/styles/prettify-tomorrow.css +0 -132
- package/docs/example.gif +0 -0
- package/docs/example.mov +0 -0
- package/docs/index.html +0 -4463
- package/docs/logo.png +0 -0
- package/docs/logo.pxm +0 -0
- package/docs/logo@2x.png +0 -0
- package/docs/main.css +0 -162
- package/docs/main.js +0 -53
- package/index.js +0 -55
- package/jsdoc.json +0 -27
- package/lib/classifier.js +0 -61
- package/lib/deploy.js +0 -62
- package/lib/helpers/cli.js +0 -255
- package/lib/helpers/util.js +0 -134
- package/lib/models/deployment.js +0 -455
- package/lib/models/request.js +0 -98
- package/lib/models/user.js +0 -138
- package/lib/server.js +0 -165
- package/lib/static/not-found.html +0 -30
- package/lib/static/page-could-not-load.html +0 -30
- package/lib/static/static-server.js +0 -69
- package/test/fixtures/docker/Dockerfile +0 -5
- package/test/fixtures/docker/index.js +0 -12
- package/test/fixtures/node/index.js +0 -8
- package/test/fixtures/node/package.json +0 -15
- package/test/fixtures/static/index.html +0 -14
- package/test/fixtures/static/main.css +0 -7
- package/test/fixtures/static/out.gifcd +0 -0
- package/test/fixtures/unknown/.gitkeep +0 -0
- package/test/lib/classifier.js +0 -51
- package/test/lib/helpers/util.js +0 -47
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<meta name="description" content="deploy.sh documentation — self-hosted deployment platform." />
|
|
7
|
+
<link rel="icon" href="/favicon.ico" />
|
|
8
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
9
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
10
|
+
<link
|
|
11
|
+
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"
|
|
12
|
+
rel="stylesheet"
|
|
13
|
+
/>
|
|
14
|
+
<title>deploy.sh docs</title>
|
|
15
|
+
<!-- GitHub Pages SPA redirect handler -->
|
|
16
|
+
<script>
|
|
17
|
+
(function (l) {
|
|
18
|
+
if (l.search[1] === '/') {
|
|
19
|
+
var decoded = l.search
|
|
20
|
+
.slice(1)
|
|
21
|
+
.split('&')
|
|
22
|
+
.map(function (s) {
|
|
23
|
+
return s.replace(/~and~/g, '&');
|
|
24
|
+
})
|
|
25
|
+
.join('?');
|
|
26
|
+
window.history.replaceState(null, null, l.pathname.slice(0, -1) + decoded + l.hash);
|
|
27
|
+
}
|
|
28
|
+
})(window.location);
|
|
29
|
+
</script>
|
|
30
|
+
</head>
|
|
31
|
+
<body>
|
|
32
|
+
<div id="root"></div>
|
|
33
|
+
<script type="module" src="./main.tsx"></script>
|
|
34
|
+
</body>
|
|
35
|
+
</html>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Link, Outlet, useLocation } from 'react-router';
|
|
2
|
+
|
|
3
|
+
const NAV = [
|
|
4
|
+
{
|
|
5
|
+
heading: 'Getting Started',
|
|
6
|
+
links: [
|
|
7
|
+
{ to: '/docs', label: 'Introduction' },
|
|
8
|
+
{ to: '/docs/deploying', label: 'Deploying Apps' },
|
|
9
|
+
],
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
heading: 'Reference',
|
|
13
|
+
links: [
|
|
14
|
+
{ to: '/docs/cli', label: 'CLI' },
|
|
15
|
+
{ to: '/docs/architecture', label: 'Architecture' },
|
|
16
|
+
],
|
|
17
|
+
},
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
export default function DocsLayout() {
|
|
21
|
+
const { pathname } = useLocation();
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div className="max-w-7xl mx-auto px-6 flex gap-10 py-8">
|
|
25
|
+
<aside className="w-48 shrink-0 max-md:hidden">
|
|
26
|
+
<nav className="sticky top-8">
|
|
27
|
+
{NAV.map((section) => (
|
|
28
|
+
<div key={section.heading}>
|
|
29
|
+
<p className="text-xs font-semibold text-text-tertiary uppercase tracking-wider mb-3">
|
|
30
|
+
{section.heading}
|
|
31
|
+
</p>
|
|
32
|
+
<ul className="flex flex-col gap-1 mb-6 list-none p-0">
|
|
33
|
+
{section.links.map((link) => (
|
|
34
|
+
<li key={link.to}>
|
|
35
|
+
<Link
|
|
36
|
+
to={link.to}
|
|
37
|
+
className={`block text-sm px-2 py-1 rounded-md transition-colors no-underline ${
|
|
38
|
+
pathname === link.to
|
|
39
|
+
? 'text-text bg-bg-hover'
|
|
40
|
+
: 'text-text-secondary hover:text-text hover:bg-bg-hover'
|
|
41
|
+
}`}
|
|
42
|
+
>
|
|
43
|
+
{link.label}
|
|
44
|
+
</Link>
|
|
45
|
+
</li>
|
|
46
|
+
))}
|
|
47
|
+
</ul>
|
|
48
|
+
</div>
|
|
49
|
+
))}
|
|
50
|
+
</nav>
|
|
51
|
+
</aside>
|
|
52
|
+
<main className="flex-1 min-w-0">
|
|
53
|
+
<Outlet />
|
|
54
|
+
</main>
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { StrictMode } from 'react';
|
|
2
|
+
import { createRoot } from 'react-dom/client';
|
|
3
|
+
import { createBrowserRouter, Navigate, RouterProvider } from 'react-router';
|
|
4
|
+
import './styles.css';
|
|
5
|
+
|
|
6
|
+
import Shell from './shell';
|
|
7
|
+
import Home from './home';
|
|
8
|
+
import DocsLayout from './layout';
|
|
9
|
+
import GettingStarted from '../app/routes/docs/index';
|
|
10
|
+
import Deploying from '../app/routes/docs/deploying';
|
|
11
|
+
import Cli from '../app/routes/docs/cli';
|
|
12
|
+
import Architecture from '../app/routes/docs/architecture';
|
|
13
|
+
|
|
14
|
+
const router = createBrowserRouter(
|
|
15
|
+
[
|
|
16
|
+
{
|
|
17
|
+
Component: Shell,
|
|
18
|
+
children: [
|
|
19
|
+
{ index: true, Component: Home },
|
|
20
|
+
{
|
|
21
|
+
path: 'docs',
|
|
22
|
+
Component: DocsLayout,
|
|
23
|
+
children: [
|
|
24
|
+
{ index: true, Component: GettingStarted },
|
|
25
|
+
{ path: 'deploying', Component: Deploying },
|
|
26
|
+
{ path: 'cli', Component: Cli },
|
|
27
|
+
{ path: 'architecture', Component: Architecture },
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
{ path: '*', element: <Navigate to="/" replace /> },
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
{ basename: '/deploy.sh' },
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
createRoot(document.getElementById('root')!).render(
|
|
38
|
+
<StrictMode>
|
|
39
|
+
<RouterProvider router={router} />
|
|
40
|
+
</StrictMode>,
|
|
41
|
+
);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Link, Outlet } from 'react-router';
|
|
2
|
+
|
|
3
|
+
export default function Shell() {
|
|
4
|
+
return (
|
|
5
|
+
<>
|
|
6
|
+
<header className="border-b border-border">
|
|
7
|
+
<div className="max-w-7xl mx-auto px-6 h-14 flex items-center justify-between">
|
|
8
|
+
<div className="flex items-center gap-8">
|
|
9
|
+
<Link to="/" className="text-sm font-semibold tracking-tight text-text no-underline">
|
|
10
|
+
deploy.sh
|
|
11
|
+
</Link>
|
|
12
|
+
<nav className="flex items-center gap-6">
|
|
13
|
+
<Link
|
|
14
|
+
to="/docs"
|
|
15
|
+
className="text-sm text-text-secondary hover:text-text transition-colors no-underline"
|
|
16
|
+
>
|
|
17
|
+
Docs
|
|
18
|
+
</Link>
|
|
19
|
+
<a
|
|
20
|
+
href="https://github.com/gabrielcsapo/deploy.sh"
|
|
21
|
+
className="text-sm text-text-secondary hover:text-text transition-colors no-underline"
|
|
22
|
+
target="_blank"
|
|
23
|
+
rel="noopener noreferrer"
|
|
24
|
+
>
|
|
25
|
+
GitHub
|
|
26
|
+
</a>
|
|
27
|
+
</nav>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</header>
|
|
31
|
+
<Outlet />
|
|
32
|
+
</>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
|
|
3
|
+
const port = process.env.PORT || 3000;
|
|
4
|
+
|
|
5
|
+
const server = createServer((req, res) => {
|
|
6
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
7
|
+
res.end(
|
|
8
|
+
JSON.stringify({
|
|
9
|
+
message: 'Hello from a Docker container!',
|
|
10
|
+
hostname: process.env.HOSTNAME,
|
|
11
|
+
uptime: process.uptime(),
|
|
12
|
+
}),
|
|
13
|
+
);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
server.listen(port, () => {
|
|
17
|
+
console.log(`Listening on port ${port}`);
|
|
18
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
|
|
3
|
+
const port = process.env.PORT || 3000;
|
|
4
|
+
|
|
5
|
+
const server = createServer((req, res) => {
|
|
6
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
7
|
+
res.end(JSON.stringify({ message: 'Hello from deploy.sh!', uptime: process.uptime() }));
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
server.listen(port, () => {
|
|
11
|
+
console.log(`Listening on port ${port}`);
|
|
12
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>Static Site Example</title>
|
|
7
|
+
<style>
|
|
8
|
+
* {
|
|
9
|
+
margin: 0;
|
|
10
|
+
padding: 0;
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
}
|
|
13
|
+
body {
|
|
14
|
+
font-family:
|
|
15
|
+
system-ui,
|
|
16
|
+
-apple-system,
|
|
17
|
+
sans-serif;
|
|
18
|
+
display: flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
justify-content: center;
|
|
21
|
+
min-height: 100vh;
|
|
22
|
+
background: #13111c;
|
|
23
|
+
color: #fafafa;
|
|
24
|
+
}
|
|
25
|
+
.card {
|
|
26
|
+
text-align: center;
|
|
27
|
+
padding: 3rem;
|
|
28
|
+
border: 1px solid #2a2540;
|
|
29
|
+
border-radius: 12px;
|
|
30
|
+
background: #1a1726;
|
|
31
|
+
}
|
|
32
|
+
h1 {
|
|
33
|
+
font-size: 1.5rem;
|
|
34
|
+
margin-bottom: 0.5rem;
|
|
35
|
+
}
|
|
36
|
+
p {
|
|
37
|
+
color: #8b82a8;
|
|
38
|
+
font-size: 0.875rem;
|
|
39
|
+
}
|
|
40
|
+
</style>
|
|
41
|
+
</head>
|
|
42
|
+
<body>
|
|
43
|
+
<div class="card">
|
|
44
|
+
<h1>Hello from deploy.sh</h1>
|
|
45
|
+
<p>This is a static site deployed with deploy.sh.</p>
|
|
46
|
+
</div>
|
|
47
|
+
</body>
|
|
48
|
+
</html>
|
package/package.json
CHANGED
|
@@ -1,66 +1,52 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deploy.sh",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "☁️ open source continuous deployment service",
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"test": "tap test/index.js test/lib",
|
|
8
|
-
"coverage": "tap test/index.js test/lib --coverage --coverage-report=lcov",
|
|
9
|
-
"lint": "eslint .",
|
|
10
|
-
"start": "./bin/deploy serve",
|
|
11
|
-
"pack": "pkg bin/deploy.js -c package.json -o packed/deploy",
|
|
12
|
-
"generate-docs": "tryitout && jsdoc -c jsdoc.json"
|
|
13
|
-
},
|
|
14
|
-
"pkg": {
|
|
15
|
-
"scripts": [
|
|
16
|
-
"bin/*",
|
|
17
|
-
"lib/**/*.js"
|
|
18
|
-
],
|
|
19
|
-
"assets": [
|
|
20
|
-
"lib/static/*"
|
|
21
|
-
],
|
|
22
|
-
"targets": [
|
|
23
|
-
"node8-alpine-x64",
|
|
24
|
-
"node8-linux-x64",
|
|
25
|
-
"node8-macos-x64",
|
|
26
|
-
"node8-win-x64"
|
|
27
|
-
]
|
|
28
|
-
},
|
|
29
|
-
"repository": {
|
|
30
|
-
"type": "git",
|
|
31
|
-
"url": "git+https://github.com/gabrielcsapo/deploy.sh.git"
|
|
32
|
-
},
|
|
3
|
+
"version": "3.0.0",
|
|
33
4
|
"bin": {
|
|
34
5
|
"deploy": "./bin/deploy.js"
|
|
35
6
|
},
|
|
36
|
-
"
|
|
37
|
-
|
|
7
|
+
"type": "module",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"dev": "vite",
|
|
10
|
+
"server": "node server/index.ts",
|
|
11
|
+
"build": "vite build",
|
|
12
|
+
"preview": "vite preview",
|
|
13
|
+
"lint": "oxlint .",
|
|
14
|
+
"format": "oxfmt --write .",
|
|
15
|
+
"format:check": "oxfmt --check .",
|
|
16
|
+
"test": "node --test server/**/*.test.ts",
|
|
17
|
+
"typecheck": "tsc --noEmit",
|
|
18
|
+
"dev:docs": "vite --config vite.docs.config.ts",
|
|
19
|
+
"build:docs": "vite build --config vite.docs.config.ts",
|
|
20
|
+
"preview:docs": "vite preview --config vite.docs.config.ts"
|
|
38
21
|
},
|
|
39
|
-
"homepage": "https://www.gabrielcsapo.com/deploy.sh",
|
|
40
22
|
"dependencies": {
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"mongoose": "^4.11.6",
|
|
49
|
-
"opn": "^5.1.0",
|
|
50
|
-
"ora": "^1.3.0",
|
|
51
|
-
"request": "^2.81.0",
|
|
52
|
-
"tar": "^4.3.0",
|
|
53
|
-
"turtler": "^1.0.2",
|
|
54
|
-
"update-notifier": "^2.2.0",
|
|
55
|
-
"woof": "^0.2.1"
|
|
23
|
+
"@react-router/dev": "7.13.0",
|
|
24
|
+
"better-sqlite3": "^12.6.2",
|
|
25
|
+
"drizzle-orm": "^0.45.1",
|
|
26
|
+
"react": "^19.2.4",
|
|
27
|
+
"react-dom": "^19.2.4",
|
|
28
|
+
"react-router": "7.13.0",
|
|
29
|
+
"vite": "^7.3.1"
|
|
56
30
|
},
|
|
57
31
|
"devDependencies": {
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
32
|
+
"@tailwindcss/typography": "^0.5.19",
|
|
33
|
+
"@tailwindcss/vite": "^4.1.18",
|
|
34
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
35
|
+
"@types/node": "^25.2.2",
|
|
36
|
+
"@types/react": "^19.2.13",
|
|
37
|
+
"@types/react-dom": "^19.2.3",
|
|
38
|
+
"@vitejs/plugin-react": "5.1.3",
|
|
39
|
+
"@vitejs/plugin-rsc": "0.5.19",
|
|
40
|
+
"drizzle-kit": "^0.31.8",
|
|
41
|
+
"oxfmt": "0.28.0",
|
|
42
|
+
"oxlint": "1.43.0",
|
|
43
|
+
"tailwindcss": "^4.1.18",
|
|
44
|
+
"typescript": "^5.9.3"
|
|
45
|
+
},
|
|
46
|
+
"pnpm": {
|
|
47
|
+
"onlyBuiltDependencies": [
|
|
48
|
+
"better-sqlite3",
|
|
49
|
+
"esbuild"
|
|
50
|
+
]
|
|
65
51
|
}
|
|
66
52
|
}
|
|
Binary file
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createFromReadableStream,
|
|
3
|
+
createTemporaryReferenceSet,
|
|
4
|
+
encodeReply,
|
|
5
|
+
setServerCallback,
|
|
6
|
+
} from '@vitejs/plugin-rsc/browser';
|
|
7
|
+
import { startTransition, StrictMode } from 'react';
|
|
8
|
+
import { hydrateRoot } from 'react-dom/client';
|
|
9
|
+
import { type DataRouter, type unstable_RSCPayload as RSCServerPayload } from 'react-router';
|
|
10
|
+
import {
|
|
11
|
+
unstable_createCallServer as createCallServer,
|
|
12
|
+
unstable_getRSCStream as getRSCStream,
|
|
13
|
+
unstable_RSCHydratedRouter as RSCHydratedRouter,
|
|
14
|
+
} from 'react-router/dom';
|
|
15
|
+
|
|
16
|
+
// Create and set the callServer function to support post-hydration server actions.
|
|
17
|
+
setServerCallback(
|
|
18
|
+
createCallServer({
|
|
19
|
+
createFromReadableStream,
|
|
20
|
+
createTemporaryReferenceSet,
|
|
21
|
+
encodeReply,
|
|
22
|
+
}),
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
// Get and decode the initial server payload
|
|
26
|
+
createFromReadableStream<RSCServerPayload>(getRSCStream()).then((payload) => {
|
|
27
|
+
startTransition(async () => {
|
|
28
|
+
const formState = payload.type === 'render' ? await payload.formState : undefined;
|
|
29
|
+
|
|
30
|
+
hydrateRoot(
|
|
31
|
+
document,
|
|
32
|
+
<StrictMode>
|
|
33
|
+
<RSCHydratedRouter createFromReadableStream={createFromReadableStream} payload={payload} />
|
|
34
|
+
</StrictMode>,
|
|
35
|
+
{
|
|
36
|
+
// @ts-expect-error - no types for this yet
|
|
37
|
+
formState,
|
|
38
|
+
},
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
declare let __reactRouterDataRouter: DataRouter;
|
|
44
|
+
|
|
45
|
+
if (import.meta.hot) {
|
|
46
|
+
import.meta.hot.on('rsc:update', () => {
|
|
47
|
+
__reactRouterDataRouter.revalidate();
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createTemporaryReferenceSet,
|
|
3
|
+
decodeAction,
|
|
4
|
+
decodeFormState,
|
|
5
|
+
decodeReply,
|
|
6
|
+
loadServerAction,
|
|
7
|
+
renderToReadableStream,
|
|
8
|
+
} from '@vitejs/plugin-rsc/rsc';
|
|
9
|
+
import { unstable_matchRSCServerRequest as matchRSCServerRequest } from 'react-router';
|
|
10
|
+
import routes from 'virtual:react-router-routes';
|
|
11
|
+
|
|
12
|
+
export function fetchServer(request: Request) {
|
|
13
|
+
return matchRSCServerRequest({
|
|
14
|
+
// Provide the React Server touchpoints.
|
|
15
|
+
createTemporaryReferenceSet,
|
|
16
|
+
decodeAction,
|
|
17
|
+
decodeFormState,
|
|
18
|
+
decodeReply,
|
|
19
|
+
loadServerAction,
|
|
20
|
+
// The incoming request.
|
|
21
|
+
request,
|
|
22
|
+
// The app routes.
|
|
23
|
+
routes,
|
|
24
|
+
// Encode the match with the React Server implementation.
|
|
25
|
+
generateResponse(match, options) {
|
|
26
|
+
return new Response(renderToReadableStream(match.payload, options), {
|
|
27
|
+
status: match.statusCode,
|
|
28
|
+
headers: match.headers,
|
|
29
|
+
});
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (import.meta.hot) {
|
|
35
|
+
import.meta.hot.accept();
|
|
36
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createFromReadableStream } from '@vitejs/plugin-rsc/ssr';
|
|
2
|
+
import { renderToReadableStream as renderHTMLToReadableStream } from 'react-dom/server.edge';
|
|
3
|
+
import {
|
|
4
|
+
unstable_routeRSCServerRequest as routeRSCServerRequest,
|
|
5
|
+
unstable_RSCStaticRouter as RSCStaticRouter,
|
|
6
|
+
} from 'react-router';
|
|
7
|
+
|
|
8
|
+
export default async function handler(
|
|
9
|
+
request: Request,
|
|
10
|
+
serverResponse: Response,
|
|
11
|
+
): Promise<Response> {
|
|
12
|
+
const bootstrapScriptContent = await import.meta.viteRsc.loadBootstrapScriptContent('index');
|
|
13
|
+
|
|
14
|
+
return await routeRSCServerRequest({
|
|
15
|
+
request,
|
|
16
|
+
serverResponse,
|
|
17
|
+
createFromReadableStream,
|
|
18
|
+
async renderHTML(getPayload, options) {
|
|
19
|
+
const payload = getPayload();
|
|
20
|
+
|
|
21
|
+
return await renderHTMLToReadableStream(<RSCStaticRouter getPayload={getPayload} />, {
|
|
22
|
+
...options,
|
|
23
|
+
bootstrapScriptContent,
|
|
24
|
+
signal: request.signal,
|
|
25
|
+
formState: await payload.formState,
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import type { Config } from '@react-router/dev/config';
|
|
4
|
+
import type { RouteConfigEntry } from '@react-router/dev/routes';
|
|
5
|
+
import { type Plugin, createIdResolver, runnerImport } from 'vite';
|
|
6
|
+
|
|
7
|
+
export function reactRouter(): Plugin[] {
|
|
8
|
+
let idResolver: ReturnType<typeof createIdResolver>;
|
|
9
|
+
|
|
10
|
+
return [
|
|
11
|
+
{
|
|
12
|
+
name: 'deploy:api',
|
|
13
|
+
async configureServer(server) {
|
|
14
|
+
const { apiMiddleware } = await import(/* @vite-ignore */ path.resolve('server/api.ts'));
|
|
15
|
+
server.middlewares.use(apiMiddleware());
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: 'react-router:config',
|
|
20
|
+
configResolved(config) {
|
|
21
|
+
idResolver = createIdResolver(config);
|
|
22
|
+
},
|
|
23
|
+
resolveId(source) {
|
|
24
|
+
if (source === 'virtual:react-router-routes') {
|
|
25
|
+
return '\0' + source;
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
async load(id) {
|
|
29
|
+
if (id === '\0virtual:react-router-routes') {
|
|
30
|
+
const findFile = (id: string) => idResolver(this.environment, id);
|
|
31
|
+
const config = await readReactRouterConfig(findFile);
|
|
32
|
+
this.addWatchFile(config.configFile);
|
|
33
|
+
this.addWatchFile(config.routesFile);
|
|
34
|
+
const code = generateRoutesCode(config);
|
|
35
|
+
return code;
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function readReactRouterConfig(findFile: (id: string) => Promise<string | undefined>) {
|
|
43
|
+
// find react-router.config.ts
|
|
44
|
+
const configFile = await findFile('./react-router.config');
|
|
45
|
+
assert(configFile, "Cannot find 'react-router.config' file");
|
|
46
|
+
const configImport = await runnerImport<{ default: Config }>(configFile!);
|
|
47
|
+
const appDirectory = path.resolve(configImport.module.default.appDirectory ?? 'app');
|
|
48
|
+
|
|
49
|
+
// find routes.ts
|
|
50
|
+
const routesFile = await findFile(path.join(appDirectory, 'routes'));
|
|
51
|
+
assert(routesFile, "Cannot find 'routes' file");
|
|
52
|
+
const routesImport = await runnerImport<{
|
|
53
|
+
default: RouteConfigEntry[];
|
|
54
|
+
}>(routesFile!);
|
|
55
|
+
|
|
56
|
+
// find root.tsx
|
|
57
|
+
const rootFile = await findFile(path.join(appDirectory, 'root'));
|
|
58
|
+
assert(rootFile, "Cannot find 'root' file");
|
|
59
|
+
|
|
60
|
+
const routes: RouteConfigEntry[] = [
|
|
61
|
+
{
|
|
62
|
+
id: 'root',
|
|
63
|
+
path: '',
|
|
64
|
+
file: rootFile!,
|
|
65
|
+
children: routesImport.module.default,
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
return { configFile, routesFile, appDirectory, routes };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// copied from
|
|
73
|
+
// https://github.com/jacob-ebey/parcel-plugin-react-router/blob/9385be813534537dfb0fe640a3e5c5607be3b61d/packages/resolver/src/resolver.ts
|
|
74
|
+
|
|
75
|
+
function generateRoutesCode(config: { appDirectory: string; routes: RouteConfigEntry[] }) {
|
|
76
|
+
let code = 'export default [';
|
|
77
|
+
const closeRouteSymbol = Symbol('CLOSE_ROUTE');
|
|
78
|
+
let stack: Array<typeof closeRouteSymbol | RouteConfigEntry> = [...config.routes];
|
|
79
|
+
while (stack.length > 0) {
|
|
80
|
+
const route = stack.pop();
|
|
81
|
+
if (!route) break;
|
|
82
|
+
if (route === closeRouteSymbol) {
|
|
83
|
+
code += ']},';
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
code += '{';
|
|
87
|
+
// TODO: route-module transform
|
|
88
|
+
code += `lazy: () => import(${JSON.stringify(path.resolve(config.appDirectory, route.file))}),`;
|
|
89
|
+
code += `id: ${JSON.stringify(route.id || createRouteId(route.file, config.appDirectory))},`;
|
|
90
|
+
if (typeof route.path === 'string') {
|
|
91
|
+
code += `path: ${JSON.stringify(route.path)},`;
|
|
92
|
+
}
|
|
93
|
+
if (route.index) {
|
|
94
|
+
code += `index: true,`;
|
|
95
|
+
}
|
|
96
|
+
if (route.caseSensitive) {
|
|
97
|
+
code += `caseSensitive: true,`;
|
|
98
|
+
}
|
|
99
|
+
if (route.children) {
|
|
100
|
+
code += ['children:['];
|
|
101
|
+
stack.push(closeRouteSymbol);
|
|
102
|
+
stack.push(...[...route.children].reverse());
|
|
103
|
+
} else {
|
|
104
|
+
code += '},';
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
code += '];\n';
|
|
108
|
+
|
|
109
|
+
return code;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function createRouteId(file: string, appDirectory: string) {
|
|
113
|
+
return path.relative(appDirectory, file).replace(/\\+/, '/').slice(0, -path.extname(file).length);
|
|
114
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
2
|
+
/// <reference types="@vitejs/plugin-rsc/types" />
|
|
3
|
+
|
|
4
|
+
declare module 'react-dom/server.edge' {
|
|
5
|
+
export * from 'react-dom/server';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
declare module 'virtual:react-router-routes' {
|
|
9
|
+
const routes: any;
|
|
10
|
+
export default routes;
|
|
11
|
+
}
|