@nosto/nosto-cli 1.0.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 (88) hide show
  1. package/.github/copilot-instructions.md +326 -0
  2. package/.github/dependabot.yml +9 -0
  3. package/.github/pull_request_template.md +12 -0
  4. package/.github/workflows/ci.yml +58 -0
  5. package/.github/workflows/release.yml +49 -0
  6. package/.husky/commit-msg +1 -0
  7. package/.prettierrc +9 -0
  8. package/LICENSE +29 -0
  9. package/README.md +154 -0
  10. package/commitlint.config.js +4 -0
  11. package/eslint.config.js +36 -0
  12. package/package.json +63 -0
  13. package/src/api/library/fetchLibraryFile.ts +18 -0
  14. package/src/api/retry.ts +28 -0
  15. package/src/api/source/fetchSourceFile.ts +33 -0
  16. package/src/api/source/listSourceFiles.ts +13 -0
  17. package/src/api/source/putSourceFile.ts +14 -0
  18. package/src/api/source/schema.ts +10 -0
  19. package/src/api/utils.ts +52 -0
  20. package/src/bootstrap.sh +26 -0
  21. package/src/commander.ts +119 -0
  22. package/src/config/authConfig.ts +42 -0
  23. package/src/config/config.ts +109 -0
  24. package/src/config/envConfig.ts +23 -0
  25. package/src/config/fileConfig.ts +39 -0
  26. package/src/config/schema.ts +70 -0
  27. package/src/config/searchTemplatesConfig.ts +33 -0
  28. package/src/console/logger.ts +93 -0
  29. package/src/console/userPrompt.ts +16 -0
  30. package/src/errors/InvalidLoginResponseError.ts +14 -0
  31. package/src/errors/MissingConfigurationError.ts +14 -0
  32. package/src/errors/NostoError.ts +13 -0
  33. package/src/errors/NotNostoTemplateError.ts +15 -0
  34. package/src/errors/withErrorHandler.ts +50 -0
  35. package/src/exports.ts +8 -0
  36. package/src/filesystem/asserts/assertGitRepo.ts +19 -0
  37. package/src/filesystem/asserts/assertNostoTemplate.ts +34 -0
  38. package/src/filesystem/calculateTreeHash.ts +28 -0
  39. package/src/filesystem/esbuild.ts +37 -0
  40. package/src/filesystem/esbuildPlugins.ts +72 -0
  41. package/src/filesystem/filesystem.ts +40 -0
  42. package/src/filesystem/isIgnored.ts +65 -0
  43. package/src/filesystem/legacyUtils.ts +10 -0
  44. package/src/filesystem/loadLibrary.ts +31 -0
  45. package/src/filesystem/processInBatches.ts +38 -0
  46. package/src/filesystem/utils/getLoaderScript.ts +28 -0
  47. package/src/index.ts +3 -0
  48. package/src/modules/login.ts +87 -0
  49. package/src/modules/logout.ts +13 -0
  50. package/src/modules/search-templates/build.ts +61 -0
  51. package/src/modules/search-templates/dev.ts +50 -0
  52. package/src/modules/search-templates/pull.ts +89 -0
  53. package/src/modules/search-templates/push.ts +121 -0
  54. package/src/modules/setup.ts +96 -0
  55. package/src/modules/status.ts +71 -0
  56. package/src/utils/withSafeEnvironment.ts +22 -0
  57. package/test/api/fetchSourceFile.test.ts +30 -0
  58. package/test/api/putSourceFile.test.ts +34 -0
  59. package/test/api/retry.test.ts +102 -0
  60. package/test/api/utils.test.ts +27 -0
  61. package/test/commander.test.ts +271 -0
  62. package/test/config/envConfig.test.ts +62 -0
  63. package/test/config/fileConfig.test.ts +63 -0
  64. package/test/config/schema.test.ts +96 -0
  65. package/test/config/searchTemplatesConfig.test.ts +43 -0
  66. package/test/console/logger.test.ts +96 -0
  67. package/test/errors/withErrorHandler.test.ts +64 -0
  68. package/test/filesystem/filesystem.test.ts +53 -0
  69. package/test/filesystem/plugins.test.ts +35 -0
  70. package/test/index.test.ts +15 -0
  71. package/test/modules/search-templates/build.legacy.test.ts +74 -0
  72. package/test/modules/search-templates/build.modern.test.ts +33 -0
  73. package/test/modules/search-templates/dev.legacy.test.ts +75 -0
  74. package/test/modules/search-templates/dev.modern.test.ts +44 -0
  75. package/test/modules/search-templates/pull.test.ts +96 -0
  76. package/test/modules/search-templates/push.test.ts +109 -0
  77. package/test/modules/setup.test.ts +49 -0
  78. package/test/modules/status.test.ts +22 -0
  79. package/test/setup.ts +28 -0
  80. package/test/utils/generateEndpointMock.ts +60 -0
  81. package/test/utils/mockCommander.ts +22 -0
  82. package/test/utils/mockConfig.ts +37 -0
  83. package/test/utils/mockConsole.ts +65 -0
  84. package/test/utils/mockFileSystem.ts +52 -0
  85. package/test/utils/mockServer.ts +76 -0
  86. package/test/utils/mockStarterManifest.ts +42 -0
  87. package/tsconfig.json +20 -0
  88. package/vitest.config.ts +33 -0
