reset-framework-cli 1.1.2 → 1.1.5

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.
Files changed (34) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +47 -47
  3. package/package.json +4 -4
  4. package/src/commands/build.js +114 -113
  5. package/src/commands/dev.js +148 -144
  6. package/src/commands/doctor.js +89 -89
  7. package/src/commands/init.js +838 -903
  8. package/src/commands/package.js +49 -44
  9. package/src/index.js +213 -213
  10. package/src/lib/context.js +66 -66
  11. package/src/lib/framework.js +150 -28
  12. package/src/lib/logger.js +11 -11
  13. package/src/lib/output.js +214 -106
  14. package/src/lib/process.js +253 -156
  15. package/src/lib/project.js +559 -475
  16. package/src/lib/toolchain.js +62 -62
  17. package/src/lib/ui.js +244 -244
  18. package/templates/basic/README.md +15 -15
  19. package/templates/basic/frontend/README.md +73 -73
  20. package/templates/basic/frontend/eslint.config.js +23 -23
  21. package/templates/basic/frontend/index.html +13 -13
  22. package/templates/basic/frontend/package.json +31 -31
  23. package/templates/basic/frontend/public/icons.svg +24 -24
  24. package/templates/basic/frontend/src/App.css +216 -138
  25. package/templates/basic/frontend/src/App.tsx +77 -76
  26. package/templates/basic/frontend/src/assets/vite.svg +1 -1
  27. package/templates/basic/frontend/src/index.css +111 -111
  28. package/templates/basic/frontend/src/lib/reset.ts +1 -1
  29. package/templates/basic/frontend/src/main.tsx +10 -10
  30. package/templates/basic/frontend/tsconfig.app.json +28 -28
  31. package/templates/basic/frontend/tsconfig.json +7 -7
  32. package/templates/basic/frontend/tsconfig.node.json +26 -26
  33. package/templates/basic/frontend/vite.config.ts +16 -16
  34. package/templates/basic/reset.config.json +58 -58
@@ -1,206 +1,303 @@
1
- import { spawn } from "node:child_process"
2
-
3
- import { logger } from "./logger.js"
4
-
5
- function sleep(ms) {
6
- return new Promise((resolve) => {
7
- setTimeout(resolve, ms)
8
- })
9
- }
10
-
11
- export function resolveNpmCommand() {
12
- return process.platform === "win32" ? "npm.cmd" : "npm"
13
- }
14
-
15
- export function resolvePackageManagerCommand(packageManager = "npm") {
16
- if (packageManager === "npm") {
17
- return resolveNpmCommand()
18
- }
19
-
20
- if (packageManager === "yarn") {
21
- return process.platform === "win32" ? "yarn.cmd" : "yarn"
22
- }
23
-
24
- if (packageManager === "pnpm") {
25
- return process.platform === "win32" ? "pnpm.cmd" : "pnpm"
26
- }
27
-
28
- if (packageManager === "bun") {
29
- return process.platform === "win32" ? "bun.exe" : "bun"
30
- }
31
-
32
- throw new Error(`Unsupported package manager: ${packageManager}`)
33
- }
34
-
35
- export function getInstallCommandArgs(packageManager = "npm") {
36
- if (packageManager === "npm") {
37
- return ["install", "--include=optional"]
38
- }
39
-
40
- if (packageManager === "yarn") {
41
- return ["install"]
42
- }
43
-
44
- return ["install"]
45
- }
46
-
1
+ import { spawn, spawnSync } from "node:child_process"
2
+
3
+ import { logger } from "./logger.js"
4
+
5
+ function sleep(ms) {
6
+ return new Promise((resolve) => {
7
+ setTimeout(resolve, ms)
8
+ })
9
+ }
10
+
11
+ export function resolveNpmCommand() {
12
+ return process.platform === "win32" ? "npm.cmd" : "npm"
13
+ }
14
+
15
+ export function resolvePackageManagerCommand(packageManager = "npm") {
16
+ if (packageManager === "npm") {
17
+ return resolveNpmCommand()
18
+ }
19
+
20
+ if (packageManager === "yarn") {
21
+ return process.platform === "win32" ? "yarn.cmd" : "yarn"
22
+ }
23
+
24
+ if (packageManager === "pnpm") {
25
+ return process.platform === "win32" ? "pnpm.cmd" : "pnpm"
26
+ }
27
+
28
+ if (packageManager === "bun") {
29
+ return process.platform === "win32" ? "bun.exe" : "bun"
30
+ }
31
+
32
+ throw new Error(`Unsupported package manager: ${packageManager}`)
33
+ }
34
+
35
+ export function getInstallCommandArgs(packageManager = "npm") {
36
+ if (packageManager === "npm") {
37
+ return ["install", "--include=optional"]
38
+ }
39
+
40
+ if (packageManager === "yarn") {
41
+ return ["install"]
42
+ }
43
+
44
+ return ["install"]
45
+ }
46
+
47
47
  export function formatCommand(command, args = []) {
48
48
  return [command, ...args].join(" ")
49
49
  }
