reset-framework-cli 1.1.5 → 1.2.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.
Files changed (34) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +57 -47
  3. package/package.json +8 -4
  4. package/src/commands/build.js +125 -113
  5. package/src/commands/dev.js +156 -144
  6. package/src/commands/doctor.js +137 -89
  7. package/src/commands/init.js +822 -822
  8. package/src/commands/package.js +41 -41
  9. package/src/index.js +215 -213
  10. package/src/lib/context.js +66 -66
  11. package/src/lib/framework.js +126 -39
  12. package/src/lib/logger.js +11 -11
  13. package/src/lib/output.js +147 -147
  14. package/src/lib/process.js +148 -148
  15. package/src/lib/project.js +759 -468
  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 -216
  25. package/templates/basic/frontend/src/App.tsx +77 -77
  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
package/src/lib/output.js CHANGED
@@ -8,93 +8,93 @@ import {
8
8
  resolveFrameworkRuntimeBinary,
9
9
  resolveFrameworkRuntimeOutputDir
10
10
  } from "./project.js"
11
-
12
- function escapePlistString(value) {
13
- return value
14
- .replaceAll("&", "&")
15
- .replaceAll("<", "&lt;")
16
- .replaceAll(">", "&gt;")
17
- }
18
-
19
- function serializePlistValue(value, indentLevel = 1) {
20
- const indent = " ".repeat(indentLevel)
21
-
22
- if (Array.isArray(value)) {
23
- if (value.length === 0) {
24
- return `${indent}<array/>\n`
25
- }
26
-
27
- return (
28
- `${indent}<array>\n` +
29
- value.map((entry) => serializePlistValue(entry, indentLevel + 1)).join("") +
30
- `${indent}</array>\n`
31
- )
32
- }
33
-
34
- if (typeof value === "object" && value !== null) {
35
- const entries = Object.entries(value)
36
- if (entries.length === 0) {
37
- return `${indent}<dict/>\n`
38
- }
39
-
40
- return (
41
- `${indent}<dict>\n` +
42
- entries
43
- .map(
44
- ([key, entryValue]) =>
45
- `${" ".repeat(indentLevel + 1)}<key>${escapePlistString(key)}</key>\n${serializePlistValue(entryValue, indentLevel + 1)}`
46
- )
47
- .join("") +
48
- `${indent}</dict>\n`
49
- )
50
- }
51
-
52
- if (typeof value === "boolean") {
53
- return `${indent}<${value ? "true" : "false"}/>\n`
54
- }
55
-
56
- if (typeof value === "number") {
57
- return `${indent}<integer>${value}</integer>\n`
58
- }
59
-
60
- return `${indent}<string>${escapePlistString(String(value))}</string>\n`
61
- }
62
-
11
+
12
+ function escapePlistString(value) {
13
+ return value
14
+ .replaceAll("&", "&amp;")
15
+ .replaceAll("<", "&lt;")
16
+ .replaceAll(">", "&gt;")
17
+ }
18
+
19
+ function serializePlistValue(value, indentLevel = 1) {
20
+ const indent = " ".repeat(indentLevel)
21
+
22
+ if (Array.isArray(value)) {
23
+ if (value.length === 0) {
24
+ return `${indent}<array/>\n`
25
+ }
26
+
27
+ return (
28
+ `${indent}<array>\n` +
29
+ value.map((entry) => serializePlistValue(entry, indentLevel + 1)).join("") +
30
+ `${indent}</array>\n`
31
+ )
32
+ }
33
+
34
+ if (typeof value === "object" && value !== null) {
35
+ const entries = Object.entries(value)
36
+ if (entries.length === 0) {
37
+ return `${indent}<dict/>\n`
38
+ }
39
+
40
+ return (
41
+ `${indent}<dict>\n` +
42
+ entries
43
+ .map(
44
+ ([key, entryValue]) =>
45
+ `${" ".repeat(indentLevel + 1)}<key>${escapePlistString(key)}</key>\n${serializePlistValue(entryValue, indentLevel + 1)}`
46
+ )
47
+ .join("") +
48
+ `${indent}</dict>\n`
49
+ )
50
+ }
51
+
52
+ if (typeof value === "boolean") {
53
+ return `${indent}<${value ? "true" : "false"}/>\n`
54
+ }
55
+
56
+ if (typeof value === "number") {
57
+ return `${indent}<integer>${value}</integer>\n`
58
+ }
59
+
60
+ return `${indent}<string>${escapePlistString(String(value))}</string>\n`
61
+ }
62
+
63
63
  function buildMacOSInfoPlist(config) {
64
- const urlTypes = Array.isArray(config.protocols?.schemes)
65
- ? config.protocols.schemes.map((schemeConfig) => ({
66
- CFBundleTypeRole: schemeConfig.role ?? "Viewer",
67
- CFBundleURLName:
68
- typeof schemeConfig.name === "string" && schemeConfig.name.trim() !== ""
69
- ? schemeConfig.name
70
- : `${config.appId}.${schemeConfig.scheme}`,
71
- CFBundleURLSchemes: [schemeConfig.scheme]
72
- }))
73
- : []
74
-
75
- const plist = {
76
- CFBundleDevelopmentRegion: "English",
77
- CFBundleDisplayName: config.productName,
78
- CFBundleExecutable: "reset-framework",
79
- CFBundleIdentifier: config.appId,
80
- CFBundleInfoDictionaryVersion: "6.0",
81
- CFBundleName: config.productName,
82
- CFBundlePackageType: "APPL",
83
- CFBundleShortVersionString: config.version,
84
- CFBundleVersion: config.version,
85
- CSResourcesFileMapped: true
86
- }
87
-
88
- if (urlTypes.length > 0) {
89
- plist.CFBundleURLTypes = urlTypes
90
- }
91
-
92
- return (
93
- `<?xml version="1.0" encoding="UTF-8"?>\n` +
94
- `<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n` +
95
- `<plist version="1.0">\n` +
96
- serializePlistValue(plist, 0) +
97
- `</plist>\n`
64
+ const urlTypes = Array.isArray(config.protocols?.schemes)
65
+ ? config.protocols.schemes.map((schemeConfig) => ({
66
+ CFBundleTypeRole: schemeConfig.role ?? "Viewer",
67
+ CFBundleURLName:
68
+ typeof schemeConfig.name === "string" && schemeConfig.name.trim() !== ""
69
+ ? schemeConfig.name
70
+ : `${config.appId}.${schemeConfig.scheme}`,
71
+ CFBundleURLSchemes: [schemeConfig.scheme]
72
+ }))
73
+ : []
74
+
75
+ const plist = {
76
+ CFBundleDevelopmentRegion: "English",
77
+ CFBundleDisplayName: config.productName,
78
+ CFBundleExecutable: "reset-framework",
79
+ CFBundleIdentifier: config.appId,
80
+ CFBundleInfoDictionaryVersion: "6.0",
81
+ CFBundleName: config.productName,
82
+ CFBundlePackageType: "APPL",
83
+ CFBundleShortVersionString: config.version,
84
+ CFBundleVersion: config.version,
85
+ CSResourcesFileMapped: true
86
+ }
87
+
88
+ if (urlTypes.length > 0) {
89
+ plist.CFBundleURLTypes = urlTypes
90
+ }
91
+
92
+ return (
93
+ `<?xml version="1.0" encoding="UTF-8"?>\n` +
94
+ `<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n` +
95
+ `<plist version="1.0">\n` +
96
+ serializePlistValue(plist, 0) +
97
+ `</plist>\n`
98
98
  )
99
99
  }
100
100
 
@@ -116,59 +116,59 @@ function shouldCopyWindowsRuntimeArtifact(entryName) {
116
116
  function escapePowerShellLiteral(value) {
117
117
  return String(value).replaceAll("'", "''")
118
118
  }
119
-
120
- export async function stageMacOSAppBundle(frameworkBuildPaths, appPaths, config, options = {}) {
121
- const { dryRun = false, outputPaths, configPath } = options
122
-
123
- if (!outputPaths) {
124
- throw new Error("stageMacOSAppBundle requires resolved output paths")
125
- }
126
-
127
- if (!existsSync(frameworkBuildPaths.releaseAppTemplate) && !dryRun) {
128
- throw new Error(
129
- `Missing runtime app bundle at ${frameworkBuildPaths.releaseAppTemplate}. Run reset-framework-cli build first.`
130
- )
131
- }
132
-
133
- if (!existsSync(outputPaths.frontendEntryFile) && !dryRun) {
134
- throw new Error(
135
- `Missing frontend build output at ${outputPaths.frontendEntryFile}. Run the frontend build first.`
136
- )
137
- }
138
-
139
- logger.info(`Staging app bundle into ${outputPaths.appBundlePath}`)
140
-
141
- if (dryRun) {
142
- logger.info(`- copy ${frameworkBuildPaths.releaseAppTemplate} -> ${outputPaths.appBundlePath}`)
143
- logger.info(`- copy ${configPath} -> ${outputPaths.bundledConfigPath}`)
144
- logger.info(`- copy ${outputPaths.frontendDistDir} -> ${outputPaths.bundledFrontendDir}`)
145
- return outputPaths
146
- }
147
-
148
- await mkdir(outputPaths.macosDir, { recursive: true })
149
- await rm(outputPaths.appBundlePath, { recursive: true, force: true })
150
- await cp(frameworkBuildPaths.releaseAppTemplate, outputPaths.appBundlePath, { recursive: true })
151
-
152
- await mkdir(outputPaths.resourcesDir, { recursive: true })
153
- await cp(configPath, outputPaths.bundledConfigPath, { force: true })
154
- await rm(outputPaths.bundledFrontendDir, { recursive: true, force: true })
155
- await cp(outputPaths.frontendDistDir, outputPaths.bundledFrontendDir, { recursive: true })
156
- await writeFile(
157
- path.join(outputPaths.appBundlePath, "Contents", "Info.plist"),
158
- buildMacOSInfoPlist(config),
159
- "utf8"
160
- )
161
-
119
+
120
+ export async function stageMacOSAppBundle(frameworkPaths, frameworkBuildPaths, config, options = {}) {
121
+ const { dryRun = false, outputPaths, configPath, preferSource = false } = options
122
+ const runtimeSourceBundle = resolveFrameworkRuntimeOutputDir(frameworkPaths, frameworkBuildPaths, "release", {
123
+ mustExist: !dryRun,
124
+ preferSource
125
+ })
126
+
127
+ if (!outputPaths) {
128
+ throw new Error("stageMacOSAppBundle requires resolved output paths")
129
+ }
130
+
131
+ if (!existsSync(outputPaths.frontendEntryFile) && !dryRun) {
132
+ throw new Error(
133
+ `Missing frontend build output at ${outputPaths.frontendEntryFile}. Run the frontend build first.`
134
+ )
135
+ }
136
+
137
+ logger.info(`Staging app bundle into ${outputPaths.appBundlePath}`)
138
+
139
+ if (dryRun) {
140
+ logger.info(`- copy ${runtimeSourceBundle} -> ${outputPaths.appBundlePath}`)
141
+ logger.info(`- copy ${configPath} -> ${outputPaths.bundledConfigPath}`)
142
+ logger.info(`- copy ${outputPaths.frontendDistDir} -> ${outputPaths.bundledFrontendDir}`)
143
+ return outputPaths
144
+ }
145
+
146
+ await mkdir(outputPaths.macosDir, { recursive: true })
147
+ await rm(outputPaths.appBundlePath, { recursive: true, force: true })
148
+ await cp(runtimeSourceBundle, outputPaths.appBundlePath, { recursive: true })
149
+
150
+ await mkdir(outputPaths.resourcesDir, { recursive: true })
151
+ await cp(configPath, outputPaths.bundledConfigPath, { force: true })
152
+ await rm(outputPaths.bundledFrontendDir, { recursive: true, force: true })
153
+ await cp(outputPaths.frontendDistDir, outputPaths.bundledFrontendDir, { recursive: true })
154
+ await writeFile(
155
+ path.join(outputPaths.appBundlePath, "Contents", "Info.plist"),
156
+ buildMacOSInfoPlist(config),
157
+ "utf8"
158
+ )
159
+
162
160
  return outputPaths
163
161
  }
164
162
 
165
- export async function stageWindowsAppBundle(frameworkBuildPaths, appPaths, config, options = {}) {
166
- const { dryRun = false, outputPaths, configPath } = options
167
- const runtimeSourceBinary = resolveFrameworkRuntimeBinary(frameworkBuildPaths, "release", {
168
- mustExist: !dryRun
163
+ export async function stageWindowsAppBundle(frameworkPaths, frameworkBuildPaths, config, options = {}) {
164
+ const { dryRun = false, outputPaths, configPath, preferSource = false } = options
165
+ const runtimeSourceBinary = resolveFrameworkRuntimeBinary(frameworkPaths, frameworkBuildPaths, "release", {
166
+ mustExist: !dryRun,
167
+ preferSource
169
168
  })
170
- const runtimeSourceDir = resolveFrameworkRuntimeOutputDir(frameworkBuildPaths, "release", {
171
- mustExist: !dryRun
169
+ const runtimeSourceDir = resolveFrameworkRuntimeOutputDir(frameworkPaths, frameworkBuildPaths, "release", {
170
+ mustExist: !dryRun,
171
+ preferSource
172
172
  })
173
173
 
174
174
  if (!outputPaths) {
@@ -218,20 +218,20 @@ export async function stageWindowsAppBundle(frameworkBuildPaths, appPaths, confi
218
218
 
219
219
  return outputPaths
220
220
  }
221
-
221
+
222
222
  export async function createMacOSZipArchive(outputPaths, options = {}) {
223
223
  const { dryRun = false } = options
224
-
225
- logger.info(`Packaging ${outputPaths.appBundlePath} -> ${outputPaths.zipPath}`)
226
-
227
- if (process.platform !== "darwin") {
228
- throw new Error("Packaging is only implemented for macOS right now")
229
- }
230
-
231
- if (!dryRun) {
232
- await mkdir(outputPaths.packagesDir, { recursive: true })
233
- }
234
-
224
+
225
+ logger.info(`Packaging ${outputPaths.appBundlePath} -> ${outputPaths.zipPath}`)
226
+
227
+ if (process.platform !== "darwin") {
228
+ throw new Error("Packaging is only implemented for macOS right now")
229
+ }
230
+
231
+ if (!dryRun) {
232
+ await mkdir(outputPaths.packagesDir, { recursive: true })
233
+ }
234
+
235
235
  await runCommand(
236
236
  "ditto",
237
237
  ["-c", "-k", "--sequesterRsrc", "--keepParent", outputPaths.appBundlePath, outputPaths.zipPath],
@@ -1,49 +1,49 @@
1
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
-
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
  }
@@ -56,11 +56,11 @@ export async function runCommand(command, args = [], options = {}) {
56
56
  const { cwd, dryRun = false, env } = options
57
57
 
58
58
  logger.info(`$ ${formatCommand(command, args)}`)
59
-
60
- if (dryRun) {
61
- return
62
- }
63
-
59
+
60
+ if (dryRun) {
61
+ return
62
+ }
63
+
64
64
  await new Promise((resolve, reject) => {
65
65
  const child = spawn(command, args, {
66
66
  cwd,
@@ -70,28 +70,28 @@ export async function runCommand(command, args = [], options = {}) {
70
70
  })
71
71
 
72
72
  child.once("error", reject)
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
-
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
+
95
95
  return await new Promise((resolve, reject) => {
96
96
  const child = spawn(command, args, {
97
97
  cwd,
@@ -99,45 +99,45 @@ export async function captureCommandOutput(command, args = [], options = {}) {
99
99
  stdio: ["ignore", "pipe", "pipe"],
100
100
  shell: shouldUseShell(command)
101
101
  })
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
-
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
+
132
132
  export function spawnCommand(command, args = [], options = {}) {
133
133
  const { cwd, dryRun = false, env } = options
134
134
 
135
135
  logger.info(`$ ${formatCommand(command, args)}`)
136
-
137
- if (dryRun) {
138
- return null
139
- }
140
-
136
+
137
+ if (dryRun) {
138
+ return null
139
+ }
140
+
141
141
  return spawn(command, args, {
142
142
  cwd,
143
143
  env: env ? { ...process.env, ...env } : process.env,
@@ -146,27 +146,27 @@ export function spawnCommand(command, args = [], options = {}) {
146
146
  shell: shouldUseShell(command)
147
147
  })
148
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
-
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
170
  export function registerChildCleanup(children) {
171
171
  const activeChildren = children.filter(Boolean)
172
172
  let cleanupPromise = null
@@ -281,23 +281,23 @@ export function registerChildCleanup(children) {
281
281
  await cleanup()
282
282
  }
283
283
  }
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
- }
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
+ }