create-shiftapi 0.0.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 +69 -0
- package/dist/index.js +146 -0
- package/package.json +26 -0
- package/templates/base/.env +1 -0
- package/templates/base/README.md +32 -0
- package/templates/base/_gitignore +22 -0
- package/templates/base/cmd/__name__/main.go +41 -0
- package/templates/base/go.mod +5 -0
- package/templates/base/internal/server/server.go +51 -0
- package/templates/base/package.json +10 -0
- package/templates/react/apps/web/index.html +12 -0
- package/templates/react/apps/web/package.json +22 -0
- package/templates/react/apps/web/src/App.tsx +42 -0
- package/templates/react/apps/web/src/main.tsx +9 -0
- package/templates/react/apps/web/tsconfig.json +19 -0
- package/templates/react/apps/web/vite.config.ts +13 -0
- package/templates/svelte/apps/web/index.html +12 -0
- package/templates/svelte/apps/web/package.json +19 -0
- package/templates/svelte/apps/web/src/App.svelte +38 -0
- package/templates/svelte/apps/web/src/main.ts +4 -0
- package/templates/svelte/apps/web/tsconfig.json +18 -0
- package/templates/svelte/apps/web/vite.config.ts +13 -0
package/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/fcjr/shiftapi/main/assets/logo.svg" alt="ShiftAPI Logo">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# create-shiftapi
|
|
6
|
+
|
|
7
|
+
Scaffold a new [ShiftAPI](https://github.com/fcjr/shiftapi) fullstack app — Go server + typed TypeScript frontend.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm create shiftapi
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or with pnpm / yarn:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pnpm create shiftapi
|
|
19
|
+
yarn create shiftapi
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
You can also pass the project name directly:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm create shiftapi my-app
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## What You Get
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
my-app/
|
|
32
|
+
cmd/my-app/main.go # Go entry point with graceful shutdown
|
|
33
|
+
internal/server/server.go # API routes and handlers
|
|
34
|
+
go.mod
|
|
35
|
+
.env # PORT config
|
|
36
|
+
.gitignore
|
|
37
|
+
package.json # Monorepo root with workspaces
|
|
38
|
+
apps/web/
|
|
39
|
+
package.json # React or Svelte frontend
|
|
40
|
+
vite.config.ts # ShiftAPI vite plugin configured
|
|
41
|
+
tsconfig.json
|
|
42
|
+
index.html
|
|
43
|
+
src/
|
|
44
|
+
main.tsx (or .ts) # App entry
|
|
45
|
+
App.tsx (or .svelte) # Demo component with typed API calls
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Prompts
|
|
49
|
+
|
|
50
|
+
| Prompt | Default |
|
|
51
|
+
|---|---|
|
|
52
|
+
| Project name | `my-app` |
|
|
53
|
+
| Framework | React / Svelte |
|
|
54
|
+
| Directory | `./<project-name>` |
|
|
55
|
+
| Go module path | `github.com/<gh-user>/<project-name>` if logged into `gh`, otherwise `<project-name>` |
|
|
56
|
+
| Server port | `8080` |
|
|
57
|
+
|
|
58
|
+
## After Scaffolding
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
cd my-app
|
|
62
|
+
go mod tidy
|
|
63
|
+
npm install
|
|
64
|
+
npm run dev
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
This starts the Go server and Vite dev server together. The frontend gets fully typed API clients generated from your Go handlers — edit a struct in Go, get instant type errors in TypeScript.
|
|
68
|
+
|
|
69
|
+
API docs are served at `http://localhost:8080/docs`.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import fs2 from "fs";
|
|
5
|
+
import os from "os";
|
|
6
|
+
import * as p from "@clack/prompts";
|
|
7
|
+
import path2 from "path";
|
|
8
|
+
|
|
9
|
+
// src/scaffold.ts
|
|
10
|
+
import { execFile } from "child_process";
|
|
11
|
+
import fs from "fs";
|
|
12
|
+
import path from "path";
|
|
13
|
+
import { fileURLToPath } from "url";
|
|
14
|
+
var renameFiles = {
|
|
15
|
+
_gitignore: ".gitignore"
|
|
16
|
+
};
|
|
17
|
+
var pkgDir = path.resolve(fileURLToPath(import.meta.url), "../..");
|
|
18
|
+
var templatesDir = path.join(pkgDir, "templates");
|
|
19
|
+
var pkgVersion = JSON.parse(
|
|
20
|
+
fs.readFileSync(path.join(pkgDir, "package.json"), "utf-8")
|
|
21
|
+
).version;
|
|
22
|
+
function replaceplaceholders(content, opts) {
|
|
23
|
+
return content.replaceAll("{{name}}", opts.name).replaceAll("{{modulePath}}", opts.modulePath).replaceAll("{{port}}", opts.port).replaceAll("{{version}}", pkgVersion);
|
|
24
|
+
}
|
|
25
|
+
function renamePath(filePath, opts) {
|
|
26
|
+
return filePath.replaceAll("__name__", opts.name);
|
|
27
|
+
}
|
|
28
|
+
function copyDir(srcDir, destDir, opts) {
|
|
29
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
30
|
+
for (const entry of fs.readdirSync(srcDir)) {
|
|
31
|
+
const srcPath = path.join(srcDir, entry);
|
|
32
|
+
const destName = renameFiles[entry] ?? entry;
|
|
33
|
+
const destPath = path.join(destDir, renamePath(destName, opts));
|
|
34
|
+
const stat = fs.statSync(srcPath);
|
|
35
|
+
if (stat.isDirectory()) {
|
|
36
|
+
copyDir(srcPath, destPath, opts);
|
|
37
|
+
} else {
|
|
38
|
+
const content = fs.readFileSync(srcPath, "utf-8");
|
|
39
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
40
|
+
fs.writeFileSync(destPath, replaceplaceholders(content, opts));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function gitInit(cwd) {
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
execFile("git", ["init"], { cwd }, (err) => {
|
|
47
|
+
if (err) {
|
|
48
|
+
reject(err);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
resolve();
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
async function scaffold(opts) {
|
|
56
|
+
copyDir(path.join(templatesDir, "base"), opts.targetDir, opts);
|
|
57
|
+
copyDir(path.join(templatesDir, opts.framework), opts.targetDir, opts);
|
|
58
|
+
await gitInit(opts.targetDir);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/index.ts
|
|
62
|
+
function expandHome(filepath) {
|
|
63
|
+
if (filepath === "~" || filepath.startsWith("~/")) {
|
|
64
|
+
return path2.join(os.homedir(), filepath.slice(1));
|
|
65
|
+
}
|
|
66
|
+
return filepath;
|
|
67
|
+
}
|
|
68
|
+
function getGitHubUser() {
|
|
69
|
+
const configDir = expandHome(
|
|
70
|
+
process.env.GH_CONFIG_DIR ?? process.env.XDG_CONFIG_HOME ? path2.join(expandHome(process.env.XDG_CONFIG_HOME), "gh") : path2.join(os.homedir(), ".config", "gh")
|
|
71
|
+
);
|
|
72
|
+
try {
|
|
73
|
+
const hosts = fs2.readFileSync(path2.join(configDir, "hosts.yml"), "utf-8");
|
|
74
|
+
const match = hosts.match(/github\.com:\s[\s\S]*?^\s+user:\s+(.+)$/m);
|
|
75
|
+
return match?.[1]?.trim() || null;
|
|
76
|
+
} catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async function main() {
|
|
81
|
+
const positionalName = process.argv[2];
|
|
82
|
+
const ghUser = getGitHubUser();
|
|
83
|
+
p.intro("create-shiftapi");
|
|
84
|
+
const project = await p.group(
|
|
85
|
+
{
|
|
86
|
+
name: () => positionalName ? Promise.resolve(positionalName) : p.text({
|
|
87
|
+
message: "Project name",
|
|
88
|
+
placeholder: "my-app",
|
|
89
|
+
defaultValue: "my-app"
|
|
90
|
+
}),
|
|
91
|
+
framework: () => p.select({
|
|
92
|
+
message: "Framework",
|
|
93
|
+
options: [
|
|
94
|
+
{ label: "React", value: "react" },
|
|
95
|
+
{ label: "Svelte", value: "svelte" }
|
|
96
|
+
]
|
|
97
|
+
}),
|
|
98
|
+
directory: ({ results }) => p.text({
|
|
99
|
+
message: "Directory",
|
|
100
|
+
placeholder: `./${results.name}`,
|
|
101
|
+
defaultValue: `./${results.name}`
|
|
102
|
+
}),
|
|
103
|
+
module: ({ results }) => p.text({
|
|
104
|
+
message: "Go module path",
|
|
105
|
+
placeholder: ghUser ? `github.com/${ghUser}/${results.name}` : results.name,
|
|
106
|
+
defaultValue: ghUser ? `github.com/${ghUser}/${results.name}` : results.name
|
|
107
|
+
}),
|
|
108
|
+
port: () => p.text({
|
|
109
|
+
message: "Server port",
|
|
110
|
+
placeholder: "8080",
|
|
111
|
+
defaultValue: "8080"
|
|
112
|
+
})
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
onCancel: () => {
|
|
116
|
+
p.cancel("Cancelled.");
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
const targetDir = path2.resolve(process.cwd(), expandHome(project.directory));
|
|
122
|
+
if (fs2.existsSync(targetDir)) {
|
|
123
|
+
p.cancel(`${targetDir} already exists.`);
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
const s = p.spinner();
|
|
127
|
+
s.start("Scaffolding project");
|
|
128
|
+
await scaffold({
|
|
129
|
+
name: project.name,
|
|
130
|
+
modulePath: project.module,
|
|
131
|
+
port: project.port,
|
|
132
|
+
framework: project.framework,
|
|
133
|
+
targetDir
|
|
134
|
+
});
|
|
135
|
+
s.stop("Project scaffolded");
|
|
136
|
+
const relDir = path2.relative(process.cwd(), targetDir) || ".";
|
|
137
|
+
p.note(
|
|
138
|
+
[`cd ${relDir}`, "go mod tidy", "npm install", "npm run dev"].join("\n"),
|
|
139
|
+
"Next steps"
|
|
140
|
+
);
|
|
141
|
+
p.outro("Happy hacking!");
|
|
142
|
+
}
|
|
143
|
+
main().catch((err) => {
|
|
144
|
+
console.error(err);
|
|
145
|
+
process.exit(1);
|
|
146
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-shiftapi",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Scaffold a new ShiftAPI fullstack app",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-shiftapi": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"templates"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsup src/index.ts --format esm",
|
|
15
|
+
"test": "vitest run"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@clack/prompts": "^1.0.0"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/node": "^25.2.3",
|
|
22
|
+
"tsup": "^8.0.0",
|
|
23
|
+
"typescript": "^5.5.0",
|
|
24
|
+
"vitest": "^2.0.0"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
PORT={{port}}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# {{name}}
|
|
2
|
+
|
|
3
|
+
Built with [ShiftAPI](https://github.com/fcjr/shiftapi) — Go server + typed TypeScript frontend.
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
go mod tidy
|
|
9
|
+
npm install
|
|
10
|
+
npm run dev
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
This starts the Go server and Vite dev server together. The frontend gets fully typed API clients generated from your Go handlers.
|
|
14
|
+
|
|
15
|
+
- API docs: http://localhost:{{port}}/docs
|
|
16
|
+
- Frontend: http://localhost:5173
|
|
17
|
+
|
|
18
|
+
## Project Structure
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
cmd/{{name}}/main.go # Go entry point
|
|
22
|
+
internal/server/server.go # API routes and handlers
|
|
23
|
+
go.mod
|
|
24
|
+
.env # Environment variables (PORT)
|
|
25
|
+
apps/web/ # Frontend (Vite + TypeScript)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Scripts
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm run dev # Start Go server + Vite dev server
|
|
32
|
+
```
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"fmt"
|
|
6
|
+
"io"
|
|
7
|
+
"log/slog"
|
|
8
|
+
"os"
|
|
9
|
+
"os/signal"
|
|
10
|
+
|
|
11
|
+
"github.com/joho/godotenv"
|
|
12
|
+
|
|
13
|
+
"{{modulePath}}/internal/server"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
func main() {
|
|
17
|
+
ctx := context.Background()
|
|
18
|
+
|
|
19
|
+
if err := godotenv.Load(); err != nil {
|
|
20
|
+
slog.Warn("error loading .env file, skipping", "error", err)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if err := run(ctx, os.Args, os.Getenv, os.Stdin, os.Stdout, os.Stderr); err != nil {
|
|
24
|
+
fmt.Fprintf(os.Stderr, "%v\n", err)
|
|
25
|
+
os.Exit(1)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
func run(ctx context.Context, args []string, getenv func(string) string, stdin io.Reader, stdout, stderr io.Writer) error {
|
|
30
|
+
ctx, cancel := signal.NotifyContext(ctx, os.Interrupt)
|
|
31
|
+
defer cancel()
|
|
32
|
+
|
|
33
|
+
port := getenv("PORT")
|
|
34
|
+
if port == "" {
|
|
35
|
+
port = "{{port}}"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
slog.Info("starting server", "port", port)
|
|
39
|
+
// docs at http://localhost:{{port}}/docs
|
|
40
|
+
return server.ListenAndServe(ctx, ":"+port)
|
|
41
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
package server
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"net/http"
|
|
6
|
+
|
|
7
|
+
"github.com/fcjr/shiftapi"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
type EchoRequest struct {
|
|
11
|
+
Message string `json:"message" validate:"required"`
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type EchoResponse struct {
|
|
15
|
+
Message string `json:"message"`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
func echo(r *http.Request, body *EchoRequest) (*EchoResponse, error) {
|
|
19
|
+
return &EchoResponse{Message: body.Message}, nil
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type Status struct {
|
|
23
|
+
OK bool `json:"ok"`
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
func health(r *http.Request) (*Status, error) {
|
|
27
|
+
return &Status{OK: true}, nil
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
func ListenAndServe(ctx context.Context, addr string) error {
|
|
31
|
+
api := shiftapi.New(shiftapi.WithInfo(shiftapi.Info{
|
|
32
|
+
Title: "{{name}}",
|
|
33
|
+
}))
|
|
34
|
+
|
|
35
|
+
shiftapi.Post(api, "/echo", echo,
|
|
36
|
+
shiftapi.WithRouteInfo(shiftapi.RouteInfo{
|
|
37
|
+
Summary: "Echo a message",
|
|
38
|
+
Description: "Returns the message you send",
|
|
39
|
+
Tags: []string{"echo"},
|
|
40
|
+
}),
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
shiftapi.Get(api, "/health", health,
|
|
44
|
+
shiftapi.WithRouteInfo(shiftapi.RouteInfo{
|
|
45
|
+
Summary: "Health check",
|
|
46
|
+
Tags: []string{"health"},
|
|
47
|
+
}),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
return shiftapi.ListenAndServe(addr, api)
|
|
51
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
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.0" />
|
|
6
|
+
<title>{{name}}</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@{{name}}/web",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "vite",
|
|
7
|
+
"build": "tsc --noEmit && vite build",
|
|
8
|
+
"typecheck": "tsc --noEmit"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"react": "^19.0.0",
|
|
12
|
+
"react-dom": "^19.0.0"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@shiftapi/vite-plugin": "^{{version}}",
|
|
16
|
+
"@types/react": "^19.0.0",
|
|
17
|
+
"@types/react-dom": "^19.0.0",
|
|
18
|
+
"@vitejs/plugin-react": "^4.0.0",
|
|
19
|
+
"typescript": "^5.5.0",
|
|
20
|
+
"vite": "^6.0.0"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import type { FormEvent } from "react";
|
|
3
|
+
import { client } from "@shiftapi/client";
|
|
4
|
+
|
|
5
|
+
export default function App() {
|
|
6
|
+
const [output, setOutput] = useState("");
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
client.GET("/health").then(({ error }) => {
|
|
10
|
+
if (error) {
|
|
11
|
+
setOutput(`Health check failed: ${error.message}`);
|
|
12
|
+
} else {
|
|
13
|
+
setOutput("Health check passed. Try sending a message.");
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
}, []);
|
|
17
|
+
|
|
18
|
+
async function handleSubmit(e: FormEvent<HTMLFormElement>) {
|
|
19
|
+
e.preventDefault();
|
|
20
|
+
const formData = new FormData(e.currentTarget);
|
|
21
|
+
const message = (formData.get("message") as string).trim();
|
|
22
|
+
if (!message) return;
|
|
23
|
+
setOutput("Loading...");
|
|
24
|
+
const { data, error } = await client.POST("/echo", { body: { message } });
|
|
25
|
+
if (error) {
|
|
26
|
+
setOutput(`Error: ${error.message}`);
|
|
27
|
+
} else {
|
|
28
|
+
setOutput(`Echo: ${data.message}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div>
|
|
34
|
+
<h1>{{name}}</h1>
|
|
35
|
+
<form onSubmit={handleSubmit}>
|
|
36
|
+
<input type="text" name="message" placeholder="Enter a message" />
|
|
37
|
+
<button type="submit">Send</button>
|
|
38
|
+
</form>
|
|
39
|
+
<pre>{output}</pre>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ES2022",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"jsx": "react-jsx",
|
|
10
|
+
"paths": {
|
|
11
|
+
"@shiftapi/client": [
|
|
12
|
+
"./node_modules/.shiftapi/client.d.ts"
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"include": [
|
|
17
|
+
"src"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineConfig } from "vite";
|
|
2
|
+
import react from "@vitejs/plugin-react";
|
|
3
|
+
import shiftapi from "@shiftapi/vite-plugin";
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [
|
|
7
|
+
react(),
|
|
8
|
+
shiftapi({
|
|
9
|
+
server: "./cmd/{{name}}",
|
|
10
|
+
goRoot: "../..",
|
|
11
|
+
}),
|
|
12
|
+
],
|
|
13
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
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.0" />
|
|
6
|
+
<title>{{name}}</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="app"></div>
|
|
10
|
+
<script type="module" src="/src/main.ts"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@{{name}}/web",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "vite",
|
|
7
|
+
"build": "tsc --noEmit && vite build",
|
|
8
|
+
"typecheck": "tsc --noEmit"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"svelte": "^5.0.0"
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"@shiftapi/vite-plugin": "^{{version}}",
|
|
15
|
+
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
|
16
|
+
"typescript": "^5.5.0",
|
|
17
|
+
"vite": "^6.0.0"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { client } from "@shiftapi/client";
|
|
3
|
+
|
|
4
|
+
let output = $state("");
|
|
5
|
+
|
|
6
|
+
$effect(() => {
|
|
7
|
+
client.GET("/health").then(({ error }) => {
|
|
8
|
+
if (error) {
|
|
9
|
+
output = `Health check failed: ${error.message}`;
|
|
10
|
+
} else {
|
|
11
|
+
output = "Health check passed. Try sending a message.";
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
async function handleSubmit(e: SubmitEvent) {
|
|
17
|
+
e.preventDefault();
|
|
18
|
+
const formData = new FormData(e.currentTarget as HTMLFormElement);
|
|
19
|
+
const message = (formData.get("message") as string).trim();
|
|
20
|
+
if (!message) return;
|
|
21
|
+
output = "Loading...";
|
|
22
|
+
const { data, error } = await client.POST("/echo", { body: { message } });
|
|
23
|
+
if (error) {
|
|
24
|
+
output = `Error: ${error.message}`;
|
|
25
|
+
} else {
|
|
26
|
+
output = `Echo: ${data.message}`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<div>
|
|
32
|
+
<h1>{{name}}</h1>
|
|
33
|
+
<form onsubmit={handleSubmit}>
|
|
34
|
+
<input type="text" name="message" placeholder="Enter a message" />
|
|
35
|
+
<button type="submit">Send</button>
|
|
36
|
+
</form>
|
|
37
|
+
<pre>{output}</pre>
|
|
38
|
+
</div>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ES2022",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"paths": {
|
|
10
|
+
"@shiftapi/client": [
|
|
11
|
+
"./node_modules/.shiftapi/client.d.ts"
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"include": [
|
|
16
|
+
"src"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineConfig } from "vite";
|
|
2
|
+
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
|
3
|
+
import shiftapi from "@shiftapi/vite-plugin";
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [
|
|
7
|
+
svelte(),
|
|
8
|
+
shiftapi({
|
|
9
|
+
server: "./cmd/{{name}}",
|
|
10
|
+
goRoot: "../..",
|
|
11
|
+
}),
|
|
12
|
+
],
|
|
13
|
+
});
|