@cmflow/cli 2.2.5 → 2.3.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.
Binary file
package/README.md CHANGED
@@ -191,7 +191,7 @@ Notes:
191
191
 
192
192
  ### Build and E2E Test
193
193
 
194
- CmFlow release are able to run `build` and `test_e2e` task during the prepare step. It's useful when you want to
194
+ CmFlow release is able to run `build` and `test_e2e` task during the `prepare` step. It's useful when you want to
195
195
  deploy a docker image on docker hub with the right revision number in the `package.json`. The only requirement, is to
196
196
  have a `build` and `test_e2e` tasks in you npm scripts.
197
197
 
@@ -287,3 +287,103 @@ allow git release note deployment, commit push and docker image deployment.
287
287
  | DOCKER_HUB_ID | The docker hub id |
288
288
  | DOCKER_HUB_PWD | The docker password account |
289
289
 
290
+
291
+
292
+ ## Jira Releases Command
293
+
294
+ The `jira-releases` command lists and (optionally) updates Jira versions related to an application version, then generates a share-ready delivery note.
295
+
296
+ ### Goal
297
+ - For a given Jira project and component, retrieve all versions whose number is less than or equal to a target tag/version.
298
+ - Display the issues associated with those versions and produce a delivery note.
299
+ - Optionally mark the versions as "released" (with today's date) and move the corresponding issues to the "Livré en production" state.
300
+ - Optionally trigger a CMS (Directus) workflow if configuration is provided.
301
+
302
+ ### Prerequisites
303
+ - Access to the Jira API via environment variables:
304
+ - `JIRA_API` (e.g., https://your-instance.atlassian.net)
305
+ - `JIRA_EMAIL`
306
+ - `JIRA_TOKEN` (API token)
307
+ - Optional: `HTTPS_PROXY` if you need a proxy
308
+ - Optional (to trigger a Directus workflow):
309
+ - `DIRECTUS_URL`
310
+ - `DIRECTUS_FLOW_ID`
311
+ - `DIRECTUS_ACCESS_TOKEN`
312
+
313
+ If all 3 Directus variables are present, `CmFlow` considers `CAN_TRIGGER_WORKFLOW=true` and will attempt to run the workflow after updating releases.
314
+
315
+ ### Usage
316
+
317
+ ```sh
318
+ cm jira-releases --project-id <PROJECT_KEY> --component <COMPONENT> --tag <VERSION> [--update] [--verbose]
319
+ ```
320
+
321
+ Aliases and options (from `bin/cm-jira-releases.js`):
322
+
323
+ - `-c, --component <component>`: Jira component filter (e.g., `API`).
324
+ - `-c, --project-id <projectId>`: Jira project key (e.g., `CMAB`).
325
+ - `-t, --tag <tag>`: target version to analyze (e.g., `0.3241.3`, `v0.3241.3`, or `version-0.3241.3`). `v` and `version-` prefixes are automatically ignored.
326
+ - `-u, --update`: updates Jira (marks the version(s) as released with today's date and moves the issues).
327
+ - `-v, --verbose`: verbose logs.
328
+
329
+ Note: Jira versions are expected to follow `<COMPONENT>-<version>` or similar. For example, if `--component API` and a Jira version is named `API-0.3241.3`, the `0.3241.3` part will be compared to `--tag` using semver.
330
+
331
+ ### Detailed behavior
332
+ 1. Fetch all Jira versions for the project (`projectId`).
333
+ 2. Keep those whose version (after stripping `component-`, `version-`, and `v` prefixes) is semver <= the provided `--tag`.
334
+ 3. For each selected version, fetch related issues and prepare a delivery note by type:
335
+ - `Story` → `feat`
336
+ - `Sous-tâche` → `tech`
337
+ - `Tâche` → `chore`
338
+ - `Bug` → `fix`
339
+ 4. Print the delivery note to stdout (example below).
340
+ 5. If `--update` is provided:
341
+ - Mark the relevant version(s) as `released=true` with `releaseDate=YYYY-MM-DD` (today's date).
342
+ - Move each related issue to the Jira state `id=18`, `name="Livré en production"` (transition errors are logged but won't stop the whole process).
343
+ 6. If the Directus config is complete, trigger the workflow by passing the list of `version` values for the processed releases.
344
+
345
+ ### Example
346
+
347
+ From this repo, a sample script is provided in `package.json`:
348
+
349
+ ```sh
350
+ yarn jira_releases
351
+ ```
352
+
353
+ which runs, with `.env` loaded:
354
+
355
+ ```sh
356
+ node --env-file=.env bin/cm.js jira-releases \
357
+ --project-id CMAB \
358
+ --component API \
359
+ --tag 0.3241.3 \
360
+ --verbose
361
+ ```
362
+
363
+ Raw CLI example:
364
+ ```
365
+ cm jira-releases --project-id CMAB --component API --tag v0.3241.3 --update
366
+ ```
367
+
368
+ ### Example of generated note
369
+ The console output includes a share-ready message (note: the tool prints the message in French by default):
370
+
371
+ ```text
372
+ Hello,
373
+ We will deploy to production version <TAG> of the <COMPONENT>
374
+ Here is what this delivery includes:
375
+
376
+ - v<version>:
377
+ - feat(CMAB-123): Story title
378
+ - fix(CMAB-456): XYZ fix
379
+
380
+ The API team
381
+ ```
382
+
383
+ The exact structure may vary depending on how many versions are included and which issues are associated.
384
+
385
+ ### Best practices
386
+ - Use a valid semver `--tag` (the `v`/`version-` prefixes are accepted).
387
+ - Ensure your Jira versions follow a consistent schema that includes the component (`<COMPONENT>-<version>`) to allow matching.
388
+ - First run without `--update` to preview the note and verify selected versions and issues.
389
+ - Provide `HTTPS_PROXY` if your environment requires it to reach Jira/Directus.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmflow/cli",
3
- "version": "2.2.5",
3
+ "version": "2.3.0",
4
4
  "description": "An awesome Git Flow",
5
5
  "author": "camfou",
6
6
  "license": "MIT",
@@ -2,6 +2,7 @@ import chalk from "chalk";
2
2
  import figures from "figures";
3
3
  import moment from "moment";
4
4
 
5
+ import { triggerDirectusFlow } from "../common/back/directus/directus-flow.js";
5
6
  import { jiraGetReleasesAndIssues } from "../common/back/jira/jira-get-releases-and-issues.js";
6
7
  import { jiraIssueMoveTo } from "../common/back/jira/jira-issue-move-to.js";
7
8
  import { updateJiraRelease } from "../common/back/jira/jira-releases.js";
@@ -69,6 +70,18 @@ export class JiraReleases {
69
70
  );
70
71
  }
71
72
  }
73
+ },
74
+ {
75
+ title: `Trigger CMS API Workflow`,
76
+ skip: () => !getConfig().CAN_TRIGGER_WORKFLOW,
77
+ task: () => {
78
+ return triggerDirectusFlow(
79
+ context.projectId,
80
+ context.releases.map(({ version }) => {
81
+ return version;
82
+ })
83
+ );
84
+ }
72
85
  }
73
86
  ];
74
87
  }
