codmir 0.1.0 → 0.2.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.
- package/README.md +20 -0
- package/dist/chunk-7HVQNURM.mjs +273 -0
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +769 -0
- package/dist/cli/index.mjs +479 -0
- package/dist/index.mjs +3 -269
- package/package.json +13 -5
|
@@ -0,0 +1,769 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/cli/index.ts
|
|
27
|
+
var import_commander = require("commander");
|
|
28
|
+
|
|
29
|
+
// src/cli/utils/auth.ts
|
|
30
|
+
var import_http = __toESM(require("http"));
|
|
31
|
+
var import_open = __toESM(require("open"));
|
|
32
|
+
|
|
33
|
+
// src/cli/utils/config.ts
|
|
34
|
+
var import_fs = __toESM(require("fs"));
|
|
35
|
+
var import_path = __toESM(require("path"));
|
|
36
|
+
var import_os = __toESM(require("os"));
|
|
37
|
+
var CONFIG_DIR = import_path.default.join(import_os.default.homedir(), ".codmir");
|
|
38
|
+
var CONFIG_FILE = import_path.default.join(CONFIG_DIR, "config.json");
|
|
39
|
+
var PROJECT_CONFIG_FILE = ".codmir.json";
|
|
40
|
+
function ensureConfigDir() {
|
|
41
|
+
if (!import_fs.default.existsSync(CONFIG_DIR)) {
|
|
42
|
+
import_fs.default.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function readConfig() {
|
|
46
|
+
ensureConfigDir();
|
|
47
|
+
if (!import_fs.default.existsSync(CONFIG_FILE)) {
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const data = import_fs.default.readFileSync(CONFIG_FILE, "utf-8");
|
|
52
|
+
return JSON.parse(data);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error("Error reading config:", error);
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function writeConfig(config) {
|
|
59
|
+
ensureConfigDir();
|
|
60
|
+
import_fs.default.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
61
|
+
}
|
|
62
|
+
function clearConfig() {
|
|
63
|
+
if (import_fs.default.existsSync(CONFIG_FILE)) {
|
|
64
|
+
import_fs.default.unlinkSync(CONFIG_FILE);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function readProjectConfig(cwd = process.cwd()) {
|
|
68
|
+
const configPath = import_path.default.join(cwd, PROJECT_CONFIG_FILE);
|
|
69
|
+
if (!import_fs.default.existsSync(configPath)) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const data = import_fs.default.readFileSync(configPath, "utf-8");
|
|
74
|
+
return JSON.parse(data);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error("Error reading project config:", error);
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function writeProjectConfig(config, cwd = process.cwd()) {
|
|
81
|
+
const configPath = import_path.default.join(cwd, PROJECT_CONFIG_FILE);
|
|
82
|
+
import_fs.default.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
83
|
+
const gitignorePath = import_path.default.join(cwd, ".gitignore");
|
|
84
|
+
if (import_fs.default.existsSync(gitignorePath)) {
|
|
85
|
+
const gitignore = import_fs.default.readFileSync(gitignorePath, "utf-8");
|
|
86
|
+
if (!gitignore.includes(PROJECT_CONFIG_FILE)) {
|
|
87
|
+
import_fs.default.appendFileSync(gitignorePath, `
|
|
88
|
+
# codmir
|
|
89
|
+
${PROJECT_CONFIG_FILE}
|
|
90
|
+
`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function getToken() {
|
|
95
|
+
const config = readConfig();
|
|
96
|
+
return config.token || null;
|
|
97
|
+
}
|
|
98
|
+
function isAuthenticated() {
|
|
99
|
+
const token = getToken();
|
|
100
|
+
return !!token;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/cli/utils/auth.ts
|
|
104
|
+
async function startOAuthFlow(baseUrl = "https://codmir.com") {
|
|
105
|
+
return new Promise((resolve, reject) => {
|
|
106
|
+
const server = import_http.default.createServer(async (req, res) => {
|
|
107
|
+
const url = new URL(req.url || "/", `http://localhost:${port}`);
|
|
108
|
+
if (url.pathname === "/callback") {
|
|
109
|
+
const token = url.searchParams.get("token");
|
|
110
|
+
const error = url.searchParams.get("error");
|
|
111
|
+
if (error) {
|
|
112
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
113
|
+
res.end(`
|
|
114
|
+
<html>
|
|
115
|
+
<body style="font-family: system-ui; padding: 40px; text-align: center;">
|
|
116
|
+
<h1>\u274C Authentication Failed</h1>
|
|
117
|
+
<p>${error}</p>
|
|
118
|
+
<p>You can close this window.</p>
|
|
119
|
+
</body>
|
|
120
|
+
</html>
|
|
121
|
+
`);
|
|
122
|
+
server.close();
|
|
123
|
+
reject(new Error(error));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (!token) {
|
|
127
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
128
|
+
res.end(`
|
|
129
|
+
<html>
|
|
130
|
+
<body style="font-family: system-ui; padding: 40px; text-align: center;">
|
|
131
|
+
<h1>\u274C No token received</h1>
|
|
132
|
+
<p>You can close this window.</p>
|
|
133
|
+
</body>
|
|
134
|
+
</html>
|
|
135
|
+
`);
|
|
136
|
+
server.close();
|
|
137
|
+
reject(new Error("No token received"));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
141
|
+
res.end(`
|
|
142
|
+
<html>
|
|
143
|
+
<body style="font-family: system-ui; padding: 40px; text-align: center;">
|
|
144
|
+
<h1>\u2705 Authentication Successful!</h1>
|
|
145
|
+
<p>You can now close this window and return to your terminal.</p>
|
|
146
|
+
<script>setTimeout(() => window.close(), 2000);</script>
|
|
147
|
+
</body>
|
|
148
|
+
</html>
|
|
149
|
+
`);
|
|
150
|
+
try {
|
|
151
|
+
const userResponse = await fetch(`${baseUrl}/api/user/profile`, {
|
|
152
|
+
headers: {
|
|
153
|
+
"Authorization": `Bearer ${token}`
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
if (!userResponse.ok) {
|
|
157
|
+
throw new Error("Failed to fetch user info");
|
|
158
|
+
}
|
|
159
|
+
const user = await userResponse.json();
|
|
160
|
+
server.close();
|
|
161
|
+
resolve({
|
|
162
|
+
token,
|
|
163
|
+
user: {
|
|
164
|
+
id: user.id,
|
|
165
|
+
email: user.email,
|
|
166
|
+
name: user.name
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
} catch (error2) {
|
|
170
|
+
server.close();
|
|
171
|
+
reject(error2);
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
res.writeHead(404);
|
|
175
|
+
res.end("Not found");
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
const port = 3333;
|
|
179
|
+
server.listen(port, () => {
|
|
180
|
+
const authUrl = `${baseUrl}/cli/auth?port=${port}`;
|
|
181
|
+
console.log("\u{1F510} Opening browser for authentication...");
|
|
182
|
+
console.log(` If browser doesn't open, visit: ${authUrl}`);
|
|
183
|
+
(0, import_open.default)(authUrl).catch(() => {
|
|
184
|
+
console.log("\n\u26A0\uFE0F Could not open browser automatically.");
|
|
185
|
+
console.log(` Please visit: ${authUrl}`);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
setTimeout(() => {
|
|
189
|
+
server.close();
|
|
190
|
+
reject(new Error("Authentication timeout"));
|
|
191
|
+
}, 5 * 60 * 1e3);
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
async function authenticateWithToken(token, baseUrl = "https://codmir.com") {
|
|
195
|
+
const response = await fetch(`${baseUrl}/api/user/profile`, {
|
|
196
|
+
headers: {
|
|
197
|
+
"Authorization": `Bearer ${token}`
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
if (!response.ok) {
|
|
201
|
+
throw new Error("Invalid token");
|
|
202
|
+
}
|
|
203
|
+
const user = await response.json();
|
|
204
|
+
return {
|
|
205
|
+
token,
|
|
206
|
+
user: {
|
|
207
|
+
id: user.id,
|
|
208
|
+
email: user.email,
|
|
209
|
+
name: user.name
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function saveAuth(authResult) {
|
|
214
|
+
const config = readConfig();
|
|
215
|
+
writeConfig({
|
|
216
|
+
...config,
|
|
217
|
+
token: authResult.token,
|
|
218
|
+
userId: authResult.user.id,
|
|
219
|
+
email: authResult.user.email,
|
|
220
|
+
name: authResult.user.name,
|
|
221
|
+
lastLogin: (/* @__PURE__ */ new Date()).toISOString()
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// src/cli/commands/login.ts
|
|
226
|
+
var import_chalk = __toESM(require("chalk"));
|
|
227
|
+
async function loginCommand(options) {
|
|
228
|
+
if (isAuthenticated()) {
|
|
229
|
+
const config = readConfig();
|
|
230
|
+
console.log(import_chalk.default.yellow("\u26A0\uFE0F Already logged in as:"), import_chalk.default.bold(config.email));
|
|
231
|
+
console.log(import_chalk.default.dim(" Run"), import_chalk.default.cyan("codmir logout"), import_chalk.default.dim("to log out first"));
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
console.log(import_chalk.default.bold("\u{1F680} codmir login"));
|
|
235
|
+
console.log(import_chalk.default.dim(" Authenticate to start using codmir CLI\n"));
|
|
236
|
+
try {
|
|
237
|
+
let authResult;
|
|
238
|
+
if (options.token) {
|
|
239
|
+
console.log(import_chalk.default.dim(" Authenticating with provided token..."));
|
|
240
|
+
authResult = await authenticateWithToken(options.token);
|
|
241
|
+
} else {
|
|
242
|
+
authResult = await startOAuthFlow();
|
|
243
|
+
}
|
|
244
|
+
saveAuth(authResult);
|
|
245
|
+
console.log(import_chalk.default.green("\n\u2705 Successfully logged in!"));
|
|
246
|
+
console.log(import_chalk.default.dim(" User:"), import_chalk.default.bold(authResult.user.name));
|
|
247
|
+
console.log(import_chalk.default.dim(" Email:"), import_chalk.default.bold(authResult.user.email));
|
|
248
|
+
console.log(import_chalk.default.dim("\n You can now use"), import_chalk.default.cyan("codmir link"), import_chalk.default.dim("to connect your project"));
|
|
249
|
+
} catch (error) {
|
|
250
|
+
console.error(import_chalk.default.red("\n\u274C Login failed:"), error instanceof Error ? error.message : "Unknown error");
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// src/client.ts
|
|
256
|
+
var CodmirClient = class {
|
|
257
|
+
constructor(config) {
|
|
258
|
+
this.config = {
|
|
259
|
+
apiKey: config.apiKey || "",
|
|
260
|
+
baseUrl: config.baseUrl || "https://codmir.com",
|
|
261
|
+
timeout: config.timeout || 3e4,
|
|
262
|
+
headers: config.headers || {}
|
|
263
|
+
};
|
|
264
|
+
this.tickets = new TicketsAPI(this.config);
|
|
265
|
+
this.testCases = new TestCasesAPI(this.config);
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Make an HTTP request to the codmir API
|
|
269
|
+
*/
|
|
270
|
+
async request(method, path3, body) {
|
|
271
|
+
const url = `${this.config.baseUrl}${path3}`;
|
|
272
|
+
const headers = {
|
|
273
|
+
"Content-Type": "application/json",
|
|
274
|
+
...this.config.headers
|
|
275
|
+
};
|
|
276
|
+
if (this.config.apiKey) {
|
|
277
|
+
headers["X-API-Key"] = this.config.apiKey;
|
|
278
|
+
}
|
|
279
|
+
const controller = new AbortController();
|
|
280
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
281
|
+
try {
|
|
282
|
+
const response = await fetch(url, {
|
|
283
|
+
method,
|
|
284
|
+
headers,
|
|
285
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
286
|
+
signal: controller.signal
|
|
287
|
+
});
|
|
288
|
+
clearTimeout(timeoutId);
|
|
289
|
+
if (!response.ok) {
|
|
290
|
+
const errorData = await response.json().catch(() => ({}));
|
|
291
|
+
throw this.createError(
|
|
292
|
+
errorData.error || `HTTP ${response.status}: ${response.statusText}`,
|
|
293
|
+
response.status,
|
|
294
|
+
errorData
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
if (response.status === 204) {
|
|
298
|
+
return {};
|
|
299
|
+
}
|
|
300
|
+
return await response.json();
|
|
301
|
+
} catch (error) {
|
|
302
|
+
clearTimeout(timeoutId);
|
|
303
|
+
if (error.name === "AbortError") {
|
|
304
|
+
throw this.createError("Request timeout", 408);
|
|
305
|
+
}
|
|
306
|
+
if (error instanceof Error && "statusCode" in error) {
|
|
307
|
+
throw error;
|
|
308
|
+
}
|
|
309
|
+
throw this.createError(
|
|
310
|
+
error.message || "Network error occurred",
|
|
311
|
+
void 0,
|
|
312
|
+
error
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
createError(message, statusCode, response) {
|
|
317
|
+
const error = new Error(message);
|
|
318
|
+
error.name = "CodmirError";
|
|
319
|
+
error.statusCode = statusCode;
|
|
320
|
+
error.response = response;
|
|
321
|
+
return error;
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
var TicketsAPI = class {
|
|
325
|
+
constructor(config) {
|
|
326
|
+
this.config = config;
|
|
327
|
+
}
|
|
328
|
+
async request(method, path3, body) {
|
|
329
|
+
const url = `${this.config.baseUrl}${path3}`;
|
|
330
|
+
const headers = {
|
|
331
|
+
"Content-Type": "application/json",
|
|
332
|
+
...this.config.headers
|
|
333
|
+
};
|
|
334
|
+
if (this.config.apiKey) {
|
|
335
|
+
headers["X-API-Key"] = this.config.apiKey;
|
|
336
|
+
}
|
|
337
|
+
const controller = new AbortController();
|
|
338
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
339
|
+
try {
|
|
340
|
+
const response = await fetch(url, {
|
|
341
|
+
method,
|
|
342
|
+
headers,
|
|
343
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
344
|
+
signal: controller.signal
|
|
345
|
+
});
|
|
346
|
+
clearTimeout(timeoutId);
|
|
347
|
+
if (!response.ok) {
|
|
348
|
+
const errorData = await response.json().catch(() => ({}));
|
|
349
|
+
const error = new Error(errorData.error || `HTTP ${response.status}`);
|
|
350
|
+
error.statusCode = response.status;
|
|
351
|
+
error.response = errorData;
|
|
352
|
+
throw error;
|
|
353
|
+
}
|
|
354
|
+
if (response.status === 204) {
|
|
355
|
+
return {};
|
|
356
|
+
}
|
|
357
|
+
return await response.json();
|
|
358
|
+
} catch (error) {
|
|
359
|
+
clearTimeout(timeoutId);
|
|
360
|
+
if (error.name === "AbortError") {
|
|
361
|
+
const timeoutError = new Error("Request timeout");
|
|
362
|
+
timeoutError.statusCode = 408;
|
|
363
|
+
throw timeoutError;
|
|
364
|
+
}
|
|
365
|
+
throw error;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Create a ticket in a board
|
|
370
|
+
*
|
|
371
|
+
* @param projectId - The project ID
|
|
372
|
+
* @param boardId - The board ID
|
|
373
|
+
* @param data - Ticket data
|
|
374
|
+
*/
|
|
375
|
+
async createInBoard(projectId, boardId, data) {
|
|
376
|
+
const response = await this.request(
|
|
377
|
+
"POST",
|
|
378
|
+
`/api/project/${projectId}/board/${boardId}/ticket`,
|
|
379
|
+
data
|
|
380
|
+
);
|
|
381
|
+
return response.ticket;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Create a ticket in a workspace
|
|
385
|
+
*
|
|
386
|
+
* @param projectId - The project ID
|
|
387
|
+
* @param workspaceId - The workspace ID
|
|
388
|
+
* @param data - Ticket data
|
|
389
|
+
*/
|
|
390
|
+
async createInWorkspace(projectId, workspaceId, data) {
|
|
391
|
+
return this.request(
|
|
392
|
+
"POST",
|
|
393
|
+
`/api/project/${projectId}/workspaces/${workspaceId}/tickets`,
|
|
394
|
+
data
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Alias for createInBoard - creates a ticket in a board
|
|
399
|
+
*/
|
|
400
|
+
async create(projectId, boardId, data) {
|
|
401
|
+
return this.createInBoard(projectId, boardId, data);
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
var TestCasesAPI = class {
|
|
405
|
+
constructor(config) {
|
|
406
|
+
this.config = config;
|
|
407
|
+
}
|
|
408
|
+
async request(method, path3, body) {
|
|
409
|
+
const url = `${this.config.baseUrl}${path3}`;
|
|
410
|
+
const headers = {
|
|
411
|
+
"Content-Type": "application/json",
|
|
412
|
+
...this.config.headers
|
|
413
|
+
};
|
|
414
|
+
if (this.config.apiKey) {
|
|
415
|
+
headers["X-API-Key"] = this.config.apiKey;
|
|
416
|
+
}
|
|
417
|
+
const controller = new AbortController();
|
|
418
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
419
|
+
try {
|
|
420
|
+
const response = await fetch(url, {
|
|
421
|
+
method,
|
|
422
|
+
headers,
|
|
423
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
424
|
+
signal: controller.signal
|
|
425
|
+
});
|
|
426
|
+
clearTimeout(timeoutId);
|
|
427
|
+
if (!response.ok) {
|
|
428
|
+
const errorData = await response.json().catch(() => ({}));
|
|
429
|
+
const error = new Error(errorData.error || `HTTP ${response.status}`);
|
|
430
|
+
error.statusCode = response.status;
|
|
431
|
+
error.response = errorData;
|
|
432
|
+
throw error;
|
|
433
|
+
}
|
|
434
|
+
if (response.status === 204) {
|
|
435
|
+
return {};
|
|
436
|
+
}
|
|
437
|
+
return await response.json();
|
|
438
|
+
} catch (error) {
|
|
439
|
+
clearTimeout(timeoutId);
|
|
440
|
+
if (error.name === "AbortError") {
|
|
441
|
+
const timeoutError = new Error("Request timeout");
|
|
442
|
+
timeoutError.statusCode = 408;
|
|
443
|
+
throw timeoutError;
|
|
444
|
+
}
|
|
445
|
+
throw error;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* List all test cases for a project
|
|
450
|
+
*
|
|
451
|
+
* @param projectId - The project ID
|
|
452
|
+
*/
|
|
453
|
+
async list(projectId) {
|
|
454
|
+
const response = await this.request(
|
|
455
|
+
"GET",
|
|
456
|
+
`/api/project/${projectId}/test-cases`
|
|
457
|
+
);
|
|
458
|
+
return response.data || [];
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Get a specific test case by ID
|
|
462
|
+
*
|
|
463
|
+
* @param projectId - The project ID
|
|
464
|
+
* @param testCaseId - The test case ID (numeric UD)
|
|
465
|
+
*/
|
|
466
|
+
async get(projectId, testCaseId) {
|
|
467
|
+
const response = await this.request(
|
|
468
|
+
"GET",
|
|
469
|
+
`/api/project/${projectId}/test-cases/${testCaseId}`
|
|
470
|
+
);
|
|
471
|
+
if (!response.data) {
|
|
472
|
+
throw new Error("Test case not found");
|
|
473
|
+
}
|
|
474
|
+
return response.data;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Create a new test case
|
|
478
|
+
*
|
|
479
|
+
* @param projectId - The project ID
|
|
480
|
+
* @param data - Test case data
|
|
481
|
+
*/
|
|
482
|
+
async create(projectId, data) {
|
|
483
|
+
const response = await this.request(
|
|
484
|
+
"POST",
|
|
485
|
+
`/api/project/${projectId}/test-cases`,
|
|
486
|
+
data
|
|
487
|
+
);
|
|
488
|
+
if (!response.data) {
|
|
489
|
+
throw new Error("Failed to create test case");
|
|
490
|
+
}
|
|
491
|
+
return response.data;
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Update an existing test case
|
|
495
|
+
*
|
|
496
|
+
* @param projectId - The project ID
|
|
497
|
+
* @param testCaseId - The test case ID (numeric UD)
|
|
498
|
+
* @param data - Test case update data
|
|
499
|
+
*/
|
|
500
|
+
async update(projectId, testCaseId, data) {
|
|
501
|
+
const response = await this.request(
|
|
502
|
+
"PATCH",
|
|
503
|
+
`/api/project/${projectId}/test-cases/${testCaseId}`,
|
|
504
|
+
data
|
|
505
|
+
);
|
|
506
|
+
if (!response.data) {
|
|
507
|
+
throw new Error("Failed to update test case");
|
|
508
|
+
}
|
|
509
|
+
return response.data;
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Delete a test case
|
|
513
|
+
*
|
|
514
|
+
* @param projectId - The project ID
|
|
515
|
+
* @param testCaseId - The test case ID (numeric UD)
|
|
516
|
+
*/
|
|
517
|
+
async delete(projectId, testCaseId) {
|
|
518
|
+
await this.request(
|
|
519
|
+
"DELETE",
|
|
520
|
+
`/api/project/${projectId}/test-cases/${testCaseId}`
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
// src/cli/commands/link.ts
|
|
526
|
+
var import_chalk2 = __toESM(require("chalk"));
|
|
527
|
+
var import_prompts = __toESM(require("prompts"));
|
|
528
|
+
async function linkCommand(options) {
|
|
529
|
+
const token = getToken();
|
|
530
|
+
if (!token) {
|
|
531
|
+
console.error(import_chalk2.default.red("\u274C Not authenticated"));
|
|
532
|
+
console.log(import_chalk2.default.dim(" Run"), import_chalk2.default.cyan("codmir login"), import_chalk2.default.dim("first"));
|
|
533
|
+
process.exit(1);
|
|
534
|
+
}
|
|
535
|
+
console.log(import_chalk2.default.bold("\n\u{1F517} codmir link"));
|
|
536
|
+
console.log(import_chalk2.default.dim(" Link this directory to a codmir project\n"));
|
|
537
|
+
const existing = readProjectConfig();
|
|
538
|
+
if (existing) {
|
|
539
|
+
console.log(import_chalk2.default.yellow("\u26A0\uFE0F This directory is already linked to:"));
|
|
540
|
+
console.log(import_chalk2.default.dim(" Project ID:"), import_chalk2.default.bold(existing.projectId));
|
|
541
|
+
if (existing.projectName) {
|
|
542
|
+
console.log(import_chalk2.default.dim(" Project:"), import_chalk2.default.bold(existing.projectName));
|
|
543
|
+
}
|
|
544
|
+
const { overwrite } = await (0, import_prompts.default)({
|
|
545
|
+
type: "confirm",
|
|
546
|
+
name: "overwrite",
|
|
547
|
+
message: "Do you want to overwrite this configuration?",
|
|
548
|
+
initial: false
|
|
549
|
+
});
|
|
550
|
+
if (!overwrite) {
|
|
551
|
+
console.log(import_chalk2.default.dim(" Cancelled"));
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
try {
|
|
556
|
+
const client = new CodmirClient({
|
|
557
|
+
apiKey: token,
|
|
558
|
+
baseUrl: process.env.CODMIR_API_URL || "https://codmir.com"
|
|
559
|
+
});
|
|
560
|
+
let projectId = options.project;
|
|
561
|
+
let orgId = options.org;
|
|
562
|
+
console.log(import_chalk2.default.dim(" Fetching your projects...\n"));
|
|
563
|
+
const response = await fetch(`${process.env.CODMIR_API_URL || "https://codmir.com"}/api/projects`, {
|
|
564
|
+
headers: {
|
|
565
|
+
"Authorization": `Bearer ${token}`
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
if (!response.ok) {
|
|
569
|
+
throw new Error("Failed to fetch projects");
|
|
570
|
+
}
|
|
571
|
+
const projects = await response.json();
|
|
572
|
+
if (!projectId) {
|
|
573
|
+
if (projects.length === 0) {
|
|
574
|
+
console.log(import_chalk2.default.yellow("\u26A0\uFE0F You don't have any projects yet"));
|
|
575
|
+
console.log(import_chalk2.default.dim(" Create a project at"), import_chalk2.default.cyan("https://codmir.com"));
|
|
576
|
+
process.exit(1);
|
|
577
|
+
}
|
|
578
|
+
const { selectedProject } = await (0, import_prompts.default)({
|
|
579
|
+
type: "select",
|
|
580
|
+
name: "selectedProject",
|
|
581
|
+
message: "Select a project to link:",
|
|
582
|
+
choices: projects.map((p) => ({
|
|
583
|
+
title: `${p.name} (${p.key})`,
|
|
584
|
+
value: p.id,
|
|
585
|
+
description: p.description || void 0
|
|
586
|
+
}))
|
|
587
|
+
});
|
|
588
|
+
if (!selectedProject) {
|
|
589
|
+
console.log(import_chalk2.default.dim(" Cancelled"));
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
projectId = selectedProject;
|
|
593
|
+
}
|
|
594
|
+
const project = projects.find((p) => p.id === projectId);
|
|
595
|
+
if (!project) {
|
|
596
|
+
console.error(import_chalk2.default.red("\u274C Project not found"));
|
|
597
|
+
process.exit(1);
|
|
598
|
+
}
|
|
599
|
+
writeProjectConfig({
|
|
600
|
+
projectId: project.id,
|
|
601
|
+
organizationId: project.organizationId || orgId,
|
|
602
|
+
projectName: project.name
|
|
603
|
+
});
|
|
604
|
+
console.log(import_chalk2.default.green("\n\u2705 Successfully linked!"));
|
|
605
|
+
console.log(import_chalk2.default.dim(" Project:"), import_chalk2.default.bold(project.name));
|
|
606
|
+
console.log(import_chalk2.default.dim(" ID:"), import_chalk2.default.bold(project.id));
|
|
607
|
+
console.log(import_chalk2.default.dim("\n Configuration saved to"), import_chalk2.default.cyan(".codmir.json"));
|
|
608
|
+
console.log(import_chalk2.default.dim(" You can now use the codmir package in your project"));
|
|
609
|
+
} catch (error) {
|
|
610
|
+
console.error(import_chalk2.default.red("\n\u274C Link failed:"), error instanceof Error ? error.message : "Unknown error");
|
|
611
|
+
process.exit(1);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// src/cli/commands/whoami.ts
|
|
616
|
+
var import_chalk3 = __toESM(require("chalk"));
|
|
617
|
+
async function whoamiCommand() {
|
|
618
|
+
if (!isAuthenticated()) {
|
|
619
|
+
console.error(import_chalk3.default.red("\u274C Not authenticated"));
|
|
620
|
+
console.log(import_chalk3.default.dim(" Run"), import_chalk3.default.cyan("codmir login"), import_chalk3.default.dim("first"));
|
|
621
|
+
process.exit(1);
|
|
622
|
+
}
|
|
623
|
+
const config = readConfig();
|
|
624
|
+
console.log(import_chalk3.default.bold("\n\u{1F464} Current User\n"));
|
|
625
|
+
console.log(import_chalk3.default.dim(" Name:"), import_chalk3.default.bold(config.name || "N/A"));
|
|
626
|
+
console.log(import_chalk3.default.dim(" Email:"), import_chalk3.default.bold(config.email || "N/A"));
|
|
627
|
+
console.log(import_chalk3.default.dim(" User ID:"), import_chalk3.default.dim(config.userId || "N/A"));
|
|
628
|
+
if (config.lastLogin) {
|
|
629
|
+
const lastLogin = new Date(config.lastLogin);
|
|
630
|
+
console.log(import_chalk3.default.dim(" Last Login:"), import_chalk3.default.dim(lastLogin.toLocaleString()));
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// src/cli/commands/logout.ts
|
|
635
|
+
var import_chalk4 = __toESM(require("chalk"));
|
|
636
|
+
async function logoutCommand() {
|
|
637
|
+
if (!isAuthenticated()) {
|
|
638
|
+
console.log(import_chalk4.default.yellow("\u26A0\uFE0F Not logged in"));
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
const config = readConfig();
|
|
642
|
+
clearConfig();
|
|
643
|
+
console.log(import_chalk4.default.green("\u2705 Successfully logged out"));
|
|
644
|
+
if (config.email) {
|
|
645
|
+
console.log(import_chalk4.default.dim(" Goodbye,"), import_chalk4.default.bold(config.email));
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// src/cli/commands/init.ts
|
|
650
|
+
var import_chalk5 = __toESM(require("chalk"));
|
|
651
|
+
var import_fs2 = __toESM(require("fs"));
|
|
652
|
+
var import_path2 = __toESM(require("path"));
|
|
653
|
+
async function initCommand() {
|
|
654
|
+
console.log(import_chalk5.default.bold("\n\u{1F389} Initialize codmir\n"));
|
|
655
|
+
const config = readProjectConfig();
|
|
656
|
+
if (!config) {
|
|
657
|
+
console.log(import_chalk5.default.yellow("\u26A0\uFE0F Project not linked"));
|
|
658
|
+
console.log(import_chalk5.default.dim(" Run"), import_chalk5.default.cyan("codmir link"), import_chalk5.default.dim("to link this project first"));
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
console.log(import_chalk5.default.dim(" Project:"), import_chalk5.default.bold(config.projectName || config.projectId));
|
|
662
|
+
const exampleFile = import_path2.default.join(process.cwd(), "codmir.example.ts");
|
|
663
|
+
if (import_fs2.default.existsSync(exampleFile)) {
|
|
664
|
+
console.log(import_chalk5.default.yellow("\n\u26A0\uFE0F codmir.example.ts already exists"));
|
|
665
|
+
} else {
|
|
666
|
+
const example = `import { CodmirClient } from 'codmir';
|
|
667
|
+
|
|
668
|
+
// Initialize the client
|
|
669
|
+
const codmir = new CodmirClient({
|
|
670
|
+
apiKey: process.env.CODMIR_API_KEY!, // Get token from: codmir login
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
// Example: Create a ticket
|
|
674
|
+
async function createTicket() {
|
|
675
|
+
const ticket = await codmir.tickets.create(
|
|
676
|
+
'${config.projectId}',
|
|
677
|
+
'board-id',
|
|
678
|
+
{
|
|
679
|
+
title: 'Example ticket',
|
|
680
|
+
description: 'Created from codmir package',
|
|
681
|
+
priority: 'HIGH',
|
|
682
|
+
}
|
|
683
|
+
);
|
|
684
|
+
|
|
685
|
+
console.log('Created ticket:', ticket.id);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// Example: Create a test case
|
|
689
|
+
async function createTestCase() {
|
|
690
|
+
const testCase = await codmir.testCases.create('${config.projectId}', {
|
|
691
|
+
title: 'Example test case',
|
|
692
|
+
suiteId: 'suite-id',
|
|
693
|
+
template: 'steps',
|
|
694
|
+
steps: [
|
|
695
|
+
{ action: 'Do something', expected: 'It works' },
|
|
696
|
+
],
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
console.log('Created test case:', testCase.id);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Run examples
|
|
703
|
+
createTicket().catch(console.error);
|
|
704
|
+
createTestCase().catch(console.error);
|
|
705
|
+
`;
|
|
706
|
+
import_fs2.default.writeFileSync(exampleFile, example);
|
|
707
|
+
console.log(import_chalk5.default.green("\n\u2705 Created"), import_chalk5.default.cyan("codmir.example.ts"));
|
|
708
|
+
}
|
|
709
|
+
console.log(import_chalk5.default.bold("\n\u{1F4DA} Next Steps:\n"));
|
|
710
|
+
console.log(import_chalk5.default.dim(" 1."), "Install the package:", import_chalk5.default.cyan("npm install codmir"));
|
|
711
|
+
console.log(import_chalk5.default.dim(" 2."), "Get your API token from:", import_chalk5.default.cyan("https://codmir.com/settings/tokens"));
|
|
712
|
+
console.log(import_chalk5.default.dim(" 3."), "Set environment variable:", import_chalk5.default.cyan("CODMIR_API_KEY=your-token"));
|
|
713
|
+
console.log(import_chalk5.default.dim(" 4."), "Check", import_chalk5.default.cyan("codmir.example.ts"), "for usage examples");
|
|
714
|
+
console.log(import_chalk5.default.dim("\n \u{1F4D6} Documentation:"), import_chalk5.default.cyan("https://codmir.com/docs"));
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// src/cli/commands/projects.ts
|
|
718
|
+
var import_chalk6 = __toESM(require("chalk"));
|
|
719
|
+
async function projectsCommand() {
|
|
720
|
+
const token = getToken();
|
|
721
|
+
if (!token) {
|
|
722
|
+
console.error(import_chalk6.default.red("\u274C Not authenticated"));
|
|
723
|
+
console.log(import_chalk6.default.dim(" Run"), import_chalk6.default.cyan("codmir login"), import_chalk6.default.dim("first"));
|
|
724
|
+
process.exit(1);
|
|
725
|
+
}
|
|
726
|
+
console.log(import_chalk6.default.bold("\n\u{1F4C1} Your Projects\n"));
|
|
727
|
+
try {
|
|
728
|
+
const response = await fetch(`${process.env.CODMIR_API_URL || "https://codmir.com"}/api/projects`, {
|
|
729
|
+
headers: {
|
|
730
|
+
"Authorization": `Bearer ${token}`
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
if (!response.ok) {
|
|
734
|
+
throw new Error("Failed to fetch projects");
|
|
735
|
+
}
|
|
736
|
+
const projects = await response.json();
|
|
737
|
+
if (projects.length === 0) {
|
|
738
|
+
console.log(import_chalk6.default.yellow(" No projects found"));
|
|
739
|
+
console.log(import_chalk6.default.dim(" Create one at"), import_chalk6.default.cyan("https://codmir.com"));
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
projects.forEach((project, index) => {
|
|
743
|
+
console.log(import_chalk6.default.bold(` ${index + 1}. ${project.name}`), import_chalk6.default.dim(`(${project.key})`));
|
|
744
|
+
console.log(import_chalk6.default.dim(" ID:"), project.id);
|
|
745
|
+
if (project.description) {
|
|
746
|
+
console.log(import_chalk6.default.dim(" Description:"), project.description);
|
|
747
|
+
}
|
|
748
|
+
console.log("");
|
|
749
|
+
});
|
|
750
|
+
console.log(import_chalk6.default.dim(" Link a project:"), import_chalk6.default.cyan("codmir link"));
|
|
751
|
+
} catch (error) {
|
|
752
|
+
console.error(import_chalk6.default.red("\n\u274C Failed to fetch projects:"), error instanceof Error ? error.message : "Unknown error");
|
|
753
|
+
process.exit(1);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// src/cli/index.ts
|
|
758
|
+
var program = new import_commander.Command();
|
|
759
|
+
program.name("codmir").description("codmir CLI - the AI that prevents wasted engineering time").version("0.1.0");
|
|
760
|
+
program.command("login").description("Authenticate with codmir").option("--token <token>", "Use an API token directly").action(loginCommand);
|
|
761
|
+
program.command("link").description("Link current directory to a codmir project").option("--project <projectId>", "Project ID to link").option("--org <orgId>", "Organization ID").action(linkCommand);
|
|
762
|
+
program.command("whoami").description("Show current authenticated user").action(whoamiCommand);
|
|
763
|
+
program.command("logout").description("Log out of codmir").action(logoutCommand);
|
|
764
|
+
program.command("init").description("Initialize codmir in your project").action(initCommand);
|
|
765
|
+
program.command("projects").alias("ls").description("List your projects").action(projectsCommand);
|
|
766
|
+
program.parse(process.argv);
|
|
767
|
+
if (!process.argv.slice(2).length) {
|
|
768
|
+
program.outputHelp();
|
|
769
|
+
}
|