@pagopa/dx-cli 0.15.2 → 0.15.4
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/bin/index.js +8 -1280
- package/dist/adapters/azure/__tests__/cloud-account-repository.test.d.ts +1 -0
- package/dist/adapters/azure/__tests__/cloud-account-repository.test.js +95 -0
- package/dist/adapters/azure/__tests__/cloud-account-service.test.d.ts +1 -0
- package/dist/adapters/azure/__tests__/cloud-account-service.test.js +95 -0
- package/dist/adapters/azure/cloud-account-repository.d.ts +12 -0
- package/dist/adapters/azure/cloud-account-repository.js +23 -0
- package/dist/adapters/azure/cloud-account-service.d.ts +22 -0
- package/dist/adapters/azure/cloud-account-service.js +255 -0
- package/dist/adapters/azure/locations.d.ts +7 -0
- package/dist/adapters/azure/locations.js +21 -0
- package/dist/adapters/codemods/__tests__/registry.test.d.ts +1 -0
- package/dist/adapters/codemods/__tests__/registry.test.js +59 -0
- package/dist/adapters/codemods/__tests__/use-azure-appsvc.test.d.ts +1 -0
- package/dist/adapters/codemods/__tests__/use-azure-appsvc.test.js +77 -0
- package/dist/adapters/codemods/__tests__/use-pnpm.test.d.ts +1 -0
- package/dist/adapters/codemods/__tests__/use-pnpm.test.js +148 -0
- package/dist/adapters/codemods/git.d.ts +2 -0
- package/dist/adapters/codemods/git.js +18 -0
- package/dist/adapters/codemods/index.d.ts +3 -0
- package/dist/adapters/codemods/index.js +9 -0
- package/dist/adapters/codemods/registry.d.ts +8 -0
- package/dist/adapters/codemods/registry.js +16 -0
- package/dist/adapters/codemods/update-code-review.d.ts +3 -0
- package/dist/adapters/codemods/update-code-review.js +60 -0
- package/dist/adapters/codemods/use-azure-appsvc.d.ts +3 -0
- package/dist/adapters/codemods/use-azure-appsvc.js +84 -0
- package/dist/adapters/codemods/use-pnpm.d.ts +22 -0
- package/dist/adapters/codemods/use-pnpm.js +214 -0
- package/dist/adapters/codemods/yaml.d.ts +2 -0
- package/dist/adapters/codemods/yaml.js +8 -0
- package/dist/adapters/commander/commands/codemod.d.ts +8 -0
- package/dist/adapters/commander/commands/codemod.js +22 -0
- package/dist/adapters/commander/commands/doctor.d.ts +4 -0
- package/dist/adapters/commander/commands/doctor.js +12 -0
- package/dist/adapters/commander/commands/info.d.ts +3 -0
- package/dist/adapters/commander/commands/info.js +9 -0
- package/dist/adapters/commander/commands/init.d.ts +7 -0
- package/dist/adapters/commander/commands/init.js +126 -0
- package/dist/adapters/commander/commands/savemoney.d.ts +2 -0
- package/dist/adapters/commander/commands/savemoney.js +26 -0
- package/dist/adapters/commander/index.d.ts +7 -0
- package/dist/adapters/commander/index.js +19 -0
- package/dist/adapters/execa/terraform.d.ts +27 -0
- package/dist/adapters/execa/terraform.js +28 -0
- package/dist/adapters/github/__tests__/github-repo.spec.d.ts +1 -0
- package/dist/adapters/github/__tests__/github-repo.spec.js +67 -0
- package/dist/adapters/github/github-repo.d.ts +2 -0
- package/dist/adapters/github/github-repo.js +31 -0
- package/dist/adapters/logtape/validation-reporter.d.ts +2 -0
- package/dist/adapters/logtape/validation-reporter.js +14 -0
- package/dist/adapters/node/__tests__/data.d.ts +18 -0
- package/dist/adapters/node/__tests__/data.js +22 -0
- package/dist/adapters/node/__tests__/package-json.test.d.ts +1 -0
- package/dist/adapters/node/__tests__/package-json.test.js +86 -0
- package/dist/adapters/node/__tests__/repository.test.d.ts +1 -0
- package/dist/adapters/node/__tests__/repository.test.js +77 -0
- package/dist/adapters/node/fs/__tests__/file-reader.test.d.ts +1 -0
- package/dist/adapters/node/fs/__tests__/file-reader.test.js +80 -0
- package/dist/adapters/node/fs/file-reader.d.ts +24 -0
- package/dist/adapters/node/fs/file-reader.js +26 -0
- package/dist/adapters/node/json/__tests__/index.test.d.ts +1 -0
- package/dist/adapters/node/json/__tests__/index.test.js +14 -0
- package/dist/adapters/node/json/index.d.ts +2 -0
- package/dist/adapters/node/json/index.js +2 -0
- package/dist/adapters/node/package-json.d.ts +2 -0
- package/dist/adapters/node/package-json.js +22 -0
- package/dist/adapters/node/release.d.ts +2 -0
- package/dist/adapters/node/release.js +33 -0
- package/dist/adapters/node/repository.d.ts +2 -0
- package/dist/adapters/node/repository.js +47 -0
- package/dist/adapters/octokit/__tests__/index.test.d.ts +1 -0
- package/dist/adapters/octokit/__tests__/index.test.js +197 -0
- package/dist/adapters/octokit/index.d.ts +24 -0
- package/dist/adapters/octokit/index.js +65 -0
- package/dist/adapters/plop/actions/__tests__/init-cloud-accounts.test.d.ts +1 -0
- package/dist/adapters/plop/actions/__tests__/init-cloud-accounts.test.js +115 -0
- package/dist/adapters/plop/actions/__tests__/provision-terraform-backend.test.d.ts +1 -0
- package/dist/adapters/plop/actions/__tests__/provision-terraform-backend.test.js +116 -0
- package/dist/adapters/plop/actions/fetch-github-release.d.ts +12 -0
- package/dist/adapters/plop/actions/fetch-github-release.js +20 -0
- package/dist/adapters/plop/actions/get-node-version.d.ts +2 -0
- package/dist/adapters/plop/actions/get-node-version.js +9 -0
- package/dist/adapters/plop/actions/get-terraform-backend.d.ts +3 -0
- package/dist/adapters/plop/actions/get-terraform-backend.js +17 -0
- package/dist/adapters/plop/actions/init-cloud-accounts.d.ts +5 -0
- package/dist/adapters/plop/actions/init-cloud-accounts.js +13 -0
- package/dist/adapters/plop/actions/provision-terraform-backend.d.ts +10 -0
- package/dist/adapters/plop/actions/provision-terraform-backend.js +16 -0
- package/dist/adapters/plop/actions/semver.d.ts +19 -0
- package/dist/adapters/plop/actions/semver.js +27 -0
- package/dist/adapters/plop/actions/setup-pnpm.d.ts +2 -0
- package/dist/adapters/plop/actions/setup-pnpm.js +26 -0
- package/dist/adapters/plop/generators/environment/__tests__/actions.test.d.ts +2 -0
- package/dist/adapters/plop/generators/environment/__tests__/actions.test.js +55 -0
- package/dist/adapters/plop/generators/environment/actions.d.ts +2 -0
- package/dist/adapters/plop/generators/environment/actions.js +54 -0
- package/dist/adapters/plop/generators/environment/index.d.ts +3 -0
- package/dist/adapters/plop/generators/environment/index.js +19 -0
- package/dist/adapters/plop/generators/environment/prompts.d.ts +66 -0
- package/dist/adapters/plop/generators/environment/prompts.js +166 -0
- package/dist/adapters/plop/generators/monorepo/actions.d.ts +51 -0
- package/dist/adapters/plop/generators/monorepo/actions.js +35 -0
- package/dist/adapters/plop/generators/monorepo/index.d.ts +6 -0
- package/dist/adapters/plop/generators/monorepo/index.js +17 -0
- package/dist/adapters/plop/generators/monorepo/prompts.d.ts +10 -0
- package/dist/adapters/plop/generators/monorepo/prompts.js +31 -0
- package/dist/adapters/plop/helpers/__tests__/resource-prefix.test.d.ts +1 -0
- package/dist/adapters/plop/helpers/__tests__/resource-prefix.test.js +113 -0
- package/dist/adapters/plop/helpers/env-short.d.ts +3 -0
- package/dist/adapters/plop/helpers/env-short.js +9 -0
- package/dist/adapters/plop/helpers/resource-prefix.d.ts +5 -0
- package/dist/adapters/plop/helpers/resource-prefix.js +18 -0
- package/dist/adapters/plop/index.d.ts +8 -0
- package/dist/adapters/plop/index.js +24 -0
- package/dist/adapters/terraform/fmt.d.ts +1 -0
- package/dist/adapters/terraform/fmt.js +17 -0
- package/dist/adapters/yaml/__tests__/index.test.d.ts +1 -0
- package/dist/adapters/yaml/__tests__/index.test.js +53 -0
- package/dist/adapters/yaml/index.d.ts +8 -0
- package/dist/adapters/yaml/index.js +9 -0
- package/dist/adapters/zod/index.d.ts +23 -0
- package/dist/adapters/zod/index.js +22 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.js +5 -0
- package/dist/domain/__tests__/data.d.ts +17 -0
- package/dist/domain/__tests__/data.js +27 -0
- package/dist/domain/__tests__/environment.test.d.ts +1 -0
- package/dist/domain/__tests__/environment.test.js +282 -0
- package/dist/domain/__tests__/info.test.d.ts +1 -0
- package/dist/domain/__tests__/info.test.js +77 -0
- package/dist/domain/__tests__/package-json.test.d.ts +1 -0
- package/dist/domain/__tests__/package-json.test.js +39 -0
- package/dist/domain/__tests__/repository.test.d.ts +1 -0
- package/dist/domain/__tests__/repository.test.js +101 -0
- package/dist/domain/__tests__/workspace.test.d.ts +1 -0
- package/dist/domain/__tests__/workspace.test.js +57 -0
- package/dist/domain/cloud-account.d.ts +28 -0
- package/dist/domain/cloud-account.js +12 -0
- package/dist/domain/codemod.d.ts +11 -0
- package/dist/domain/codemod.js +1 -0
- package/dist/domain/dependencies.d.ts +10 -0
- package/dist/domain/dependencies.js +1 -0
- package/dist/domain/doctor.d.ts +10 -0
- package/dist/domain/doctor.js +50 -0
- package/dist/domain/environment.d.ts +40 -0
- package/dist/domain/environment.js +57 -0
- package/dist/domain/github-repo.d.ts +6 -0
- package/dist/domain/github-repo.js +8 -0
- package/dist/domain/github.d.ts +37 -0
- package/dist/domain/github.js +29 -0
- package/dist/domain/info.d.ts +11 -0
- package/dist/domain/info.js +52 -0
- package/dist/domain/package-json.d.ts +42 -0
- package/dist/domain/package-json.js +69 -0
- package/dist/domain/remote-backend.d.ts +8 -0
- package/dist/domain/remote-backend.js +9 -0
- package/dist/domain/repository.d.ts +13 -0
- package/dist/domain/repository.js +72 -0
- package/dist/domain/validation.d.ts +16 -0
- package/dist/domain/validation.js +1 -0
- package/dist/domain/workspace.d.ts +9 -0
- package/dist/domain/workspace.js +32 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +35 -0
- package/dist/use-cases/__tests__/apply-codemod.test.d.ts +1 -0
- package/dist/use-cases/__tests__/apply-codemod.test.js +73 -0
- package/dist/use-cases/__tests__/list-codemods.test.d.ts +1 -0
- package/dist/use-cases/__tests__/list-codemods.test.js +38 -0
- package/dist/use-cases/apply-codemod.d.ts +5 -0
- package/dist/use-cases/apply-codemod.js +14 -0
- package/dist/use-cases/list-codemods.d.ts +4 -0
- package/dist/use-cases/list-codemods.js +1 -0
- package/package.json +18 -7
- package/templates/environment/bootstrapper/{{env.name}}/data.tf.hbs +13 -0
- package/templates/environment/bootstrapper/{{env.name}}/main.tf.hbs +70 -0
- package/templates/environment/bootstrapper/{{env.name}}/providers.tf.hbs +26 -0
- package/templates/environment/core/{{env.name}}/main.tf.hbs +21 -0
- package/templates/environment/core/{{env.name}}/outputs.tf.hbs +8 -0
- package/templates/environment/core/{{env.name}}/providers.tf.hbs +21 -0
- package/templates/environment/shared/backend.tf.hbs +14 -0
- package/templates/environment/shared/locals.tf.hbs +26 -0
- package/templates/monorepo/.editorconfig +8 -0
- package/templates/monorepo/.node-version.hbs +1 -0
- package/templates/monorepo/.pre-commit-config.yaml.hbs +34 -0
- package/templates/monorepo/.prettierignore +5 -0
- package/templates/monorepo/.terraform-version.hbs +1 -0
- package/templates/monorepo/.trivyignore +16 -0
- package/templates/monorepo/README.md.hbs +163 -0
- package/templates/monorepo/infra/repository/main.tf.hbs +11 -0
- package/templates/monorepo/infra/repository/outputs.tf.hbs +11 -0
- package/templates/monorepo/infra/repository/providers.tf.hbs +13 -0
- package/templates/monorepo/package.json.hbs +7 -0
- package/templates/monorepo/pnpm-workspace.yaml +7 -0
- package/templates/monorepo/turbo.json +29 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { getLogger } from "@logtape/logtape";
|
|
2
|
+
export const makeValidationReporter = () => {
|
|
3
|
+
const logger = getLogger(["dx-cli", "validation"]);
|
|
4
|
+
return {
|
|
5
|
+
reportCheckResult(result) {
|
|
6
|
+
if (result.isValid) {
|
|
7
|
+
logger.info(`✅ ${result.successMessage}`);
|
|
8
|
+
}
|
|
9
|
+
else {
|
|
10
|
+
logger.error(`❌ ${result.errorMessage}`);
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { PackageJson } from "../../../domain/package-json.js";
|
|
2
|
+
export declare const makeMockPackageJson: (overrides?: Partial<PackageJson>) => {
|
|
3
|
+
dependencies: Map<string, string> | {
|
|
4
|
+
aDependency: string;
|
|
5
|
+
anotherDependency: string;
|
|
6
|
+
};
|
|
7
|
+
devDependencies: Map<string, string> | {
|
|
8
|
+
turbo: string;
|
|
9
|
+
typescript: string;
|
|
10
|
+
};
|
|
11
|
+
name: string;
|
|
12
|
+
scripts: Map<string & import("zod/v4").$brand<"ScriptName">, string> | {
|
|
13
|
+
build: string;
|
|
14
|
+
"code-review": string;
|
|
15
|
+
};
|
|
16
|
+
packageManager?: "npm" | "pnpm" | "yarn" | undefined;
|
|
17
|
+
};
|
|
18
|
+
export declare const makeMockPnpmWorkspaceYaml: () => string;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const makeMockPackageJson = (overrides = {}) => {
|
|
2
|
+
const basePackageJson = {
|
|
3
|
+
dependencies: {
|
|
4
|
+
aDependency: "^4.17.21",
|
|
5
|
+
anotherDependency: "^8.0.0",
|
|
6
|
+
},
|
|
7
|
+
devDependencies: {
|
|
8
|
+
turbo: "^2.5.2",
|
|
9
|
+
typescript: "^5.0.0",
|
|
10
|
+
},
|
|
11
|
+
name: "aPackageName",
|
|
12
|
+
scripts: {
|
|
13
|
+
build: "tsc",
|
|
14
|
+
"code-review": "eslint .",
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
return { ...basePackageJson, ...overrides };
|
|
18
|
+
};
|
|
19
|
+
export const makeMockPnpmWorkspaceYaml = () => `
|
|
20
|
+
packages:
|
|
21
|
+
- packages/*
|
|
22
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { vol } from "memfs";
|
|
2
|
+
import { err, ok } from "neverthrow";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { makePackageJsonReader } from "../package-json.js";
|
|
6
|
+
import { makeMockPackageJson } from "./data.js";
|
|
7
|
+
vi.mock("node:fs/promises");
|
|
8
|
+
describe("makePackageJsonReader", () => {
|
|
9
|
+
const mockPackageJson = makeMockPackageJson();
|
|
10
|
+
const rootDir = "/some/dir";
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
vol.reset();
|
|
13
|
+
vol.fromJSON({
|
|
14
|
+
"./package.json": JSON.stringify(mockPackageJson),
|
|
15
|
+
}, rootDir);
|
|
16
|
+
});
|
|
17
|
+
describe("getScripts", () => {
|
|
18
|
+
it("should parse scripts from package.json", async () => {
|
|
19
|
+
const spy = vi.spyOn(fs, "readFile");
|
|
20
|
+
const packageJsonReader = makePackageJsonReader();
|
|
21
|
+
const result = await packageJsonReader.getScripts(rootDir);
|
|
22
|
+
expect(result).toStrictEqual(ok(new Map().set("build", "tsc").set("code-review", "eslint .")));
|
|
23
|
+
expect(spy).toHaveBeenCalledWith(`${rootDir}/package.json`, "utf-8");
|
|
24
|
+
});
|
|
25
|
+
it("should return an empty array when no scripts exist", async () => {
|
|
26
|
+
vol.fromJSON({
|
|
27
|
+
"./package.json": JSON.stringify(makeMockPackageJson({ scripts: undefined })),
|
|
28
|
+
}, rootDir);
|
|
29
|
+
const spy = vi.spyOn(fs, "readFile");
|
|
30
|
+
const packageJsonReader = makePackageJsonReader();
|
|
31
|
+
const result = await packageJsonReader.getScripts(rootDir);
|
|
32
|
+
expect(result).toStrictEqual(ok(new Map()));
|
|
33
|
+
expect(spy).toHaveBeenCalledWith(`${rootDir}/package.json`, "utf-8");
|
|
34
|
+
});
|
|
35
|
+
it("should return an error when package.json does not exist", async () => {
|
|
36
|
+
vol.reset();
|
|
37
|
+
const packageJsonReader = makePackageJsonReader();
|
|
38
|
+
const result = await packageJsonReader.getScripts(rootDir);
|
|
39
|
+
expect(result).toStrictEqual(err(new Error("Failed to read file: /some/dir/package.json")));
|
|
40
|
+
});
|
|
41
|
+
it("should return an error when package.json is invalid JSON", async () => {
|
|
42
|
+
vol.fromJSON({
|
|
43
|
+
"./package.json": "invalid json content",
|
|
44
|
+
}, rootDir);
|
|
45
|
+
const packageJsonReader = makePackageJsonReader();
|
|
46
|
+
const result = await packageJsonReader.getScripts(rootDir);
|
|
47
|
+
expect(result).toStrictEqual(err(new Error("Failed to parse JSON")));
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
describe("getDependencies", () => {
|
|
51
|
+
it("should parse devDependencies from package.json", async () => {
|
|
52
|
+
const spy = vi.spyOn(fs, "readFile");
|
|
53
|
+
const packageJsonReader = makePackageJsonReader();
|
|
54
|
+
const result = await packageJsonReader.getDependencies(rootDir, "dev");
|
|
55
|
+
expect(result).toStrictEqual(ok(new Map().set("turbo", "^2.5.2").set("typescript", "^5.0.0")));
|
|
56
|
+
expect(spy).toHaveBeenCalledWith(`${rootDir}/package.json`, "utf-8");
|
|
57
|
+
});
|
|
58
|
+
it("should parse dependencies from package.json", async () => {
|
|
59
|
+
const spy = vi.spyOn(fs, "readFile");
|
|
60
|
+
const packageJsonReader = makePackageJsonReader();
|
|
61
|
+
const result = await packageJsonReader.getDependencies(rootDir, "prod");
|
|
62
|
+
expect(result).toStrictEqual(ok(new Map()
|
|
63
|
+
.set("aDependency", "^4.17.21")
|
|
64
|
+
.set("anotherDependency", "^8.0.0")));
|
|
65
|
+
expect(spy).toHaveBeenCalledWith(`${rootDir}/package.json`, "utf-8");
|
|
66
|
+
});
|
|
67
|
+
it("should return an empty array when no dependencies exist", async () => {
|
|
68
|
+
vol.reset();
|
|
69
|
+
vol.fromJSON({
|
|
70
|
+
"./package.json": JSON.stringify(makeMockPackageJson({
|
|
71
|
+
dependencies: undefined,
|
|
72
|
+
devDependencies: undefined,
|
|
73
|
+
})),
|
|
74
|
+
}, rootDir);
|
|
75
|
+
const packageJsonReader = makePackageJsonReader();
|
|
76
|
+
const result = await packageJsonReader.getDependencies(rootDir, "dev");
|
|
77
|
+
expect(result).toStrictEqual(ok(new Map()));
|
|
78
|
+
});
|
|
79
|
+
it("should return an error when package.json does not exist for dependencies", async () => {
|
|
80
|
+
vol.reset();
|
|
81
|
+
const packageJsonReader = makePackageJsonReader();
|
|
82
|
+
const result = await packageJsonReader.getDependencies(rootDir, "dev");
|
|
83
|
+
expect(result).toStrictEqual(err(new Error("Failed to read file: /some/dir/package.json")));
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import * as glob from "glob";
|
|
2
|
+
import { fs, vol } from "memfs";
|
|
3
|
+
import { err, ok } from "neverthrow";
|
|
4
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { makeRepositoryReader } from "../repository.js";
|
|
6
|
+
import { makeMockPackageJson, makeMockPnpmWorkspaceYaml } from "./data.js";
|
|
7
|
+
vi.mock("node:fs/promises");
|
|
8
|
+
const mockFileSystem = {
|
|
9
|
+
"./packages/bar/package.json": JSON.stringify(makeMockPackageJson({ name: "bar" })),
|
|
10
|
+
"./packages/foo/package.json": JSON.stringify(makeMockPackageJson({ name: "foo" })),
|
|
11
|
+
"./pnpm-workspace.yaml": makeMockPnpmWorkspaceYaml(),
|
|
12
|
+
};
|
|
13
|
+
describe("makeRepositoryReader", () => {
|
|
14
|
+
const repoRoot = "/some/repo/root";
|
|
15
|
+
vi.mock("glob", { spy: true });
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
vi.clearAllMocks();
|
|
18
|
+
vol.reset();
|
|
19
|
+
vol.fromJSON(mockFileSystem, repoRoot);
|
|
20
|
+
});
|
|
21
|
+
describe("getWorkspaces", () => {
|
|
22
|
+
it("should return an error when pnpm-workspace.yaml does not exist", async () => {
|
|
23
|
+
vol.reset();
|
|
24
|
+
const repositoryReader = makeRepositoryReader();
|
|
25
|
+
const result = await repositoryReader.getWorkspaces(repoRoot);
|
|
26
|
+
expect(result).toStrictEqual(err(new Error("Failed to read file: /some/repo/root/pnpm-workspace.yaml")));
|
|
27
|
+
});
|
|
28
|
+
it("should return no workspaces if the packages entry is not in the pnpm-workspace.yaml file", async () => {
|
|
29
|
+
vol.reset();
|
|
30
|
+
vol.fromJSON({ ...mockFileSystem, "./pnpm-workspace.yaml": "a yaml" }, repoRoot);
|
|
31
|
+
const readFileSpy = vi.spyOn(fs.promises, "readFile");
|
|
32
|
+
const globSpy = vi.spyOn(glob, "glob");
|
|
33
|
+
const repositoryReader = makeRepositoryReader();
|
|
34
|
+
const result = await repositoryReader.getWorkspaces(repoRoot);
|
|
35
|
+
expect(result).toStrictEqual(ok([]));
|
|
36
|
+
expect(readFileSpy).nthCalledWith(1, "/some/repo/root/pnpm-workspace.yaml", "utf-8");
|
|
37
|
+
expect(globSpy).not.toHaveBeenCalled();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
it("should return the workspaces", async () => {
|
|
41
|
+
const spyReadFile = vi.spyOn(fs.promises, "readFile");
|
|
42
|
+
const globSpy = vi.spyOn(glob, "glob");
|
|
43
|
+
globSpy.mockResolvedValueOnce(["packages/foo", "packages/bar"]);
|
|
44
|
+
const repositoryReader = makeRepositoryReader();
|
|
45
|
+
const result = await repositoryReader.getWorkspaces(repoRoot);
|
|
46
|
+
expect(result).toStrictEqual(ok([
|
|
47
|
+
{
|
|
48
|
+
name: "foo",
|
|
49
|
+
path: "/some/repo/root/packages/foo",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: "bar",
|
|
53
|
+
path: "/some/repo/root/packages/bar",
|
|
54
|
+
},
|
|
55
|
+
]));
|
|
56
|
+
expect(spyReadFile).nthCalledWith(1, "/some/repo/root/pnpm-workspace.yaml", "utf-8");
|
|
57
|
+
expect(globSpy).toHaveBeenCalledTimes(1);
|
|
58
|
+
expect(globSpy).toHaveBeenCalledWith("packages/*", {
|
|
59
|
+
cwd: repoRoot,
|
|
60
|
+
});
|
|
61
|
+
expect(spyReadFile).nthCalledWith(2, "/some/repo/root/packages/foo/package.json", "utf-8");
|
|
62
|
+
expect(spyReadFile).nthCalledWith(3, "/some/repo/root/packages/bar/package.json", "utf-8");
|
|
63
|
+
});
|
|
64
|
+
it("should return an error when the glob function fails", async () => {
|
|
65
|
+
const readFileSpy = vi.spyOn(fs.promises, "readFile");
|
|
66
|
+
const globSpy = vi.spyOn(glob, "glob");
|
|
67
|
+
// First read the pnpm-workspace.yaml file
|
|
68
|
+
globSpy.mockImplementationOnce(() => Promise.reject(new Error("glob failed")));
|
|
69
|
+
const repositoryReader = makeRepositoryReader();
|
|
70
|
+
const result = await repositoryReader.getWorkspaces(repoRoot);
|
|
71
|
+
expect(result).toStrictEqual(err(new Error("Failed to resolve workspace glob: packages/*")));
|
|
72
|
+
expect(readFileSpy).toBeCalledWith("/some/repo/root/pnpm-workspace.yaml", "utf-8");
|
|
73
|
+
expect(globSpy).toBeCalledWith("packages/*", {
|
|
74
|
+
cwd: repoRoot,
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { fs, vol } from "memfs";
|
|
2
|
+
import { err, ok } from "neverthrow";
|
|
3
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { z } from "zod/v4";
|
|
5
|
+
import { fileExists, readFile, readFileAndDecode } from "../file-reader.js";
|
|
6
|
+
vi.mock("node:fs/promises");
|
|
7
|
+
describe("readFileAndDecode", () => {
|
|
8
|
+
const testSchema = z.object({
|
|
9
|
+
name: z.string(),
|
|
10
|
+
version: z.string(),
|
|
11
|
+
});
|
|
12
|
+
const validData = {
|
|
13
|
+
name: "test-package",
|
|
14
|
+
version: "1.0.0",
|
|
15
|
+
};
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
vol.reset();
|
|
18
|
+
vol.fromJSON({
|
|
19
|
+
"./file.json": JSON.stringify(validData),
|
|
20
|
+
"./file.yaml": "name: test-package\nversion: 1.0.0",
|
|
21
|
+
"./invalid.json": JSON.stringify({ invalidKey: "anInvalidKey" }),
|
|
22
|
+
}, ".");
|
|
23
|
+
});
|
|
24
|
+
it("should read and parse a file", async () => {
|
|
25
|
+
const spy = vi.spyOn(fs.promises, "readFile");
|
|
26
|
+
const result = await readFileAndDecode("file.json", testSchema);
|
|
27
|
+
expect(result).toStrictEqual(ok(validData));
|
|
28
|
+
expect(spy).toHaveBeenCalledWith("file.json", "utf-8");
|
|
29
|
+
});
|
|
30
|
+
it("should return error when file does not exist", async () => {
|
|
31
|
+
const result = await readFileAndDecode("nonexistent.json", testSchema);
|
|
32
|
+
expect(result).toStrictEqual(err(new Error("Failed to read file: nonexistent.json")));
|
|
33
|
+
});
|
|
34
|
+
it("should return error when JSON parsing fails", async () => {
|
|
35
|
+
const result = await readFileAndDecode("file.yaml", testSchema);
|
|
36
|
+
expect(result).toStrictEqual(err(new Error("Failed to parse JSON")));
|
|
37
|
+
});
|
|
38
|
+
it("should return error when schema decoding fails", async () => {
|
|
39
|
+
const spy = vi.spyOn(fs.promises, "readFile");
|
|
40
|
+
const result = await readFileAndDecode("invalid.json", testSchema);
|
|
41
|
+
expect(result).toStrictEqual(err(new Error("Input is not valid for the given schema")));
|
|
42
|
+
expect(spy).toHaveBeenCalledWith("invalid.json", "utf-8");
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
describe("readFile", () => {
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
vol.reset();
|
|
48
|
+
vol.fromJSON({
|
|
49
|
+
"./existing.txt": "hello world",
|
|
50
|
+
}, ".");
|
|
51
|
+
});
|
|
52
|
+
it("should read a file when it exists", async () => {
|
|
53
|
+
const spy = vi.spyOn(fs.promises, "readFile");
|
|
54
|
+
const result = await readFile("existing.txt");
|
|
55
|
+
expect(result).toStrictEqual(ok("hello world"));
|
|
56
|
+
expect(spy).toHaveBeenCalledWith("existing.txt", "utf-8");
|
|
57
|
+
});
|
|
58
|
+
it("should return error when file does not exist", async () => {
|
|
59
|
+
const result = await readFile("/missing.txt");
|
|
60
|
+
expect(result).toStrictEqual(err(new Error("Failed to read file: /missing.txt")));
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe("fileExists", () => {
|
|
64
|
+
beforeEach(() => {
|
|
65
|
+
vol.reset();
|
|
66
|
+
vol.fromJSON({
|
|
67
|
+
"./file.txt": "content",
|
|
68
|
+
}, ".");
|
|
69
|
+
});
|
|
70
|
+
it("should return true when the path exists", async () => {
|
|
71
|
+
const spy = vi.spyOn(fs.promises, "stat");
|
|
72
|
+
const result = await fileExists("file.txt");
|
|
73
|
+
expect(result).toStrictEqual(ok(true));
|
|
74
|
+
expect(spy).toHaveBeenCalledWith("file.txt");
|
|
75
|
+
});
|
|
76
|
+
it("should return the error when the path does not exist", async () => {
|
|
77
|
+
const result = await fileExists("missing.txt");
|
|
78
|
+
expect(result).toStrictEqual(err(new Error("missing.txt not found.")));
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ResultAsync } from "neverthrow";
|
|
2
|
+
import { z } from "zod/v4";
|
|
3
|
+
/**
|
|
4
|
+
* Reads a file from a directory with a specific filename.
|
|
5
|
+
*
|
|
6
|
+
* @param filePath - The path to the file to read
|
|
7
|
+
* @returns ResultAsync with file content or error
|
|
8
|
+
*/
|
|
9
|
+
export declare const readFile: (filePath: string) => ResultAsync<string, Error>;
|
|
10
|
+
/**
|
|
11
|
+
* Generic function to read a file and parse its content with a given zod schema.
|
|
12
|
+
*
|
|
13
|
+
* @param filePath - The path to the file to read
|
|
14
|
+
* @param schema - The zod schema to parse the file content with
|
|
15
|
+
* @returns ResultAsync with the parsed data or an error
|
|
16
|
+
*/
|
|
17
|
+
export declare const readFileAndDecode: <T>(filePath: string, schema: z.ZodSchema<T>) => ResultAsync<T, Error>;
|
|
18
|
+
/**
|
|
19
|
+
* Checks if a file exists.
|
|
20
|
+
*
|
|
21
|
+
* @param path - The path to the file to check
|
|
22
|
+
* @returns ResultAsync with true if the file exists, false otherwise
|
|
23
|
+
*/
|
|
24
|
+
export declare const fileExists: (path: string) => ResultAsync<boolean, Error>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ResultAsync } from "neverthrow";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import { decode } from "../../zod/index.js";
|
|
4
|
+
import { parseJson } from "../json/index.js";
|
|
5
|
+
/**
|
|
6
|
+
* Reads a file from a directory with a specific filename.
|
|
7
|
+
*
|
|
8
|
+
* @param filePath - The path to the file to read
|
|
9
|
+
* @returns ResultAsync with file content or error
|
|
10
|
+
*/
|
|
11
|
+
export const readFile = (filePath) => ResultAsync.fromPromise(fs.readFile(filePath, "utf-8"), (cause) => new Error(`Failed to read file: ${filePath}`, { cause }));
|
|
12
|
+
/**
|
|
13
|
+
* Generic function to read a file and parse its content with a given zod schema.
|
|
14
|
+
*
|
|
15
|
+
* @param filePath - The path to the file to read
|
|
16
|
+
* @param schema - The zod schema to parse the file content with
|
|
17
|
+
* @returns ResultAsync with the parsed data or an error
|
|
18
|
+
*/
|
|
19
|
+
export const readFileAndDecode = (filePath, schema) => readFile(filePath).andThen(parseJson).andThen(decode(schema));
|
|
20
|
+
/**
|
|
21
|
+
* Checks if a file exists.
|
|
22
|
+
*
|
|
23
|
+
* @param path - The path to the file to check
|
|
24
|
+
* @returns ResultAsync with true if the file exists, false otherwise
|
|
25
|
+
*/
|
|
26
|
+
export const fileExists = (path) => ResultAsync.fromPromise(fs.stat(path), () => new Error(`${path} not found.`)).map(() => true);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { err, ok } from "neverthrow";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { parseJson } from "../index.js";
|
|
4
|
+
describe("parseJson", () => {
|
|
5
|
+
const validJSONString = '{"name": "test"}';
|
|
6
|
+
it("should return JS object when valid JSON is provided", () => {
|
|
7
|
+
const result = parseJson(validJSONString);
|
|
8
|
+
expect(result).toStrictEqual(ok({ name: "test" }));
|
|
9
|
+
});
|
|
10
|
+
it("should return error when invalid JSON is provided", () => {
|
|
11
|
+
const result = parseJson("invalid JSON");
|
|
12
|
+
expect(result).toStrictEqual(err(new Error("Failed to parse JSON")));
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import * as process from "node:process";
|
|
3
|
+
import { packageJsonSchema, } from "../../domain/package-json.js";
|
|
4
|
+
import { readFileAndDecode } from "./fs/file-reader.js";
|
|
5
|
+
export const makePackageJsonReader = () => ({
|
|
6
|
+
getDependencies: (cwd = process.cwd(), type) => {
|
|
7
|
+
const packageJsonPath = join(cwd, "package.json");
|
|
8
|
+
return readFileAndDecode(packageJsonPath, packageJsonSchema).map((packageJson) => {
|
|
9
|
+
const key = type === "dev" ? "devDependencies" : "dependencies";
|
|
10
|
+
return packageJson[key];
|
|
11
|
+
});
|
|
12
|
+
},
|
|
13
|
+
getRootRequiredScripts: () => new Map().set("code-review", "eslint ."),
|
|
14
|
+
getScripts: (cwd = process.cwd()) => {
|
|
15
|
+
const packageJsonPath = join(cwd, "package.json");
|
|
16
|
+
return readFileAndDecode(packageJsonPath, packageJsonSchema).map(({ scripts }) => scripts);
|
|
17
|
+
},
|
|
18
|
+
readPackageJson: (cwd = process.cwd()) => {
|
|
19
|
+
const packageJsonPath = join(cwd, "package.json");
|
|
20
|
+
return readFileAndDecode(packageJsonPath, packageJsonSchema);
|
|
21
|
+
},
|
|
22
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as assert from "node:assert/strict";
|
|
2
|
+
import semverParse from "semver/functions/parse.js";
|
|
3
|
+
import { z } from "zod/v4";
|
|
4
|
+
const nodeReleaseSchema = z.object({
|
|
5
|
+
lts: z.union([z.string(), z.boolean()]).nullable(),
|
|
6
|
+
version: z.string(),
|
|
7
|
+
});
|
|
8
|
+
export async function getLatestByCodename(codename) {
|
|
9
|
+
const releases = await nodeReleases();
|
|
10
|
+
for (const release of releases) {
|
|
11
|
+
if (release.lts === codename) {
|
|
12
|
+
// throws if the version is invalid
|
|
13
|
+
return semverParse(release.version, {}, true);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
throw new Error(`No LTS release found for codename: ${codename}`);
|
|
17
|
+
}
|
|
18
|
+
async function nodeReleases() {
|
|
19
|
+
try {
|
|
20
|
+
const response = await fetch("https://nodejs.org/dist/index.json");
|
|
21
|
+
assert.ok(response.ok, `Failed to fetch Node.js releases: ${response.statusText}`);
|
|
22
|
+
const json = await response.json();
|
|
23
|
+
return z.array(nodeReleaseSchema).parse(json);
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
if (err instanceof z.ZodError) {
|
|
27
|
+
throw new Error("Invalid data format for Node.js releases", {
|
|
28
|
+
cause: err,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
throw err;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as glob from "glob";
|
|
2
|
+
import { okAsync, ResultAsync } from "neverthrow";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { z } from "zod/v4";
|
|
5
|
+
import { packageJsonSchema } from "../../domain/package-json.js";
|
|
6
|
+
import { workspaceSchema } from "../../domain/workspace.js";
|
|
7
|
+
import { parseYaml } from "../yaml/index.js";
|
|
8
|
+
import { decode } from "../zod/index.js";
|
|
9
|
+
import { fileExists, readFile, readFileAndDecode } from "./fs/file-reader.js";
|
|
10
|
+
const findRepositoryRoot = (dir = process.cwd()) => {
|
|
11
|
+
const gitPath = path.join(dir, ".git");
|
|
12
|
+
return fileExists(gitPath)
|
|
13
|
+
.mapErr(() => new Error("Could not find repository root. Make sure to have the repo initialized."))
|
|
14
|
+
.map(() => dir);
|
|
15
|
+
};
|
|
16
|
+
const resolveWorkspacePattern = (repoRoot, pattern) => ResultAsync.fromPromise(
|
|
17
|
+
// For now it is not possible to use the fs.glob function (from node:fs/promises)
|
|
18
|
+
// because it is not possible to run it on Node 20.x
|
|
19
|
+
glob.glob(pattern, { cwd: repoRoot }), (cause) => new Error(`Failed to resolve workspace glob: ${pattern}`, {
|
|
20
|
+
cause,
|
|
21
|
+
})).map((subDirectories) =>
|
|
22
|
+
// Create the absolute path to the subdirectory
|
|
23
|
+
subDirectories.map((directory) => path.join(repoRoot, directory)));
|
|
24
|
+
const getWorkspaces = (repoRoot) => readFile(path.join(repoRoot, "pnpm-workspace.yaml"))
|
|
25
|
+
.andThen(parseYaml)
|
|
26
|
+
// Decode the pnpm-workspace.yaml file to a zod schema
|
|
27
|
+
.andThen((obj) =>
|
|
28
|
+
// If no packages are defined, go on with an empty array
|
|
29
|
+
decode(z.object({ packages: z.array(z.string()) }))(obj).orElse(() => okAsync({ packages: [] })))
|
|
30
|
+
.andThen(({ packages }) =>
|
|
31
|
+
// For every package pattern in the pnpm-workspace.yaml file, get the list of subdirectories
|
|
32
|
+
ResultAsync.combine(packages.map((pattern) => resolveWorkspacePattern(repoRoot, pattern)))
|
|
33
|
+
.map((workspacesList) => workspacesList.flat())
|
|
34
|
+
.andThen((workspaceFolders) => {
|
|
35
|
+
// For every subdirectory, read the package.json file and decode it to a zod schema
|
|
36
|
+
const workspaceResults = workspaceFolders.map((nodeWorkspaceDirectory) => readFileAndDecode(path.join(nodeWorkspaceDirectory, "package.json"), packageJsonSchema).map(({ name }) =>
|
|
37
|
+
// Create the workspace object using the package.json name and the nodeWorkspaceDirectory
|
|
38
|
+
workspaceSchema.parse({ name, path: nodeWorkspaceDirectory })));
|
|
39
|
+
// Execute and combine the results
|
|
40
|
+
return ResultAsync.combine(workspaceResults);
|
|
41
|
+
}));
|
|
42
|
+
export const makeRepositoryReader = () => ({
|
|
43
|
+
fileExists,
|
|
44
|
+
findRepositoryRoot,
|
|
45
|
+
getWorkspaces,
|
|
46
|
+
readFile,
|
|
47
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|