create-shiftapi 0.0.5 → 0.0.7
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/dist/index.js
CHANGED
|
@@ -16,10 +16,14 @@ var renameFiles = {
|
|
|
16
16
|
};
|
|
17
17
|
var pkgDir = path.resolve(fileURLToPath(import.meta.url), "../..");
|
|
18
18
|
var templatesDir = path.join(pkgDir, "templates");
|
|
19
|
-
var pkgVersion =
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
var pkgVersion = "0.0.0";
|
|
20
|
+
try {
|
|
21
|
+
pkgVersion = JSON.parse(
|
|
22
|
+
fs.readFileSync(path.join(pkgDir, "package.json"), "utf-8")
|
|
23
|
+
).version;
|
|
24
|
+
} catch {
|
|
25
|
+
}
|
|
26
|
+
function replacePlaceholders(content, opts) {
|
|
23
27
|
return content.replaceAll("{{name}}", opts.name).replaceAll("{{modulePath}}", opts.modulePath).replaceAll("{{port}}", opts.port).replaceAll("{{version}}", pkgVersion);
|
|
24
28
|
}
|
|
25
29
|
function renamePath(filePath, opts) {
|
|
@@ -37,7 +41,7 @@ function copyDir(srcDir, destDir, opts) {
|
|
|
37
41
|
} else {
|
|
38
42
|
const content = fs.readFileSync(srcPath, "utf-8");
|
|
39
43
|
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
40
|
-
fs.writeFileSync(destPath,
|
|
44
|
+
fs.writeFileSync(destPath, replacePlaceholders(content, opts));
|
|
41
45
|
}
|
|
42
46
|
}
|
|
43
47
|
}
|
|
@@ -52,6 +56,39 @@ function gitInit(cwd) {
|
|
|
52
56
|
});
|
|
53
57
|
});
|
|
54
58
|
}
|
|
59
|
+
function checkCommand(cmd) {
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
execFile(cmd, ["version"], (err) => {
|
|
62
|
+
if (err) {
|
|
63
|
+
reject(new Error(`"${cmd}" is not installed or not on PATH. Please install it and try again.`));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
resolve();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
async function installDeps(cwd) {
|
|
71
|
+
await checkCommand("go");
|
|
72
|
+
await new Promise((resolve, reject) => {
|
|
73
|
+
execFile("go", ["mod", "tidy"], { cwd }, (err) => {
|
|
74
|
+
if (err) {
|
|
75
|
+
reject(err);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
resolve();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
await checkCommand("npm");
|
|
82
|
+
await new Promise((resolve, reject) => {
|
|
83
|
+
execFile("npm", ["install"], { cwd }, (err) => {
|
|
84
|
+
if (err) {
|
|
85
|
+
reject(err);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
resolve();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
55
92
|
async function scaffold(opts) {
|
|
56
93
|
copyDir(path.join(templatesDir, "base"), opts.targetDir, opts);
|
|
57
94
|
copyDir(path.join(templatesDir, opts.framework), opts.targetDir, opts);
|
|
@@ -78,15 +115,20 @@ function getGitHubUser() {
|
|
|
78
115
|
}
|
|
79
116
|
}
|
|
80
117
|
async function main() {
|
|
81
|
-
const
|
|
118
|
+
const positionalArg = process.argv[2];
|
|
82
119
|
const ghUser = getGitHubUser();
|
|
83
120
|
p.intro("create-shiftapi");
|
|
84
121
|
const project = await p.group(
|
|
85
122
|
{
|
|
86
|
-
|
|
123
|
+
rawName: () => positionalArg ? Promise.resolve(positionalArg) : p.text({
|
|
87
124
|
message: "Project name",
|
|
88
125
|
placeholder: "my-app",
|
|
89
|
-
defaultValue: "my-app"
|
|
126
|
+
defaultValue: "my-app",
|
|
127
|
+
validate: (value) => {
|
|
128
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(value)) {
|
|
129
|
+
return "Project name must only contain letters, numbers, hyphens, and underscores";
|
|
130
|
+
}
|
|
131
|
+
}
|
|
90
132
|
}),
|
|
91
133
|
framework: () => p.select({
|
|
92
134
|
message: "Framework",
|
|
@@ -97,18 +139,28 @@ async function main() {
|
|
|
97
139
|
}),
|
|
98
140
|
directory: ({ results }) => p.text({
|
|
99
141
|
message: "Directory",
|
|
100
|
-
placeholder: `./${results.
|
|
101
|
-
defaultValue: `./${results.
|
|
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
|
|
142
|
+
placeholder: `./${results.rawName}`,
|
|
143
|
+
defaultValue: `./${results.rawName}`
|
|
107
144
|
}),
|
|
145
|
+
module: ({ results }) => {
|
|
146
|
+
const name = path2.basename(results.rawName);
|
|
147
|
+
const defaultModule = ghUser ? `github.com/${ghUser}/${name}` : name;
|
|
148
|
+
return p.text({
|
|
149
|
+
message: "Go module path",
|
|
150
|
+
placeholder: defaultModule,
|
|
151
|
+
defaultValue: defaultModule
|
|
152
|
+
});
|
|
153
|
+
},
|
|
108
154
|
port: () => p.text({
|
|
109
155
|
message: "Server port",
|
|
110
156
|
placeholder: "8080",
|
|
111
|
-
defaultValue: "8080"
|
|
157
|
+
defaultValue: "8080",
|
|
158
|
+
validate: (value) => {
|
|
159
|
+
const n = parseInt(value, 10);
|
|
160
|
+
if (isNaN(n) || n < 1 || n > 65535) {
|
|
161
|
+
return "Port must be a number between 1 and 65535";
|
|
162
|
+
}
|
|
163
|
+
}
|
|
112
164
|
})
|
|
113
165
|
},
|
|
114
166
|
{
|
|
@@ -126,18 +178,29 @@ async function main() {
|
|
|
126
178
|
const s = p.spinner();
|
|
127
179
|
s.start("Scaffolding project");
|
|
128
180
|
await scaffold({
|
|
129
|
-
name: project.
|
|
181
|
+
name: path2.basename(project.rawName),
|
|
130
182
|
modulePath: project.module,
|
|
131
183
|
port: project.port,
|
|
132
184
|
framework: project.framework,
|
|
133
185
|
targetDir
|
|
134
186
|
});
|
|
135
187
|
s.stop("Project scaffolded");
|
|
188
|
+
const shouldInstallDeps = await p.confirm({
|
|
189
|
+
message: "Install dependencies? (go mod tidy & npm install)",
|
|
190
|
+
initialValue: true
|
|
191
|
+
});
|
|
192
|
+
if (p.isCancel(shouldInstallDeps)) {
|
|
193
|
+
p.cancel("Cancelled.");
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
if (shouldInstallDeps) {
|
|
197
|
+
s.start("Installing dependencies");
|
|
198
|
+
await installDeps(targetDir);
|
|
199
|
+
s.stop("Dependencies installed");
|
|
200
|
+
}
|
|
136
201
|
const relDir = path2.relative(process.cwd(), targetDir) || ".";
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
"Next steps"
|
|
140
|
-
);
|
|
202
|
+
const steps = shouldInstallDeps ? [`cd ${relDir}`, "npm run dev"] : [`cd ${relDir}`, "go mod tidy", "npm install", "npm run dev"];
|
|
203
|
+
p.note(steps.join("\n"), "Next steps");
|
|
141
204
|
p.outro("Happy hacking!");
|
|
142
205
|
}
|
|
143
206
|
main().catch((err) => {
|
package/package.json
CHANGED
|
@@ -1,42 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import { client } from "@shiftapi/client";
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { $api } from "./api";
|
|
4
3
|
|
|
5
4
|
export default function App() {
|
|
6
|
-
const [
|
|
5
|
+
const [message, setMessage] = useState("");
|
|
6
|
+
const health = $api.useQuery("get", "/health");
|
|
7
|
+
const echo = $api.useMutation("post", "/echo");
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
}
|
|
9
|
+
if (health.isLoading) return <p>Loading...</p>;
|
|
10
|
+
if (health.error) return <p>Health check failed: {health.error.message}</p>;
|
|
31
11
|
|
|
32
12
|
return (
|
|
33
13
|
<div>
|
|
34
14
|
<h1>{{name}}</h1>
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
15
|
+
<input
|
|
16
|
+
type="text"
|
|
17
|
+
value={message}
|
|
18
|
+
onChange={(e) => setMessage(e.target.value)}
|
|
19
|
+
placeholder="Enter a message"
|
|
20
|
+
/>
|
|
21
|
+
<button onClick={() => echo.mutate({ body: { message } })}>Send</button>
|
|
22
|
+
{echo.isPending && <p>Loading...</p>}
|
|
23
|
+
{echo.error && <p>Error: {echo.error.message}</p>}
|
|
24
|
+
{echo.data && <pre>Echo: {echo.data.message}</pre>}
|
|
40
25
|
</div>
|
|
41
26
|
);
|
|
42
27
|
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { StrictMode } from "react";
|
|
2
2
|
import { createRoot } from "react-dom/client";
|
|
3
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
3
4
|
import App from "./App";
|
|
4
5
|
|
|
6
|
+
const queryClient = new QueryClient();
|
|
7
|
+
|
|
5
8
|
createRoot(document.getElementById("root")!).render(
|
|
6
9
|
<StrictMode>
|
|
7
|
-
<
|
|
10
|
+
<QueryClientProvider client={queryClient}>
|
|
11
|
+
<App />
|
|
12
|
+
</QueryClientProvider>
|
|
8
13
|
</StrictMode>,
|
|
9
14
|
);
|