hono-svelte 0.4.0 → 0.4.2
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/CHANGELOG.md +20 -0
- package/README.md +14 -1
- package/dist/cli.js +19 -3
- package/dist/index.d.ts +1 -1
- package/dist/scaffold.d.ts +2 -1
- package/dist/scaffold.js +308 -13
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.2
|
|
4
|
+
|
|
5
|
+
- `hono-svelte init` now scaffolds a complete runnable app (works with
|
|
6
|
+
`npx`, `bunx` and `pnpm dlx` — writes text files only, no install/network).
|
|
7
|
+
Minimal flavor (default): `package.json`, `tsconfig.json`, `.gitignore`,
|
|
8
|
+
`vite.config.ts` (with `pages({ dts: true })`), two pages (`index` zero-JS,
|
|
9
|
+
`hello` with `data`), `src/routes/index.ts`, `src/styles.css`.
|
|
10
|
+
`init --full` adds auth (cookie session), dashboard with nested layout,
|
|
11
|
+
and typed RPC (`hc<AppType>`) — `/auth` → login → `/dashboard`.
|
|
12
|
+
Generated scripts (`dev`/`build`/`typecheck`/`start`) run under npm and bun.
|
|
13
|
+
`InitFlavor` type exported.
|
|
14
|
+
|
|
15
|
+
## 0.4.1
|
|
16
|
+
|
|
17
|
+
- Fix `hono-svelte init` refusing every file on Windows: `isSafeAppPath()`
|
|
18
|
+
now normalizes `\` vs `/` and compares case-insensitively on win32
|
|
19
|
+
(path traversal still blocked).
|
|
20
|
+
- Fix `bin` path without `./` prefix — npm was silently stripping the CLI
|
|
21
|
+
from the published tarball.
|
|
22
|
+
|
|
3
23
|
## 0.4.0
|
|
4
24
|
|
|
5
25
|
- **Typed `c.render` entries (opt-in)**: `pages({ dts })` writes a `.d.ts` file
|
package/README.md
CHANGED
|
@@ -193,7 +193,20 @@ app.onError(errorHandler()); // renders `500.svelte` with status 500
|
|
|
193
193
|
## CLI
|
|
194
194
|
|
|
195
195
|
```sh
|
|
196
|
-
|
|
196
|
+
# Start a new app (works with npx, bunx or pnpm dlx — no install needed):
|
|
197
|
+
bunx hono-svelte init # minimal: 2 pages + routes + configs
|
|
198
|
+
bunx hono-svelte init --full # + auth, dashboard w/ layout, typed RPC
|
|
199
|
+
|
|
200
|
+
cd my-app && bun install && bun run dev # or: npm install && npm run dev
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
`init` scaffolds a complete runnable app: `package.json`, `tsconfig.json`,
|
|
204
|
+
`vite.config.ts` (with `pages({ dts: true })`), `src/pages/` and
|
|
205
|
+
`src/routes/`. The `--full` flavor adds cookie-session auth, a dashboard with
|
|
206
|
+
nested layout, and a typed API (`hc<AppType>`) — open `/auth`, sign in, explore
|
|
207
|
+
`/dashboard`. Generated scripts run under npm and bun.
|
|
208
|
+
|
|
209
|
+
```sh
|
|
197
210
|
npx hono-svelte doctor # check dist, link and vite config
|
|
198
211
|
npx hono-svelte doctor --app=./my-app # check another app directory
|
|
199
212
|
```
|
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
-
import { doctorChecks, checkAppLink, initFiles, isSafeAppPath } from "./scaffold.js";
|
|
7
|
+
import { doctorChecks, checkAppLink, initFiles, isSafeAppPath, } from "./scaffold.js";
|
|
8
8
|
const args = process.argv.slice(2);
|
|
9
9
|
const command = args[0];
|
|
10
10
|
function flagValue(name) {
|
|
@@ -12,9 +12,14 @@ function flagValue(name) {
|
|
|
12
12
|
const hit = args.find((a) => a.startsWith(pref));
|
|
13
13
|
return hit ? hit.slice(pref.length) : undefined;
|
|
14
14
|
}
|
|
15
|
+
function hasFlag(name) {
|
|
16
|
+
return args.includes(`--${name}`);
|
|
17
|
+
}
|
|
15
18
|
const appRoot = flagValue("app") ?? process.cwd();
|
|
19
|
+
const flavor = hasFlag("full") ? "full" : "minimal";
|
|
16
20
|
function init() {
|
|
17
|
-
|
|
21
|
+
const files = initFiles(flavor);
|
|
22
|
+
for (const file of files) {
|
|
18
23
|
const dest = join(appRoot, file.path);
|
|
19
24
|
if (!isSafeAppPath(appRoot, dest)) {
|
|
20
25
|
console.error(`[hono-svelte] refusing to write outside ${appRoot}: ${file.path}`);
|
|
@@ -29,6 +34,16 @@ function init() {
|
|
|
29
34
|
writeFileSync(dest, file.content);
|
|
30
35
|
console.log(`[hono-svelte] wrote ${file.path}`);
|
|
31
36
|
}
|
|
37
|
+
console.log(`[hono-svelte] done (${flavor}). Next:`);
|
|
38
|
+
if (flavor === "minimal") {
|
|
39
|
+
console.log(` 1. npm install (or: bun install)`);
|
|
40
|
+
console.log(` 2. npm run dev (or: bun run dev)`);
|
|
41
|
+
console.log(` Tip: --full scaffolds auth + dashboard + layout + typed RPC.`);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
console.log(` 1. npm install (or: bun install)`);
|
|
45
|
+
console.log(` 2. npm run dev (or: bun run dev) → /auth → /dashboard`);
|
|
46
|
+
}
|
|
32
47
|
}
|
|
33
48
|
function packageRootForCli() {
|
|
34
49
|
return join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
@@ -54,6 +69,7 @@ if (command === "init")
|
|
|
54
69
|
else if (command === "doctor")
|
|
55
70
|
doctor();
|
|
56
71
|
else {
|
|
57
|
-
console.log("Usage: hono-svelte <init|doctor> [--app=<dir>]");
|
|
72
|
+
console.log("Usage: hono-svelte <init|doctor> [--app=<dir>] [--full]");
|
|
73
|
+
console.log(" init [--full] scaffold a runnable app (minimal by default)");
|
|
58
74
|
process.exitCode = 1;
|
|
59
75
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export type { AssetsResolver, ManifestAssetsOptions, ViteManifest, ViteManifestC
|
|
|
10
10
|
export { notFoundHandler, errorHandler } from "./handlers.js";
|
|
11
11
|
export type { ErrorPageOptions, ServerErrorHandlerOptions } from "./handlers.js";
|
|
12
12
|
export { initFiles, doctorChecks, checkAppLink, versionsMatch, isSafeAppPath } from "./scaffold.js";
|
|
13
|
-
export type { DoctorIssue, DoctorOptions, InitFile } from "./scaffold.js";
|
|
13
|
+
export type { DoctorIssue, DoctorOptions, InitFile, InitFlavor } from "./scaffold.js";
|
|
14
14
|
export type SsrPageLoader = () => Promise<{
|
|
15
15
|
default: unknown;
|
|
16
16
|
}>;
|
package/dist/scaffold.d.ts
CHANGED
|
@@ -3,7 +3,8 @@ export type InitFile = {
|
|
|
3
3
|
content: string;
|
|
4
4
|
skipIfExists: boolean;
|
|
5
5
|
};
|
|
6
|
-
export
|
|
6
|
+
export type InitFlavor = "minimal" | "full";
|
|
7
|
+
export declare function initFiles(flavor?: InitFlavor): InitFile[];
|
|
7
8
|
export type DoctorIssue = {
|
|
8
9
|
/** Machine-readable code, e.g. "missing-dist". */
|
|
9
10
|
code: string;
|
package/dist/scaffold.js
CHANGED
|
@@ -2,16 +2,19 @@
|
|
|
2
2
|
//
|
|
3
3
|
// The package exposes pure helpers (`initFiles`, `doctorChecks`) so they can
|
|
4
4
|
// be unit-tested; the thin CLI wrapper lives in `src/cli.ts` (compiled to
|
|
5
|
-
// `dist/cli.js`, invoked via the `hono-svelte
|
|
5
|
+
// `dist/cli.js`, invoked via the `hono-svelte` bin).
|
|
6
|
+
//
|
|
7
|
+
// Runtime note: `init` writes plain text files only — no install, no network.
|
|
8
|
+
// It works identically under `npx` (npm), `bunx` (bun) and `pnpm dlx`.
|
|
9
|
+
// The generated app is runtime-agnostic too: `bun run dev`, `bun run build`
|
|
10
|
+
// and `bun run start` work because every script shells out to `vite` / `node`
|
|
11
|
+
// (both runnable from bun).
|
|
6
12
|
import { existsSync } from "node:fs";
|
|
7
13
|
import { isAbsolute, join, resolve } from "node:path";
|
|
8
|
-
const ENV_DTS =
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
(entryName: string, props?: RenderProps): Response | Promise<Response>;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
14
|
+
const ENV_DTS = `// Entry names are generated by \`pages({ dts: true })\` into
|
|
15
|
+
// \`src/hono-svelte-entries.d.ts\` (see vite.config.ts). \`c.render()\` only
|
|
16
|
+
// accepts those names — a typo fails the typecheck.
|
|
17
|
+
/// <reference path="./hono-svelte-entries.d.ts" />
|
|
15
18
|
`;
|
|
16
19
|
const VITE_CONFIG = `import build from "@hono/vite-build/node";
|
|
17
20
|
import devServer from "@hono/vite-dev-server";
|
|
@@ -20,7 +23,7 @@ import { pages } from "hono-svelte/vite";
|
|
|
20
23
|
import { resolve } from "node:path";
|
|
21
24
|
import { defineConfig } from "vite";
|
|
22
25
|
|
|
23
|
-
const appPages = pages();
|
|
26
|
+
const appPages = pages({ dts: true });
|
|
24
27
|
|
|
25
28
|
export default defineConfig(({ command, mode }) => {
|
|
26
29
|
if (mode === "client") {
|
|
@@ -50,16 +53,298 @@ export default defineConfig(({ command, mode }) => {
|
|
|
50
53
|
};
|
|
51
54
|
});
|
|
52
55
|
`;
|
|
56
|
+
const PACKAGE_JSON = `{
|
|
57
|
+
"name": "my-hono-svelte-app",
|
|
58
|
+
"type": "module",
|
|
59
|
+
"private": true,
|
|
60
|
+
"scripts": {
|
|
61
|
+
"dev": "vite",
|
|
62
|
+
"build": "npm run build:client && npm run build:server",
|
|
63
|
+
"build:client": "vite build --mode client",
|
|
64
|
+
"build:server": "vite build --mode production",
|
|
65
|
+
"typecheck": "tsc --noEmit",
|
|
66
|
+
"start": "node ./dist/index.js"
|
|
67
|
+
},
|
|
68
|
+
"dependencies": {
|
|
69
|
+
"hono": "^4",
|
|
70
|
+
"hono-svelte": "latest",
|
|
71
|
+
"svelte": "^5"
|
|
72
|
+
},
|
|
73
|
+
"devDependencies": {
|
|
74
|
+
"@hono/vite-build": "^1",
|
|
75
|
+
"@hono/vite-dev-server": "^0",
|
|
76
|
+
"@sveltejs/vite-plugin-svelte": "^7",
|
|
77
|
+
"@types/node": "^22",
|
|
78
|
+
"typescript": "^5",
|
|
79
|
+
"vite": "^8"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
`;
|
|
83
|
+
const TSCONFIG_JSON = `{
|
|
84
|
+
"compilerOptions": {
|
|
85
|
+
"target": "ESNext",
|
|
86
|
+
"module": "ESNext",
|
|
87
|
+
"moduleResolution": "Bundler",
|
|
88
|
+
"strict": true,
|
|
89
|
+
"skipLibCheck": true,
|
|
90
|
+
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
|
91
|
+
"types": ["vite/client", "node"]
|
|
92
|
+
},
|
|
93
|
+
"include": ["src/**/*", "vite.config.ts"],
|
|
94
|
+
"exclude": ["node_modules", "dist"]
|
|
95
|
+
}
|
|
96
|
+
`;
|
|
97
|
+
const GITIGNORE_TEXT = `node_modules
|
|
98
|
+
dist
|
|
99
|
+
`;
|
|
53
100
|
const INDEX_SVELTE = `<main>
|
|
54
101
|
<h1>Welcome</h1>
|
|
55
|
-
<a href="/
|
|
102
|
+
<p><a href="/hello">Say hello</a></p>
|
|
56
103
|
</main>
|
|
57
104
|
`;
|
|
58
|
-
|
|
59
|
-
|
|
105
|
+
const HELLO_SVELTE = `<script lang="ts">
|
|
106
|
+
let { name }: { name?: string } = $props();
|
|
107
|
+
</script>
|
|
108
|
+
|
|
109
|
+
<main>
|
|
110
|
+
<h1>Hello, {name ?? "stranger"}!</h1>
|
|
111
|
+
<p><a href="/">Back home</a></p>
|
|
112
|
+
</main>
|
|
113
|
+
`;
|
|
114
|
+
const STYLES_CSS = `body {
|
|
115
|
+
font-family: system-ui, sans-serif;
|
|
116
|
+
margin: 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
main {
|
|
120
|
+
max-width: 640px;
|
|
121
|
+
margin: 4rem auto;
|
|
122
|
+
padding: 0 1rem;
|
|
123
|
+
}
|
|
124
|
+
`;
|
|
125
|
+
const FULL_LIB_AUTH = `import type { Context } from "hono";
|
|
126
|
+
import { getCookie } from "hono/cookie";
|
|
127
|
+
|
|
128
|
+
export const SESSION_COOKIE = "hono_demo_session";
|
|
129
|
+
export const SESSION_VALUE = "authenticated";
|
|
130
|
+
|
|
131
|
+
export function hasSession(c: Context): boolean {
|
|
132
|
+
return getCookie(c, SESSION_COOKIE) === SESSION_VALUE;
|
|
133
|
+
}
|
|
134
|
+
`;
|
|
135
|
+
const FULL_MW_AUTH = `import { createMiddleware } from "hono/factory";
|
|
136
|
+
import { hasSession } from "../lib/auth";
|
|
137
|
+
|
|
138
|
+
export function requireSession() {
|
|
139
|
+
return createMiddleware(async (c, next) => {
|
|
140
|
+
if (!hasSession(c)) return c.redirect("/auth");
|
|
141
|
+
return next();
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
`;
|
|
145
|
+
const FULL_PAGE_AUTH = `<main>
|
|
146
|
+
<h1>Sign in</h1>
|
|
147
|
+
<form action="/auth/login" method="post">
|
|
148
|
+
<div>
|
|
149
|
+
<label for="email">Email</label>
|
|
150
|
+
<input id="email" name="email" type="email" required />
|
|
151
|
+
</div>
|
|
152
|
+
<div>
|
|
153
|
+
<label for="password">Password</label>
|
|
154
|
+
<input id="password" name="password" type="password" required />
|
|
155
|
+
</div>
|
|
156
|
+
<button type="submit">Sign in</button>
|
|
157
|
+
</form>
|
|
158
|
+
<p>Any email + password works.</p>
|
|
159
|
+
<p><a href="/">Back home</a></p>
|
|
160
|
+
</main>
|
|
161
|
+
`;
|
|
162
|
+
const FULL_PAGE_DASH_INDEX = `<script lang="ts">
|
|
163
|
+
import { hc } from "hono/client";
|
|
164
|
+
import type { AppType } from "../../routes";
|
|
165
|
+
|
|
166
|
+
const client = hc<AppType>("/");
|
|
167
|
+
|
|
168
|
+
let count = $state(0);
|
|
169
|
+
let time = $state<string | null>(null);
|
|
170
|
+
|
|
171
|
+
async function fetchTime() {
|
|
172
|
+
const res = await client.api.clock.$get();
|
|
173
|
+
if (res.ok) time = (await res.json()).now;
|
|
174
|
+
}
|
|
175
|
+
</script>
|
|
176
|
+
|
|
177
|
+
<div>
|
|
178
|
+
<p>Count: {count}</p>
|
|
179
|
+
<button type="button" onclick={() => (count += 1)}>Increment</button>
|
|
180
|
+
<p>
|
|
181
|
+
<button type="button" onclick={fetchTime}>Fetch time</button>
|
|
182
|
+
</p>
|
|
183
|
+
</div>
|
|
184
|
+
`;
|
|
185
|
+
const FULL_PAGE_DASH_PAGE1 = `<section>
|
|
186
|
+
<h2>Page 1 content</h2>
|
|
187
|
+
<p>Same layout, zero JS.</p>
|
|
188
|
+
<p><a href="/dashboard">Back to overview</a></p>
|
|
189
|
+
</section>
|
|
190
|
+
`;
|
|
191
|
+
const FULL_PAGE_DASH_LAYOUT = `<script lang="ts">
|
|
192
|
+
import type { Snippet } from "svelte";
|
|
193
|
+
|
|
194
|
+
type Props = { title?: string; description?: string; children: Snippet };
|
|
195
|
+
let { title = "Dashboard", description = "", children }: Props = $props();
|
|
196
|
+
</script>
|
|
197
|
+
|
|
198
|
+
<main>
|
|
199
|
+
<header>
|
|
200
|
+
<h1>{title}</h1>
|
|
201
|
+
</header>
|
|
202
|
+
{@render children()}
|
|
203
|
+
</main>
|
|
204
|
+
`;
|
|
205
|
+
const MIN_ROUTES_INDEX = `import { Hono } from "hono";
|
|
206
|
+
import { shell } from "hono-svelte";
|
|
207
|
+
|
|
208
|
+
const app = new Hono().use("/*", shell({ title: "My App" }));
|
|
209
|
+
|
|
210
|
+
app.get("/", (c) => c.render("index"));
|
|
211
|
+
app.get("/hello", (c) => c.render("hello", { data: { name: "Ada" } }));
|
|
212
|
+
|
|
213
|
+
export default app;
|
|
214
|
+
`;
|
|
215
|
+
export function initFiles(flavor = "minimal") {
|
|
216
|
+
const base = [
|
|
217
|
+
{ path: "package.json", content: PACKAGE_JSON, skipIfExists: true },
|
|
218
|
+
{ path: "tsconfig.json", content: TSCONFIG_JSON, skipIfExists: true },
|
|
219
|
+
{ path: ".gitignore", content: GITIGNORE_TEXT, skipIfExists: true },
|
|
60
220
|
{ path: "src/env.d.ts", content: ENV_DTS, skipIfExists: true },
|
|
61
221
|
{ path: "vite.config.ts", content: VITE_CONFIG, skipIfExists: true },
|
|
222
|
+
{ path: "src/styles.css", content: STYLES_CSS, skipIfExists: true },
|
|
62
223
|
{ path: "src/pages/index.svelte", content: INDEX_SVELTE, skipIfExists: true },
|
|
224
|
+
{ path: "src/pages/hello.svelte", content: HELLO_SVELTE, skipIfExists: true },
|
|
225
|
+
{ path: "src/routes/index.ts", content: MIN_ROUTES_INDEX, skipIfExists: true },
|
|
226
|
+
];
|
|
227
|
+
if (flavor === "minimal")
|
|
228
|
+
return base;
|
|
229
|
+
return [...base, ...fullFlavorFiles()];
|
|
230
|
+
}
|
|
231
|
+
function fullFlavorFiles() {
|
|
232
|
+
const dashboardRoutes = `import { Hono } from "hono";
|
|
233
|
+
import { requireSession } from "../middleware/auth";
|
|
234
|
+
|
|
235
|
+
const app = new Hono()
|
|
236
|
+
.get("/", requireSession(), (c) =>
|
|
237
|
+
c.render("dashboard/index", {
|
|
238
|
+
data: {
|
|
239
|
+
title: "Your space is ready.",
|
|
240
|
+
description: "Counter + clock via typed RPC.",
|
|
241
|
+
},
|
|
242
|
+
}),
|
|
243
|
+
)
|
|
244
|
+
.get("/page1", requireSession(), (c) =>
|
|
245
|
+
c.render("dashboard/page1", {
|
|
246
|
+
data: { title: "Page 1", description: "A second page, same layout." },
|
|
247
|
+
}),
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
export default app;
|
|
251
|
+
`;
|
|
252
|
+
const siteRoutes = `import { Hono } from "hono";
|
|
253
|
+
import { hasSession } from "../lib/auth";
|
|
254
|
+
|
|
255
|
+
const app = new Hono().get("/", async (c) => {
|
|
256
|
+
if (hasSession(c)) return c.redirect("/dashboard");
|
|
257
|
+
return c.render("index");
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
export default app;
|
|
261
|
+
`;
|
|
262
|
+
const authRoutes = `import { Hono } from "hono";
|
|
263
|
+
import { setCookie } from "hono/cookie";
|
|
264
|
+
import { hasSession, SESSION_COOKIE, SESSION_VALUE } from "../lib/auth";
|
|
265
|
+
|
|
266
|
+
const app = new Hono();
|
|
267
|
+
|
|
268
|
+
app.get("/", (c) => {
|
|
269
|
+
if (hasSession(c)) return c.redirect("/dashboard");
|
|
270
|
+
return c.render("auth");
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
app.post("/login", async (c) => {
|
|
274
|
+
const body = await c.req.parseBody();
|
|
275
|
+
const email = String(body.email ?? "");
|
|
276
|
+
const password = String(body.password ?? "");
|
|
277
|
+
if (!email.includes("@") || !password) return c.redirect("/auth?error=invalid");
|
|
278
|
+
setCookie(c, SESSION_COOKIE, SESSION_VALUE, {
|
|
279
|
+
maxAge: 3600,
|
|
280
|
+
path: "/",
|
|
281
|
+
httpOnly: true,
|
|
282
|
+
sameSite: "Lax",
|
|
283
|
+
});
|
|
284
|
+
return c.redirect("/dashboard");
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
app.post("/logout", (c) => {
|
|
288
|
+
setCookie(c, SESSION_COOKIE, "", { maxAge: 0, path: "/" });
|
|
289
|
+
return c.redirect("/");
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
export default app;
|
|
293
|
+
`;
|
|
294
|
+
const apiRoutes = `import { Hono } from "hono";
|
|
295
|
+
import { hasSession } from "../lib/auth";
|
|
296
|
+
|
|
297
|
+
const app = new Hono().get("/clock", (c) => {
|
|
298
|
+
if (!hasSession(c)) return c.json({ error: "Not authenticated" }, 401);
|
|
299
|
+
return c.json({ now: new Date().toISOString() });
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
export default app;
|
|
303
|
+
`;
|
|
304
|
+
const appRoutes = `import { Hono } from "hono";
|
|
305
|
+
import { shell } from "hono-svelte";
|
|
306
|
+
import api from "./api";
|
|
307
|
+
import auth from "./auth";
|
|
308
|
+
import dashboard from "./dashboard";
|
|
309
|
+
import site from "./site";
|
|
310
|
+
|
|
311
|
+
const app = new Hono();
|
|
312
|
+
|
|
313
|
+
const routes = app
|
|
314
|
+
.use("/*", shell({ title: "My App", lang: "en" }))
|
|
315
|
+
.route("/", site)
|
|
316
|
+
.route("/auth", auth)
|
|
317
|
+
.route("/dashboard", dashboard)
|
|
318
|
+
.route("/api", api);
|
|
319
|
+
|
|
320
|
+
export type AppType = typeof routes;
|
|
321
|
+
|
|
322
|
+
export default app;
|
|
323
|
+
`;
|
|
324
|
+
return [
|
|
325
|
+
{ path: "src/lib/auth.ts", content: FULL_LIB_AUTH, skipIfExists: true },
|
|
326
|
+
{ path: "src/middleware/auth.ts", content: FULL_MW_AUTH, skipIfExists: true },
|
|
327
|
+
{ path: "src/routes/index.ts", content: appRoutes, skipIfExists: false },
|
|
328
|
+
{ path: "src/routes/site.ts", content: siteRoutes, skipIfExists: true },
|
|
329
|
+
{ path: "src/routes/auth.ts", content: authRoutes, skipIfExists: true },
|
|
330
|
+
{ path: "src/routes/dashboard.ts", content: dashboardRoutes, skipIfExists: true },
|
|
331
|
+
{ path: "src/routes/api.ts", content: apiRoutes, skipIfExists: true },
|
|
332
|
+
{ path: "src/pages/auth.svelte", content: FULL_PAGE_AUTH, skipIfExists: true },
|
|
333
|
+
{
|
|
334
|
+
path: "src/pages/dashboard/index.svelte",
|
|
335
|
+
content: FULL_PAGE_DASH_INDEX,
|
|
336
|
+
skipIfExists: true,
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
path: "src/pages/dashboard/page1.svelte",
|
|
340
|
+
content: FULL_PAGE_DASH_PAGE1,
|
|
341
|
+
skipIfExists: true,
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
path: "src/pages/dashboard/layout.svelte",
|
|
345
|
+
content: FULL_PAGE_DASH_LAYOUT,
|
|
346
|
+
skipIfExists: true,
|
|
347
|
+
},
|
|
63
348
|
];
|
|
64
349
|
}
|
|
65
350
|
export function versionsMatch(_linkedVersion, _appWants) {
|
|
@@ -101,7 +386,17 @@ export function checkAppLink(appRoot) {
|
|
|
101
386
|
return null;
|
|
102
387
|
}
|
|
103
388
|
export function isSafeAppPath(appRoot, target) {
|
|
389
|
+
// NOTE: compare case-insensitively on Windows (C:\Users vs c:\users) and
|
|
390
|
+
// accept both separators. `join("C:\\x", "src/a")` yields backslashes,
|
|
391
|
+
// so checking `startsWith(root + "/")` always failed on win32 and `init`
|
|
392
|
+
// refused every file.
|
|
104
393
|
const root = resolve(appRoot);
|
|
105
394
|
const abs = isAbsolute(target) ? target : resolve(root, target);
|
|
106
|
-
|
|
395
|
+
const norm = (p) => p.replace(/\\/g, "/");
|
|
396
|
+
const lower = (p) => (process.platform === "win32" ? p.toLowerCase() : p);
|
|
397
|
+
const nRoot = lower(norm(root));
|
|
398
|
+
const nAbs = lower(norm(abs));
|
|
399
|
+
// Exact root itself is not a writable file, but treat as safe for symmetry
|
|
400
|
+
// with the old check; callers only pass file paths.
|
|
401
|
+
return nAbs === nRoot || nAbs.startsWith(nRoot + "/");
|
|
107
402
|
}
|