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 = JSON.parse(
20
- fs.readFileSync(path.join(pkgDir, "package.json"), "utf-8")
21
- ).version;
22
- function replaceplaceholders(content, opts) {
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, replaceplaceholders(content, opts));
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 positionalName = process.argv[2];
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
- name: () => positionalName ? Promise.resolve(positionalName) : p.text({
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.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
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.name,
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
- p.note(
138
- [`cd ${relDir}`, "go mod tidy", "npm install", "npm run dev"].join("\n"),
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,6 +1,6 @@
1
1
  {
2
2
  "name": "create-shiftapi",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "Scaffold a new ShiftAPI fullstack app",
5
5
  "author": "Frank Chiarulli Jr. <frank@frankchiarulli.com>",
6
6
  "license": "MIT",
@@ -8,6 +8,8 @@
8
8
  "typecheck": "tsc --noEmit"
9
9
  },
10
10
  "dependencies": {
11
+ "@tanstack/react-query": "^5.0.0",
12
+ "openapi-react-query": "^0.2.0",
11
13
  "react": "^19.0.0",
12
14
  "react-dom": "^19.0.0"
13
15
  },
@@ -1,42 +1,27 @@
1
- import { useEffect, useState } from "react";
2
- import type { FormEvent } from "react";
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 [output, setOutput] = useState("");
5
+ const [message, setMessage] = useState("");
6
+ const health = $api.useQuery("get", "/health");
7
+ const echo = $api.useMutation("post", "/echo");
7
8
 
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
- }
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
- <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>
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
  }
@@ -0,0 +1,4 @@
1
+ import createClient from "openapi-react-query";
2
+ import { client } from "@shiftapi/client";
3
+
4
+ export const $api = createClient(client);
@@ -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
- <App />
10
+ <QueryClientProvider client={queryClient}>
11
+ <App />
12
+ </QueryClientProvider>
8
13
  </StrictMode>,
9
14
  );