@pagopa/dx-cli 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/index.js +381 -69
- package/package.json +9 -4
package/bin/index.js
CHANGED
|
@@ -2,22 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import "core-js/actual/set/index.js";
|
|
5
|
-
import { configure, getConsoleSink, getLogger as
|
|
6
|
-
|
|
7
|
-
// src/adapters/codemods/example.ts
|
|
8
|
-
import { okAsync } from "neverthrow";
|
|
9
|
-
var apply = () => {
|
|
10
|
-
console.log("Hello from example codemod!");
|
|
11
|
-
return okAsync(void 0);
|
|
12
|
-
};
|
|
13
|
-
var example_default = {
|
|
14
|
-
apply,
|
|
15
|
-
description: "An example codemod that does nothing",
|
|
16
|
-
id: "example"
|
|
17
|
-
};
|
|
5
|
+
import { configure, getConsoleSink, getLogger as getLogger9 } from "@logtape/logtape";
|
|
18
6
|
|
|
19
7
|
// src/adapters/codemods/registry.ts
|
|
20
|
-
import { okAsync
|
|
8
|
+
import { okAsync } from "neverthrow";
|
|
21
9
|
var LocalCodemodRegistry = class {
|
|
22
10
|
#m;
|
|
23
11
|
constructor() {
|
|
@@ -27,22 +15,296 @@ var LocalCodemodRegistry = class {
|
|
|
27
15
|
this.#m.set(codemod.id, codemod);
|
|
28
16
|
}
|
|
29
17
|
getAll() {
|
|
30
|
-
return
|
|
18
|
+
return okAsync(Array.from(this.#m.values()));
|
|
31
19
|
}
|
|
32
20
|
getById(id) {
|
|
33
|
-
return
|
|
21
|
+
return okAsync(this.#m.get(id));
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// src/adapters/codemods/update-code-review.ts
|
|
26
|
+
import { getLogger as getLogger2 } from "@logtape/logtape";
|
|
27
|
+
import { replaceInFile } from "replace-in-file";
|
|
28
|
+
|
|
29
|
+
// src/adapters/codemods/git.ts
|
|
30
|
+
import { getLogger } from "@logtape/logtape";
|
|
31
|
+
import { Octokit } from "octokit";
|
|
32
|
+
var getLatestCommitSha = async (owner, repo, ref = "main") => {
|
|
33
|
+
const octokit = new Octokit();
|
|
34
|
+
const response = await octokit.rest.repos.getCommit({
|
|
35
|
+
owner,
|
|
36
|
+
ref,
|
|
37
|
+
repo
|
|
38
|
+
});
|
|
39
|
+
return response.data.sha;
|
|
40
|
+
};
|
|
41
|
+
var getLatestCommitShaOrRef = async (owner, repo, ref = "main") => {
|
|
42
|
+
const logger2 = getLogger(["dx-cli", "codemod"]);
|
|
43
|
+
return getLatestCommitSha(owner, repo, ref).catch(() => {
|
|
44
|
+
logger2.warn(
|
|
45
|
+
"Failed to fetch the latest commit from {owner}/{repo}, fallback to {fallback}",
|
|
46
|
+
{ fallback: ref, owner, repo }
|
|
47
|
+
);
|
|
48
|
+
return ref;
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// src/adapters/codemods/update-code-review.ts
|
|
53
|
+
var updateJSCodeReview = async (sha) => {
|
|
54
|
+
const logger2 = getLogger2(["dx-cli", "codemod"]);
|
|
55
|
+
const results = await replaceInFile({
|
|
56
|
+
allowEmptyPaths: true,
|
|
57
|
+
files: [".github/workflows/*.yaml"],
|
|
58
|
+
from: [/pagopa\/dx\/.github\/workflows\/js_code_review.yaml@(\S+)/g],
|
|
59
|
+
to: [`pagopa/dx/.github/workflows/js_code_review.yaml@${sha}`]
|
|
60
|
+
});
|
|
61
|
+
const updated = results.filter((r) => r.hasChanged).map((r) => r.file);
|
|
62
|
+
updated.forEach((filename) => {
|
|
63
|
+
logger2.info("Workflow {filename} updated", { filename });
|
|
64
|
+
});
|
|
65
|
+
return updated;
|
|
66
|
+
};
|
|
67
|
+
var updateCodeReview = {
|
|
68
|
+
apply: async () => {
|
|
69
|
+
const logger2 = getLogger2(["dx-cli", "codemod"]);
|
|
70
|
+
const owner = "pagopa";
|
|
71
|
+
const repo = "dx";
|
|
72
|
+
return getLatestCommitSha(owner, repo).then(async (sha) => {
|
|
73
|
+
await updateJSCodeReview(sha);
|
|
74
|
+
}).catch(() => {
|
|
75
|
+
logger2.error(
|
|
76
|
+
"Failed to fetch the latest commit sha from {owner}/{repo}",
|
|
77
|
+
{
|
|
78
|
+
owner,
|
|
79
|
+
repo
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
description: "Update js_code_review workflow to its latest version",
|
|
85
|
+
id: "update-code-review"
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// src/adapters/codemods/use-azure-appsvc.ts
|
|
89
|
+
import { getLogger as getLogger3 } from "@logtape/logtape";
|
|
90
|
+
import { replaceInFile as replaceInFile2 } from "replace-in-file";
|
|
91
|
+
import YAML from "yaml";
|
|
92
|
+
var isChildOf = (path2, key) => {
|
|
93
|
+
const ancestor = path2.at(-1);
|
|
94
|
+
return YAML.isPair(ancestor) && YAML.isScalar(ancestor.key) && typeof ancestor.key.value === "string" && ancestor.key.value === key;
|
|
95
|
+
};
|
|
96
|
+
var migrateWorkflow = (sha) => (workflow, filename) => {
|
|
97
|
+
const logger2 = getLogger3(["dx-cli", "codemod"]);
|
|
98
|
+
logger2.debug("Processing {filename} file", { filename });
|
|
99
|
+
const document = YAML.parseDocument(workflow);
|
|
100
|
+
let updated = false;
|
|
101
|
+
YAML.visit(document, {
|
|
102
|
+
Map(_, map, path2) {
|
|
103
|
+
if (isChildOf(path2, "jobs") || isChildOf(path2, "with")) {
|
|
104
|
+
return void 0;
|
|
105
|
+
}
|
|
106
|
+
if (map.has("jobs")) {
|
|
107
|
+
return void 0;
|
|
108
|
+
}
|
|
109
|
+
if (map.has("uses")) {
|
|
110
|
+
const uses = map.get("uses");
|
|
111
|
+
if (typeof uses === "string" && uses.match(
|
|
112
|
+
/^pagopa\/dx\/.github\/workflows\/(web|function)_app_deploy/
|
|
113
|
+
)) {
|
|
114
|
+
logger2.debug("Adding disable_auto_staging_deploy");
|
|
115
|
+
map.addIn(["with", "disable_auto_staging_deploy"], true);
|
|
116
|
+
updated = true;
|
|
117
|
+
return void 0;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return YAML.visit.SKIP;
|
|
121
|
+
},
|
|
122
|
+
Pair(_, pair) {
|
|
123
|
+
if (YAML.isScalar(pair.key)) {
|
|
124
|
+
if (pair.key.value === "function_app_name") {
|
|
125
|
+
updated = true;
|
|
126
|
+
logger2.debug("Updating function_app_name to web_app_name");
|
|
127
|
+
return new YAML.Pair("web_app_name", pair.value);
|
|
128
|
+
}
|
|
129
|
+
if (pair.key.value === "use_staging_slot") {
|
|
130
|
+
updated = true;
|
|
131
|
+
logger2.debug("Removing use_staging_slot");
|
|
132
|
+
return YAML.visit.REMOVE;
|
|
133
|
+
}
|
|
134
|
+
if (pair.key.value === "uses") {
|
|
135
|
+
updated = true;
|
|
136
|
+
logger2.debug("Updating uses value");
|
|
137
|
+
return new YAML.Pair(
|
|
138
|
+
"uses",
|
|
139
|
+
`pagopa/dx/.github/workflows/release-azure-appsvc.yaml@${sha}`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
if (updated) {
|
|
146
|
+
logger2.info("Workflow {filename} updated", {
|
|
147
|
+
filename
|
|
148
|
+
});
|
|
149
|
+
return YAML.stringify(document);
|
|
34
150
|
}
|
|
151
|
+
logger2.debug("No changes applied to {filename}", { filename });
|
|
152
|
+
return workflow;
|
|
153
|
+
};
|
|
154
|
+
var useAzureAppsvc = {
|
|
155
|
+
apply: async () => {
|
|
156
|
+
const sha = await getLatestCommitShaOrRef("pagopa", "dx");
|
|
157
|
+
await replaceInFile2({
|
|
158
|
+
allowEmptyPaths: true,
|
|
159
|
+
files: [".github/workflows/*.yaml"],
|
|
160
|
+
processor: migrateWorkflow(sha)
|
|
161
|
+
});
|
|
162
|
+
},
|
|
163
|
+
description: "Refactor legacy deploy workflows to use release-azure-appsvc",
|
|
164
|
+
id: "use-azure-appsvc"
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// src/adapters/codemods/use-pnpm.ts
|
|
168
|
+
import { getLogger as getLogger4 } from "@logtape/logtape";
|
|
169
|
+
import { $ } from "execa";
|
|
170
|
+
import * as fs from "fs/promises";
|
|
171
|
+
import { replaceInFile as replaceInFile3 } from "replace-in-file";
|
|
172
|
+
import YAML2 from "yaml";
|
|
173
|
+
async function preparePackageJsonForPnpm() {
|
|
174
|
+
const packageJson2 = await fs.readFile("package.json", "utf-8");
|
|
175
|
+
const manifest = JSON.parse(packageJson2);
|
|
176
|
+
let workspaces = [];
|
|
177
|
+
if (Object.hasOwn(manifest, "packageManager")) {
|
|
178
|
+
delete manifest.packageManager;
|
|
179
|
+
}
|
|
180
|
+
if (Object.hasOwn(manifest, "workspaces")) {
|
|
181
|
+
if (Array.isArray(manifest.workspaces)) {
|
|
182
|
+
workspaces = manifest.workspaces;
|
|
183
|
+
}
|
|
184
|
+
delete manifest.workspaces;
|
|
185
|
+
}
|
|
186
|
+
await fs.writeFile("package.json", JSON.stringify(manifest, null, 2));
|
|
187
|
+
return workspaces;
|
|
188
|
+
}
|
|
189
|
+
async function removeFiles(...files) {
|
|
190
|
+
await Promise.all(
|
|
191
|
+
files.map(
|
|
192
|
+
(file) => (
|
|
193
|
+
// Remove the file if it exists, fail silently if it doesn't.
|
|
194
|
+
fs.rm(file, { force: true, recursive: true }).catch(() => void 0)
|
|
195
|
+
)
|
|
196
|
+
)
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
async function replaceYarnOccurrences() {
|
|
200
|
+
const logger2 = getLogger4(["dx-cli", "codemod"]);
|
|
201
|
+
logger2.info("Replacing yarn occurrences in files...");
|
|
202
|
+
const results = await replaceInFile3({
|
|
203
|
+
allowEmptyPaths: true,
|
|
204
|
+
files: ["**/*.json", "**/*.md", "**/Dockerfile", "**/docker-compose.yml"],
|
|
205
|
+
from: [
|
|
206
|
+
"https://yarnpkg.com/",
|
|
207
|
+
"https://classic.yarnpkg.com/",
|
|
208
|
+
/yarn workspace (\S+)/g,
|
|
209
|
+
/yarn workspace/g,
|
|
210
|
+
/yarn install --immutable/g,
|
|
211
|
+
/yarn -q dlx/g,
|
|
212
|
+
/Yarn/gi
|
|
213
|
+
],
|
|
214
|
+
ignore: ["**/node_modules/**", "**/dist/**", "**/build/**"],
|
|
215
|
+
to: [
|
|
216
|
+
"https://pnpm.io/",
|
|
217
|
+
"https://pnpm.io/",
|
|
218
|
+
"pnpm --filter $1",
|
|
219
|
+
"pnpm --filter <package-selector>",
|
|
220
|
+
"pnpm install --frozen-lockfile",
|
|
221
|
+
"pnpm dlx",
|
|
222
|
+
"pnpm"
|
|
223
|
+
]
|
|
224
|
+
});
|
|
225
|
+
const count = results.reduce(
|
|
226
|
+
(acc, file) => file.hasChanged ? acc + 1 : acc,
|
|
227
|
+
0
|
|
228
|
+
);
|
|
229
|
+
logger2.info("Replaced yarn occurrences in {count} files", { count });
|
|
230
|
+
}
|
|
231
|
+
async function updateDXWorkflows() {
|
|
232
|
+
const logger2 = getLogger4(["dx-cli", "codemod"]);
|
|
233
|
+
logger2.info("Updating Github Workflows workflows...");
|
|
234
|
+
const sha = await getLatestCommitShaOrRef("pagopa", "dx");
|
|
235
|
+
const ignore = await updateJSCodeReview(sha);
|
|
236
|
+
await replaceInFile3({
|
|
237
|
+
allowEmptyPaths: true,
|
|
238
|
+
files: [".github/workflows/*.yaml"],
|
|
239
|
+
ignore,
|
|
240
|
+
processor: migrateWorkflow(sha)
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
async function writePnpmWorkspaceFile(workspaces) {
|
|
244
|
+
const pnpmWorkspace = {
|
|
245
|
+
packages: workspaces.length > 0 ? workspaces : ["apps/*", "packages/*"]
|
|
246
|
+
};
|
|
247
|
+
const yamlContent = YAML2.stringify(pnpmWorkspace);
|
|
248
|
+
await fs.writeFile("pnpm-workspace.yaml", yamlContent, "utf-8");
|
|
249
|
+
}
|
|
250
|
+
var apply = async (info) => {
|
|
251
|
+
if (info.packageManager === "pnpm") {
|
|
252
|
+
throw new Error("Project is already using pnpm");
|
|
253
|
+
}
|
|
254
|
+
const logger2 = getLogger4(["dx-cli", "codemod"]);
|
|
255
|
+
logger2.info("Remove unused fields from {file}", {
|
|
256
|
+
file: "package.json"
|
|
257
|
+
});
|
|
258
|
+
const workspaces = await preparePackageJsonForPnpm();
|
|
259
|
+
logger2.info("Create {file}", {
|
|
260
|
+
file: "pnpm-workspace.yaml"
|
|
261
|
+
});
|
|
262
|
+
await writePnpmWorkspaceFile(workspaces);
|
|
263
|
+
logger2.info("Remove node_modules and yarn files");
|
|
264
|
+
await removeFiles(
|
|
265
|
+
".yarnrc",
|
|
266
|
+
".yarnrc.yml",
|
|
267
|
+
"yarn.config.cjs",
|
|
268
|
+
".yarn",
|
|
269
|
+
".pnp.cjs",
|
|
270
|
+
".pnp.loader.cjs",
|
|
271
|
+
"node_modules"
|
|
272
|
+
);
|
|
273
|
+
logger2.info("Importing {source} to {target}", {
|
|
274
|
+
source: "yarn.lock",
|
|
275
|
+
target: "pnpm-lock.yaml"
|
|
276
|
+
});
|
|
277
|
+
try {
|
|
278
|
+
await fs.access("yarn.lock");
|
|
279
|
+
await $`corepack pnpm@latest import yarn.lock`;
|
|
280
|
+
await removeFiles("yarn.lock");
|
|
281
|
+
} catch {
|
|
282
|
+
logger2.info("No yarn.lock file found, skipping import.");
|
|
283
|
+
}
|
|
284
|
+
await $`corepack pnpm@latest add --config pnpm-plugin-pagopa`;
|
|
285
|
+
await replaceYarnOccurrences();
|
|
286
|
+
await updateDXWorkflows();
|
|
287
|
+
logger2.info("Setting pnpm as the package manager...");
|
|
288
|
+
await $`corepack use pnpm@latest`;
|
|
289
|
+
};
|
|
290
|
+
var use_pnpm_default = {
|
|
291
|
+
apply,
|
|
292
|
+
description: "A codemod that switches the project to use pnpm",
|
|
293
|
+
id: "use-pnpm"
|
|
35
294
|
};
|
|
36
295
|
|
|
37
296
|
// src/adapters/codemods/index.ts
|
|
38
297
|
var registry = new LocalCodemodRegistry();
|
|
39
|
-
registry.add(
|
|
298
|
+
registry.add(use_pnpm_default);
|
|
299
|
+
registry.add(useAzureAppsvc);
|
|
300
|
+
registry.add(updateCodeReview);
|
|
40
301
|
var codemods_default = registry;
|
|
41
302
|
|
|
42
303
|
// src/adapters/commander/index.ts
|
|
43
|
-
import { Command as
|
|
304
|
+
import { Command as Command6 } from "commander";
|
|
44
305
|
|
|
45
306
|
// src/adapters/commander/commands/codemod.ts
|
|
307
|
+
import { getLogger as getLogger5 } from "@logtape/logtape";
|
|
46
308
|
import { Command } from "commander";
|
|
47
309
|
var makeCodemodCommand = ({
|
|
48
310
|
applyCodemodById: applyCodemodById2,
|
|
@@ -50,16 +312,14 @@ var makeCodemodCommand = ({
|
|
|
50
312
|
}) => new Command("codemod").description("Manage and apply migration scripts to the repository").addCommand(
|
|
51
313
|
new Command("list").description("List available migration scripts").action(async function() {
|
|
52
314
|
await listCodemods2().andTee(
|
|
53
|
-
(codemods) => (
|
|
54
|
-
// eslint-disable-next-line no-console
|
|
55
|
-
console.table(codemods, ["id", "description"])
|
|
56
|
-
)
|
|
315
|
+
(codemods) => console.table(codemods, ["id", "description"])
|
|
57
316
|
).orTee((error) => this.error(error.message));
|
|
58
317
|
})
|
|
59
318
|
).addCommand(
|
|
60
319
|
new Command("apply").argument("<id>", "The id of the codemod to apply").description("Apply migration scripts to the repository").action(async function(id) {
|
|
320
|
+
const logger2 = getLogger5(["dx-cli", "codemod"]);
|
|
61
321
|
await applyCodemodById2(id).andTee(() => {
|
|
62
|
-
|
|
322
|
+
logger2.info("Codemod applied \u2705");
|
|
63
323
|
}).orTee((error) => this.error(error.message));
|
|
64
324
|
})
|
|
65
325
|
);
|
|
@@ -140,7 +400,7 @@ var checkMonorepoScripts = async (dependencies, config2) => {
|
|
|
140
400
|
|
|
141
401
|
// src/domain/repository.ts
|
|
142
402
|
import { ok as ok2 } from "neverthrow";
|
|
143
|
-
import
|
|
403
|
+
import fs2 from "path";
|
|
144
404
|
import coerce from "semver/functions/coerce.js";
|
|
145
405
|
import semverGte from "semver/functions/gte.js";
|
|
146
406
|
var isVersionValid = (version, minVersion) => {
|
|
@@ -155,7 +415,7 @@ var checkPreCommitConfig = async (dependencies, config2) => {
|
|
|
155
415
|
const { repositoryReader: repositoryReader2 } = dependencies;
|
|
156
416
|
const checkName = "Pre-commit Configuration";
|
|
157
417
|
const preCommitResult = await repositoryReader2.fileExists(
|
|
158
|
-
|
|
418
|
+
fs2.join(config2.repository.root, ".pre-commit-config.yaml")
|
|
159
419
|
);
|
|
160
420
|
if (preCommitResult.isOk() && preCommitResult.value) {
|
|
161
421
|
return ok2({
|
|
@@ -176,7 +436,7 @@ var checkTurboConfig = async (dependencies, config2) => {
|
|
|
176
436
|
const checkName = "Turbo Configuration";
|
|
177
437
|
const repoRoot2 = config2.repository.root;
|
|
178
438
|
const turboResult = await repositoryReader2.fileExists(
|
|
179
|
-
|
|
439
|
+
fs2.join(repoRoot2, "turbo.json")
|
|
180
440
|
);
|
|
181
441
|
if (turboResult.isErr()) {
|
|
182
442
|
return ok2({
|
|
@@ -315,7 +575,7 @@ var makeDoctorCommand = (dependencies, config2) => new Command2().name("doctor")
|
|
|
315
575
|
import { Command as Command3 } from "commander";
|
|
316
576
|
|
|
317
577
|
// src/domain/info.ts
|
|
318
|
-
import { getLogger } from "@logtape/logtape";
|
|
578
|
+
import { getLogger as getLogger6 } from "@logtape/logtape";
|
|
319
579
|
import { join } from "path";
|
|
320
580
|
var detectFromLockFile = async (dependencies, config2) => {
|
|
321
581
|
const { repositoryReader: repositoryReader2 } = dependencies;
|
|
@@ -341,7 +601,7 @@ var detectPackageManager = async (dependencies, config2) => {
|
|
|
341
601
|
var detectNodeVersion = async ({ repositoryReader: repositoryReader2 }, nodeVersionFilePath) => await repositoryReader2.readFile(nodeVersionFilePath).map((nodeVersion) => nodeVersion.trim()).unwrapOr(void 0);
|
|
342
602
|
var detectTerraformVersion = async ({ repositoryReader: repositoryReader2 }, terraformVersionFilePath) => await repositoryReader2.readFile(terraformVersionFilePath).map((tfVersion) => tfVersion.trim()).unwrapOr(void 0);
|
|
343
603
|
var detectTurboVersion = ({ devDependencies }) => devDependencies.get("turbo")?.trim();
|
|
344
|
-
var getInfo =
|
|
604
|
+
var getInfo = (dependencies, config2) => async () => ({
|
|
345
605
|
node: await detectNodeVersion(
|
|
346
606
|
{ repositoryReader: dependencies.repositoryReader },
|
|
347
607
|
`${config2.repository.root}/.node-version`
|
|
@@ -354,36 +614,73 @@ var getInfo = async (dependencies, config2) => ({
|
|
|
354
614
|
turbo: detectTurboVersion(dependencies.packageJson)
|
|
355
615
|
});
|
|
356
616
|
var printInfo = (result) => {
|
|
357
|
-
const logger2 =
|
|
617
|
+
const logger2 = getLogger6("json");
|
|
358
618
|
logger2.info(JSON.stringify(result));
|
|
359
619
|
};
|
|
360
620
|
|
|
361
621
|
// src/adapters/commander/commands/info.ts
|
|
362
622
|
var makeInfoCommand = (dependencies, config2) => new Command3().name("info").description("Display information about the project").action(async () => {
|
|
363
|
-
const result = await getInfo(dependencies, config2);
|
|
623
|
+
const result = await getInfo(dependencies, config2)();
|
|
364
624
|
printInfo(result);
|
|
365
625
|
});
|
|
366
626
|
|
|
367
|
-
// src/adapters/commander/commands/
|
|
627
|
+
// src/adapters/commander/commands/init.ts
|
|
628
|
+
import scaffoldMonorepo from "@pagopa/monorepo-generator";
|
|
368
629
|
import { Command as Command4 } from "commander";
|
|
630
|
+
import { Result, ResultAsync as ResultAsync4 } from "neverthrow";
|
|
631
|
+
import nodePlop from "node-plop";
|
|
632
|
+
var initPlop = () => ResultAsync4.fromPromise(
|
|
633
|
+
nodePlop(),
|
|
634
|
+
() => new Error("Failed to initialize plop")
|
|
635
|
+
);
|
|
636
|
+
var getGenerator = (plopAPI) => Result.fromThrowable(
|
|
637
|
+
plopAPI.getGenerator,
|
|
638
|
+
() => new Error("Generator not found")
|
|
639
|
+
);
|
|
640
|
+
var runGenerator = (generator) => ResultAsync4.fromPromise(
|
|
641
|
+
generator.runPrompts(),
|
|
642
|
+
() => new Error("Failed to run the generator prompts")
|
|
643
|
+
).andThen(
|
|
644
|
+
(answers) => ResultAsync4.fromPromise(
|
|
645
|
+
generator.runActions(answers),
|
|
646
|
+
() => new Error("Failed to run the generator actions")
|
|
647
|
+
)
|
|
648
|
+
);
|
|
649
|
+
var makeInitCommand = () => new Command4().name("init").description(
|
|
650
|
+
"Command to initialize resources (like projects, subscriptions, ...)"
|
|
651
|
+
).addCommand(
|
|
652
|
+
new Command4("project").description("Initialize a new monorepo project").action(async function() {
|
|
653
|
+
await initPlop().andTee(scaffoldMonorepo).andThen((plop) => getGenerator(plop)("monorepo")).andThen(runGenerator).andTee(() => {
|
|
654
|
+
console.log("Monorepo initialized successfully \u2705");
|
|
655
|
+
}).orTee((err2) => {
|
|
656
|
+
this.error(err2.message);
|
|
657
|
+
});
|
|
658
|
+
})
|
|
659
|
+
);
|
|
660
|
+
|
|
661
|
+
// src/adapters/commander/commands/version.ts
|
|
662
|
+
import { Command as Command5 } from "commander";
|
|
369
663
|
|
|
370
664
|
// src/domain/version.ts
|
|
371
|
-
import { getLogger as
|
|
665
|
+
import { getLogger as getLogger7 } from "@logtape/logtape";
|
|
372
666
|
function printVersion() {
|
|
373
|
-
const logger2 =
|
|
374
|
-
logger2.info(`dx CLI version: ${"0.
|
|
667
|
+
const logger2 = getLogger7(["dx-cli", "version"]);
|
|
668
|
+
logger2.info(`dx CLI version: ${"0.7.0"}`);
|
|
375
669
|
}
|
|
376
670
|
|
|
377
671
|
// src/adapters/commander/commands/version.ts
|
|
378
|
-
var makeVersionCommand = () => new
|
|
672
|
+
var makeVersionCommand = () => new Command5().name("version").alias("v").action(() => printVersion());
|
|
379
673
|
|
|
380
674
|
// src/adapters/commander/index.ts
|
|
381
|
-
var makeCli = (deps2, config2) => {
|
|
382
|
-
const program2 = new
|
|
383
|
-
program2.name("dx").description("The CLI for DX-Platform").version("0.
|
|
675
|
+
var makeCli = (deps2, config2, cliDeps) => {
|
|
676
|
+
const program2 = new Command6();
|
|
677
|
+
program2.name("dx").description("The CLI for DX-Platform").version("0.7.0");
|
|
384
678
|
program2.addCommand(makeDoctorCommand(deps2, config2));
|
|
385
679
|
if (process.env.ENABLE_CODEMODS) {
|
|
386
|
-
program2.addCommand(makeCodemodCommand(
|
|
680
|
+
program2.addCommand(makeCodemodCommand(cliDeps));
|
|
681
|
+
}
|
|
682
|
+
if (process.env.ENABLE_INIT_COMMAND) {
|
|
683
|
+
program2.addCommand(makeInitCommand());
|
|
387
684
|
}
|
|
388
685
|
program2.addCommand(makeVersionCommand());
|
|
389
686
|
program2.addCommand(makeInfoCommand(deps2, config2));
|
|
@@ -391,9 +688,9 @@ var makeCli = (deps2, config2) => {
|
|
|
391
688
|
};
|
|
392
689
|
|
|
393
690
|
// src/adapters/logtape/validation-reporter.ts
|
|
394
|
-
import { getLogger as
|
|
691
|
+
import { getLogger as getLogger8 } from "@logtape/logtape";
|
|
395
692
|
var makeValidationReporter = () => {
|
|
396
|
-
const logger2 =
|
|
693
|
+
const logger2 = getLogger8(["dx-cli", "validation"]);
|
|
397
694
|
return {
|
|
398
695
|
reportCheckResult(result) {
|
|
399
696
|
if (result.isValid) {
|
|
@@ -410,31 +707,31 @@ import { join as join2 } from "path";
|
|
|
410
707
|
import * as process3 from "process";
|
|
411
708
|
|
|
412
709
|
// src/adapters/node/fs/file-reader.ts
|
|
413
|
-
import { ResultAsync as
|
|
414
|
-
import
|
|
710
|
+
import { ResultAsync as ResultAsync6 } from "neverthrow";
|
|
711
|
+
import fs3 from "fs/promises";
|
|
415
712
|
|
|
416
713
|
// src/adapters/zod/index.ts
|
|
417
|
-
import { ResultAsync as
|
|
418
|
-
var decode = (schema) =>
|
|
714
|
+
import { ResultAsync as ResultAsync5 } from "neverthrow";
|
|
715
|
+
var decode = (schema) => ResultAsync5.fromThrowable(
|
|
419
716
|
schema.parseAsync,
|
|
420
717
|
(cause) => new Error("File content is not valid for the given schema", { cause })
|
|
421
718
|
);
|
|
422
719
|
|
|
423
720
|
// src/adapters/node/json/index.ts
|
|
424
|
-
import { Result } from "neverthrow";
|
|
425
|
-
var parseJson =
|
|
721
|
+
import { Result as Result2 } from "neverthrow";
|
|
722
|
+
var parseJson = Result2.fromThrowable(
|
|
426
723
|
JSON.parse,
|
|
427
724
|
(cause) => new Error("Failed to parse JSON", { cause })
|
|
428
725
|
);
|
|
429
726
|
|
|
430
727
|
// src/adapters/node/fs/file-reader.ts
|
|
431
|
-
var
|
|
432
|
-
|
|
728
|
+
var readFile2 = (filePath) => ResultAsync6.fromPromise(
|
|
729
|
+
fs3.readFile(filePath, "utf-8"),
|
|
433
730
|
(cause) => new Error(`Failed to read file: ${filePath}`, { cause })
|
|
434
731
|
);
|
|
435
|
-
var readFileAndDecode = (filePath, schema) =>
|
|
436
|
-
var fileExists = (path2) =>
|
|
437
|
-
|
|
732
|
+
var readFileAndDecode = (filePath, schema) => readFile2(filePath).andThen(parseJson).andThen(decode(schema));
|
|
733
|
+
var fileExists = (path2) => ResultAsync6.fromPromise(
|
|
734
|
+
fs3.stat(path2),
|
|
438
735
|
() => new Error(`${path2} not found.`)
|
|
439
736
|
).map(() => true);
|
|
440
737
|
|
|
@@ -464,14 +761,14 @@ var makePackageJsonReader = () => ({
|
|
|
464
761
|
|
|
465
762
|
// src/adapters/node/repository.ts
|
|
466
763
|
import * as glob from "glob";
|
|
467
|
-
import { okAsync as
|
|
764
|
+
import { okAsync as okAsync2, ResultAsync as ResultAsync7 } from "neverthrow";
|
|
468
765
|
import * as path from "path";
|
|
469
766
|
import { z as z3 } from "zod/v4";
|
|
470
767
|
|
|
471
768
|
// src/adapters/yaml/index.ts
|
|
472
|
-
import { Result as
|
|
769
|
+
import { Result as Result3 } from "neverthrow";
|
|
473
770
|
import yaml from "yaml";
|
|
474
|
-
var parseYaml =
|
|
771
|
+
var parseYaml = Result3.fromThrowable(
|
|
475
772
|
(content) => yaml.parse(content),
|
|
476
773
|
() => new Error("Failed to parse YAML")
|
|
477
774
|
);
|
|
@@ -485,7 +782,7 @@ var findRepositoryRoot = (dir = process.cwd()) => {
|
|
|
485
782
|
)
|
|
486
783
|
).map(() => dir);
|
|
487
784
|
};
|
|
488
|
-
var resolveWorkspacePattern = (repoRoot2, pattern) =>
|
|
785
|
+
var resolveWorkspacePattern = (repoRoot2, pattern) => ResultAsync7.fromPromise(
|
|
489
786
|
// For now it is not possible to use the fs.glob function (from node:fs/promises)
|
|
490
787
|
// because it is not possible to run it on Node 20.x
|
|
491
788
|
glob.glob(pattern, { cwd: repoRoot2 }),
|
|
@@ -498,17 +795,17 @@ var resolveWorkspacePattern = (repoRoot2, pattern) => ResultAsync6.fromPromise(
|
|
|
498
795
|
subDirectories.map((directory) => path.join(repoRoot2, directory))
|
|
499
796
|
)
|
|
500
797
|
);
|
|
501
|
-
var getWorkspaces = (repoRoot2) =>
|
|
798
|
+
var getWorkspaces = (repoRoot2) => readFile2(path.join(repoRoot2, "pnpm-workspace.yaml")).andThen(parseYaml).andThen(
|
|
502
799
|
(obj) => (
|
|
503
800
|
// If no packages are defined, go on with an empty array
|
|
504
801
|
decode(z3.object({ packages: z3.array(z3.string()) }))(obj).orElse(
|
|
505
|
-
() =>
|
|
802
|
+
() => okAsync2({ packages: [] })
|
|
506
803
|
)
|
|
507
804
|
)
|
|
508
805
|
).andThen(
|
|
509
806
|
({ packages }) => (
|
|
510
807
|
// For every package pattern in the pnpm-workspace.yaml file, get the list of subdirectories
|
|
511
|
-
|
|
808
|
+
ResultAsync7.combine(
|
|
512
809
|
packages.map((pattern) => resolveWorkspacePattern(repoRoot2, pattern))
|
|
513
810
|
).map((workspacesList) => workspacesList.flat()).andThen((workspaceFolders) => {
|
|
514
811
|
const workspaceResults = workspaceFolders.map(
|
|
@@ -522,7 +819,7 @@ var getWorkspaces = (repoRoot2) => readFile(path.join(repoRoot2, "pnpm-workspace
|
|
|
522
819
|
)
|
|
523
820
|
)
|
|
524
821
|
);
|
|
525
|
-
return
|
|
822
|
+
return ResultAsync7.combine(workspaceResults);
|
|
526
823
|
})
|
|
527
824
|
)
|
|
528
825
|
);
|
|
@@ -530,7 +827,7 @@ var makeRepositoryReader = () => ({
|
|
|
530
827
|
fileExists,
|
|
531
828
|
findRepositoryRoot,
|
|
532
829
|
getWorkspaces,
|
|
533
|
-
readFile
|
|
830
|
+
readFile: readFile2
|
|
534
831
|
});
|
|
535
832
|
|
|
536
833
|
// src/config.ts
|
|
@@ -544,10 +841,23 @@ var getConfig = (repositoryRoot2) => ({
|
|
|
544
841
|
});
|
|
545
842
|
|
|
546
843
|
// src/use-cases/apply-codemod.ts
|
|
547
|
-
import { errAsync, okAsync as
|
|
548
|
-
var
|
|
549
|
-
(codemod) => codemod ?
|
|
550
|
-
)
|
|
844
|
+
import { errAsync, okAsync as okAsync3, ResultAsync as ResultAsync8 } from "neverthrow";
|
|
845
|
+
var getCodemodById = (registry2, id) => registry2.getById(id).andThen(
|
|
846
|
+
(codemod) => codemod ? okAsync3(codemod) : errAsync(new Error(`Codemod with id ${id} not found`))
|
|
847
|
+
);
|
|
848
|
+
var safeGetInfo = (getInfo2) => ResultAsync8.fromPromise(
|
|
849
|
+
getInfo2(),
|
|
850
|
+
(error) => new Error("Failed to get info", { cause: error })
|
|
851
|
+
);
|
|
852
|
+
var applyCodemodById = (registry2, getInfo2) => (id) => ResultAsync8.combine([
|
|
853
|
+
safeGetInfo(getInfo2),
|
|
854
|
+
getCodemodById(registry2, id)
|
|
855
|
+
]).andThen(
|
|
856
|
+
([info, codemod]) => ResultAsync8.fromPromise(codemod.apply(info), (error) => {
|
|
857
|
+
const message = error instanceof Error ? `: ${error.message}` : "";
|
|
858
|
+
return new Error("Failed to apply codemod" + message, { cause: error });
|
|
859
|
+
})
|
|
860
|
+
);
|
|
551
861
|
|
|
552
862
|
// src/use-cases/list-codemods.ts
|
|
553
863
|
var listCodemods = (registry2) => () => registry2.getAll();
|
|
@@ -570,7 +880,7 @@ await configure({
|
|
|
570
880
|
}
|
|
571
881
|
}
|
|
572
882
|
});
|
|
573
|
-
var logger =
|
|
883
|
+
var logger = getLogger9(["dx-cli"]);
|
|
574
884
|
var repositoryReader = makeRepositoryReader();
|
|
575
885
|
var packageJsonReader = makePackageJsonReader();
|
|
576
886
|
var validationReporter = makeValidationReporter();
|
|
@@ -589,13 +899,15 @@ if (repoPackageJson.isErr()) {
|
|
|
589
899
|
}
|
|
590
900
|
var packageJson = repoPackageJson.value;
|
|
591
901
|
var deps = {
|
|
592
|
-
applyCodemodById: applyCodemodById(codemods_default),
|
|
593
|
-
listCodemods: listCodemods(codemods_default),
|
|
594
902
|
packageJson,
|
|
595
903
|
packageJsonReader,
|
|
596
904
|
repositoryReader,
|
|
597
905
|
validationReporter
|
|
598
906
|
};
|
|
599
907
|
var config = getConfig(repositoryRoot);
|
|
600
|
-
var
|
|
908
|
+
var useCases = {
|
|
909
|
+
applyCodemodById: applyCodemodById(codemods_default, getInfo(deps, config)),
|
|
910
|
+
listCodemods: listCodemods(codemods_default)
|
|
911
|
+
};
|
|
912
|
+
var program = makeCli(deps, config, useCases);
|
|
601
913
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pagopa/dx-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A CLI useful to manage DX tools.",
|
|
6
6
|
"repository": {
|
|
@@ -22,16 +22,21 @@
|
|
|
22
22
|
"@logtape/logtape": "^1.0.0",
|
|
23
23
|
"commander": "^14.0.0",
|
|
24
24
|
"core-js": "^3.44.0",
|
|
25
|
+
"execa": "^9.6.0",
|
|
25
26
|
"glob": "^11.0.3",
|
|
26
27
|
"neverthrow": "^8.2.0",
|
|
28
|
+
"node-plop": "^0.32.1",
|
|
29
|
+
"octokit": "^5.0.3",
|
|
30
|
+
"replace-in-file": "^8.3.0",
|
|
27
31
|
"semver": "^7.7.2",
|
|
28
32
|
"yaml": "^2.8.0",
|
|
29
|
-
"zod": "^3.25.28"
|
|
33
|
+
"zod": "^3.25.28",
|
|
34
|
+
"@pagopa/monorepo-generator": "^0.6.0"
|
|
30
35
|
},
|
|
31
36
|
"devDependencies": {
|
|
32
37
|
"@tsconfig/node22": "22.0.2",
|
|
33
38
|
"@types/node": "^22.16.2",
|
|
34
|
-
"@types/semver": "^7.7.
|
|
39
|
+
"@types/semver": "^7.7.1",
|
|
35
40
|
"@vitest/coverage-v8": "^3.2.4",
|
|
36
41
|
"eslint": "^9.30.0",
|
|
37
42
|
"memfs": "^4.23.0",
|
|
@@ -40,7 +45,7 @@
|
|
|
40
45
|
"typescript": "~5.8.3",
|
|
41
46
|
"vitest": "^3.2.4",
|
|
42
47
|
"vitest-mock-extended": "^3.1.0",
|
|
43
|
-
"@pagopa/eslint-config": "^5.
|
|
48
|
+
"@pagopa/eslint-config": "^5.1.0"
|
|
44
49
|
},
|
|
45
50
|
"engines": {
|
|
46
51
|
"node": ">=22.0.0"
|