@credibledata/cred-cli 0.0.6

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 (259) hide show
  1. package/.github/workflows/build.yml +23 -0
  2. package/.mocharc.json +10 -0
  3. package/.prettierignore +2 -0
  4. package/.prettierrc +4 -0
  5. package/README-DEV.md +40 -0
  6. package/README.md +11 -0
  7. package/bun.lock +1158 -0
  8. package/dist/client/api.js +7715 -0
  9. package/dist/client/api.js.map +1 -0
  10. package/dist/client/base.js +69 -0
  11. package/dist/client/base.js.map +1 -0
  12. package/dist/client/common.js +162 -0
  13. package/dist/client/common.js.map +1 -0
  14. package/dist/client/configuration.js +44 -0
  15. package/dist/client/configuration.js.map +1 -0
  16. package/dist/client/index.js +32 -0
  17. package/dist/client/index.js.map +1 -0
  18. package/dist/clientconfiguration.js +205 -0
  19. package/dist/clientconfiguration.js.map +1 -0
  20. package/dist/clientconfiguration.spec.js +381 -0
  21. package/dist/clientconfiguration.spec.js.map +1 -0
  22. package/dist/commands/add.js +66 -0
  23. package/dist/commands/add.js.map +1 -0
  24. package/dist/commands/connection/createconnections.js +89 -0
  25. package/dist/commands/connection/createconnections.js.map +1 -0
  26. package/dist/commands/connection/createconnections.spec.js +219 -0
  27. package/dist/commands/connection/createconnections.spec.js.map +1 -0
  28. package/dist/commands/connection/deleteconnection.js +52 -0
  29. package/dist/commands/connection/deleteconnection.js.map +1 -0
  30. package/dist/commands/connection/deleteconnection.spec.js +130 -0
  31. package/dist/commands/connection/deleteconnection.spec.js.map +1 -0
  32. package/dist/commands/connection/listconnections.js +50 -0
  33. package/dist/commands/connection/listconnections.js.map +1 -0
  34. package/dist/commands/connection/listconnections.spec.js +117 -0
  35. package/dist/commands/connection/listconnections.spec.js.map +1 -0
  36. package/dist/commands/get.js +28 -0
  37. package/dist/commands/get.js.map +1 -0
  38. package/dist/commands/group/addmember.js +75 -0
  39. package/dist/commands/group/addmember.js.map +1 -0
  40. package/dist/commands/group/addmember.spec.js +148 -0
  41. package/dist/commands/group/addmember.spec.js.map +1 -0
  42. package/dist/commands/group/creategroup.js +55 -0
  43. package/dist/commands/group/creategroup.js.map +1 -0
  44. package/dist/commands/group/creategroup.spec.js +122 -0
  45. package/dist/commands/group/creategroup.spec.js.map +1 -0
  46. package/dist/commands/group/deletegroup.js +48 -0
  47. package/dist/commands/group/deletegroup.js.map +1 -0
  48. package/dist/commands/group/deletegroup.spec.js +104 -0
  49. package/dist/commands/group/deletegroup.spec.js.map +1 -0
  50. package/dist/commands/group/getgroup.js +50 -0
  51. package/dist/commands/group/getgroup.js.map +1 -0
  52. package/dist/commands/group/getgroup.spec.js +111 -0
  53. package/dist/commands/group/getgroup.spec.js.map +1 -0
  54. package/dist/commands/group/listgroups.js +56 -0
  55. package/dist/commands/group/listgroups.js.map +1 -0
  56. package/dist/commands/group/listgroups.spec.js +118 -0
  57. package/dist/commands/group/listgroups.spec.js.map +1 -0
  58. package/dist/commands/group/removemember.js +69 -0
  59. package/dist/commands/group/removemember.js.map +1 -0
  60. package/dist/commands/group/removemember.spec.js +138 -0
  61. package/dist/commands/group/removemember.spec.js.map +1 -0
  62. package/dist/commands/groupaccesstoken/createToken.js +53 -0
  63. package/dist/commands/groupaccesstoken/createToken.js.map +1 -0
  64. package/dist/commands/groupaccesstoken/createToken.spec.js +127 -0
  65. package/dist/commands/groupaccesstoken/createToken.spec.js.map +1 -0
  66. package/dist/commands/groupaccesstoken/deleteToken.js +24 -0
  67. package/dist/commands/groupaccesstoken/deleteToken.js.map +1 -0
  68. package/dist/commands/groupaccesstoken/deleteToken.spec.js +103 -0
  69. package/dist/commands/groupaccesstoken/deleteToken.spec.js.map +1 -0
  70. package/dist/commands/groupaccesstoken/groupContainer.js +71 -0
  71. package/dist/commands/groupaccesstoken/groupContainer.js.map +1 -0
  72. package/dist/commands/groupaccesstoken/listTokens.js +25 -0
  73. package/dist/commands/groupaccesstoken/listTokens.js.map +1 -0
  74. package/dist/commands/groupaccesstoken/listTokens.spec.js +115 -0
  75. package/dist/commands/groupaccesstoken/listTokens.spec.js.map +1 -0
  76. package/dist/commands/list.js +75 -0
  77. package/dist/commands/list.js.map +1 -0
  78. package/dist/commands/misc/login.js +290 -0
  79. package/dist/commands/misc/login.js.map +1 -0
  80. package/dist/commands/misc/login.spec.js +243 -0
  81. package/dist/commands/misc/login.spec.js.map +1 -0
  82. package/dist/commands/misc/logout.js +11 -0
  83. package/dist/commands/misc/logout.js.map +1 -0
  84. package/dist/commands/misc/logout.spec.js +30 -0
  85. package/dist/commands/misc/logout.spec.js.map +1 -0
  86. package/dist/commands/misc/loopbackOAuth.js +110 -0
  87. package/dist/commands/misc/loopbackOAuth.js.map +1 -0
  88. package/dist/commands/misc/set-access-token.js +27 -0
  89. package/dist/commands/misc/set-access-token.js.map +1 -0
  90. package/dist/commands/misc/set-access-token.spec.js +65 -0
  91. package/dist/commands/misc/set-access-token.spec.js.map +1 -0
  92. package/dist/commands/package/deletepackage.js +54 -0
  93. package/dist/commands/package/deletepackage.js.map +1 -0
  94. package/dist/commands/package/deletepackage.spec.js +136 -0
  95. package/dist/commands/package/deletepackage.spec.js.map +1 -0
  96. package/dist/commands/package/listpackages.js +57 -0
  97. package/dist/commands/package/listpackages.js.map +1 -0
  98. package/dist/commands/package/listpackages.spec.js +129 -0
  99. package/dist/commands/package/listpackages.spec.js.map +1 -0
  100. package/dist/commands/package/publish.js +157 -0
  101. package/dist/commands/package/publish.js.map +1 -0
  102. package/dist/commands/package/publish.spec.js +511 -0
  103. package/dist/commands/package/publish.spec.js.map +1 -0
  104. package/dist/commands/package/updatepackage.js +75 -0
  105. package/dist/commands/package/updatepackage.js.map +1 -0
  106. package/dist/commands/package/updatepackage.spec.js +189 -0
  107. package/dist/commands/package/updatepackage.spec.js.map +1 -0
  108. package/dist/commands/project/createproject.js +75 -0
  109. package/dist/commands/project/createproject.js.map +1 -0
  110. package/dist/commands/project/createproject.spec.js +154 -0
  111. package/dist/commands/project/createproject.spec.js.map +1 -0
  112. package/dist/commands/project/deleteproject.js +49 -0
  113. package/dist/commands/project/deleteproject.js.map +1 -0
  114. package/dist/commands/project/deleteproject.spec.js +106 -0
  115. package/dist/commands/project/deleteproject.spec.js.map +1 -0
  116. package/dist/commands/project/getproject.js +44 -0
  117. package/dist/commands/project/getproject.js.map +1 -0
  118. package/dist/commands/project/getproject.spec.js +99 -0
  119. package/dist/commands/project/getproject.spec.js.map +1 -0
  120. package/dist/commands/project/listprojects.js +52 -0
  121. package/dist/commands/project/listprojects.js.map +1 -0
  122. package/dist/commands/project/listprojects.spec.js +111 -0
  123. package/dist/commands/project/listprojects.spec.js.map +1 -0
  124. package/dist/commands/project/setproject.js +26 -0
  125. package/dist/commands/project/setproject.js.map +1 -0
  126. package/dist/commands/project/setproject.spec.js +58 -0
  127. package/dist/commands/project/setproject.spec.js.map +1 -0
  128. package/dist/commands/project/updateproject.js +68 -0
  129. package/dist/commands/project/updateproject.js.map +1 -0
  130. package/dist/commands/project/updateproject.spec.js +129 -0
  131. package/dist/commands/project/updateproject.spec.js.map +1 -0
  132. package/dist/commands/rm.js +68 -0
  133. package/dist/commands/rm.js.map +1 -0
  134. package/dist/commands/set.js +17 -0
  135. package/dist/commands/set.js.map +1 -0
  136. package/dist/commands/update.js +42 -0
  137. package/dist/commands/update.js.map +1 -0
  138. package/dist/commands/version/archive-unarchive-version.js +79 -0
  139. package/dist/commands/version/archive-unarchive-version.js.map +1 -0
  140. package/dist/commands/version/archive-unarchive-version.spec.js +244 -0
  141. package/dist/commands/version/archive-unarchive-version.spec.js.map +1 -0
  142. package/dist/commands/version/listversions.js +46 -0
  143. package/dist/commands/version/listversions.js.map +1 -0
  144. package/dist/commands/version/listversions.spec.js +121 -0
  145. package/dist/commands/version/listversions.spec.js.map +1 -0
  146. package/dist/common/UserGroupId.js +112 -0
  147. package/dist/common/UserGroupId.js.map +1 -0
  148. package/dist/common/UserGroupId.spec.js +92 -0
  149. package/dist/common/UserGroupId.spec.js.map +1 -0
  150. package/dist/constants.js +90 -0
  151. package/dist/constants.js.map +1 -0
  152. package/dist/index.js +159 -0
  153. package/dist/index.js.map +1 -0
  154. package/dist/logger.js +77 -0
  155. package/dist/logger.js.map +1 -0
  156. package/dist/logger.spec.js +125 -0
  157. package/dist/logger.spec.js.map +1 -0
  158. package/dist/polyfills/file.polyfill.js +6 -0
  159. package/dist/polyfills/file.polyfill.js.map +1 -0
  160. package/dist/utils.js +110 -0
  161. package/dist/utils.js.map +1 -0
  162. package/dist/utils.spec.js +158 -0
  163. package/dist/utils.spec.js.map +1 -0
  164. package/dist/validator.js +53 -0
  165. package/dist/validator.js.map +1 -0
  166. package/dist/validator.spec.js +41 -0
  167. package/dist/validator.spec.js.map +1 -0
  168. package/docker/Dockerfile.ci +18 -0
  169. package/docker-compose.yml +18 -0
  170. package/eslint.config.mjs +45 -0
  171. package/generate-client.sh +27 -0
  172. package/mocha.setup.ts +4 -0
  173. package/openapitools.json +7 -0
  174. package/package.json +75 -0
  175. package/src/client/.openapi-generator/FILES +9 -0
  176. package/src/client/.openapi-generator/VERSION +1 -0
  177. package/src/client/.openapi-generator-ignore +23 -0
  178. package/src/client/api.ts +9729 -0
  179. package/src/client/base.ts +86 -0
  180. package/src/client/common.ts +150 -0
  181. package/src/client/configuration.ts +110 -0
  182. package/src/client/git_push.sh +57 -0
  183. package/src/client/index.ts +18 -0
  184. package/src/clientconfiguration.spec.ts +490 -0
  185. package/src/clientconfiguration.ts +263 -0
  186. package/src/commands/add.ts +125 -0
  187. package/src/commands/connection/createconnections.spec.ts +403 -0
  188. package/src/commands/connection/createconnections.ts +109 -0
  189. package/src/commands/connection/deleteconnection.spec.ts +189 -0
  190. package/src/commands/connection/deleteconnection.ts +57 -0
  191. package/src/commands/connection/listconnections.spec.ts +132 -0
  192. package/src/commands/connection/listconnections.ts +58 -0
  193. package/src/commands/get.ts +31 -0
  194. package/src/commands/group/addmember.spec.ts +257 -0
  195. package/src/commands/group/addmember.ts +85 -0
  196. package/src/commands/group/creategroup.spec.ts +146 -0
  197. package/src/commands/group/creategroup.ts +54 -0
  198. package/src/commands/group/deletegroup.spec.ts +120 -0
  199. package/src/commands/group/deletegroup.ts +45 -0
  200. package/src/commands/group/getgroup.spec.ts +129 -0
  201. package/src/commands/group/getgroup.ts +47 -0
  202. package/src/commands/group/listgroups.spec.ts +137 -0
  203. package/src/commands/group/listgroups.ts +45 -0
  204. package/src/commands/group/removemember.spec.ts +224 -0
  205. package/src/commands/group/removemember.ts +75 -0
  206. package/src/commands/groupaccesstoken/createToken.spec.ts +211 -0
  207. package/src/commands/groupaccesstoken/createToken.ts +72 -0
  208. package/src/commands/groupaccesstoken/deleteToken.spec.ts +147 -0
  209. package/src/commands/groupaccesstoken/deleteToken.ts +29 -0
  210. package/src/commands/groupaccesstoken/groupContainer.ts +83 -0
  211. package/src/commands/groupaccesstoken/listTokens.spec.ts +147 -0
  212. package/src/commands/groupaccesstoken/listTokens.ts +29 -0
  213. package/src/commands/list.ts +131 -0
  214. package/src/commands/misc/login.spec.ts +213 -0
  215. package/src/commands/misc/login.ts +325 -0
  216. package/src/commands/misc/logout.spec.ts +32 -0
  217. package/src/commands/misc/logout.ts +9 -0
  218. package/src/commands/misc/loopbackOAuth.ts +127 -0
  219. package/src/commands/misc/set-access-token.spec.ts +95 -0
  220. package/src/commands/misc/set-access-token.ts +32 -0
  221. package/src/commands/package/deletepackage.spec.ts +164 -0
  222. package/src/commands/package/deletepackage.ts +58 -0
  223. package/src/commands/package/listpackages.spec.ts +147 -0
  224. package/src/commands/package/listpackages.ts +67 -0
  225. package/src/commands/package/publish.spec.ts +597 -0
  226. package/src/commands/package/publish.ts +211 -0
  227. package/src/commands/package/updatepackage.spec.ts +319 -0
  228. package/src/commands/package/updatepackage.ts +95 -0
  229. package/src/commands/project/createproject.spec.ts +247 -0
  230. package/src/commands/project/createproject.ts +70 -0
  231. package/src/commands/project/deleteproject.spec.ts +123 -0
  232. package/src/commands/project/deleteproject.ts +48 -0
  233. package/src/commands/project/getproject.spec.ts +109 -0
  234. package/src/commands/project/getproject.ts +43 -0
  235. package/src/commands/project/listprojects.spec.ts +121 -0
  236. package/src/commands/project/listprojects.ts +62 -0
  237. package/src/commands/project/setproject.spec.ts +63 -0
  238. package/src/commands/project/setproject.ts +14 -0
  239. package/src/commands/project/updateproject.spec.ts +217 -0
  240. package/src/commands/project/updateproject.ts +82 -0
  241. package/src/commands/rm.ts +101 -0
  242. package/src/commands/set.ts +16 -0
  243. package/src/commands/update.ts +64 -0
  244. package/src/commands/version/archive-unarchive-version.spec.ts +456 -0
  245. package/src/commands/version/archive-unarchive-version.ts +111 -0
  246. package/src/commands/version/listversions.spec.ts +138 -0
  247. package/src/commands/version/listversions.ts +45 -0
  248. package/src/common/UserGroupId.spec.ts +117 -0
  249. package/src/common/UserGroupId.ts +137 -0
  250. package/src/constants.ts +116 -0
  251. package/src/index.ts +209 -0
  252. package/src/logger.spec.ts +158 -0
  253. package/src/logger.ts +86 -0
  254. package/src/polyfills/file.polyfill.ts +6 -0
  255. package/src/utils.spec.ts +135 -0
  256. package/src/utils.ts +115 -0
  257. package/src/validator.spec.ts +51 -0
  258. package/src/validator.ts +44 -0
  259. package/tsconfig.json +17 -0
