@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.
- package/dist/adapters/commander/commands/__tests__/{init.test.js → add.test.js} +1 -1
- package/dist/adapters/commander/commands/add.d.ts +11 -1
- package/dist/adapters/commander/commands/add.js +69 -5
- package/dist/adapters/commander/commands/init.d.ts +1 -8
- package/dist/adapters/commander/commands/init.js +5 -63
- package/dist/adapters/commander/index.js +1 -1
- package/dist/adapters/plop/actions/setup-pnpm.js +7 -7
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/domain/__tests__/data.js +1 -1
- package/dist/domain/__tests__/repository.test.js +40 -30
- package/dist/domain/doctor.js +3 -4
- package/dist/domain/repository.d.ts +1 -1
- package/dist/domain/repository.js +18 -11
- package/package.json +1 -1
- package/templates/environment/bootstrapper/{{env.name}}/main.tf.hbs +8 -2
- package/templates/environment/core/{{env.name}}/main.tf.hbs +3 -1
- package/templates/monorepo/README.md.hbs +23 -27
- package/templates/monorepo/nx.json +79 -0
- package/templates/monorepo/pnpm-workspace.yaml +3 -0
- package/templates/monorepo/turbo.json +0 -29
- /package/dist/adapters/commander/commands/__tests__/{init.test.d.ts → add.test.d.ts} +0 -0
|
@@ -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 "../
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: ({
|
|
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,
|
|
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 {
|
|
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((
|
|
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
|
|
15
|
+
const $ = $_({
|
|
16
16
|
cwd,
|
|
17
17
|
env,
|
|
18
18
|
extendEnv: false, // Don't include process.env variables
|
|
19
19
|
});
|
|
20
|
-
await
|
|
21
|
-
await
|
|
22
|
-
await
|
|
23
|
-
await
|
|
24
|
-
await
|
|
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
package/dist/config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { errAsync, ok, okAsync } from "neverthrow";
|
|
2
2
|
import { describe, expect, it } from "vitest";
|
|
3
|
-
import {
|
|
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("
|
|
31
|
+
describe("checkNxConfig", () => {
|
|
32
32
|
const config = makeMockConfig();
|
|
33
33
|
const repositoryRoot = makeMockRepositoryRoot();
|
|
34
|
-
it("should return ok result with successful validation when
|
|
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("
|
|
38
|
-
const result = await
|
|
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: "
|
|
40
|
+
checkName: "Nx Configuration",
|
|
41
41
|
isValid: true,
|
|
42
|
-
successMessage: "
|
|
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/
|
|
44
|
+
expect(deps.repositoryReader.fileExists).toHaveBeenCalledWith("a/repo/root/nx.json");
|
|
45
45
|
});
|
|
46
|
-
it("should return ok result with failed validation when
|
|
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
|
|
50
|
+
const result = await checkNxConfig(deps, repositoryRoot, config);
|
|
51
51
|
expect(result).toStrictEqual(ok({
|
|
52
|
-
checkName: "
|
|
53
|
-
errorMessage: "
|
|
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
|
|
57
|
+
it("should return ok result with failed validation when nx.json does not exist", async () => {
|
|
58
58
|
const deps = makeMockDependencies();
|
|
59
|
-
const errorMessage = "
|
|
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
|
|
61
|
+
const result = await checkNxConfig(deps, repositoryRoot, config);
|
|
62
62
|
expect(result).toStrictEqual(ok({
|
|
63
|
-
checkName: "
|
|
63
|
+
checkName: "Nx Configuration",
|
|
64
64
|
errorMessage,
|
|
65
65
|
isValid: false,
|
|
66
66
|
}));
|
|
67
67
|
});
|
|
68
|
-
it("should return
|
|
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
|
|
82
|
+
const result = await checkNxConfig(deps, repositoryRoot, config);
|
|
73
83
|
expect(result).toStrictEqual(ok({
|
|
74
|
-
checkName: "
|
|
75
|
-
errorMessage: "
|
|
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
|
|
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("
|
|
83
|
-
const result = await
|
|
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: "
|
|
86
|
-
errorMessage: `
|
|
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
|
|
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("
|
|
94
|
-
const result = await
|
|
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: "
|
|
106
|
+
checkName: "Nx Configuration",
|
|
97
107
|
isValid: true,
|
|
98
|
-
successMessage: "
|
|
108
|
+
successMessage: "Nx configuration is present in the monorepo root and Nx dependency is installed",
|
|
99
109
|
}));
|
|
100
110
|
});
|
|
101
111
|
});
|
package/dist/domain/doctor.js
CHANGED
|
@@ -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(
|
|
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
|
|
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
|
|
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
|
|
33
|
+
export const checkNxConfig = async (dependencies, repositoryRoot, config) => {
|
|
34
34
|
const { packageJsonReader, repositoryReader } = dependencies;
|
|
35
|
-
const checkName = "
|
|
36
|
-
const
|
|
37
|
-
if (
|
|
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:
|
|
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
|
|
53
|
-
if (!
|
|
59
|
+
const nxVersion = dependenciesResult.value.get("nx");
|
|
60
|
+
if (!nxVersion) {
|
|
54
61
|
return ok({
|
|
55
62
|
checkName,
|
|
56
|
-
errorMessage: "
|
|
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(
|
|
67
|
+
if (!isVersionValid(nxVersion, config.minVersions.nx)) {
|
|
61
68
|
return ok({
|
|
62
69
|
checkName,
|
|
63
|
-
errorMessage: `
|
|
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: "
|
|
77
|
+
successMessage: "Nx configuration is present in the monorepo root and Nx dependency is installed",
|
|
71
78
|
});
|
|
72
79
|
};
|
package/package.json
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
52
|
-
```bash
|
|
53
|
-
pnpm build
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
## Release management
|
|
51
|
+
3. Install dependencies using `pnpm`
|
|
57
52
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
|
59
|
+
pnpm nx run-many -t build
|
|
66
60
|
```
|
|
67
61
|
|
|
68
62
|
## Useful commands
|
|
69
63
|
|
|
70
|
-
This project uses `pnpm` and `
|
|
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
|
|
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
|
|
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
|
-
|
|
132
|
-
|
|
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,
|
|
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
|
|
138
|
-
Only apps and packages mentioned in the
|
|
139
|
-
- Review the `Version Packages` PR and merge it when ready.
|
|
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
|
+
}
|
|
@@ -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
|
-
}
|
|
File without changes
|