@nosto/nosto-cli 1.0.4 → 1.1.0

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 (45) hide show
  1. package/.github/copilot-instructions.md +2 -2
  2. package/.github/dependabot.yml +12 -1
  3. package/README.md +2 -2
  4. package/eslint.config.js +11 -1
  5. package/package.json +8 -8
  6. package/src/api/retry.ts +24 -8
  7. package/src/config/authConfig.ts +2 -2
  8. package/src/config/config.ts +7 -1
  9. package/src/errors/InvalidLoginResponseError.ts +1 -1
  10. package/src/errors/withErrorHandler.ts +10 -14
  11. package/src/filesystem/homeDirectory.ts +3 -0
  12. package/src/filesystem/processInBatches.ts +1 -0
  13. package/src/modules/login.ts +10 -4
  14. package/src/modules/search-templates/push.ts +1 -21
  15. package/src/modules/setup.ts +1 -3
  16. package/src/modules/status.ts +1 -1
  17. package/src/vite-env.d.ts +1 -0
  18. package/test/api/retry.test.ts +41 -1
  19. package/test/commander.test.ts +31 -2
  20. package/test/config/authConfig.test.ts +62 -0
  21. package/test/config/config.test.ts +71 -0
  22. package/test/console/logger.test.ts +16 -0
  23. package/test/console/userPrompt.test.ts +30 -0
  24. package/test/errors/withErrorHandler.test.ts +80 -5
  25. package/test/filesystem/asserts/assertGitRepo.test.ts +35 -0
  26. package/test/filesystem/asserts/assertNostoTemplate.test.ts +53 -0
  27. package/test/filesystem/filesystem.test.ts +17 -1
  28. package/test/filesystem/plugins.test.ts +73 -2
  29. package/test/filesystem/processInBatches.test.ts +34 -0
  30. package/test/modules/login.test.ts +38 -0
  31. package/test/modules/logout.test.ts +24 -0
  32. package/test/modules/search-templates/build.legacy.test.ts +25 -0
  33. package/test/modules/search-templates/build.modern.test.ts +5 -1
  34. package/test/modules/search-templates/dev.legacy.test.ts +23 -0
  35. package/test/modules/search-templates/pull.test.ts +38 -1
  36. package/test/modules/search-templates/push.test.ts +145 -1
  37. package/test/modules/setup.test.ts +16 -0
  38. package/test/modules/status.test.ts +45 -4
  39. package/test/setup.ts +11 -0
  40. package/test/utils/generateEndpointMock.ts +10 -3
  41. package/test/utils/mockAuthServer.ts +72 -0
  42. package/test/utils/mockConsole.ts +20 -0
  43. package/test/utils/mockFileSystem.ts +46 -12
  44. package/test/utils/mockStarterManifest.ts +3 -2
  45. package/vitest.config.ts +13 -5
@@ -4,7 +4,7 @@ import { pushSearchTemplate } from "#modules/search-templates/push.ts"
4
4
  import { setupMockConfig } from "#test/utils/mockConfig.ts"
5
5
  import { setupMockConsole } from "#test/utils/mockConsole.ts"
6
6
  import { setupMockFileSystem } from "#test/utils/mockFileSystem.ts"
7
- import { mockPutSourceFile, setupMockServer } from "#test/utils/mockServer.ts"
7
+ import { mockFetchSourceFile, mockPutSourceFile, setupMockServer } from "#test/utils/mockServer.ts"
8
8
 
9
9
  const fs = setupMockFileSystem()
10
10
  const server = setupMockServer()
@@ -106,4 +106,148 @@ describe("Push Search Template", () => {
106
106
 
107
107
  await pushSearchTemplate({ paths: [], force: true })
108
108
  })
