@pagopa/dx-cli 0.21.1 → 0.21.3

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 (29) hide show
  1. package/dist/adapters/azure/__tests__/cloud-account-service.test.js +9 -1
  2. package/dist/adapters/azure/cloud-account-service.js +7 -0
  3. package/dist/adapters/commander/commands/__tests__/add.test.js +12 -4
  4. package/dist/adapters/commander/commands/__tests__/init.test.d.ts +4 -0
  5. package/dist/adapters/commander/commands/__tests__/init.test.js +48 -0
  6. package/dist/adapters/commander/commands/add.js +5 -2
  7. package/dist/adapters/commander/commands/init.d.ts +2 -0
  8. package/dist/adapters/commander/commands/init.js +30 -4
  9. package/dist/adapters/pagopa-technology/__tests__/authorization.test.js +349 -31
  10. package/dist/adapters/pagopa-technology/azure-authorization-config.d.ts +13 -0
  11. package/dist/adapters/pagopa-technology/azure-authorization-config.js +43 -0
  12. package/dist/adapters/pagopa-technology/{authorization.d.ts → azure-authorization.d.ts} +2 -2
  13. package/dist/adapters/pagopa-technology/azure-authorization.js +239 -0
  14. package/dist/adapters/plop/actions/__tests__/init-cloud-accounts.test.js +7 -0
  15. package/dist/adapters/plop/actions/__tests__/provision-terraform-backend.test.js +3 -0
  16. package/dist/adapters/plop/generators/environment/__tests__/actions.test.js +1 -0
  17. package/dist/adapters/plop/generators/environment/__tests__/prompts.test.js +156 -2
  18. package/dist/adapters/plop/generators/environment/prompts.d.ts +10 -0
  19. package/dist/adapters/plop/generators/environment/prompts.js +45 -1
  20. package/dist/domain/authorization.d.ts +6 -9
  21. package/dist/domain/authorization.js +27 -10
  22. package/dist/domain/github.d.ts +1 -0
  23. package/dist/domain/github.js +1 -0
  24. package/dist/index.js +2 -2
  25. package/dist/use-cases/__tests__/request-authorization.test.js +5 -3
  26. package/dist/use-cases/request-authorization.d.ts +2 -2
  27. package/dist/use-cases/request-authorization.js +2 -2
  28. package/package.json +1 -1
  29. package/dist/adapters/pagopa-technology/authorization.js +0 -124
@@ -94,6 +94,7 @@ export declare class RepositoryNotFoundError extends Error {
94
94
  constructor(owner: string, name: string);
95
95
  }
96
96
  export declare const githubAppCredentialsSchema: z.ZodObject<{
97
+ clientId: z.ZodString;
97
98
  id: z.ZodString;
98
99
  installationId: z.ZodString;
99
100
  key: z.ZodString;
@@ -38,6 +38,7 @@ export class RepositoryNotFoundError extends Error {
38
38
  }
39
39
  }