@@ -0,0 +1,96 @@
1
+ import { describe, expect, it } from "vitest"
2
+
3
+ import { LogLevel, PersistentConfigSchema, RuntimeConfigSchema } from "#config/schema.ts"
4
+
5
+ describe("Schema", () => {
6
+ describe("PersistentConfigSchema", () => {
7
+ it("should validate valid configuration", () => {
8
+ const validConfig = {
9
+ merchant: "test-merchant"
10
+ }
11
+
12
+ const result = PersistentConfigSchema.parse(validConfig)
13
+
14
+ expect(result.merchant).toBe("test-merchant")
15
+ expect(result.templatesEnv).toBe("main")
16
+ expect(result.apiUrl).toBe("https://api.nosto.com")
17
+ expect(result.logLevel).toBe("info")
18
+ })
19
+
20
+ it("should apply default values", () => {
21
+ const minimalConfig = {
22
+ merchant: "test-merchant"
23
+ }
24
+
25
+ const result = PersistentConfigSchema.parse(minimalConfig)
26
+
27
+ expect(result.templatesEnv).toBe("main")
28
+ expect(result.apiUrl).toBe("https://api.nosto.com")
29
+ expect(result.libraryUrl).toBe("https://d11ffvpvtnmt0d.cloudfront.net/library")
30
+ expect(result.logLevel).toBe("info")
31
+ expect(result.maxRequests).toBe(15)
32
+ expect(result.apiKey).toBeUndefined()
33
+ })
34
+
35
+ it("should validate log level enum", () => {
36
+ LogLevel.forEach(level => {
37
+ const config = {
38
+ merchant: "test-merchant",
39
+ logLevel: level
40
+ }
41
+
42
+ const result = PersistentConfigSchema.parse(config)
43
+ expect(result.logLevel).toBe(level)
44
+ })
45
+ })
46
+
47
+ it("should coerce maxRequests to number", () => {
48
+ const config = {
49
+ merchant: "test-merchant",
50
+ maxRequests: "20" as unknown
51
+ }
52
+
53
+ const result = PersistentConfigSchema.parse(config)
54
+ expect(result.maxRequests).toBe(20)
55
+ expect(typeof result.maxRequests).toBe("number")
56
+ })
57
+
58
+ it("should throw on invalid log level", () => {
59
+ const invalidConfig = {
60
+ merchant: "test-merchant",
61
+ logLevel: "invalid-level"
62
+ }
63
+
64
+ expect(() => PersistentConfigSchema.parse(invalidConfig)).toThrow()
65
+ })
66
+
67
+ it("should require merchant", () => {
68
+ expect(() => PersistentConfigSchema.parse({})).toThrow()
69
+ expect(() => PersistentConfigSchema.parse({ apiKey: "test" })).toThrow()
70
+ })
71
+ })
72
+
73
+ describe("RuntimeConfigSchema", () => {
74
+ it("should apply default values", () => {
75
+ const result = RuntimeConfigSchema.parse({})
76
+
77
+ expect(result.projectPath).toBe(".")
78
+ expect(result.dryRun).toBe(false)
79
+ expect(result.verbose).toBe(false)
80
+ })
81
+
82
+ it("should parse provided values", () => {
83
+ const config = {
84
+ projectPath: "/custom/path",
85
+ dryRun: true,
86
+ verbose: true
87
+ }
88
+
89
+ const result = RuntimeConfigSchema.parse(config)
90
+
91
+ expect(result.projectPath).toBe("/custom/path")
92
+ expect(result.dryRun).toBe(true)
93
+ expect(result.verbose).toBe(true)
94
+ })
95
+ })
96
+ })
@@ -0,0 +1,43 @@
1
+ import { describe, expect, it } from "vitest"
2
+
3
+ import { parseSearchTemplatesConfigFile } from "#config/searchTemplatesConfig.ts"
4
+ import { NostoError } from "#errors/NostoError.ts"
5
+ import { setupMockStarterManifest } from "#test/utils/mockStarterManifest.ts"
6
+
7
+ describe("Search Templates Config", () => {
8
+ describe("parseSearchTemplatesConfigFile", () => {
9
+ it("should return legacy mode when config file does not exist", async () => {
10
+ const result = await parseSearchTemplatesConfigFile({ projectPath: "." })
11
+
12
+ expect(result).toEqual({
13
+ mode: "legacy",
14
+ data: {
15
+ onBuild: expect.any(Function),
16
+ onBuildWatch: expect.any(Function)
17
+ }
18
+ })
19
+ })
20
+
21
+ it("should return modern mode with valid config file", async () => {
22
+ const manifest = setupMockStarterManifest()
23
+ const result = await parseSearchTemplatesConfigFile({ projectPath: "." })
24
+
25
+ expect(result.mode).toBe("modern")
26
+ expect(result.data.onBuild).toBe(manifest.onBuild)
27
+ expect(result.data.onBuildWatch).toBe(manifest.onBuildWatch)
28
+ })
29
+
30
+ it("should throw NostoError for invalid config schema", async () => {
31
+ setupMockStarterManifest({
32
+ mockScript: {
33
+ // @ts-expect-error Testing invalid schema
34
+ invalidProp: "this should not be here"
35
+ }
36
+ })
37
+ await expect(parseSearchTemplatesConfigFile({ projectPath: "." })).rejects.toThrow(NostoError)
38
+ await expect(parseSearchTemplatesConfigFile({ projectPath: "." })).rejects.toThrow(
39
+ "Invalid nosto.config.ts file:"
40
+ )
41
+ })
42
+ })
43
+ })
@@ -0,0 +1,96 @@
1
+ import { describe, expect, it, vi } from "vitest"
2
+
3
+ import { Logger } from "#console/logger.ts"
4
+
5
+ // Undo mocking of logger in setup.ts
6
+ vi.mock("#console/logger.ts", async importOriginal => importOriginal<typeof import("#console/logger.ts")>())
7
+
8
+ describe("Logger", () => {
9
+ it("prints message to raw log", () => {
10
+ const consoleMock = vi.spyOn(console, "log").mockImplementation(() => undefined)
11
+ Logger.raw("Raw message")
12
+ expect(consoleMock).toHaveBeenCalledWith("Raw message")
13
+ })
14
+
15
+ it("prints raw log when log level is error", () => {
16
+ const consoleMock = vi.spyOn(console, "log").mockImplementation(() => undefined)
17
+ Logger.context.logLevel = "error"
18
+ Logger.raw("Raw message")
19
+ expect(consoleMock).toHaveBeenCalledWith("Raw message")
20
+ })
21
+
22
+ it("prints raw log extra payload", () => {
23
+ const consoleMock = vi.spyOn(console, "log").mockImplementation(() => undefined)
24
+ Logger.raw("Raw message", new Error("Extra payload"))
25
+ expect(consoleMock).toHaveBeenCalledWith(new Error("Extra payload"))
26
+ })
27
+
28
+ it("prints debug message when log level is debug", () => {
29
+ const consoleMock = vi.spyOn(console, "debug").mockImplementation(() => undefined)
30
+ Logger.context.logLevel = "debug"
31
+ Logger.debug("Debug message")
32
+ expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining("Debug message"))
33
+ })
34
+
35
+ it("does not print debug message when log level is info", () => {
36
+ const consoleMock = vi.spyOn(console, "debug").mockImplementation(() => undefined)
37
+ Logger.context.logLevel = "info"
38
+ Logger.debug("Debug message")
39
+ expect(consoleMock).not.toHaveBeenCalledWith(expect.stringContaining("Debug message"))
40
+ })
41
+
42
+ it("prints info message when log level is info", () => {
43
+ const consoleMock = vi.spyOn(console, "info").mockImplementation(() => undefined)
44
+ Logger.context.logLevel = "info"
45
+ Logger.info("Info message")
46
+ expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining("Info message"))
47
+ })
48
+
49
+ it("does not print info message when log level is warn", () => {
50
+ const consoleMock = vi.spyOn(console, "info").mockImplementation(() => undefined)
51
+ Logger.context.logLevel = "warn"
52
+ Logger.info("Info message")
53
+ expect(consoleMock).not.toHaveBeenCalledWith(expect.stringContaining("Info message"))
54
+ })
55
+
56
+ it("prints success message when log level is info", () => {
57
+ const consoleMock = vi.spyOn(console, "info").mockImplementation(() => undefined)
58
+ Logger.context.logLevel = "info"
59
+ Logger.success("Success message")
60
+ expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining("Success message"))
61
+ })
62
+
63
+ it("does not print success message when log level is warn", () => {
64
+ const consoleMock = vi.spyOn(console, "log").mockImplementation(() => undefined)
65
+ Logger.context.logLevel = "warn"
66
+ Logger.success("Success message")
67
+ expect(consoleMock).not.toHaveBeenCalledWith(expect.stringContaining("Success message"))
68
+ })
69
+
70
+ it("prints warn message when log level is warn", () => {
71
+ const consoleMock = vi.spyOn(console, "warn").mockImplementation(() => undefined)
72
+ Logger.context.logLevel = "warn"
73
+ Logger.warn("Warn message")
74
+ expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining("Warn message"))
75
+ })
76
+
77
+ it("does not print warn message when log level is error", () => {
78
+ const consoleMock = vi.spyOn(console, "warn").mockImplementation(() => undefined)
79
+ Logger.context.logLevel = "error"
80
+ Logger.warn("Warn message")
81
+ expect(consoleMock).not.toHaveBeenCalledWith(expect.stringContaining("Warn message"))
82
+ })
83
+
84
+ it("prints error message when log level is error", () => {
85
+ const consoleMock = vi.spyOn(console, "error").mockImplementation(() => undefined)
86
+ Logger.context.logLevel = "error"
87
+ Logger.error("Error message")
88
+ expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining("Error message"))
89
+ })
90
+
91
+ it("prints extra payload for errors", () => {
92
+ const consoleMock = vi.spyOn(console, "error").mockImplementation(() => undefined)
93
+ Logger.error("Error message", new Error("Test error"))
94
+ expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining("Error: Test error"))
95
+ })
96
+ })
@@ -0,0 +1,64 @@
1
+ import { HTTPError, TimeoutError } from "ky"
2
+ import { describe, expect, it, vi } from "vitest"
3
+
4
+ import { MissingConfigurationError } from "#errors/MissingConfigurationError.ts"
5
+ import { withErrorHandler } from "#errors/withErrorHandler.ts"
6
+ import { setupMockConfig } from "#test/utils/mockConfig.ts"
7
+
8
+ describe("Error Handler", () => {
9
+ it("should execute function without error handling if no error occurs", async () => {
10
+ const mockFn = vi.fn()
11
+
12
+ await withErrorHandler(mockFn)
13
+
14
+ expect(mockFn).toHaveBeenCalled()
15
+ })
16
+
17
+ it("should handle MissingConfigurationError", async () => {
18
+ const mockFn = vi.fn(() => {
19
+ throw new MissingConfigurationError("Test config error")
20
+ })
21
+
22
+ await withErrorHandler(mockFn)
23
+ })
24
+
25
+ it("should handle HTTPError", async () => {
26
+ setupMockConfig({ verbose: true })
27
+ const mockFn = vi.fn(() => {
28
+ throw new HTTPError(
29
+ {
30
+ status: 500,
31
+ statusText: "Internal Server Error"
32
+ } as Response,
33
+ {
34
+ method: "GET",
35
+ url: "https://example.com/api"
36
+ } as Request,
37
+ {} as never
38
+ )
39
+ })
40
+
41
+ await withErrorHandler(mockFn)
42
+ })
43
+
44
+ it("should handle HTTPError", async () => {
45
+ setupMockConfig({ verbose: true })
46
+ const mockFn = vi.fn(() => {
47
+ throw new TimeoutError({
48
+ method: "GET",
49
+ url: "https://example.com/api"
50
+ } as Request)
51
+ })
52
+
53
+ await withErrorHandler(mockFn)
54
+ })
55
+
56
+ it("should rethrow unknown errors", async () => {
57
+ const unknownError = new Error("Unknown error")
58
+ const mockFn = vi.fn(() => {
59
+ throw unknownError
60
+ })
61
+
62
+ await expect(withErrorHandler(mockFn)).rejects.toThrow("Unknown error")
63
+ })
64
+ })
@@ -0,0 +1,53 @@
1
+ import { describe, expect, it } from "vitest"
2
+
3
+ import { listAllFiles, writeFile } from "#filesystem/filesystem.ts"
4
+ import { setupMockConfig } from "#test/utils/mockConfig.ts"
5
+ import { setupMockFileSystem } from "#test/utils/mockFileSystem.ts"
6
+
7
+ const mockFileSystem = setupMockFileSystem()
8
+
9
+ describe("Filesystem", () => {
10
+ it("should write file when not in dry run mode", () => {
11
+ writeFile("file.txt", "content")
12
+ mockFileSystem.expectFile("file.txt").toContain("content")
13
+ })
14
+
15
+ it("should not write file when in dry run mode", () => {
16
+ setupMockConfig({ dryRun: true })
17
+
18
+ writeFile("file.txt", "content")
19
+ mockFileSystem.expectFile("file.txt").not.toExist()
20
+ })
21
+
22
+ it("should list all files", () => {
23
+ mockFileSystem.writeFile("file1.txt", "content1")
24
+ mockFileSystem.writeFile("file2.txt", "content2")
25
+ expect(listAllFiles(".")).toEqual({
26
+ allFiles: ["file1.txt", "file2.txt"],
27
+ unfilteredFileCount: 2
28
+ })
29
+ })
30
+
31
+ it("should list all files with project path", () => {
32
+ setupMockConfig({ projectPath: "/test/folder" })
33
+ mockFileSystem.writeFolder("/test/folder")
34
+ mockFileSystem.writeFile("/test/folder/file1.txt", "content1")
35
+ mockFileSystem.writeFile("/test/folder/file2.txt", "content2")
36
+
37
+ const result = listAllFiles("/test/folder")
38
+
39
+ expect(result).toEqual({
40
+ allFiles: ["file1.txt", "file2.txt"],
41
+ unfilteredFileCount: 2
42
+ })
43
+ })
44
+
45
+ it("should not include folders in the list", () => {
46
+ mockFileSystem.writeFile("subfolder/file1.txt", "content1")
47
+ mockFileSystem.writeFile("other/subfolder/file2.txt", "content2")
48
+ expect(listAllFiles(".")).toEqual({
49
+ allFiles: ["subfolder/file1.txt", "other/subfolder/file2.txt"],
50
+ unfilteredFileCount: 2
51
+ })
52
+ })
53
+ })
@@ -0,0 +1,35 @@
1
+ import { PluginBuild } from "esbuild"
2
+ import { describe, it } from "vitest"
3
+
4
+ import { createLoaderPlugin } from "#filesystem/esbuildPlugins.ts"
5
+ import { setupMockFileSystem } from "#test/utils/mockFileSystem.ts"
6
+
7
+ const fs = setupMockFileSystem()
8
+
9
+ describe("createLoaderPlugin", () => {
10
+ it("creates loader after successful build", () => {
11
+ const plugin = createLoaderPlugin()
12
+ fs.writeFolder("build")
13
+
14
+ plugin.setup({
15
+ onEnd(callback: (result: { errors: unknown[] }) => void) {
16
+ callback({ errors: [] })
17
+ }
18
+ } as PluginBuild)
19
+
20
+ fs.expectFile("build/loader.js").toExist()
21
+ })
22
+
23
+ it("exits early on errors", () => {
24
+ const plugin = createLoaderPlugin()
25
+ fs.writeFolder("build")
26
+
27
+ plugin.setup({
28
+ onEnd(callback: (result: { errors: unknown[] }) => void) {
29
+ callback({ errors: [new Error("Test error")] })
30
+ }
31
+ } as PluginBuild)
32
+
33
+ fs.expectFile("build/loader.js").not.toExist()
34
+ })
35
+ })
@@ -0,0 +1,15 @@
1
+ import { describe, expect, it, vi } from "vitest"
2
+
3
+ import * as commanderWrapper from "#commander.ts"
4
+
5
+ describe("index", () => {
6
+ it("should pass process.argv to runCLI", async () => {
7
+ const spy = vi.spyOn(commanderWrapper, "runCLI")
8
+
9
+ const argv = ["/cwd", "nosto", "setup"]
10
+ process.argv = argv
11
+ await import("#/index.ts")
12
+
13
+ expect(spy).toHaveBeenCalledWith(argv)
14
+ })
15
+ })
@@ -0,0 +1,74 @@
1
+ import * as esbuild from "esbuild"
2
+ import { beforeEach, describe, expect, it, vi } from "vitest"
3
+
4
+ import { buildSearchTemplate } from "#modules/search-templates/build.ts"
5
+ import { setupMockConfig } from "#test/utils/mockConfig.ts"
6
+ import { mockFetchLibraryFile, setupMockServer } from "#test/utils/mockServer.ts"
7
+
8
+ const server = setupMockServer()
9
+
10
+ vi.mock("esbuild", () => ({
11
+ context: vi.fn()
12
+ }))
13
+
14
+ describe("Search Templates build / legacy", () => {
15
+ const mockContext = {
16
+ rebuild: vi.fn(),
17
+ dispose: vi.fn(),
18
+ watch: vi.fn(),
19
+ serve: vi.fn(),
20
+ cancel: vi.fn()
21
+ }
22
+
23
+ beforeEach(() => {
24
+ setupMockConfig({
25
+ apiKey: "test-key",
26
+ merchant: "test-merchant",
27
+ projectPath: "/test-project"
28
+ })
29
+
30
+ vi.mocked(esbuild.context).mockResolvedValue(mockContext)
31
+
32
+ // Mock library file fetches
33
+ mockFetchLibraryFile(server, {
34
+ path: "nosto.module.js",
35
+ response: "// nosto.module.js content"
36
+ })
37
+ mockFetchLibraryFile(server, {
38
+ path: "nosto.module.js.map",
39
+ response: "// nosto.module.js.map content"
40
+ })
41
+ mockFetchLibraryFile(server, {
42
+ path: "nosto.d.ts",
43
+ response: "// nosto.d.ts content"
44
+ })
45
+ })
46
+
47
+ describe("buildSearchTemplate", () => {
48
+ it("should build templates without watch mode", async () => {
49
+ await buildSearchTemplate({ watch: false })
50
+
51
+ expect(mockContext.rebuild).toHaveBeenCalled()
52
+ expect(mockContext.dispose).toHaveBeenCalled()
53
+ expect(mockContext.watch).not.toHaveBeenCalled()
54
+ })
55
+
56
+ it("should build templates with watch mode", async () => {
57
+ await buildSearchTemplate({ watch: true })
58
+
59
+ expect(mockContext.watch).toHaveBeenCalled()
60
+ expect(mockContext.rebuild).not.toHaveBeenCalled()
61
+ expect(mockContext.dispose).not.toHaveBeenCalled()
62
+ })
63
+
64
+ it("should set up SIGINT handler in watch mode", async () => {
65
+ const processOnSpy = vi.spyOn(process, "on").mockImplementation(() => process)
66
+
67
+ await buildSearchTemplate({ watch: true })
68
+
69
+ expect(processOnSpy).toHaveBeenCalledWith("SIGINT", expect.any(Function))
70
+
71
+ processOnSpy.mockRestore()
72
+ })
73
+ })
74
+ })
@@ -0,0 +1,33 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest"
2
+
3
+ import { parseSearchTemplatesConfigFile } from "#config/searchTemplatesConfig.ts"
4
+ import { buildSearchTemplate } from "#modules/search-templates/build.ts"
5
+ import { setupMockConfig } from "#test/utils/mockConfig.ts"
6
+ import { setupMockStarterManifest } from "#test/utils/mockStarterManifest.ts"
7
+
8
+ describe("Search Templates build / modern", () => {
9
+ beforeEach(() => {
10
+ setupMockStarterManifest()
11
+ })
12
+ it("should build templates without watch mode", async () => {
13
+ const manifest = setupMockStarterManifest({
14
+ mockScript: { onBuild: vi.fn() }
15
+ })
16
+ setupMockConfig({
17
+ searchTemplates: await parseSearchTemplatesConfigFile({ projectPath: "." })
18
+ })
19
+ await buildSearchTemplate({ watch: false })
20
+ expect(manifest.onBuild).toHaveBeenCalled()
21
+ })
22
+
23
+ it("should build templates with watch mode", async () => {
24
+ const manifest = setupMockStarterManifest({
25
+ mockScript: { onBuildWatch: vi.fn() }
26
+ })
27
+ setupMockConfig({
28
+ searchTemplates: await parseSearchTemplatesConfigFile({ projectPath: "." })
29
+ })
30
+ await buildSearchTemplate({ watch: true })
31
+ expect(manifest.onBuildWatch).toHaveBeenCalled()
32
+ })
33
+ })
@@ -0,0 +1,75 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest"
2
+
3
+ import * as esbuild from "#filesystem/esbuild.ts"
4
+ import { searchTemplateDevMode } from "#modules/search-templates/dev.ts"
5
+ import { setupMockConfig } from "#test/utils/mockConfig.ts"
6
+ import { setupMockConsole } from "#test/utils/mockConsole.ts"
7
+ import { setupMockFileSystem } from "#test/utils/mockFileSystem.ts"
8
+ import { mockFetchLibraryFile, setupMockServer } from "#test/utils/mockServer.ts"
9
+
10
+ const fs = setupMockFileSystem()
11
+ const server = setupMockServer()
12
+ const mockConsole = setupMockConsole()
13
+
14
+ describe("Search Templates dev mode / legacy", () => {
15
+ const mockContext = {
16
+ watch: vi.fn(),
17
+ dispose: vi.fn()
18
+ }
19
+
20
+ beforeEach(() => {
21
+ vi.spyOn(esbuild, "getBuildContext").mockReturnValue(
22
+ mockContext as unknown as ReturnType<typeof esbuild.getBuildContext>
23
+ )
24
+
25
+ setupMockConfig({
26
+ libraryUrl: "https://library.nosto.com"
27
+ })
28
+ mockFetchLibraryFile(server, {
29
+ path: "nosto.module.js",
30
+ response: "// nosto.module.js content"
31
+ })
32
+ mockFetchLibraryFile(server, {
33
+ path: "nosto.module.js.map",
34
+ response: "// nosto.module.js.map content"
35
+ })
36
+ mockFetchLibraryFile(server, {
37
+ path: "nosto.d.ts",
38
+ response: "// nosto.d.ts content"
39
+ })
40
+ })
41
+
42
+ it("should start watching when confirmed", async () => {
43
+ const getBuildContext = vi.spyOn(esbuild, "getBuildContext")
44
+
45
+ await searchTemplateDevMode()
46
+
47
+ expect(getBuildContext).toHaveBeenCalledWith({ plugins: [expect.objectContaining({ name: "push-on-rebuild" })] })
48
+ expect(mockContext.watch).toHaveBeenCalled()
49
+ })
50
+
51
+ it("should skip confirmation when flag is set", async () => {
52
+ await searchTemplateDevMode()
53
+
54
+ mockConsole.expect.user.not.toHaveBeenPrompted()
55
+ expect(mockContext.watch).toHaveBeenCalled()
56
+ })
57
+
58
+ it("should set up SIGINT handler", async () => {
59
+ const processOnSpy = vi.spyOn(process, "on").mockImplementation(() => process)
60
+
61
+ await searchTemplateDevMode()
62
+
63
+ expect(processOnSpy).toHaveBeenCalledWith("SIGINT", expect.any(Function))
64
+
65
+ processOnSpy.mockRestore()
66
+ })
67
+
68
+ it("should have pulled the library", async () => {
69
+ await searchTemplateDevMode()
70
+
71
+ fs.expectFile(".nostocache/library/nosto.module.js").toContain('"// nosto.module.js content"')
72
+ fs.expectFile(".nostocache/library/nosto.module.js.map").toContain('"// nosto.module.js.map content"')
73
+ fs.expectFile(".nostocache/library/nosto.d.ts").toContain('"// nosto.d.ts content"')
74
+ })
75
+ })
@@ -0,0 +1,44 @@
1
+ import { describe, expect, it, vi } from "vitest"
2
+
3
+ import { OnStartDevProps } from "#config/schema.ts"
4
+ import { parseSearchTemplatesConfigFile } from "#config/searchTemplatesConfig.ts"
5
+ import { searchTemplateDevMode } from "#modules/search-templates/dev.ts"
6
+ import * as pushCommandModule from "#modules/search-templates/push.ts"
7
+ import { setupMockConfig } from "#test/utils/mockConfig.ts"
8
+ import { setupMockStarterManifest } from "#test/utils/mockStarterManifest.ts"
9
+
10
+ describe("Search Templates dev mode / modern", () => {
11
+ it("should build templates with watch mode", async () => {
12
+ const manifest = setupMockStarterManifest({
13
+ mockScript: {
14
+ onBuildWatch: vi.fn()
15
+ }
16
+ })
17
+ setupMockConfig({
18
+ searchTemplates: await parseSearchTemplatesConfigFile({ projectPath: "." })
19
+ })
20
+
21
+ await searchTemplateDevMode()
22
+ expect(manifest.onBuildWatch).toHaveBeenCalled()
23
+ })
24
+
25
+ it("runs the callback after build", async () => {
26
+ const manifest = setupMockStarterManifest({
27
+ mockScript: {
28
+ onBuildWatch: vi.fn().mockImplementation(async (args: OnStartDevProps) => {
29
+ args.onAfterBuild()
30
+ })
31
+ }
32
+ })
33
+ setupMockConfig({
34
+ searchTemplates: await parseSearchTemplatesConfigFile({ projectPath: "." })
35
+ })
36
+
37
+ vi.spyOn(pushCommandModule, "pushSearchTemplate").mockImplementation(vi.fn())
38
+
39
+ await searchTemplateDevMode()
40
+
41
+ expect(manifest.onBuildWatch).toHaveBeenCalled()
42
+ expect(pushCommandModule.pushSearchTemplate).toHaveBeenCalledWith({ paths: ["build"], force: false })
43
+ })
44
+ })