109
+
110
+ it("should abort if remote template is already up to date", async () => {
111
+ fs.writeFile("index.js", "old content")
112
+ mockFetchSourceFile(server, {
113
+ path: "build/hash",
114
+ response: "34a780ad578b997db55b260beb60b501f3e04d30ba1a51fcf43cd8dd1241780d",
115
+ contentType: "raw"
116
+ })
117
+
118
+ await pushSearchTemplate({ paths: [], force: false })
119
+ expect(terminal.getSpy("success")).toHaveBeenCalledWith("Remote template is already up to date.")
120
+ fs.expectFile("/.nostocache/hash").toContain("34a780ad578b997db55b260beb60b501f3e04d30ba1a51fcf43cd8dd1241780d")
121
+ })
122
+
123
+ it("should skip prompt when remote and last seen hashes match", async () => {
124
+ fs.writeFile("index.js", "content with @nosto/preact")
125
+ fs.writeFile(".nostocache/hash", "matching-hash")
126
+
127
+ mockFetchSourceFile(server, {
128
+ path: "build/hash",
129
+ response: "matching-hash",
130
+ contentType: "raw"
131
+ })
132
+ mockPutSourceFile(server, { path: "index.js" })
133
+ mockPutSourceFile(server, { path: "build/hash" })
134
+
135
+ await pushSearchTemplate({ paths: [], force: false })
136
+
137
+ expect(terminal.getSpy("info")).toHaveBeenCalledWith("Pushing template from: /")
138
+ expect(terminal.getSpy("info")).toHaveBeenCalledWith("Found 2 files to push (1 source, 1 built, 0 ignored).")
139
+ })
140
+
141
+ it("should handle build/hash file already existing", async () => {
142
+ fs.writeFile("index.js", "content with @nosto/preact")
143
+ fs.writeFile(".nostocache/hash", "matching-hash")
144
+ fs.writeFile("build/hash", "matching-hash")
145
+
146
+ mockFetchSourceFile(server, {
147
+ path: "build/hash",
148
+ response: "matching-hash",
149
+ contentType: "raw"
150
+ })
151
+ mockPutSourceFile(server, { path: "index.js" })
152
+ mockPutSourceFile(server, { path: "build/hash" })
153
+
154
+ await pushSearchTemplate({ paths: [], force: false })
155
+
156
+ expect(terminal.getSpy("info")).toHaveBeenCalledWith("Pushing template from: /")
157
+ expect(terminal.getSpy("info")).toHaveBeenCalledWith("Found 2 files to push (1 source, 1 built, 1 ignored).")
158
+ })
159
+
160
+ it("should prompt for confirmation when remote hash exists but no last seen hash", async () => {
161
+ fs.writeFile("index.js", "content with @nosto/preact")
162
+
163
+ mockFetchSourceFile(server, {
164
+ path: "build/hash",
165
+ response: "remote-hash",
166
+ contentType: "raw"
167
+ })
168
+ mockPutSourceFile(server, { path: "index.js" })
169
+ mockPutSourceFile(server, { path: "build/hash" })
170
+
171
+ terminal.setUserResponse("y")
172
+
173
+ await pushSearchTemplate({ paths: [], force: false })
174
+
175
+ terminal.expect.user.toHaveBeenPromptedWith(
176
+ "It seems that this is the first time you are pushing to this environment. Please make sure your local copy is up to date. Continue? (y/N):"
177
+ )
178
+ })
179
+
180
+ it("should prompt for confirmation when remote hash differs from last seen hash", async () => {
181
+ fs.writeFile("index.js", "content with @nosto/preact")
182
+ fs.writeFile(".nostocache/hash", "old-hash")
183
+
184
+ mockFetchSourceFile(server, {
185
+ path: "build/hash",
186
+ response: "different-remote-hash",
187
+ contentType: "raw"
188
+ })
189
+ mockPutSourceFile(server, { path: "index.js" })
190
+ mockPutSourceFile(server, { path: "build/hash" })
191
+
192
+ terminal.setUserResponse("y")
193
+
194
+ await pushSearchTemplate({ paths: [], force: false })
195
+
196
+ terminal.expect.user.toHaveBeenPromptedWith(
197
+ "It seems that the template has been changed since your last push. Are you sure you want to continue? (y/N):"
198
+ )
199
+ })
200
+
201
+ it("should prompt for confirmation when no remote hash exists", async () => {
202
+ fs.writeFile("index.js", "content with @nosto/preact")
203
+ fs.writeFile(".nostocache/hash", "some-hash")
204
+
205
+ mockFetchSourceFile(server, {
206
+ path: "build/hash",
207
+ error: { status: 404, message: "Not Found" }
208
+ })
209
+ mockPutSourceFile(server, { path: "index.js" })
210
+ mockPutSourceFile(server, { path: "build/hash" })
211
+
212
+ terminal.setUserResponse("y")
213
+
214
+ await pushSearchTemplate({ paths: [], force: false })
215
+
216
+ terminal.expect.user.toHaveBeenPromptedWith(
217
+ "It seems that this is the first time you are pushing to this environment. Please make sure your local copy is up to date. Continue? (y/N):"
218
+ )
219
+ })
220
+
221
+ it("should cancel operation when user declines prompt for first-time push", async () => {
222
+ fs.writeFile("index.js", "content with @nosto/preact")
223
+
224
+ mockFetchSourceFile(server, {
225
+ path: "build/hash",
226
+ response: "remote-hash",
227
+ contentType: "raw"
228
+ })
229
+
230
+ terminal.setUserResponse("N")
231
+
232
+ await pushSearchTemplate({ paths: [], force: false })
233
+
234
+ expect(terminal.getSpy("info")).toHaveBeenCalledWith("Push operation cancelled by user.")
235
+ })
236
+
237
+ it("should cancel operation when user declines prompt for conflicting changes", async () => {
238
+ fs.writeFile("index.js", "content with @nosto/preact")
239
+ fs.writeFile(".nostocache/hash", "old-hash")
240
+
241
+ mockFetchSourceFile(server, {
242
+ path: "build/hash",
243
+ response: "different-remote-hash",
244
+ contentType: "raw"
245
+ })
246
+
247
+ terminal.setUserResponse("N")
248
+
249
+ await pushSearchTemplate({ paths: [], force: false })
250
+
251
+ expect(terminal.getSpy("info")).toHaveBeenCalledWith("Push operation cancelled by user.")
252
+ })
109
253
  })