40
40
  export const githubAppCredentialsSchema = z.object({
41
+ clientId: z.string().nonempty(),
41
42
  id: z.string().nonempty(),
42
43
  installationId: z.string().nonempty(),
43
44
  key: z.string().nonempty(),
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import { makeValidationReporter } from "./adapters/logtape/validation-reporter.j
8
8
  import { makePackageJsonReader } from "./adapters/node/package-json.js";
9
9
  import { makeRepositoryReader } from "./adapters/node/repository.js";
10
10
  import { getGitHubPAT, OctokitGitHubService, } from "./adapters/octokit/index.js";
11
- import { makeAuthorizationService } from "./adapters/pagopa-technology/authorization.js";
11
+ import { makeAzureAuthorizationService } from "./adapters/pagopa-technology/azure-authorization.js";
12
12
  import { getConfig } from "./config.js";
13
13
  import { getInfo } from "./domain/info.js";
14
14
  import { applyCodemodById } from "./use-cases/apply-codemod.js";
@@ -60,7 +60,7 @@ export const runCli = async (version) => {
60
60
  auth,
61
61
  });
62
62
  const gitHubService = new OctokitGitHubService(octokit);
63
- const authorizationService = makeAuthorizationService(gitHubService);
63
+ const authorizationService = makeAzureAuthorizationService(gitHubService);
64
64
  const deps = {
65
65
  authorizationService,
66
66
  gitHubService,
@@ -4,10 +4,12 @@
4
4
  import { errAsync, okAsync } from "neverthrow";
5
5
  import { describe, expect, it } from "vitest";
6
6
  import { mock } from "vitest-mock-extended";
7
- import { AuthorizationError, AuthorizationResult, IdentityAlreadyExistsError, requestAuthorizationInputSchema, } from "../../domain/authorization.js";
7
+ import { AuthorizationError, AuthorizationResult, requestAuthorizationInputSchema, } from "../../domain/authorization.js";
8
8
  import { requestAuthorization } from "../request-authorization.js";
9
9
  const makeSampleInput = () => requestAuthorizationInputSchema.parse({
10
10
  bootstrapIdentityId: "test-bootstrap-identity-id",
11
+ envShort: "d",
12
+ prefix: "test",
11
13
  repoName: "test-repo",
12
14
  subscriptionName: "test-subscription",
13
15
  });
@@ -25,10 +27,10 @@ describe("requestAuthorization", () => {
25
27
  it("should propagate errors from the authorization service", async () => {
26
28
  const authorizationService = mock();
27
29
  const input = makeSampleInput();
28
- authorizationService.requestAuthorization.mockReturnValue(errAsync(new IdentityAlreadyExistsError("test-bootstrap-identity-id")));
30
+ authorizationService.requestAuthorization.mockReturnValue(errAsync(new AuthorizationError("Unable to update file")));
29
31
  const result = await requestAuthorization(authorizationService)(input);
30
32
  expect(result.isErr()).toBe(true);
31
- expect(result._unsafeUnwrapErr()).toBeInstanceOf(IdentityAlreadyExistsError);
33
+ expect(result._unsafeUnwrapErr()).toBeInstanceOf(AuthorizationError);
32
34
  });
33
35
  it("should propagate generic authorization errors", async () => {
34
36
  const authorizationService = mock();
@@ -2,14 +2,14 @@
2
2
  * Request Authorization Use Case
3
3
  *
4
4
  * Orchestrates an authorization request by delegating to the
5
- * technology-agnostic AuthorizationService.
5
+ * AuthorizationService.
6
6
  */
7
7
  import { ResultAsync } from "neverthrow";
8
8
  import { AuthorizationError, AuthorizationResult, AuthorizationService, RequestAuthorizationInput } from "../domain/authorization.js";
9
9
  /**
10
10
  * Creates a function that requests authorization for a bootstrap identity.
11
11
  *
12
- * @param authorizationService - The service handling platform-specific authorization logic
12
+ * @param authorizationService - The service handling authorization logic
13
13
  * @returns A function that takes input and returns a ResultAsync with the authorization result
14
14
  */
15
15
  export declare const requestAuthorization: (authorizationService: AuthorizationService) => (input: RequestAuthorizationInput) => ResultAsync<AuthorizationResult, AuthorizationError>;
@@ -2,12 +2,12 @@
2
2
  * Request Authorization Use Case
3
3
  *
4
4
  * Orchestrates an authorization request by delegating to the
5
- * technology-agnostic AuthorizationService.
5
+ * AuthorizationService.
6
6
  */
7
7
  /**
8
8
  * Creates a function that requests authorization for a bootstrap identity.
9
9
  *
10
- * @param authorizationService - The service handling platform-specific authorization logic
10
+ * @param authorizationService - The service handling authorization logic
11
11
  * @returns A function that takes input and returns a ResultAsync with the authorization result
12
12
  */
13
13
  export const requestAuthorization = (authorizationService) => (input) => authorizationService.requestAuthorization(input);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pagopa/dx-cli",
3
- "version": "0.21.1",
3
+ "version": "0.21.3",
4
4
  "type": "module",
5
5
  "description": "A CLI useful to manage DX tools.",
6
6
  "repository": {
@@ -1,124 +0,0 @@
1
- /**
2
- * PagoPA Technology Authorization Adapter
3
- *
4
- * Implements the AuthorizationService interface for the PagoPA Azure
5
- * authorization workflow. Encapsulates all platform-specific details:
6
- * the target GitHub repository, file paths, branch naming, JSON file
7
- * parsing, and pull request creation.
8
- */
9
- import { getLogger } from "@logtape/logtape";
10
- import { err, errAsync, ok, okAsync, ResultAsync } from "neverthrow";
11
- import { z } from "zod";
12
- import { AuthorizationError, AuthorizationResult, IdentityAlreadyExistsError, InvalidAuthorizationFileFormatError, } from "../../domain/authorization.js";
13
- const authorizationFileSchema = z
14
- .object({
15
- directory_readers: z
16
- .object({
17
- service_principals_name: z.array(z.string()),
18
- })
19
- .loose(),
20
- })
21
- .loose();
22
- const addIdentity = (content, identityId) => {
23
- let parsed;
24
- try {
25
- parsed = JSON.parse(content);
26
- }
27
- catch {
28
- return err(new InvalidAuthorizationFileFormatError("File content is not valid JSON"));
29
- }
30
- const result = authorizationFileSchema.safeParse(parsed);
31
- if (!result.success) {
32
- return err(new InvalidAuthorizationFileFormatError("Could not find directory_readers.service_principals_name list"));
33
- }
34
- const jsonContent = result.data;
35
- if (jsonContent.directory_readers.service_principals_name.includes(identityId)) {
36
- return err(new IdentityAlreadyExistsError(identityId));
37
- }
38
- const updated = {
39
- ...jsonContent,
40
- directory_readers: {
41
- ...jsonContent.directory_readers,
42
- service_principals_name: [
43
- ...jsonContent.directory_readers.service_principals_name,
44
- identityId,
45
- ],
46
- },
47
- };
48
- return ok(JSON.stringify(updated, null, 2));
49
- };
50
- const REPO_OWNER = "pagopa";
51
- const REPO_NAME = "eng-azure-authorization";
52
- const BASE_BRANCH = "main";
53
- export const makeAuthorizationService = (gitHubService) => ({
54
- requestAuthorization(input) {
55
- const logger = getLogger(["dx-cli", "pagopa-authorization"]);
56
- const { bootstrapIdentityId, repoName, subscriptionName } = input;
57
- const filePath = `src/azure-subscriptions/subscriptions/${subscriptionName}/terraform.tfvars.json`;
58
- const branchName = `feats/add-${repoName}-${subscriptionName}-bootstrap-identity`;
59
- return (
60
- // Step 1: Create branch first to avoid race condition with main branch updates
61
- ResultAsync.fromPromise(gitHubService.createBranch({
62
- branchName,
63
- fromRef: BASE_BRANCH,
64
- owner: REPO_OWNER,
65
- repo: REPO_NAME,
66
- }), () => new AuthorizationError(`Unable to create branch ${branchName} in ${REPO_OWNER}/${REPO_NAME}`))
67
- .orTee((error) => {
68
- logger.error(error.message);
69
- })
70
- // Step 2: Fetch file content from the newly created branch
71
- .andThen(() => ResultAsync.fromPromise(gitHubService.getFileContent({
72
- owner: REPO_OWNER,
73
- path: filePath,
74
- ref: branchName,
75
- repo: REPO_NAME,
76
- }), () => new AuthorizationError(`Unable to get ${filePath} in ${REPO_OWNER}/${REPO_NAME}`)))
77
- .orTee((error) => {
78
- logger.error(error.message);
79
- })
80
- // Modify the file content, detecting duplicates and format errors
81
- .andThen(({ content, sha }) => addIdentity(content, bootstrapIdentityId)
82
- .mapErr((error) => {
83
- if (error instanceof IdentityAlreadyExistsError) {
84
- logger.warn("Identity already exists", {
85
- identityId: bootstrapIdentityId,
86
- subscription: subscriptionName,
87
- });
88
- }
89
- else {
90
- logger.error("Failed to modify tfvars", {
91
- error: error.message,
92
- });
93
- }
94
- return error;
95
- })
96
- .match((updatedContent) => okAsync({ sha, updatedContent }), (error) => errAsync(error)))
97
- // Update the file on the new branch
98
- .andThen(({ sha, updatedContent }) => ResultAsync.fromPromise(gitHubService.updateFile({
99
- branch: branchName,
100
- content: updatedContent,
101
- message: `Add directory reader for ${subscriptionName}`,
102
- owner: REPO_OWNER,
103
- path: filePath,
104
- repo: REPO_NAME,
105
- sha,
106
- }), () => new AuthorizationError(`Unable to update ${filePath} on branch ${branchName} in ${REPO_OWNER}/${REPO_NAME}`)))
107
- .orTee((error) => {
108
- logger.error(error.message);
109
- })
110
- // Create a pull request for review
111
- .andThen(() => ResultAsync.fromPromise(gitHubService.createPullRequest({
112
- base: BASE_BRANCH,
113
- body: `This PR adds the bootstrap identity \`${bootstrapIdentityId}\` to the directory readers for subscription \`${subscriptionName}\`.`,
114
- head: branchName,
115
- owner: REPO_OWNER,
116
- repo: REPO_NAME,
117
- title: `Add directory reader for ${subscriptionName}`,
118
- }), () => new AuthorizationError(`Unable to create pull request from ${branchName} to ${BASE_BRANCH} in ${REPO_OWNER}/${REPO_NAME}`)))
119
- .orTee((error) => {
120
- logger.error(error.message);
121
- })
122
- .map((pr) => new AuthorizationResult(pr.url)));
123
- },
124
- });