package/src/index.ts ADDED
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env node
2
+ import axios from "axios";
3
+ import { program } from "commander";
4
+ import kleur from "kleur";
5
+ import omelette from "omelette";
6
+ import cliPackage from "../package.json";
7
+ import { ClientConfiguration } from "./clientconfiguration";
8
+ import { registerAddCommands } from "./commands/add";
9
+ import { registerGetCommands } from "./commands/get";
10
+ import { registerListCommands } from "./commands/list";
11
+ import { login } from "./commands/misc/login";
12
+ import { logout } from "./commands/misc/logout";
13
+ import { setAccessToken } from "./commands/misc/set-access-token";
14
+ import { listPackages } from "./commands/package/listpackages";
15
+ import { publish } from "./commands/package/publish";
16
+ import { listProjects } from "./commands/project/listprojects";
17
+ import { registerRmCommands } from "./commands/rm";
18
+ import { registerSetCommands } from "./commands/set";
19
+ import { registerUpdateCommands } from "./commands/update";
20
+ import {
21
+ archiveVersion,
22
+ unarchiveVersion,
23
+ } from "./commands/version/archive-unarchive-version";
24
+ import { ASCII_TITLE, controlPlanesByEnv } from "./constants";
25
+
26
+ const clientConfig = new ClientConfiguration();
27
+
28
+ const getActiveEnvironment = () => {
29
+ return clientConfig.getEnvironment();
30
+ };
31
+ axios.defaults.baseURL = controlPlanesByEnv[getActiveEnvironment()];
32
+
33
+ const cli = program
34
+ .version(
35
+ cliPackage.version,
36
+ "-V, --cli-version",
37
+ "Output the CLI version number",
38
+ )
39
+ .description(kleur.green(ASCII_TITLE));
40
+
41
+ // Nested CRUD commands for resource management
42
+ registerGetCommands(cli, getActiveEnvironment());
43
+ registerListCommands(cli, getActiveEnvironment());
44
+ registerUpdateCommands(cli, getActiveEnvironment());
45
+ registerRmCommands(cli, getActiveEnvironment());
46
+ registerAddCommands(cli, getActiveEnvironment());
47
+ registerSetCommands(cli);
48
+
49
+ // Root commands
50
+ cli.command("publish")
51
+ .description("Publish a new version of the package.")
52
+ .option("--debug")
53
+ .option("-y, --yes", "Skip confirmation.")
54
+ .option(
55
+ "--replication <replication_count>",
56
+ "The number of replicas to create.",
57
+ (v) => (v === undefined ? undefined : parseInt(v)),
58
+ undefined,
59
+ )
60
+ .option(
61
+ "--set-latest",
62
+ "Set this version as the latest version of the package.",
63
+ false,
64
+ )
65
+ .action((options) => {
66
+ publish(
67
+ controlPlanesByEnv[getActiveEnvironment()],
68
+ options.replicationCount,
69
+ options.debug,
70
+ options.yes,
71
+ options.setLatest,
72
+ );
73
+ });
74
+
75
+ cli.command("archive")
76
+ .description("Archive a version of a package.")
77
+ .argument(
78
+ "<packageName>",
79
+ "The name of the package to archive the version of.",
80
+ )
81
+ .argument("<versionId>", "The id of the version to archive.")
82
+ .option("--debug")
83
+ .option("-y, --yes", "Skip confirmation.")
84
+ .action((packageName, versionId, options) => {
85
+ archiveVersion(
86
+ controlPlanesByEnv[getActiveEnvironment()],
87
+ packageName,
88
+ versionId,
89
+ options.debug,
90
+ options.yes,
91
+ );
92
+ });
93
+
94
+ cli.command("unarchive")
95
+ .description("Unarchive a version of a package.")
96
+ .argument(
97
+ "<packageName>",
98
+ "The name of the package to unarchive the version of.",
99
+ )
100
+ .argument("<versionId>", "The id of the version to unarchive.")
101
+ .option("--debug")
102
+ .option("-y, --yes", "Skip confirmation.")
103
+ .action((packageName, versionId, options) => {
104
+ unarchiveVersion(
105
+ controlPlanesByEnv[getActiveEnvironment()],
106
+ packageName,
107
+ versionId,
108
+ options.debug,
109
+ options.yes,
110
+ );
111
+ });
112
+
113
+ cli.command("login")
114
+ .description("Login into Credible service.")
115
+ .argument("<organizationName>", "The organization to login to.")
116
+ .action((organizationName) => {
117
+ // TODO: Uncomment this when aws videoamp cluster is ready
118
+ // if (organizationName === "videoamp") {
119
+ // clientConfig.setEnvironment("videoamp" as Environment);
120
+ // }
121
+ login(organizationName, controlPlanesByEnv[getActiveEnvironment()]);
122
+ });
123
+
124
+ cli.command("logout")
125
+ .description("Resets Credible service credentials")
126
+ .action(logout);
127
+
128
+ cli.command("set-access-token")
129
+ .description("Set a service account JWT token for CLI authentication")
130
+ .argument("<jwtToken>", "The JWT token to use for authentication")
131
+ .option("-o, --organization <organizationName>", "Organization name to set")
132
+ .action((jwtToken, options) => {
133
+ setAccessToken(jwtToken, options.organization);
134
+ });
135
+
136
+ cli.command("status")
137
+ .description("Shows the currently active organization and project.")
138
+ .action(() => {
139
+ const config = new ClientConfiguration();
140
+ config.printStatus();
141
+ });
142
+
143
+ const complete = omelette("credible").tree({
144
+ // CRUD commands
145
+ get: ["project", "group"],
146
+ ls: [
147
+ "project",
148
+ "connection",
149
+ "package",
150
+ "version",
151
+ "group",
152
+ "group-access-token",
153
+ ],
154
+ update: ["project", "package"],
155
+ rm: [
156
+ "project",
157
+ "connection",
158
+ "package",
159
+ "group",
160
+ "member",
161
+ "group-access-token",
162
+ ],
163
+ add: ["project", "connection", "group", "member", "group-access-token"],
164
+ // Misc commands
165
+ set: ["project"],
166
+ login: [],
167
+ logout: [],
168
+ "set-access-token": [],
169
+ status: [],
170
+ publish: [],
171
+ archive: [],
172
+ unarchive: [],
173
+ });
174
+
175
+ complete.onAsync("$3", async ({ reply, line }) => {
176
+ const projectRelatedCommands = ["set project", "get project", "rm project"];
177
+ if (projectRelatedCommands.some((cmd) => line.includes(cmd))) {
178
+ reply(
179
+ listProjects(controlPlanesByEnv[getActiveEnvironment()], false).then(
180
+ (result) => result.projects.map((project) => project.name),
181
+ ),
182
+ );
183
+ }
184
+ const packageRelatedCommands = ["rm package", "ls version"];
185
+ if (packageRelatedCommands.some((cmd) => line.includes(cmd))) {
186
+ reply(
187
+ listPackages(controlPlanesByEnv[getActiveEnvironment()], false).then(
188
+ (result) => result.packages.map((_package) => _package.name),
189
+ ),
190
+ );
191
+ }
192
+ });
193
+
194
+ complete.next(() => {
195
+ program.parse(process.argv);
196
+ });
197
+
198
+ if (~process.argv.indexOf("--cleanup")) {
199
+ complete.cleanupShellInitFile();
200
+ process.exit(0);
201
+ }
202
+
203
+ if (~process.argv.indexOf("--install")) {
204
+ complete.setupShellInitFile();
205
+ console.log("CLI installed successfully!");
206
+ process.exit(0);
207
+ }
208
+
209
+ complete.init();
@@ -0,0 +1,158 @@
1
+ import { expect } from "chai";
2
+ import sinon from "sinon";
3
+ import { AxiosError } from "axios";
4
+ import { logger, logAxiosError } from "./logger";
5
+
6
+ describe("logAxiosError", () => {
7
+ let loggerStub: sinon.SinonStubbedInstance<typeof logger>;
8
+ let sandbox: sinon.SinonSandbox;
9
+
10
+ beforeEach(() => {
11
+ sandbox = sinon.createSandbox();
12
+ loggerStub = sandbox.stub(logger);
13
+ });
14
+
15
+ afterEach(() => {
16
+ sandbox.restore();
17
+ });
18
+
19
+ it("should log 401 Unauthorized error", () => {
20
+ const error = {
21
+ response: {
22
+ status: 401,
23
+ data: null,
24
+ },
25
+ request: { path: "/api/test" },
26
+ isAxiosError: true,
27
+ } as unknown as AxiosError;
28
+
29
+ logAxiosError(error, false);
30
+
31
+ expect(loggerStub.error.calledWith(sinon.match("Unauthorized"))).to.be
32
+ .true;
33
+ });
34
+
35
+ it("should log 403 Forbidden error", () => {
36
+ const error = {
37
+ response: {
38
+ status: 403,
39
+ data: null,
40
+ },
41
+ request: { path: "/api/test" },
42
+ isAxiosError: true,
43
+ } as unknown as AxiosError;
44
+
45
+ logAxiosError(error, false);
46
+
47
+ expect(loggerStub.error.calledWith(sinon.match("Forbidden"))).to.be.true;
48
+ });
49
+
50
+ it("should log 500 Internal Server Error", () => {
51
+ const error = {
52
+ response: {
53
+ status: 500,
54
+ data: null,
55
+ },
56
+ request: { path: "/api/test" },
57
+ isAxiosError: true,
58
+ } as unknown as AxiosError;
59
+
60
+ logAxiosError(error, false);
61
+
62
+ expect(loggerStub.error.calledWith(sinon.match("Internal Server Error")))
63
+ .to.be.true;
64
+ });
65
+
66
+ it("should log response data when present", () => {
67
+ const error = {
68
+ response: {
69
+ status: 400,
70
+ data: "Bad request details",
71
+ },
72
+ request: { path: "/api/test" },
73
+ isAxiosError: true,
74
+ } as unknown as AxiosError;
75
+
76
+ logAxiosError(error, false);
77
+
78
+ expect(loggerStub.error.calledWith(sinon.match("Bad request details"))).to
79
+ .be.true;
80
+ });
81
+
82
+ it("should log debug info with status and path when debug is true", () => {
83
+ const error = {
84
+ response: {
85
+ status: 401,
86
+ data: null,
87
+ },
88
+ request: { path: "/api/test" },
89
+ isAxiosError: true,
90
+ } as unknown as AxiosError;
91
+
92
+ logAxiosError(error, true);
93
+
94
+ expect(loggerStub.error.calledWith(sinon.match("Error 401 at /api/test")))
95
+ .to.be.true;
96
+ });
97
+
98
+ it("should not log debug info when debug is false", () => {
99
+ const error = {
100
+ response: {
101
+ status: 401,
102
+ data: null,
103
+ },
104
+ request: { path: "/api/test" },
105
+ isAxiosError: true,
106
+ } as unknown as AxiosError;
107
+
108
+ logAxiosError(error, false);
109
+
110
+ expect(loggerStub.error.calledWith(sinon.match("Error 401 at"))).to.be
111
+ .false;
112
+ });
113
+
114
+ it("should handle request error with no response", () => {
115
+ const error = {
116
+ response: undefined,
117
+ request: {
118
+ _options: {
119
+ method: "GET",
120
+ path: "/api/resource",
121
+ hostname: "example.com",
122
+ port: 443,
123
+ },
124
+ },
125
+ code: "ECONNREFUSED",
126
+ message: "Connection refused",
127
+ isAxiosError: true,
128
+ } as unknown as AxiosError;
129
+
130
+ logAxiosError(error, false);
131
+
132
+ expect(
133
+ loggerStub.error.calledWith(
134
+ sinon.match(
135
+ "Failed to GET resource /api/resource on example.com:443",
136
+ ),
137
+ ),
138
+ ).to.be.true;
139
+ });
140
+
141
+ it("should handle setup error with no request or response", () => {
142
+ const error = {
143
+ response: undefined,
144
+ request: undefined,
145
+ message: "Invalid URL",
146
+ isAxiosError: true,
147
+ } as unknown as AxiosError;
148
+
149
+ logAxiosError(error, false);
150
+
151
+ expect(
152
+ (loggerStub.error as sinon.SinonStub).calledWith(
153
+ "Error",
154
+ "Invalid URL",
155
+ ),
156
+ ).to.be.true;
157
+ });
158
+ });
package/src/logger.ts ADDED
@@ -0,0 +1,86 @@
1
+ import { AxiosError } from "axios";
2
+ import kleur from "kleur";
3
+ import winston from "winston";
4
+
5
+ const logColors = {
6
+ error: kleur.red,
7
+ warn: kleur.yellow,
8
+ info: kleur.cyan,
9
+ http: kleur.magenta,
10
+ debug: kleur.gray,
11
+ };
12
+
13
+ // Custom console log format
14
+ const consoleFormat = winston.format.printf(({ level, message, timestamp }) => {
15
+ const colorFn = logColors[level] || ((text: string) => text);
16
+ return `[${kleur.gray(timestamp as string)}] ${colorFn(level.toUpperCase())}: ${message}`;
17
+ });
18
+
19
+ // Create Winston logger
20
+ export const logger = winston.createLogger({
21
+ level: "debug",
22
+ format: winston.format.combine(
23
+ winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
24
+ winston.format.errors({ stack: true }),
25
+ winston.format.splat(),
26
+ ),
27
+ transports: [
28
+ // Console transport with colored logs
29
+ new winston.transports.Console({
30
+ format: consoleFormat,
31
+ }),
32
+
33
+ // NOTE disabled app.log logging
34
+ // File transport (logs without colors)
35
+ // new winston.transports.File({
36
+ // filename: "app.log",
37
+ // level: "info",
38
+ // format: winston.format.json(),
39
+ // }),
40
+ ],
41
+ });
42
+
43
+ export const logAxiosError = (error: AxiosError, debug: boolean) => {
44
+ if (error.response) {
45
+ // The request was made and the server responded with a status code
46
+ // that falls out of the range of 2xx
47
+ if (debug) {
48
+ logger.error(
49
+ `Error ${error.response.status} at ${error.request.path}`,
50
+ );
51
+ }
52
+ switch (error.response.status) {
53
+ case 401:
54
+ logger.error(
55
+ "Unauthorized: Please double check that you are logged in",
56
+ );
57
+ break;
58
+ case 403:
59
+ logger.error(
60
+ "Forbidden: You're not allowed to perform this operation",
61
+ );
62
+ break;
63
+ case 500:
64
+ logger.error("Internal Server Error");
65
+ break;
66
+ default:
67
+ break;
68
+ }
69
+ if (error.response.data) {
70
+ logger.error(error.response.data);
71
+ }
72
+ } else if (error.request) {
73
+ // The request was made but no response was received
74
+ // `error.request` is an instance of XMLHttpRequest in the browser and an instance of
75
+ // http.ClientRequest in node.js
76
+ const requestOptions = error.request._options;
77
+ logger.error(
78
+ `Failed to ${requestOptions.method} resource ${requestOptions.path} on ${requestOptions.hostname}:${requestOptions.port}`,
79
+ );
80
+
81
+ logger.error(error.code, error.message);
82
+ } else {
83
+ // Something happened in setting up the request that triggered an Error
84
+ logger.error("Error", error.message);
85
+ }
86
+ };
@@ -0,0 +1,6 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars -- reserved for future globalThis polyfill */
2
+ import { File, Blob } from "formdata-node";
3
+
4
+ // TODO: Polyfill leads to errors when pushing packages.
5
+ //globalThis.File = File as unknown as typeof globalThis.File;
6
+ //globalThis.Blob = Blob as unknown as typeof globalThis.Blob;
@@ -0,0 +1,135 @@
1
+ import axios from "axios";
2
+ import { expect } from "chai";
3
+ import inquirer from "inquirer";
4
+ import sinon from "sinon";
5
+ import * as loggerModule from "./logger";
6
+ import { getConfirmation, prettifyJson, printOutError, sleep } from "./utils";
7
+
8
+ describe("utils", () => {
9
+ let sandbox: sinon.SinonSandbox;
10
+ let loggerStub: sinon.SinonStubbedInstance<typeof loggerModule.logger>;
11
+ let logAxiosErrorStub: sinon.SinonStub;
12
+
13
+ beforeEach(() => {
14
+ sandbox = sinon.createSandbox();
15
+ loggerStub = sandbox.stub(loggerModule.logger);
16
+ logAxiosErrorStub = sandbox.stub(loggerModule, "logAxiosError");
17
+ });
18
+
19
+ afterEach(() => {
20
+ sandbox.restore();
21
+ });
22
+
23
+ describe("sleep", () => {
24
+ it("should wait for the specified time", async function () {
25
+ this.timeout(100);
26
+ const start = Date.now();
27
+ await sleep(50);
28
+ const elapsed = Date.now() - start;
29
+ expect(elapsed).to.be.at.least(45);
30
+ });
31
+ });
32
+
33
+ describe("prettifyJson", () => {
34
+ it("should format JSON with 2-space indentation", () => {
35
+ const obj = { name: "test", value: 123 };
36
+ const result = prettifyJson(obj);
37
+ expect(result).to.equal('{\n "name": "test",\n "value": 123\n}');
38
+ });
39
+
40
+ it("should handle empty objects", () => {
41
+ const result = prettifyJson({});
42
+ expect(result).to.equal("{}");
43
+ });
44
+
45
+ it("should handle nested objects", () => {
46
+ const obj = { nested: { key: "value" } };
47
+ const result = prettifyJson(obj);
48
+ expect(result).to.include("nested");
49
+ expect(result).to.include("key");
50
+ expect(result).to.include("value");
51
+ });
52
+ });
53
+
54
+ describe("printOutError", () => {
55
+ it("should handle axios errors", () => {
56
+ const axiosError = {
57
+ isAxiosError: true,
58
+ response: {
59
+ status: 404,
60
+ data: { message: "Not found" },
61
+ },
62
+ config: {},
63
+ name: "AxiosError",
64
+ message: "Request failed",
65
+ };
66
+ sandbox.stub(axios, "isAxiosError").returns(true);
67
+
68
+ printOutError(axiosError as any, false);
69
+
70
+ expect(logAxiosErrorStub.calledOnce).to.be.true;
71
+ expect(logAxiosErrorStub.firstCall.args[0]).to.equal(axiosError);
72
+ expect(logAxiosErrorStub.firstCall.args[1]).to.equal(false);
73
+ });
74
+
75
+ it("should handle non-axios errors", () => {
76
+ const error = new Error("Generic error");
77
+ sandbox.stub(axios, "isAxiosError").returns(false);
78
+
79
+ printOutError(error, false);
80
+
81
+ expect(loggerStub.error.calledOnce).to.be.true;
82
+ expect(loggerStub.error.calledWith(sinon.match("Unexpected error"))).to
83
+ .be.true;
84
+ // Check that error was passed as second argument (Winston accepts Error objects)
85
+ const errorCall = loggerStub.error.getCall(0);
86
+ expect(errorCall.args.length).to.be.at.least(2);
87
+ expect((errorCall.args as any)[1]).to.equal(error);
88
+ });
89
+
90
+ it("should log debug info when debug is true", () => {
91
+ const error = new Error("Test error");
92
+ sandbox.stub(axios, "isAxiosError").returns(false);
93
+
94
+ printOutError(error, true);
95
+
96
+ expect(loggerStub.debug.calledWith(error)).to.be.true;
97
+ });
98
+
99
+ it("should not log debug info when debug is false", () => {
100
+ const error = new Error("Test error");
101
+ sandbox.stub(axios, "isAxiosError").returns(false);
102
+
103
+ printOutError(error, false);
104
+
105
+ expect(loggerStub.debug.called).to.be.false;
106
+ });
107
+ });
108
+
109
+ describe("getConfirmation", () => {
110
+ it("should return true when user confirms", async () => {
111
+ const promptStub = sandbox
112
+ .stub(inquirer, "prompt")
113
+ .resolves({ confirm: true });
114
+
115
+ const result = await getConfirmation("Are you sure?");
116
+
117
+ expect(result).to.be.true;
118
+ expect(promptStub.calledOnce).to.be.true;
119
+ expect(promptStub.firstCall.args[0][0].message).to.equal(
120
+ "Are you sure?",
121
+ );
122
+ });
123
+
124
+ it("should return false when user denies", async () => {
125
+ const promptStub = sandbox
126
+ .stub(inquirer, "prompt")
127
+ .resolves({ confirm: false });
128
+
129
+ const result = await getConfirmation("Are you sure?");
130
+
131
+ expect(result).to.be.false;
132
+ expect(promptStub.calledOnce).to.be.true;
133
+ });
134
+ });
135
+ });