@@ -0,0 +1,36 @@
1
+ import { getConfig } from "../../get-config.js";
2
+ import { httpRequest } from "../http/http-request.js";
3
+
4
+ /**
5
+ * Trigger a Directus Flow
6
+ *
7
+ * @param {string} projectId - The project identifier to send to the flow
8
+ * @param {string[]} versions - The versions list to send to the flow
9
+ * @param {object} [opts] - Additional request options (e.g., rejectUnauthorized)
10
+ * @returns {Promise<unknown>} The parsed JSON response
11
+ */
12
+ export async function triggerDirectusFlow(projectId, versions, opts = {}) {
13
+ const { DIRECTUS_URL, DIRECTUS_FLOW_ID, DIRECTUS_ACCESS_TOKEN } = getConfig();
14
+
15
+ const endpoint = `${DIRECTUS_URL}/flows/trigger/${DIRECTUS_FLOW_ID}`;
16
+
17
+ const options = {
18
+ method: "post",
19
+ callee: "DIRECTUS",
20
+ headers: {
21
+ Authorization: `Bearer ${DIRECTUS_ACCESS_TOKEN}`,
22
+ "Content-Type": "application/json"
23
+ },
24
+ body: {
25
+ projectId,
26
+ versions
27
+ },
28
+ ...opts
29
+ };
30
+
31
+ if (options.body && options.headers?.["Content-Type"] === "application/json") {
32
+ options.body = JSON.stringify(options.body);
33
+ }
34
+
35
+ return httpRequest(endpoint, options);
36
+ }
@@ -0,0 +1,62 @@
1
+ // Use vi.hoisted to create mock references that are available to mock factories
2
+ const httpRequestMock = vi.hoisted(() => vi.fn());
3
+
4
+ // Mock get-config to provide DIRECTUS_* env values
5
+ vi.mock("../../get-config.js", () => ({
6
+ getConfig: () => ({
7
+ DIRECTUS_URL: "https://directus.example.com",
8
+ DIRECTUS_FLOW_ID: "flow-123",
9
+ DIRECTUS_ACCESS_TOKEN: "token-abc"
10
+ })
11
+ }));
12
+
13
+ // Spy/mock for httpRequest
14
+ vi.mock("../http/http-request.js", () => ({
15
+ httpRequest: httpRequestMock
16
+ }));
17
+
18
+ // Now import the SUT after mocks are in place
19
+ import { triggerDirectusFlow } from "./directus-flow.js";
20
+
21
+ describe("triggerDirectusFlow", () => {
22
+ beforeEach(() => {
23
+ httpRequestMock.mockReset();
24
+ });
25
+
26
+ it("should call httpRequest with the expected endpoint, headers and body", async () => {
27
+ httpRequestMock.mockResolvedValue({ ok: true, data: { status: "queued" } });
28
+
29
+ const projectId = "CMAB";
30
+ const versions = ["2.3.0", "2.2.0"];
31
+ const opts = { rejectUnauthorized: true };
32
+
33
+ const result = await triggerDirectusFlow(projectId, versions, opts);
34
+
35
+ expect(result).toEqual({ ok: true, data: { status: "queued" } });
36
+
37
+ expect(httpRequestMock).toHaveBeenCalledTimes(1);
38
+
39
+ const expectedEndpoint = "https://directus.example.com/flows/trigger/flow-123";
40
+ const expectedBody = JSON.stringify({ projectId, versions });
41
+
42
+ expect(httpRequestMock).toHaveBeenCalledWith(
43
+ expectedEndpoint,
44
+ expect.objectContaining({
45
+ method: "post",
46
+ callee: "DIRECTUS",
47
+ rejectUnauthorized: true,
48
+ headers: expect.objectContaining({
49
+ Authorization: "Bearer token-abc",
50
+ "Content-Type": "application/json"
51
+ }),
52
+ body: expectedBody
53
+ })
54
+ );
55
+ });
56
+
57
+ it("should propagate errors from httpRequest", async () => {
58
+ httpRequestMock.mockRejectedValue(new Error("network error"));
59
+
60
+ await expect(triggerDirectusFlow("CMAB", ["1.0.0"])).rejects.toThrow("network error");
61
+ });
62
+ });
@@ -90,6 +90,9 @@ export function getConfig() {
90
90
  JIRA_API,
91
91
  JIRA_EMAIL,
92
92
  JIRA_TOKEN,
93
+ DIRECTUS_URL,
94
+ DIRECTUS_FLOW_ID,
95
+ DIRECTUS_ACCESS_TOKEN,
93
96
  HTTPS_PROXY
94
97
  } = process.env;
95
98
 
@@ -140,6 +143,10 @@ export function getConfig() {
140
143
  JIRA_EMAIL,
141
144
  JIRA_TOKEN,
142
145
  HTTPS_PROXY,
146
+ DIRECTUS_URL,
147
+ DIRECTUS_FLOW_ID,
148
+ DIRECTUS_ACCESS_TOKEN,
149
+ CAN_TRIGGER_WORKFLOW: DIRECTUS_ACCESS_TOKEN && DIRECTUS_FLOW_ID && DIRECTUS_URL,
143
150
  WORKSPACES: pkg.workspaces || [],
144
151
  ...getCI()
145
152
  };