@pagopa/dx-cli 0.18.13 → 0.19.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.
@@ -5,7 +5,7 @@ import { errAsync, okAsync } from "neverthrow";
5
5
  import { describe, expect, it } from "vitest";
6
6
  import { mock } from "vitest-mock-extended";
7
7
  import { AuthorizationError, AuthorizationResult, IdentityAlreadyExistsError, } from "../../../../domain/authorization.js";
8
- import { authorizeCloudAccounts } from "../init.js";
8
+ import { authorizeCloudAccounts } from "../add.js";
9
9
  const makeEnvPayload = (overrides = {}) => ({
10
10
  env: {
11
11
  cloudAccounts: [
@@ -8,4 +8,14 @@
8
8
  * - environment: Add a new deployment environment to the project
9
9
  */
10
10
  import { Command } from "commander";
11
- export declare const makeAddCommand: () => Command;
11
+ import { ResultAsync } from "neverthrow";
12
+ import type { Payload as EnvironmentPayload } from "../../plop/generators/environment/index.js";
13
+ import { AuthorizationResult, AuthorizationService } from "../../../domain/authorization.js";
14
+ /**
15
+ * Authorize a Cloud Account (Azure Subscription, AWS Account, ...), creating a Pull Request for each account that requires authorization.
16
+ */
17
+ export declare const authorizeCloudAccounts: (authorizationService: AuthorizationService) => (envPayload: EnvironmentPayload) => ResultAsync<AuthorizationResult[], never>;
18
+ export type AddCommandDependencies = {
19
+ authorizationService: AuthorizationService;
20
+ };
21
+ export declare const makeAddCommand: (deps: AddCommandDependencies) => Command;
@@ -1,3 +1,5 @@
1
+ import { getLogger } from "@logtape/logtape";
2
+ import chalk from "chalk";
1
3
  /**
2
4
  * Add command - Scaffold new components into existing workspaces
3
5
  *
@@ -8,20 +10,82 @@
8
10
  * - environment: Add a new deployment environment to the project
9
11
  */
10
12
  import { Command } from "commander";
11
- import { ResultAsync } from "neverthrow";
13
+ import { okAsync, ResultAsync } from "neverthrow";
14
+ import { requestAuthorizationInputSchema, } from "../../../domain/authorization.js";
15
+ import { environmentShort } from "../../../domain/environment.js";
16
+ import { isAzureLocation, locationShort } from "../../azure/locations.js";
12
17
  import { getPlopInstance, runDeploymentEnvironmentGenerator, } from "../../plop/index.js";
13
18
  import { checkPreconditions } from "./init.js";
14
- const addEnvironmentAction = () => checkPreconditions()
19
+ /**
20
+ * Authorize a Cloud Account (Azure Subscription, AWS Account, ...), creating a Pull Request for each account that requires authorization.
21
+ */
22
+ export const authorizeCloudAccounts = (authorizationService) => (envPayload) => {
23
+ const accountsToInitialize = envPayload.init?.cloudAccountsToInitialize ?? [];
24
+ if (accountsToInitialize.length === 0) {
25
+ return okAsync([]);
26
+ }
27
+ const logger = getLogger(["dx-cli", "add-environment"]);
28
+ const { name, prefix } = envPayload.env;
29
+ const envShort = environmentShort[name];
30
+ const requestAll = async () => {
31
+ const results = [];
32
+ for (const account of accountsToInitialize) {
33
+ if (!isAzureLocation(account.defaultLocation)) {
34
+ logger.warn("Skipping authorization for {account}: unsupported location", { account: account.displayName });
35
+ continue;
36
+ }
37
+ const locShort = locationShort[account.defaultLocation];
38
+ const input = requestAuthorizationInputSchema.safeParse({
39
+ bootstrapIdentityId: `${prefix}-${envShort}-${locShort}-bootstrap-id-01`,
40
+ repoName: envPayload.github.repo,
41
+ subscriptionName: account.displayName,
42
+ });
43
+ if (!input.success) {
44
+ logger.warn("Skipping authorization for {account}: invalid input", {
45
+ account: account.displayName,
46
+ });
47
+ continue;
48
+ }
49
+ const result = await authorizationService.requestAuthorization(input.data);
50
+ result.match((authResult) => results.push(authResult), (error) => logger.warn("Authorization request failed for {account}: {error}", {
51
+ account: account.displayName,
52
+ error: error.message,
53
+ }));
54
+ }
55
+ return results;
56
+ };
57
+ return ResultAsync.fromPromise(requestAll(), () => []).orElse(() => okAsync([]));
58
+ };
59
+ const displaySummary = (result) => {
60
+ const { authorizationPrs } = result;
61
+ console.log(chalk.green.bold("\nCloud environment created successfully!"));
62
+ let step = 1;
63
+ console.log(chalk.green.bold("\nNext Steps:"));
64
+ if (authorizationPrs.length > 0) {
65
+ console.log(`${step++}. Review the Azure authorization Pull Request(s):`);
66
+ for (const authPr of authorizationPrs) {
67
+ console.log(` - ${chalk.underline(authPr.url)}`);
68
+ }
69
+ }
70
+ console.log(`${step}. Visit ${chalk.underline("https://dx.pagopa.it/getting-started")} to deploy your first project\n`);
71
+ };
72
+ const addEnvironmentAction = (authorizationService) => checkPreconditions()
15
73
  .andThen(() => ResultAsync.fromPromise(getPlopInstance(), () => new Error("Failed to initialize plop")))
16
- .andThen((plop) => ResultAsync.fromPromise(runDeploymentEnvironmentGenerator(plop), () => new Error("Failed to run the deployment environment generator")));
17
- export const makeAddCommand = () => new Command()
74
+ .andThen((plop) => ResultAsync.fromPromise(runDeploymentEnvironmentGenerator(plop), () => new Error("Failed to run the deployment environment generator")))
75
+ .andThen((payload) => authorizeCloudAccounts(authorizationService)(payload).map((authorizationPrs) => ({
76
+ authorizationPrs,
77
+ })));
78
+ export const makeAddCommand = (deps) => new Command()
18
79
  .name("add")
19
80
  .description("Add a new component to your workspace")
20
81
  .addCommand(new Command("environment")
21
82
  .description("Add a new deployment environment")
22
83
  .action(async function () {
23
- const result = await addEnvironmentAction();
84
+ const result = await addEnvironmentAction(deps.authorizationService);
24
85
  if (result.isErr()) {
25
86
  this.error(result.error.message);
26
87
  }
88
+ else {
89
+ displaySummary(result.value);
90
+ }
27
91
  }));
@@ -1,7 +1,5 @@
1
1
  import { Command } from "commander";
2
2
  import { ResultAsync } from "neverthrow";
3
- import type { Payload as EnvironmentPayload } from "../../plop/generators/environment/index.js";
4
- import { AuthorizationResult, AuthorizationService } from "../../../domain/authorization.js";
5
3
  import { GitHubService } from "../../../domain/github.js";
6
4
  export declare const checkPreconditions: () => ResultAsync<import("execa").Result<{
7
5
  environment: {
@@ -11,13 +9,8 @@ export declare const checkPreconditions: () => ResultAsync<import("execa").Resul
11
9
  };
12
10
  shell: true;
13
11
  }>, Error>;
14
- /**
15
- * Authorize a Cloud Account (Azure Subscription, AWS Account, ...), creating a Pull Request for each account that requires authorization.
16
- */
17
- export declare const authorizeCloudAccounts: (authorizationService: AuthorizationService) => (envPayload: EnvironmentPayload) => ResultAsync<AuthorizationResult[], never>;
18
12
  type InitCommandDependencies = {
19
- authorizationService: AuthorizationService;
20
13
  gitHubService: GitHubService;
21
14
  };
22
- export declare const makeInitCommand: ({ authorizationService, gitHubService, }: InitCommandDependencies) => Command;
15
+ export declare const makeInitCommand: ({ gitHubService, }: InitCommandDependencies) => Command;
23
16
  export {};
@@ -6,12 +6,9 @@ import { okAsync, ResultAsync } from "neverthrow";
6
6
  import * as path from "node:path";
7
7
  import { oraPromise } from "ora";
8
8
  import { z } from "zod";
9
- import { requestAuthorizationInputSchema, } from "../../../domain/authorization.js";
10
- import { environmentShort } from "../../../domain/environment.js";
11
9
  import { Repository, } from "../../../domain/github.js";
12
- import { isAzureLocation, locationShort } from "../../azure/locations.js";
13
10
  import { tf$ } from "../../execa/terraform.js";
14
- import { getPlopInstance, runDeploymentEnvironmentGenerator, runMonorepoGenerator, } from "../../plop/index.js";
11
+ import { getPlopInstance, runMonorepoGenerator } from "../../plop/index.js";
15
12
  import { exitWithError } from "../index.js";
16
13
  const withSpinner = (text, successText, failText, promise) => ResultAsync.fromPromise(oraPromise(promise, {
17
14
  failText,
@@ -19,7 +16,7 @@ const withSpinner = (text, successText, failText, promise) => ResultAsync.fromPr
19
16
  text,
20
17
  }), (cause) => new Error(failText, { cause }));
21
18
  const displaySummary = (initResult) => {
22
- const { authorizationPrs, pr, repository } = initResult;
19
+ const { pr, repository } = initResult;
23
20
  console.log(chalk.green.bold("\nWorkspace created successfully!"));
24
21
  if (repository) {
25
22
  console.log(`- Name: ${chalk.cyan(repository.name)}`);
@@ -32,12 +29,6 @@ const displaySummary = (initResult) => {
32
29
  let step = 1;
33
30
  console.log(chalk.green.bold("\nNext Steps:"));
34
31
  console.log(`${step++}. Review the Pull Request in the GitHub repository: ${chalk.underline(pr.url)}`);
35
- if (authorizationPrs.length > 0) {
36
- console.log(`${step++}. Review the Azure authorization Pull Request(s):`);
37
- for (const authPr of authorizationPrs) {
38
- console.log(` - ${chalk.underline(authPr.url)}`);
39
- }
40
- }
41
32
  console.log(`${step}. Visit ${chalk.underline("https://dx.pagopa.it/getting-started")} to deploy your first project\n`);
42
33
  }
43
34
  else {
@@ -123,65 +114,16 @@ const handleGeneratorError = (err) => {
123
114
  }
124
115
  return new Error("Failed to run the generator");
125
116
  };
126
- /**
127
- * Authorize a Cloud Account (Azure Subscription, AWS Account, ...), creating a Pull Request for each account that requires authorization.
128
- */
129
- export const authorizeCloudAccounts = (authorizationService) => (envPayload) => {
130
- const accountsToInitialize = envPayload.init?.cloudAccountsToInitialize ?? [];
131
- if (accountsToInitialize.length === 0) {
132
- return okAsync([]);
133
- }
134
- const logger = getLogger(["dx-cli", "init"]);
135
- const { name, prefix } = envPayload.env;
136
- const envShort = environmentShort[name];
137
- const requestAll = async () => {
138
- const results = [];
139
- for (const account of accountsToInitialize) {
140
- if (!isAzureLocation(account.defaultLocation)) {
141
- logger.warn("Skipping authorization for {account}: unsupported location", { account: account.displayName });
142
- continue;
143
- }
144
- const locShort = locationShort[account.defaultLocation];
145
- const input = requestAuthorizationInputSchema.safeParse({
146
- bootstrapIdentityId: `${prefix}-${envShort}-${locShort}-bootstrap-id-01`,
147
- repoName: envPayload.github.repo,
148
- subscriptionName: account.displayName,
149
- });
150
- if (!input.success) {
151
- logger.warn("Skipping authorization for {account}: invalid input", {
152
- account: account.displayName,
153
- });
154
- continue;
155
- }
156
- const result = await authorizationService.requestAuthorization(input.data);
157
- result.match((authResult) => results.push(authResult), (error) => logger.warn("Authorization request failed for {account}: {error}", {
158
- account: account.displayName,
159
- error: error.message,
160
- }));
161
- }
162
- return results;
163
- };
164
- return ResultAsync.fromPromise(requestAll(), () => []).orElse(() => okAsync([]));
165
- };
166
- export const makeInitCommand = ({ authorizationService, gitHubService, }) => new Command()
117
+ export const makeInitCommand = ({ gitHubService, }) => new Command()
167
118
  .name("init")
168
119
  .description("Initialize a new DX workspace")
169
120
  .action(async function () {
170
121
  await checkPreconditions()
171
- .andTee(() => {
172
- console.log(chalk.blue.bold("\nWorkspace Info"));
173
- })
174
122
  .andThen(() => ResultAsync.fromPromise(getPlopInstance(), () => new Error("Failed to initialize plop")))
175
- .andThen((plop) => ResultAsync.fromPromise(runMonorepoGenerator(plop, gitHubService), handleGeneratorError)
123
+ .andThen((plop) => ResultAsync.fromPromise(runMonorepoGenerator(plop, gitHubService), handleGeneratorError))
176
124
  .andTee((payload) => {
177
125
  process.chdir(payload.repoName);
178
- console.log(chalk.blue.bold("\nCloud Environment"));
179
126
  })
180
- .andThen((monorepoPayload) => ResultAsync.fromPromise(runDeploymentEnvironmentGenerator(plop, {
181
- owner: monorepoPayload.repoOwner,
182
- repo: monorepoPayload.repoName,
183
- }), handleGeneratorError).map((envPayload) => ({ envPayload, monorepoPayload }))))
184
- .andTee(() => console.log()) // Print a new line before the gh repo creation logs
185
- .andThen(({ envPayload, monorepoPayload }) => handleNewGitHubRepository(gitHubService)(monorepoPayload).andThen((repoPr) => authorizeCloudAccounts(authorizationService)(envPayload).map((authorizationPrs) => ({ ...repoPr, authorizationPrs }))))
127
+ .andThen((payload) => handleNewGitHubRepository(gitHubService)(payload))
186
128
  .match(displaySummary, exitWithError(this));
187
129
  });
@@ -13,7 +13,7 @@ export const makeCli = (deps, config, cliDeps, version) => {
13
13
  program.addCommand(makeInitCommand(deps));
14
14
  program.addCommand(makeSavemoneyCommand());
15
15
  program.addCommand(makeInfoCommand(deps));
16
- program.addCommand(makeAddCommand());
16
+ program.addCommand(makeAddCommand(deps));
17
17
  return program;
18
18
  };
19
19
  export const exitWithError = (command) => (error) => {
@@ -1,4 +1,4 @@
1
- import { $ } from "execa";
1
+ import { $ as $_ } from "execa";
2
2
  import path from "node:path";
3
3
  import { payloadSchema } from "../generators/monorepo/prompts.js";
4
4
  export default function (plop) {
@@ -12,16 +12,16 @@ export default function (plop) {
12
12
  .filter(([key]) => !key.startsWith("npm_config_"))
13
13
  // Disable corepack download prompt
14
14
  .concat([["COREPACK_ENABLE_DOWNLOAD_PROMPT", "0"]]));
15
- const pnpm$ = $({
15
+ const $ = $_({
16
16
  cwd,
17
17
  env,
18
18
  extendEnv: false, // Don't include process.env variables
19
19
  });
20
- await pnpm$ `corepack enable`;
21
- await pnpm$ `corepack use pnpm@latest`;
22
- await pnpm$ `pnpm -w add -D turbo @changesets/cli @devcontainers/cli`;
23
- await pnpm$ `pnpm changeset init`;
24
- await pnpm$ `pnpm devcontainer templates apply -t ghcr.io/pagopa/devcontainer-templates/node:1`;
20
+ await $ `corepack enable`;
21
+ await $ `corepack use pnpm@latest`;
22
+ await $ `npx --yes nx@latest init --interactive=false --aiAgents=copilot`;
23
+ await $ `pnpm -w add -D @devcontainers/cli @nx/js @nx/eslint @nx/vitest`;
24
+ await $ `pnpm devcontainer templates apply -t ghcr.io/pagopa/devcontainer-templates/node:1`;
25
25
  return "Monorepo bootstrapped";
26
26
  });
27
27
  }
package/dist/config.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export type Config = {
2
2
  minVersions: {
3
- turbo: string;
3
+ nx: string;
4
4
  };
5
5
  };
6
6
  export declare const getConfig: () => Config;
package/dist/config.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export const getConfig = () => ({
2
2
  minVersions: {
3
- turbo: "2",
3
+ nx: "22",
4
4
  },
5
5
  });
@@ -22,7 +22,7 @@ export const makeMockDependencies = () => ({
22
22
  });
23
23
  export const makeMockConfig = () => ({
24
24
  minVersions: {
25
- turbo: "2.5.0",
25
+ nx: "22",
26
26
  },
27
27
  });
28
28
  export const makeMockRepositoryRoot = () => "a/repo/root";
@@ -1,6 +1,6 @@
1
1
  import { errAsync, ok, okAsync } from "neverthrow";
2
2
  import { describe, expect, it } from "vitest";
3
- import { checkPreCommitConfig, checkTurboConfig } from "../repository.js";
3
+ import { checkNxConfig, checkPreCommitConfig } from "../repository.js";
4
4
  import { makeMockConfig, makeMockDependencies, makeMockRepositoryRoot, } from "./data.js";
5
5
  describe("checkPreCommitConfig", () => {
6
6
  it("should return ok result with successful validation when .pre-commit-config.yaml exists", async () => {
@@ -28,74 +28,84 @@ describe("checkPreCommitConfig", () => {
28
28
  }));
29
29
  });
30
30
  });
31
- describe("checkTurboConfig", () => {
31
+ describe("checkNxConfig", () => {
32
32
  const config = makeMockConfig();
33
33
  const repositoryRoot = makeMockRepositoryRoot();
34
- it("should return ok result with successful validation when turbo.json exists and turbo dependency is present", async () => {
34
+ it("should return ok result with successful validation when nx.json exists and nx dependency is present", async () => {
35
35
  const deps = makeMockDependencies();
36
36
  deps.repositoryReader.fileExists.mockReturnValueOnce(okAsync(true));
37
- deps.packageJsonReader.getDependencies.mockReturnValueOnce(okAsync(new Map().set("turbo", "^2.5.2")));
38
- const result = await checkTurboConfig(deps, repositoryRoot, config);
37
+ deps.packageJsonReader.getDependencies.mockReturnValueOnce(okAsync(new Map().set("nx", "^22.6.1")));
38
+ const result = await checkNxConfig(deps, repositoryRoot, config);
39
39
  expect(result).toStrictEqual(ok({
40
- checkName: "Turbo Configuration",
40
+ checkName: "Nx Configuration",
41
41
  isValid: true,
42
- successMessage: "Turbo configuration is present in the monorepo root and turbo dependency is installed",
42
+ successMessage: "Nx configuration is present in the monorepo root and Nx dependency is installed",
43
43
  }));
44
- expect(deps.repositoryReader.fileExists).toHaveBeenCalledWith("a/repo/root/turbo.json");
44
+ expect(deps.repositoryReader.fileExists).toHaveBeenCalledWith("a/repo/root/nx.json");
45
45
  });
46
- it("should return ok result with failed validation when turbo.json exists but turbo dependency is missing", async () => {
46
+ it("should return ok result with failed validation when nx.json exists but Nx dependency is missing", async () => {
47
47
  const deps = makeMockDependencies();
48
48
  deps.repositoryReader.fileExists.mockReturnValueOnce(okAsync(true));
49
49
  deps.packageJsonReader.getDependencies.mockReturnValueOnce(okAsync(new Map().set("eslint", "^8.0.0")));
50
- const result = await checkTurboConfig(deps, repositoryRoot, config);
50
+ const result = await checkNxConfig(deps, repositoryRoot, config);
51
51
  expect(result).toStrictEqual(ok({
52
- checkName: "Turbo Configuration",
53
- errorMessage: "Turbo dependency not found in devDependencies. Please add 'turbo' to your devDependencies.",
52
+ checkName: "Nx Configuration",
53
+ errorMessage: "Nx dependency not found in devDependencies. Please add 'nx' to your devDependencies.",
54
54
  isValid: false,
55
55
  }));
56
56
  });
57
- it("should return ok result with failed validation when turbo.json does not exist", async () => {
57
+ it("should return ok result with failed validation when nx.json does not exist", async () => {
58
58
  const deps = makeMockDependencies();
59
- const errorMessage = "turbo.json not found in repository root. Make sure to have Turbo configured for the monorepo.";
59
+ const errorMessage = "nx.json not found in repository root. Make sure to have Nx configured for the monorepo.";
60
60
  deps.repositoryReader.fileExists.mockReturnValueOnce(errAsync(new Error(errorMessage)));
61
- const result = await checkTurboConfig(deps, repositoryRoot, config);
61
+ const result = await checkNxConfig(deps, repositoryRoot, config);
62
62
  expect(result).toStrictEqual(ok({
63
- checkName: "Turbo Configuration",
63
+ checkName: "Nx Configuration",
64
64
  errorMessage,
65
65
  isValid: false,
66
66
  }));
67
67
  });
68
- it("should return the error message when turbo is not listed in devDependencies", async () => {
68
+ it("should return ok result with failed validation when fileExists returns ok(false)", async () => {
69
+ const deps = makeMockDependencies();
70
+ deps.repositoryReader.fileExists.mockReturnValueOnce(okAsync(false));
71
+ const result = await checkNxConfig(deps, repositoryRoot, config);
72
+ expect(result).toStrictEqual(ok({
73
+ checkName: "Nx Configuration",
74
+ errorMessage: "nx.json not found in repository root. Make sure to have Nx configured for the monorepo.",
75
+ isValid: false,
76
+ }));
77
+ });
78
+ it("should return the error message when nx is not listed in devDependencies", async () => {
69
79
  const deps = makeMockDependencies();
70
80
  deps.repositoryReader.fileExists.mockReturnValueOnce(okAsync(true));
71
81
  deps.packageJsonReader.getDependencies.mockReturnValueOnce(okAsync(new Map().set("eslint", "^8.0.0")));
72
- const result = await checkTurboConfig(deps, repositoryRoot, config);
82
+ const result = await checkNxConfig(deps, repositoryRoot, config);
73
83
  expect(result).toStrictEqual(ok({
74
- checkName: "Turbo Configuration",
75
- errorMessage: "Turbo dependency not found in devDependencies. Please add 'turbo' to your devDependencies.",
84
+ checkName: "Nx Configuration",
85
+ errorMessage: "Nx dependency not found in devDependencies. Please add 'nx' to your devDependencies.",
76
86
  isValid: false,
77
87
  }));
78
88
  });
79
- it("should return the error message when turbo version is less than minimum", async () => {
89
+ it("should return the error message when nx version is less than minimum", async () => {
80
90
  const deps = makeMockDependencies();
81
91
  deps.repositoryReader.fileExists.mockReturnValueOnce(okAsync(true));
82
- deps.packageJsonReader.getDependencies.mockReturnValueOnce(okAsync(new Map().set("turbo", "1.0.0")));
83
- const result = await checkTurboConfig(deps, repositoryRoot, config);
92
+ deps.packageJsonReader.getDependencies.mockReturnValueOnce(okAsync(new Map().set("nx", "1.0.0")));
93
+ const result = await checkNxConfig(deps, repositoryRoot, config);
84
94
  expect(result).toStrictEqual(ok({
85
- checkName: "Turbo Configuration",
86
- errorMessage: `Turbo version (1.0.0) is too low. Minimum required version is ${config.minVersions.turbo}.`,
95
+ checkName: "Nx Configuration",
96
+ errorMessage: `Nx version (1.0.0) is too low. Minimum required version is ${config.minVersions.nx}.`,
87
97
  isValid: false,
88
98
  }));
89
99
  });
90
- it("should return the success message when turbo version is ok", async () => {
100
+ it("should return the success message when nx version is ok", async () => {
91
101
  const deps = makeMockDependencies();
92
102
  deps.repositoryReader.fileExists.mockReturnValueOnce(okAsync(true));
93
- deps.packageJsonReader.getDependencies.mockReturnValueOnce(okAsync(new Map().set("turbo", config.minVersions.turbo)));
94
- const result = await checkTurboConfig(deps, repositoryRoot, config);
103
+ deps.packageJsonReader.getDependencies.mockReturnValueOnce(okAsync(new Map().set("nx", config.minVersions.nx)));
104
+ const result = await checkNxConfig(deps, repositoryRoot, config);
95
105
  expect(result).toStrictEqual(ok({
96
- checkName: "Turbo Configuration",
106
+ checkName: "Nx Configuration",
97
107
  isValid: true,
98
- successMessage: "Turbo configuration is present in the monorepo root and turbo dependency is installed",
108
+ successMessage: "Nx configuration is present in the monorepo root and Nx dependency is installed",
99
109
  }));
100
110
  });
101
111
  });
@@ -1,7 +1,6 @@
1
1
  import { ResultAsync } from "neverthrow";
2
2
  import { checkMonorepoScripts } from "./package-json.js";
3
- import { checkPreCommitConfig } from "./repository.js";
4
- import { checkTurboConfig } from "./repository.js";
3
+ import { checkNxConfig, checkPreCommitConfig } from "./repository.js";
5
4
  import { checkWorkspaces } from "./workspace.js";
6
5
  export const runDoctor = async (dependencies, config) => {
7
6
  // Get repository root - doctor command requires being in a repository
@@ -21,9 +20,9 @@ export const runDoctor = async (dependencies, config) => {
21
20
  const repositoryRoot = repoRootResult.value;
22
21
  const doctorChecks = [
23
22
  ResultAsync.fromPromise(checkPreCommitConfig(dependencies, repositoryRoot), () => new Error("Error checking pre-commit configuration")),
24
- ResultAsync.fromPromise(checkTurboConfig(dependencies, repositoryRoot, config), () => new Error("Error checking Turbo configuration")),
23
+ ResultAsync.fromPromise(checkNxConfig(dependencies, repositoryRoot, config), () => new Error("Error checking Nx configuration")),
25
24
  ResultAsync.fromPromise(checkMonorepoScripts(dependencies, repositoryRoot), () => new Error("Error checking monorepo scripts")),
26
- ResultAsync.fromPromise(checkWorkspaces(dependencies, repositoryRoot), () => new Error("Error checking monorepo scripts")),
25
+ ResultAsync.fromPromise(checkWorkspaces(dependencies, repositoryRoot), () => new Error("Error checking workspaces")),
27
26
  ];
28
27
  return ResultAsync.combine(doctorChecks).match(toDoctorResult, () => ({
29
28
  checks: [],
@@ -10,4 +10,4 @@ export type RepositoryReader = {
10
10
  readFile(path: string): ResultAsync<string, Error>;
11
11
  };
12
12
  export declare const checkPreCommitConfig: (dependencies: Pick<Dependencies, "repositoryReader">, repositoryRoot: string) => Promise<ValidationCheckResult>;
13
- export declare const checkTurboConfig: (dependencies: Pick<Dependencies, "packageJsonReader" | "repositoryReader">, repositoryRoot: string, config: Config) => Promise<ValidationCheckResult>;
13
+ export declare const checkNxConfig: (dependencies: Pick<Dependencies, "packageJsonReader" | "repositoryReader">, repositoryRoot: string, config: Config) => Promise<ValidationCheckResult>;
@@ -30,14 +30,21 @@ export const checkPreCommitConfig = async (dependencies, repositoryRoot) => {
30
30
  isValid: false,
31
31
  });
32
32
  };
33
- export const checkTurboConfig = async (dependencies, repositoryRoot, config) => {
33
+ export const checkNxConfig = async (dependencies, repositoryRoot, config) => {
34
34
  const { packageJsonReader, repositoryReader } = dependencies;
35
- const checkName = "Turbo Configuration";
36
- const turboResult = await repositoryReader.fileExists(fs.join(repositoryRoot, "turbo.json"));
37
- if (turboResult.isErr()) {
35
+ const checkName = "Nx Configuration";
36
+ const nxResult = await repositoryReader.fileExists(fs.join(repositoryRoot, "nx.json"));
37
+ if (nxResult.isErr()) {
38
38
  return ok({
39
39
  checkName,
40
- errorMessage: turboResult.error.message,
40
+ errorMessage: nxResult.error.message,
41
+ isValid: false,
42
+ });
43
+ }
44
+ if (!nxResult.value) {
45
+ return ok({
46
+ checkName,
47
+ errorMessage: "nx.json not found in repository root. Make sure to have Nx configured for the monorepo.",
41
48
  isValid: false,
42
49
  });
43
50
  }
@@ -49,24 +56,24 @@ export const checkTurboConfig = async (dependencies, repositoryRoot, config) =>
49
56
  isValid: false,
50
57
  });
51
58
  }
52
- const turboVersion = dependenciesResult.value.get("turbo");
53
- if (!turboVersion) {
59
+ const nxVersion = dependenciesResult.value.get("nx");
60
+ if (!nxVersion) {
54
61
  return ok({
55
62
  checkName,
56
- errorMessage: "Turbo dependency not found in devDependencies. Please add 'turbo' to your devDependencies.",
63
+ errorMessage: "Nx dependency not found in devDependencies. Please add 'nx' to your devDependencies.",
57
64
  isValid: false,
58
65
  });
59
66
  }
60
- if (!isVersionValid(turboVersion, config.minVersions.turbo)) {
67
+ if (!isVersionValid(nxVersion, config.minVersions.nx)) {
61
68
  return ok({
62
69
  checkName,
63
- errorMessage: `Turbo version (${turboVersion}) is too low. Minimum required version is ${config.minVersions.turbo}.`,
70
+ errorMessage: `Nx version (${nxVersion}) is too low. Minimum required version is ${config.minVersions.nx}.`,
64
71
  isValid: false,
65
72
  });
66
73
  }
67
74
  return ok({
68
75
  checkName,
69
76
  isValid: true,
70
- successMessage: "Turbo configuration is present in the monorepo root and turbo dependency is installed",
77
+ successMessage: "Nx configuration is present in the monorepo root and Nx dependency is installed",
71
78
  });
72
79
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pagopa/dx-cli",
3
- "version": "0.18.13",
3
+ "version": "0.19.1",
4
4
  "type": "module",
5
5
  "description": "A CLI useful to manage DX tools.",
6
6
  "repository": {
@@ -1,3 +1,9 @@
1
+ locals {
2
+ bootstrapper_tags = merge(local.tags, {
3
+ Source = "https://github.com/{{@root.github.owner}}/{{@root.github.repo}}/blob/main/infra/bootstrapper/{{@root.env.name}}"
4
+ })
5
+ }
6
+
1
7
  {{#each cloudAccountsByCsp.azure}}
2
8
  {{#if (eq displayName "PROD-IO")}}
3
9
  module "azure-{{displayName}}_bootstrap" {
@@ -45,7 +51,7 @@ module "azure-{{displayName}}_bootstrap" {
45
51
  nat_gateway_resource_group_id = data.azurerm_resource_group.common_itn_01.id
46
52
  opex_resource_group_id = data.azurerm_resource_group.dashboards.id
47
53
 
48
- tags = local.tags
54
+ tags = local.bootstrapper_tags
49
55
  }
50
56
  {{else}}
51
57
  module "azure-{{displayName}}_core_values" {
@@ -112,7 +118,7 @@ module "azure-{{displayName}}_bootstrap" {
112
118
  private_dns_zone_resource_group_id = module.azure-{{displayName}}_core_values.network_resource_group_id
113
119
  opex_resource_group_id = module.azure-{{displayName}}_core_values.opex_resource_group_id
114
120
 
115
- tags = local.tags
121
+ tags = local.bootstrapper_tags
116
122
  }
117
123
  {{/if}}
118
124
  {{/each}}
@@ -12,7 +12,9 @@ module "azure-{{displayName}}_core" {
12
12
  app_name = "core"
13
13
  })
14
14
 
15
- tags = local.tags
15
+ tags = merge(local.tags, {
16
+ Source = "https://github.com/{{@root.github.owner}}/{{@root.github.repo}}/blob/main/infra/core/{{@root.env.name}}"
17
+ })
16
18
  }
17
19
 
18
20
  {{/each}}
@@ -48,49 +48,38 @@ cd {{ repoName }}
48
48
  nodenv install
49
49
  ```
50
50
 
51
- 3. Build all the workspaces contained by this repo
52
- ```bash
53
- pnpm build
54
- ```
55
-
56
- ## Release management
51
+ 3. Install dependencies using `pnpm`
57
52
 
58
- We use [changesets](https://github.com/changesets/changesets) to automate package versioning and releases.
59
-
60
- Each Pull Request that includes changes that require a version bump must include a _changeset file_ that describes the introduced changes.
61
-
62
- To create a _changeset file_ run the following command and follow the instructions.
53
+ ```bashbash
54
+ pnpm install
55
+ ```
63
56
 
57
+ 4. Build all the workspaces contained by this repo
64
58
  ```bash
65
- pnpm changeset
59
+ pnpm nx run-many -t build
66
60
  ```
67
61
 
68
62
  ## Useful commands
69
63
 
70
- This project uses `pnpm` and `turbo` with workspaces to manage projects and dependencies. Here is a list of useful commands to work in this repo.
64
+ This project uses `pnpm` and `nx` with workspaces to manage projects and dependencies. Here is a list of useful commands to work in this repo.
71
65
 
72
66
  ### Work with workspaces
73
67
 
74
68
  ```bash
75
- # build all the workspaces using turbo
76
- pnpm build
77
- # or
78
- pnpm turbo build
69
+ # build all the workspaces using Nx
70
+ pnpm nx run-many -t build
79
71
 
80
72
  # to execute COMMAND on WORKSPACE_NAME
81
73
  pnpm --filter WORKSPACE_NAME COMMAND
82
74
  # to execute COMMAND on all workspaces
83
75
  pnpm -r COMMAND
84
-
85
- # run the typecheck script on all workspaces
86
- pnpm -r typecheck
87
76
  ```
88
77
 
89
78
  ### Add dependencies
90
79
 
91
80
  ```bash
92
81
  # add a dependency to the workspace root
93
- pnpm -w add turbo
82
+ pnpm -w add vitest
94
83
 
95
84
  # add vitest as devDependency on citizen-func
96
85
  pnpm --filter citizen-func add -D vitest
@@ -128,15 +117,22 @@ User documentation doesn't usually go in here. For public packages, it must go i
128
117
 
129
118
  ## Releases
130
119
 
131
- Releases are handled using [Changeset](https://github.com/changesets/changesets).
132
- Changeset takes care of bumping packages, updating the changelog, and tagging the repository accordingly.
120
+ We use [Nx Release](https://nx.dev/features/manage-releases) to automate package versioning and releases.
121
+
122
+ Each Pull Request that includes changes that require a version bump must include a _version plan file_ that describes the introduced changes.
123
+
124
+ To create a _version plan file_ run the following command and follow the instructions.
125
+
126
+ ```bash
127
+ pnpm nx release plan
128
+ ```
133
129
 
134
130
  #### How it works
135
131
 
136
- - When opening a Pull Request with a change intended to be published, [add a changeset file](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md) to the proposed changes.
137
- - Once the Pull Request is merged, a new Pull Request named `Version Packages` will be automatically opened with all the release changes such as version bumping for each involved app or package and changelog update; if an open `Version Packages` PR already exists, it will be updated and the package versions calculated accordingly (see https://github.com/changesets/changesets/blob/main/docs/decisions.md#how-changesets-are-combined).
138
- Only apps and packages mentioned in the changeset files will be bumped.
139
- - Review the `Version Packages` PR and merge it when ready. Changeset files will be deleted.
132
+ - When opening a Pull Request with a change intended to be published, add a version plan file that describes the change and the version bump type (patch, minor, major) for each involved app or package.
133
+ - Once the Pull Request is merged, a new Pull Request named `Version Packages` will be automatically opened with all the release changes such as version bumping for each involved app or package and changelog update; if an open `Version Packages` PR already exists, it will be updated and the package versions calculated accordingly.
134
+ Only apps and packages mentioned in the version plan files will be bumped.
135
+ - Review the `Version Packages` PR and merge it when ready. Version plan files will be deleted.
140
136
  - A Release entry is created for each app or package whose version has been bumped.
141
137
 
142
138
  ### Folder structure
@@ -0,0 +1,79 @@
1
+ {
2
+ "$schema": "./node_modules/nx/schemas/nx-schema.json",
3
+ "parallel": 10,
4
+ "targetDefaults": {
5
+ "lint": {
6
+ "dependsOn": ["^build"],
7
+ "options": {
8
+ "fix": true
9
+ },
10
+ "configurations": {
11
+ "ci": {
12
+ "fix": false
13
+ }
14
+ }
15
+ },
16
+ "test": {
17
+ "configurations": {
18
+ "ci": {
19
+ "coverage": true
20
+ }
21
+ }
22
+ }
23
+ },
24
+ "release": {
25
+ "versionPlans": true,
26
+ "projectsRelationship": "independent",
27
+ "version": {},
28
+ "changelog": {
29
+ "projectChangelogs": {
30
+ "createRelease": false,
31
+ "file": "{projectRoot}/CHANGELOG.md",
32
+ "renderOptions": {
33
+ "authors": true,
34
+ "applyUsernameToAuthors": true,
35
+ "commitReferences": true,
36
+ "versionTitleDate": true
37
+ }
38
+ }
39
+ },
40
+ "git": {
41
+ "commit": false,
42
+ "tag": true,
43
+ "stageChanges": false
44
+ },
45
+ "releaseTag": {
46
+ "pattern": "{projectName}@{version}"
47
+ }
48
+ },
49
+ "plugins": [
50
+ {
51
+ "plugin": "@nx/vitest",
52
+ "options": {
53
+ "testTargetName": "test"
54
+ },
55
+ "include": ["apps/**", "packages/**"]
56
+ },
57
+ {
58
+ "plugin": "@nx/eslint/plugin",
59
+ "options": {
60
+ "targetName": "lint"
61
+ }
62
+ },
63
+ {
64
+ "plugin": "@nx/js/typescript",
65
+ "options": {
66
+ "typecheck": {
67
+ "targetName": "typecheck"
68
+ },
69
+ "build": {
70
+ "targetName": "build",
71
+ "configName": "tsconfig.json",
72
+ "buildDepsName": "build-deps",
73
+ "watchDepsName": "watch-deps"
74
+ }
75
+ }
76
+ }
77
+ ],
78
+ "analytics": false
79
+ }
@@ -5,3 +5,6 @@ packages:
5
5
  linkWorkspacePackages: true
6
6
  packageImportMethod: "clone-or-copy"
7
7
  cleanupUnusedCatalogs: true
8
+
9
+ onlyBuiltDependencies:
10
+ - nx
@@ -1,29 +0,0 @@
1
- {
2
- "$schema": "https://turbo.build/schema.json",
3
- "tasks": {
4
- "build": {
5
- "dependsOn": ["^build"],
6
- "inputs": [],
7
- "outputs": []
8
- },
9
- "typecheck": {
10
- "dependsOn": ["^build"],
11
- "inputs": []
12
- },
13
- "lint:check": {
14
- "inputs": []
15
- },
16
- "format:check": {},
17
- "test": {
18
- "dependsOn": ["^build"],
19
- "inputs": [],
20
- "outputLogs": "errors-only"
21
- },
22
- "test:coverage": {
23
- "dependsOn": ["^build"],
24
- "inputs": [],
25
- "outputs": ["coverage"],
26
- "outputLogs": "errors-only"
27
- }
28
- }
29
- }