50
50
 
51
+ function shouldUseShell(command) {
52
+ return process.platform === "win32" && typeof command === "string" && /\.(cmd|bat)$/i.test(command)
53
+ }
54
+
51
55
  export async function runCommand(command, args = [], options = {}) {
52
56
  const { cwd, dryRun = false, env } = options
53
57
 
54
58
  logger.info(`$ ${formatCommand(command, args)}`)
55
-
56
- if (dryRun) {
57
- return
58
- }
59
-
59
+
60
+ if (dryRun) {
61
+ return
62
+ }
63
+
60
64
  await new Promise((resolve, reject) => {
61
65
  const child = spawn(command, args, {
62
66
  cwd,
63
67
  env: env ? { ...process.env, ...env } : process.env,
64
- stdio: "inherit"
68
+ stdio: "inherit",
69
+ shell: shouldUseShell(command)
65
70
  })
66
71
 
67
72
  child.once("error", reject)
68
- child.once("exit", (code, signal) => {
69
- if (code === 0) {
70
- resolve(undefined)
71
- return
72
- }
73
-
74
- reject(
75
- new Error(
76
- signal
77
- ? `Command terminated by signal: ${signal}`
78
- : `Command exited with code ${code}`
79
- )
80
- )
81
- })
82
- })
83
- }
84
-
85
- export async function captureCommandOutput(command, args = [], options = {}) {
86
- const { cwd, env } = options
87
-
88
- await Promise.resolve()
89
-
73
+ child.once("exit", (code, signal) => {
74
+ if (code === 0) {
75
+ resolve(undefined)
76
+ return
77
+ }
78
+
79
+ reject(
80
+ new Error(
81
+ signal
82
+ ? `Command terminated by signal: ${signal}`
83
+ : `Command exited with code ${code}`
84
+ )
85
+ )
86
+ })
87
+ })
88
+ }
89
+
90
+ export async function captureCommandOutput(command, args = [], options = {}) {
91
+ const { cwd, env } = options
92
+
93
+ await Promise.resolve()
94
+
90
95
  return await new Promise((resolve, reject) => {
91
96
  const child = spawn(command, args, {
92
97
  cwd,
93
98
  env: env ? { ...process.env, ...env } : process.env,
94
- stdio: ["ignore", "pipe", "pipe"]
95
- })
96
-
97
- let stdout = ""
98
- let stderr = ""
99
-
100
- child.stdout?.on("data", (chunk) => {
101
- stdout += String(chunk)
102
- })
103
-
104
- child.stderr?.on("data", (chunk) => {
105
- stderr += String(chunk)
106
- })
107
-
108
- child.once("error", reject)
109
- child.once("exit", (code, signal) => {
110
- if (code === 0) {
111
- resolve(stdout.trim())
112
- return
113
- }
114
-
115
- reject(
116
- new Error(
117
- signal
118
- ? `Command terminated by signal: ${signal}`
119
- : (stderr.trim() || stdout.trim() || `Command exited with code ${code}`)
120
- )
121
- )
99
+ stdio: ["ignore", "pipe", "pipe"],
100
+ shell: shouldUseShell(command)
122
101
  })
123
- })
124
- }
125
-
102
+
103
+ let stdout = ""
104
+ let stderr = ""
105
+
106
+ child.stdout?.on("data", (chunk) => {
107
+ stdout += String(chunk)
108
+ })
109
+
110
+ child.stderr?.on("data", (chunk) => {
111
+ stderr += String(chunk)
112
+ })
113
+
114
+ child.once("error", reject)
115
+ child.once("exit", (code, signal) => {
116
+ if (code === 0) {
117
+ resolve(stdout.trim())
118
+ return
119
+ }
120
+
121
+ reject(
122
+ new Error(
123
+ signal
124
+ ? `Command terminated by signal: ${signal}`
125
+ : (stderr.trim() || stdout.trim() || `Command exited with code ${code}`)
126
+ )
127
+ )
128
+ })
129
+ })
130
+ }
131
+
126
132
  export function spawnCommand(command, args = [], options = {}) {
127
133
  const { cwd, dryRun = false, env } = options
128
134
 
129
135
  logger.info(`$ ${formatCommand(command, args)}`)
130
-
131
- if (dryRun) {
132
- return null
133
- }
134
-
136
+
137
+ if (dryRun) {
138
+ return null
139
+ }
140
+
135
141
  return spawn(command, args, {
136
142
  cwd,
137
143
  env: env ? { ...process.env, ...env } : process.env,
138
- stdio: "inherit"
144
+ detached: process.platform !== "win32",
145
+ stdio: "inherit",
146
+ shell: shouldUseShell(command)
139
147
  })
140
148
  }