@@ -31,6 +31,22 @@ describe("Setup Module", () => {
31
31
  fs.expectFile(".nosto.json").toExist()
32
32
  })
33
33
 
34
+ it("should create config file with merchant param", async () => {
35
+ await printSetupHelp(".", { merchant: "test-merchant" })
36
+
37
+ fs.expectFile(".nosto.json").toExist()
38
+ fs.expectFile(".nosto.json").toContain(expect.stringContaining('"merchant": "test-merchant"'))
39
+ })
40
+
41
+ it("should include environmental variables in created config file", async () => {
42
+ process.env.NOSTO_API_KEY = "env-variable-key"
43
+ terminal.setUserResponse("Y")
44
+
45
+ await printSetupHelp(".", {})
46
+
47
+ fs.expectFile(".nosto.json").toContain(expect.stringContaining('"apiKey": "env-variable-key"'))
48
+ })
49
+
34
50
  it("should not create config file when user declines", async () => {
35
51
  terminal.setUserResponse("N")
36
52
 
@@ -1,5 +1,6 @@
1
- import { describe, expect, it } from "vitest"
1
+ import { describe, expect, it, vi } from "vitest"
2
2
 
3
+ import * as config from "#config/config.ts"
3
4
  import { printStatus } from "#modules/status.ts"
4
5
  import { setupMockConfig } from "#test/utils/mockConfig.ts"
5
6
  import { setupMockConsole } from "#test/utils/mockConsole.ts"
@@ -12,11 +13,51 @@ describe("Status Module", () => {
12
13
  expect(terminal.getSpy("info")).toHaveBeenCalledWith("Configuration is not valid:")
13
14
  })
14
15
 
15
- it("should indicate valid configuration", async () => {
16
- setupMockConfig({ merchant: "test-merchant" })
16
+ it("should indicate valid configuration for api key", async () => {
17
+ setupMockConfig({ merchant: "test-merchant", apiKey: "test-key" })
17
18
 
18
19
  await expect(printStatus(".")).resolves.not.toThrow()
19
20
  expect(terminal.getSpy("info")).toHaveBeenCalledWith("Configuration seems to be valid:")
20
- expect(terminal.getSpy("error")).toHaveBeenCalledWith("Some required configuration is missing\n")
21
+ expect(terminal.getSpy("info")).toHaveBeenCalledWith(expect.stringContaining("Using API key for authentication"))
22
+ })
23
+
24
+ it("should indicate valid configuration for user auth", async () => {
25
+ setupMockConfig({
26
+ merchant: "test-merchant",
27
+ apiKey: "",
28
+ auth: { user: "test", token: "test", expiresAt: new Date(Date.now() + 3600 * 1000) }
29
+ })
30
+
31
+ await expect(printStatus(".")).resolves.not.toThrow()
32
+ expect(terminal.getSpy("info")).toHaveBeenCalledWith("Configuration seems to be valid:")
33
+ expect(terminal.getSpy("info")).toHaveBeenCalledWith(
34
+ expect.stringContaining("Using user account for authentication")
35
+ )
36
+ })
37
+
38
+ it("should indicate invalid user auth", async () => {
39
+ setupMockConfig({ merchant: "test-merchant", apiKey: "", auth: { user: "", token: "", expiresAt: new Date(0) } })
40
+
41
+ await expect(printStatus(".")).resolves.not.toThrow()
42
+ expect(terminal.getSpy("info")).toHaveBeenCalledWith("Configuration is not valid:")
43
+ expect(terminal.getSpy("info")).toHaveBeenCalledWith(expect.stringContaining("Missing authentication"))
44
+ })
45
+
46
+ it("should indicate expired user auth", async () => {
47
+ setupMockConfig({
48
+ merchant: "test-merchant",
49
+ apiKey: "",
50
+ auth: { user: "test", token: "test", expiresAt: new Date(1) }
51
+ })
52
+
53
+ await expect(printStatus(".")).resolves.not.toThrow()
54
+ expect(terminal.getSpy("info")).toHaveBeenCalledWith("Configuration is not valid:")
55
+ expect(terminal.getSpy("info")).toHaveBeenCalledWith(expect.stringContaining("Authentication token expired"))
56
+ })
57
+
58
+ it("should rethrow unknown errors in config loading", async () => {
59
+ vi.spyOn(config, "loadConfig").mockRejectedValueOnce(new Error("Unknown error"))
60
+
61
+ await expect(printStatus(".")).rejects.toThrow(/Unknown error/)
21
62
  })
22
63
  })
package/test/setup.ts CHANGED
@@ -2,6 +2,7 @@ import { Volume } from "memfs"
2
2
  import { beforeEach } from "vitest"
3
3
  import { vi } from "vitest"
4
4
 
5
+ import { mockHttpServer } from "./utils/mockAuthServer.ts"
5
6
  import { mockedConsoleIn, mockedConsoleOut } from "./utils/mockConsole.ts"
6
7
 
7
8
  export const testVolume = Volume.fromJSON({}, "/")
@@ -26,3 +27,13 @@ vi.mock("#/console/logger.ts", () => mockedConsoleOut)
26
27
  vi.mock("node:test", () => {
27
28
  throw new Error("You seem to have accidentally imported node:test instead of vitest.")
28
29
  })
30
+
31
+ vi.mock("open", () => {
32
+ return {
33
+ default: vi.fn()
34
+ }
35
+ })
36
+
37
+ vi.mock("node:http", () => ({
38
+ default: mockHttpServer
39
+ }))
@@ -4,14 +4,19 @@ import type { SetupServer } from "msw/node"
4
4
  type HttpMethod = keyof typeof http
5
5
 
6
6
  export type MockParams<ResponseT extends DefaultBodyType | void> =
7
- | (ResponseT extends void ? object : { response: ResponseT })
7
+ | (ResponseT extends void ? object : { response: ResponseT; contentType?: "json" | "raw" })
8
8
  | {
9
9
  error: { status: number; message: string }
10
10
  }
11
11
 
12
12
  export const generateEndpointMock = (
13
13
  server: SetupServer,
14
- { method, path, ...params }: { method: HttpMethod; path: string } & MockParams<DefaultBodyType>
14
+ {
15
+ method,
16
+ path,
17
+ contentType,
18
+ ...params
19
+ }: { method: HttpMethod; path: string; contentType?: "json" | "raw" } & MockParams<DefaultBodyType>
15
20
  ) => {
16
21
  let invocations: unknown[] = []
17
22
 
@@ -36,7 +41,9 @@ export const generateEndpointMock = (
36
41
 
37
42
  const requestsWithBody = ["POST", "PUT", "PATCH"]
38
43
  invocations.push(requestsWithBody.includes(request.method) ? await toBody(request) : {})
39
-
44
+ if (contentType === "raw") {
45
+ return HttpResponse.text(returnedResponse as string, { status })
46
+ }
40
47
  return HttpResponse.json(returnedResponse, { status })
41
48
  })
42
49
  server.use(handler)
@@ -0,0 +1,72 @@
1
+ import type { IncomingMessage, RequestListener, ServerResponse } from "node:http"
2
+ import { AddressInfo } from "node:net"
3
+
4
+ import { vi } from "vitest"
5
+
6
+ import { AuthConfig } from "#config/schema.ts"
7
+
8
+ // Mock the entire http module at the top level
9
+ let queuedServerRequest: Partial<IncomingMessage> | null = null
10
+ const queuedServerResponse = {
11
+ writeHead: vi.fn(),
12
+ end: vi.fn()
13
+ }
14
+
15
+ let mockedAddressFn: () => string | AddressInfo | null
16
+ export const mockHttpServer: {
17
+ createServer: (listener: RequestListener) => unknown
18
+ } = {
19
+ createServer: listener => {
20
+ process.nextTick(() => {
21
+ if (queuedServerRequest) {
22
+ listener(queuedServerRequest as IncomingMessage, queuedServerResponse as unknown as ServerResponse)
23
+ queuedServerRequest = null
24
+ }
25
+ })
26
+ return {
27
+ listen: (port: number, hostname?: string | (() => void), callback?: () => void) => {
28
+ const actualCallback = typeof hostname === "function" ? hostname : callback
29
+ if (actualCallback) {
30
+ process.nextTick(actualCallback)
31
+ }
32
+ return {}
33
+ },
34
+ close: (callback?: () => void) => {
35
+ if (callback) {
36
+ process.nextTick(callback)
37
+ }
38
+ },
39
+ address: () => {
40
+ if (mockedAddressFn) {
41
+ return mockedAddressFn()
42
+ }
43
+ return {
44
+ port: 3000, // Mock port number
45
+ address: "localhost",
46
+ family: "IPv4"
47
+ }
48
+ }
49
+ }
50
+ }
51
+ }
52
+
53
+ export function setupMockAuthServer() {
54
+ return {
55
+ mockValidResponse: (res: AuthConfig) => {
56
+ const params = new URLSearchParams({
57
+ user: res.user,
58
+ token: res.token,
59
+ expiresAt: res.expiresAt.toISOString()
60
+ })
61
+ queuedServerRequest = {
62
+ url: `/?${params.toString()}`
63
+ }
64
+ },
65
+ mockGenericResponse: (res: Partial<IncomingMessage>) => {
66
+ queuedServerRequest = res
67
+ },
68
+ mockServerAddress: (callback: string | AddressInfo | null) => {
69
+ mockedAddressFn = () => callback
70
+ }
71
+ }
72
+ }
@@ -19,6 +19,7 @@ export const mockedConsoleOut = {
19
19
  merchantId: "",
20
20
  isDryRun: false
21
21
  },
22
+ success: vi.fn(),
22
23
  raw: vi.fn(),
23
24
  debug: vi.fn(),
24
25
  info: vi.fn(),
@@ -36,9 +37,28 @@ export function setupMockConsole() {
36
37
  setUserResponse: (response: string) => {
37
38
  mockedConsoleIn.userResponse = response
38
39
  },
40
+ setContext: (context: Partial<typeof mockedConsoleOut.Logger.context>) => {
41
+ mockedConsoleOut.Logger.context = {
42
+ ...mockedConsoleOut.Logger.context,
43
+ ...context
44
+ }
45
+ },
39
46
  clearPrompts: () => {
40
47
  mockedConsoleIn.recordedPrompts = []
41
48
  },
49
+ resetMocks: () => {
50
+ mockedConsoleOut.Logger.success.mockReset()
51
+ mockedConsoleOut.Logger.raw.mockReset()
52
+ mockedConsoleOut.Logger.debug.mockReset()
53
+ mockedConsoleOut.Logger.info.mockReset()
54
+ mockedConsoleOut.Logger.warn.mockReset()
55
+ mockedConsoleOut.Logger.error.mockReset()
56
+ mockedConsoleOut.Logger.context = {
57
+ logLevel: "info",
58
+ merchantId: "",
59
+ isDryRun: false
60
+ }
61
+ },
42
62
  getSpy: (method: Exclude<keyof typeof mockedConsoleOut.Logger, "context">) => {
43
63
  return mockedConsoleOut.Logger[method]
44
64
  },
@@ -2,30 +2,64 @@ import fs from "fs"
2
2
  import path from "path"
3
3
  import { expect } from "vitest"
4
4
 
5
+ import { getDefaultConfig } from "#config/config.ts"
6
+ import { PersistentConfig } from "#config/schema.ts"
7
+ import { HomeDirectory } from "#filesystem/homeDirectory.ts"
5
8
  import { testVolume } from "#test/setup.ts"
6
9
 
7
10
  export function setupMockFileSystem() {
8
11
  const fs = testVolume
12
+ const authFilePath = path.join(HomeDirectory, ".nosto", ".auth.json")
13
+
14
+ function writeFileContent(targetFile: string, content: string) {
15
+ const dir = path.join("/", targetFile.substring(0, targetFile.lastIndexOf("/")))
16
+ const filePath = path.join("/", targetFile)
17
+ // Ensure parent directory exists
18
+ if (dir && !fs.existsSync(dir)) {
19
+ fs.mkdirSync(dir, { recursive: true })
20
+ }
21
+ if (fs.existsSync(filePath)) {
22
+ fs.unlinkSync(filePath)
23
+ }
24
+ fs.writeFileSync(filePath, content)
25
+ }
26
+
9
27
  return {
10
- writeFile: (targetFile: string, content: string) => {
11
- const dir = path.join("/", targetFile.substring(0, targetFile.lastIndexOf("/")))
12
- const filePath = path.join("/", targetFile)
13
- // Ensure parent directory exists
14
- if (dir && !fs.existsSync(dir)) {
15
- fs.mkdirSync(dir, { recursive: true })
28
+ writeFile: (targetFile: string, content: unknown | string) => {
29
+ if (typeof content === "string") {
30
+ writeFileContent(targetFile, content)
31
+ } else {
32
+ writeFileContent(targetFile, JSON.stringify(content))
16
33
  }
17
- if (fs.existsSync(filePath)) {
18
- fs.unlinkSync(filePath)
19
- }
20
- fs.writeFileSync(filePath, content)
21
34
  },
22
35
  writeFolder: (targetFolder: string) => {
23
36
  const dir = path.join("/", targetFolder)
24
37
  fs.mkdirSync(dir, { recursive: true })
25
38
  },
39
+ chmod: (targetFile: string, mode: number) => {
40
+ const filePath = path.join("/", targetFile)
41
+ fs.chmodSync(filePath, mode)
42
+ },
26
43
  expectFile: (targetFile: string) => {
27
44
  const filePath = path.join("/", targetFile)
28
45
  return makeFileMatcher(filePath)
46
+ },
47
+ mockConfigFile: (overrides: Partial<PersistentConfig> = {}) => {
48
+ const content = {
49
+ ...getDefaultConfig(),
50
+ merchant: "test-merchant",
51
+ apiKey: "test-api-key",
52
+ ...overrides
53
+ }
54
+ writeFileContent(".nosto.json", JSON.stringify(content))
55
+ },
56
+ mockUserAuthentication: () => {
57
+ const userAuth = { user: "test", token: "test", expiresAt: new Date(Date.now() + 1000 * 60 * 60) }
58
+ fs.mkdirSync(path.dirname(authFilePath), { recursive: true })
59
+ writeFileContent(authFilePath, JSON.stringify(userAuth))
60
+ },
61
+ paths: {
62
+ authFile: authFilePath
29
63
  }
30
64
  }
31
65
  }
@@ -34,7 +68,7 @@ export function makeFileMatcher(path: string) {
34
68
  return {
35
69
  toContain: (expectedContent: string) => {
36
70
  const content = fs.readFileSync(path, "utf8")
37
- expect(content, `File ${path} has content ${content}`).toEqual(expectedContent)
71
+ expect(content, `File ${path} actually has\n ${content}`).toEqual(expectedContent)
38
72
  },
39
73
  toExist: () => {
40
74
  return expect(fs.existsSync(path), `File ${path} does not exist when it was expected to`).toBe(true)
@@ -42,7 +76,7 @@ export function makeFileMatcher(path: string) {
42
76
  not: {
43
77
  toContain: (expectedContent: string) => {
44
78
  const content = fs.readFileSync(path, "utf8")
45
- expect(content, `File ${path} has content ${content}`).not.toEqual(expectedContent)
79
+ expect(content, `File ${path} actually has\n ${content}`).not.toEqual(expectedContent)
46
80
  },
47
81
  toExist: () => {
48
82
  return expect(fs.existsSync(path), `File ${path} exists when it was not expected to`).toBe(false)
@@ -2,6 +2,7 @@ import path from "path"
2
2
  import { vi } from "vitest"
3
3
 
4
4
  import { SearchTemplatesConfig } from "#config/schema.ts"
5
+ import { Logger } from "#console/logger.ts"
5
6
  import { makeConfig } from "#exports.ts"
6
7
 
7
8
  import { setupMockFileSystem } from "./mockFileSystem.ts"
@@ -15,10 +16,10 @@ export function setupMockStarterManifest({ projectPath, mockScript }: Props = {}
15
16
  const fs = setupMockFileSystem()
16
17
  const manifest = makeConfig({
17
18
  onBuild: async () => {
18
- console.log("Building...")
19
+ Logger.debug("Building...")
19
20
  },
20
21
  onBuildWatch: async () => {
21
- console.log("Watching...")
22
+ Logger.debug("Watching...")
22
23
  },
23
24
  ...mockScript
24
25
  })
package/vitest.config.ts CHANGED
@@ -13,18 +13,26 @@ export default defineConfig({
13
13
  include: ["test/**/*.test.ts"],
14
14
  exclude: ["node_modules", "dist"],
15
15
  setupFiles: ["test/setup.ts"],
16
+ mockReset: true,
16
17
  restoreMocks: true,
17
18
  reporters: ["default"],
18
19
  coverage: {
19
20
  provider: "v8",
20
21
  reporter: ["text", "json", "html"],
21
22
  thresholds: {
22
- statements: 85,
23
- branches: 80,
24
- functions: 85,
25
- lines: 85
23
+ statements: 90,
24
+ branches: 90,
25
+ functions: 90,
26
+ lines: 90
26
27
  },
27
- exclude: ["node_modules/", "test/", "vitest.config.ts", "src/bootstrap.sh", "*.config.js"]
28
+ exclude: [
29
+ "node_modules/",
30
+ "test/",
31
+ "vitest.config.ts",
32
+ "src/bootstrap.sh",
33
+ "*.config.js",
34
+ "src/filesystem/homeDirectory.ts"
35
+ ]
28
36
  }
29
37
  },
30
38
  esbuild: {