149
+
150
+ export function waitForProcessExit(child, label) {
151
+ return new Promise((resolve, reject) => {
152
+ child.once("error", reject)
153
+ child.once("exit", (code, signal) => {
154
+ if (code === 0 || signal === "SIGINT" || signal === "SIGTERM") {
155
+ resolve(undefined)
156
+ return
157
+ }
158
+
159
+ reject(
160
+ new Error(
161
+ signal
162
+ ? `${label} terminated by signal: ${signal}`
163
+ : `${label} exited with code ${code}`
164
+ )
165
+ )
166
+ })
167
+ })
168
+ }
169
+
170
+ export function registerChildCleanup(children) {
171
+ const activeChildren = children.filter(Boolean)
172
+ let cleanupPromise = null
141
173
 
142
- export function waitForProcessExit(child, label) {
143
- return new Promise((resolve, reject) => {
144
- child.once("error", reject)
145
- child.once("exit", (code, signal) => {
146
- if (code === 0 || signal === "SIGINT" || signal === "SIGTERM") {
147
- resolve(undefined)
174
+ const waitForChildExit = (child, timeoutMs = 3000) =>
175
+ new Promise((resolve) => {
176
+ if (!child || child.exitCode !== null || child.signalCode !== null) {
177
+ resolve(true)
148
178
  return
149
179
  }
150
180
 
151
- reject(
152
- new Error(
153
- signal
154
- ? `${label} terminated by signal: ${signal}`
155
- : `${label} exited with code ${code}`
156
- )
157
- )
181
+ const handleExit = () => {
182
+ clearTimeout(timeout)
183
+ child.off("error", handleError)
184
+ resolve(true)
185
+ }
186
+
187
+ const handleError = () => {
188
+ clearTimeout(timeout)
189
+ child.off("exit", handleExit)
190
+ resolve(true)
191
+ }
192
+
193
+ const timeout = setTimeout(() => {
194
+ child.off("exit", handleExit)
195
+ child.off("error", handleError)
196
+ resolve(false)
197
+ }, timeoutMs)
198
+
199
+ child.once("exit", handleExit)
200
+ child.once("error", handleError)
158
201
  })
159
- })
160
- }
161
202
 
162
- export function registerChildCleanup(children) {
163
- const activeChildren = children.filter(Boolean)
203
+ const terminateChild = async (child) => {
204
+ if (!child || child.pid == null || child.exitCode !== null || child.signalCode !== null) {
205
+ return
206
+ }
207
+
208
+ if (process.platform === "win32") {
209
+ const result = spawnSync("taskkill.exe", ["/pid", String(child.pid), "/t", "/f"], {
210
+ stdio: "ignore",
211
+ windowsHide: true
212
+ })
213
+
214
+ if (result.status !== 0 && child.exitCode === null && child.signalCode === null) {
215
+ try {
216
+ child.kill("SIGTERM")
217
+ } catch {
218
+ // Ignore late shutdown races.
219
+ }
220
+ }
164
221
 
165
- const cleanup = () => {
166
- for (const child of activeChildren) {
167
- if (child.exitCode === null && !child.killed) {
168
- child.kill("SIGTERM")
222
+ await waitForChildExit(child, 2000)
223
+ return
224
+ }
225
+
226
+ try {
227
+ process.kill(-child.pid, "SIGTERM")
228
+ } catch (error) {
229
+ if (error?.code !== "ESRCH") {
230
+ try {
231
+ child.kill("SIGTERM")
232
+ } catch {
233
+ // Ignore late shutdown races.
234
+ }
169
235
  }
170
236
  }
237
+
238
+ if (await waitForChildExit(child, 3000)) {
239
+ return
240
+ }
241
+
242
+ try {
243
+ process.kill(-child.pid, "SIGKILL")
244
+ } catch (error) {
245
+ if (error?.code !== "ESRCH") {
246
+ try {
247
+ child.kill("SIGKILL")
248
+ } catch {
249
+ // Ignore late shutdown races.
250
+ }
251
+ }
252
+ }
253
+
254
+ await waitForChildExit(child, 1000)
255
+ }
256
+
257
+ const cleanup = async () => {
258
+ if (cleanupPromise) {
259
+ return cleanupPromise
260
+ }
261
+
262
+ cleanupPromise = (async () => {
263
+ await Promise.all(activeChildren.map((child) => terminateChild(child)))
264
+ })()
265
+
266
+ return cleanupPromise
171
267
  }
172
268
 
173
269
  const handleSignal = () => {
174
- cleanup()
175
- process.exit(0)
270
+ void cleanup().finally(() => {
271
+ process.exit(0)
272
+ })
176
273
  }
177
274
 
178
275
  process.on("SIGINT", handleSignal)
179
276
  process.on("SIGTERM", handleSignal)
180
277
 
181
- return () => {
278
+ return async () => {
182
279
  process.off("SIGINT", handleSignal)
183
280
  process.off("SIGTERM", handleSignal)
184
- cleanup()
281
+ await cleanup()
185
282
  }
186
283
  }
187
-
188
- export async function waitForUrl(url, options = {}) {
189
- const { intervalMs = 250, timeoutMs = 15000 } = options
190
- const deadline = Date.now() + timeoutMs
191
-
192
- while (Date.now() < deadline) {
193
- try {
194
- const response = await fetch(url)
195
- if (response.status < 500) {
196
- return
197
- }
198
- } catch {
199
- // Retry until timeout.
200
- }
201
-
202
- await sleep(intervalMs)
203
- }
204
-
205
- throw new Error(`Timed out waiting for ${url}`)
206
- }
284
+
285
+ export async function waitForUrl(url, options = {}) {
286
+ const { intervalMs = 250, timeoutMs = 15000 } = options
287
+ const deadline = Date.now() + timeoutMs
288
+
289
+ while (Date.now() < deadline) {
290
+ try {
291
+ const response = await fetch(url)
292
+ if (response.status < 500) {
293
+ return
294
+ }
295
+ } catch {
296
+ // Retry until timeout.
297
+ }
298
+
299
+ await sleep(intervalMs)
300
+ }
301
+
302
+ throw new Error(`Timed out waiting for ${url}`)
303
+ }