@pagopa/dx-cli 0.14.3 → 0.14.5
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/README.md +3 -0
- package/bin/index.js +144 -61
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -122,10 +122,13 @@ This command will:
|
|
|
122
122
|
|
|
123
123
|
- Check that required tools (e.g., Terraform CLI) are installed
|
|
124
124
|
- Interactively prompt for project metadata (cloud provider, region, environments, cost center, etc.)
|
|
125
|
+
- **Check that the target GitHub repository does not already exist before proceeding**
|
|
125
126
|
- Generate a monorepo structure following PagoPA DevEx guidelines
|
|
126
127
|
- Create a remote GitHub repository using Terraform
|
|
127
128
|
- Push the initial codebase to the newly created repository
|
|
128
129
|
|
|
130
|
+
If the specified GitHub repository already exists, the command will fail early with a clear error message, preventing accidental overwrites.
|
|
131
|
+
|
|
129
132
|
**Example usage:**
|
|
130
133
|
|
|
131
134
|
```bash
|
package/bin/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import "core-js/actual/set/index.js";
|
|
5
5
|
import { configure, getConsoleSink } from "@logtape/logtape";
|
|
6
|
-
import { Octokit as
|
|
6
|
+
import { Octokit as Octokit3 } from "octokit";
|
|
7
7
|
|
|
8
8
|
// src/adapters/codemods/registry.ts
|
|
9
9
|
import { okAsync } from "neverthrow";
|
|
@@ -201,7 +201,8 @@ var useAzureAppsvc = {
|
|
|
201
201
|
// src/adapters/codemods/use-pnpm.ts
|
|
202
202
|
import { getLogger as getLogger4 } from "@logtape/logtape";
|
|
203
203
|
import { $ } from "execa";
|
|
204
|
-
import
|
|
204
|
+
import assert from "assert/strict";
|
|
205
|
+
import fs from "fs/promises";
|
|
205
206
|
import { replaceInFile as replaceInFile3 } from "replace-in-file";
|
|
206
207
|
import semver from "semver";
|
|
207
208
|
import YAML4 from "yaml";
|
|
@@ -262,6 +263,17 @@ async function preparePackageJsonForPnpm() {
|
|
|
262
263
|
await fs.writeFile("package.json", JSON.stringify(manifest, null, 2));
|
|
263
264
|
return workspaces;
|
|
264
265
|
}
|
|
266
|
+
async function writePnpmWorkspaceFile(workspaces, packageExtensions) {
|
|
267
|
+
const pnpmWorkspace = {
|
|
268
|
+
cleanupUnusedCatalogs: true,
|
|
269
|
+
linkWorkspacePackages: true,
|
|
270
|
+
packageExtensions,
|
|
271
|
+
packageImportMethod: "clone-or-copy",
|
|
272
|
+
packages: workspaces.length > 0 ? workspaces : ["apps/*", "packages/*"]
|
|
273
|
+
};
|
|
274
|
+
const yamlContent = YAML4.stringify(pnpmWorkspace);
|
|
275
|
+
await fs.writeFile("pnpm-workspace.yaml", yamlContent, "utf-8");
|
|
276
|
+
}
|
|
265
277
|
async function removeFiles(...files) {
|
|
266
278
|
await Promise.all(
|
|
267
279
|
files.map(
|
|
@@ -285,7 +297,7 @@ async function replacePMOccurrences() {
|
|
|
285
297
|
/\b(yarn workspace|npm -(\b-workspace\b|\bw\b))\b/g,
|
|
286
298
|
/\b(yarn install --immutable|npm ci)\b/g,
|
|
287
299
|
/\b(yarn -q dlx|npx)\b/g,
|
|
288
|
-
|
|
300
|
+
/(^|\s)(Yarn|npm)(?!\S)/gi
|
|
289
301
|
],
|
|
290
302
|
ignore: ["**/node_modules/**", "**/dist/**", "**/build/**"],
|
|
291
303
|
to: [
|
|
@@ -321,33 +333,20 @@ async function updateDXWorkflows() {
|
|
|
321
333
|
processor: migrateWorkflow(sha)
|
|
322
334
|
});
|
|
323
335
|
}
|
|
324
|
-
async
|
|
325
|
-
const pnpmWorkspace = {
|
|
326
|
-
packageExtensions,
|
|
327
|
-
packages: workspaces.length > 0 ? workspaces : ["apps/*", "packages/*"]
|
|
328
|
-
};
|
|
329
|
-
const yamlContent = YAML4.stringify(pnpmWorkspace);
|
|
330
|
-
await fs.writeFile("pnpm-workspace.yaml", yamlContent, "utf-8");
|
|
331
|
-
}
|
|
332
|
-
var apply = async (info) => {
|
|
336
|
+
var usePnpm = async (packageManager, currentNodeVersion) => {
|
|
333
337
|
const minNodeVersion = "20.19.5";
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
process.exit(1);
|
|
340
|
-
}
|
|
341
|
-
if (info.packageManager === "pnpm") {
|
|
342
|
-
throw new Error("Project is already using pnpm");
|
|
343
|
-
}
|
|
344
|
-
const pm = info.packageManager === "yarn" ? new Yarn() : new NPM();
|
|
338
|
+
assert.notEqual(packageManager, "pnpm", "Project is already using pnpm");
|
|
339
|
+
assert.ok(
|
|
340
|
+
semver.gte(currentNodeVersion, minNodeVersion),
|
|
341
|
+
`his codemod requires Node.js >= ${minNodeVersion}. Current version: ${currentNodeVersion}`
|
|
342
|
+
);
|
|
345
343
|
const logger = getLogger4(["dx-cli", "codemod"]);
|
|
344
|
+
const pm = packageManager === "yarn" ? new Yarn() : new NPM();
|
|
346
345
|
const localWorkspaces = await pm.listWorkspaces();
|
|
347
|
-
logger.info("Using the {protocol} protocol for local dependencies", {
|
|
348
|
-
protocol: "workspace:"
|
|
349
|
-
});
|
|
350
346
|
if (localWorkspaces.length > 0) {
|
|
347
|
+
logger.info("Using the {protocol} protocol for local dependencies", {
|
|
348
|
+
protocol: "workspace:"
|
|
349
|
+
});
|
|
351
350
|
await replaceInFile3({
|
|
352
351
|
allowEmptyPaths: true,
|
|
353
352
|
files: ["**/package.json"],
|
|
@@ -359,12 +358,11 @@ var apply = async (info) => {
|
|
|
359
358
|
file: "package.json"
|
|
360
359
|
});
|
|
361
360
|
const workspaces = await preparePackageJsonForPnpm();
|
|
362
|
-
const packageExtensions =
|
|
361
|
+
const packageExtensions = packageManager === "yarn" ? await extractPackageExtensions() : void 0;
|
|
363
362
|
logger.info("Create {file}", {
|
|
364
363
|
file: "pnpm-workspace.yaml"
|
|
365
364
|
});
|
|
366
365
|
await writePnpmWorkspaceFile(workspaces, packageExtensions);
|
|
367
|
-
await $`corepack pnpm@latest add --config pnpm-plugin-pagopa`;
|
|
368
366
|
logger.info("Remove node_modules and yarn files");
|
|
369
367
|
await removeFiles(
|
|
370
368
|
".yarnrc",
|
|
@@ -375,8 +373,8 @@ var apply = async (info) => {
|
|
|
375
373
|
".pnp.loader.cjs",
|
|
376
374
|
"node_modules"
|
|
377
375
|
);
|
|
378
|
-
const
|
|
379
|
-
if (
|
|
376
|
+
const stat = await fs.stat(pm.lockFileName);
|
|
377
|
+
if (stat.isFile()) {
|
|
380
378
|
logger.info("Importing {source} to {target}", {
|
|
381
379
|
source: pm.lockFileName,
|
|
382
380
|
target: "pnpm-lock.yaml"
|
|
@@ -395,6 +393,16 @@ var apply = async (info) => {
|
|
|
395
393
|
logger.info("Setting pnpm as the package manager...");
|
|
396
394
|
await $`corepack use pnpm@latest`;
|
|
397
395
|
};
|
|
396
|
+
var apply = async (info) => {
|
|
397
|
+
const logger = getLogger4(["dx-cli", "codemod"]);
|
|
398
|
+
try {
|
|
399
|
+
await usePnpm(info.packageManager, process.versions.node);
|
|
400
|
+
} catch (error) {
|
|
401
|
+
if (error instanceof Error) {
|
|
402
|
+
logger.error(error.message);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
};
|
|
398
406
|
var use_pnpm_default = {
|
|
399
407
|
apply,
|
|
400
408
|
description: "Migrate the project to use pnpm as the package manager",
|
|
@@ -757,10 +765,38 @@ import loadMonorepoScaffolder, {
|
|
|
757
765
|
import chalk from "chalk";
|
|
758
766
|
import { Command as Command4 } from "commander";
|
|
759
767
|
import { $ as $3 } from "execa";
|
|
760
|
-
import { okAsync as okAsync2, ResultAsync as ResultAsync6 } from "neverthrow";
|
|
768
|
+
import { errAsync, okAsync as okAsync2, ResultAsync as ResultAsync6 } from "neverthrow";
|
|
761
769
|
import * as path from "path";
|
|
762
770
|
import { oraPromise } from "ora";
|
|
763
771
|
|
|
772
|
+
// src/domain/github.ts
|
|
773
|
+
var PullRequest = class {
|
|
774
|
+
constructor(url) {
|
|
775
|
+
this.url = url;
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
var Repository = class {
|
|
779
|
+
constructor(name, owner) {
|
|
780
|
+
this.name = name;
|
|
781
|
+
this.owner = owner;
|
|
782
|
+
}
|
|
783
|
+
get fullName() {
|
|
784
|
+
return `${this.owner}/${this.name}`;
|
|
785
|
+
}
|
|
786
|
+
get ssh() {
|
|
787
|
+
return `git@github.com:${this.owner}/${this.name}.git`;
|
|
788
|
+
}
|
|
789
|
+
get url() {
|
|
790
|
+
return `https://github.com/${this.owner}/${this.name}`;
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
var RepositoryNotFoundError = class extends Error {
|
|
794
|
+
constructor(owner, name) {
|
|
795
|
+
super(`Repository ${owner}/${name} not found`);
|
|
796
|
+
this.name = "RepositoryNotFoundError";
|
|
797
|
+
}
|
|
798
|
+
};
|
|
799
|
+
|
|
764
800
|
// src/adapters/execa/terraform.ts
|
|
765
801
|
import { $ as $2 } from "execa";
|
|
766
802
|
var tf$ = $2({
|
|
@@ -796,18 +832,6 @@ var decode = (schema) => (data) => ResultAsync5.fromPromise(
|
|
|
796
832
|
);
|
|
797
833
|
|
|
798
834
|
// src/adapters/commander/commands/init.ts
|
|
799
|
-
var Repository = class {
|
|
800
|
-
constructor(name, owner) {
|
|
801
|
-
this.name = name;
|
|
802
|
-
this.owner = owner;
|
|
803
|
-
}
|
|
804
|
-
get ssh() {
|
|
805
|
-
return `git@github.com:${this.owner}/${this.name}.git`;
|
|
806
|
-
}
|
|
807
|
-
get url() {
|
|
808
|
-
return `https://github.com/${this.owner}/${this.name}`;
|
|
809
|
-
}
|
|
810
|
-
};
|
|
811
835
|
var withSpinner = (text, successText, failText, promise) => ResultAsync6.fromPromise(
|
|
812
836
|
oraPromise(promise, {
|
|
813
837
|
failText,
|
|
@@ -819,7 +843,20 @@ var withSpinner = (text, successText, failText, promise) => ResultAsync6.fromPro
|
|
|
819
843
|
return new Error(failText, { cause });
|
|
820
844
|
}
|
|
821
845
|
);
|
|
822
|
-
var validateAnswers = (
|
|
846
|
+
var validateAnswers = (githubService) => (answers) => ResultAsync6.fromPromise(
|
|
847
|
+
githubService.getRepository(answers.repoOwner, answers.repoName),
|
|
848
|
+
(error) => error
|
|
849
|
+
).andThen(
|
|
850
|
+
({ fullName }) => errAsync(new Error(`Repository ${fullName} already exists.`))
|
|
851
|
+
).orElse(
|
|
852
|
+
(error) => error instanceof RepositoryNotFoundError ? (
|
|
853
|
+
// If repository is not found, it's safe to proceed
|
|
854
|
+
okAsync2(answers)
|
|
855
|
+
) : (
|
|
856
|
+
// Otherwise, propagate the error
|
|
857
|
+
errAsync(error)
|
|
858
|
+
)
|
|
859
|
+
).map(() => answers);
|
|
823
860
|
var runGeneratorActions = (generator) => (answers) => withSpinner(
|
|
824
861
|
"Creating workspace files...",
|
|
825
862
|
"Workspace files created successfully!",
|
|
@@ -911,8 +948,8 @@ var initializeGitRepository = (repository) => {
|
|
|
911
948
|
pushToOrigin()
|
|
912
949
|
).map(() => ({ branchName, repository }));
|
|
913
950
|
};
|
|
914
|
-
var handleNewGitHubRepository = (
|
|
915
|
-
(localWorkspace) => createPullRequest(
|
|
951
|
+
var handleNewGitHubRepository = (githubService) => (answers) => createRemoteRepository(answers).andThen(initializeGitRepository).andThen(
|
|
952
|
+
(localWorkspace) => createPullRequest(githubService)(localWorkspace).map((pr) => ({
|
|
916
953
|
pr,
|
|
917
954
|
repository: localWorkspace.repository
|
|
918
955
|
}))
|
|
@@ -928,14 +965,14 @@ var makeInitResult = (answers, { pr, repository }) => {
|
|
|
928
965
|
repository
|
|
929
966
|
};
|
|
930
967
|
};
|
|
931
|
-
var createPullRequest = (
|
|
968
|
+
var createPullRequest = (githubService) => ({
|
|
932
969
|
branchName,
|
|
933
970
|
repository
|
|
934
971
|
}) => withSpinner(
|
|
935
972
|
"Creating Pull Request...",
|
|
936
973
|
"Pull Request created successfully!",
|
|
937
974
|
"Failed to create Pull Request.",
|
|
938
|
-
|
|
975
|
+
githubService.createPullRequest({
|
|
939
976
|
base: "main",
|
|
940
977
|
body: "This PR contains the scaffolded monorepo structure.",
|
|
941
978
|
head: branchName,
|
|
@@ -943,9 +980,9 @@ var createPullRequest = (octokit2) => ({
|
|
|
943
980
|
repo: repository.name,
|
|
944
981
|
title: "Scaffold repository"
|
|
945
982
|
})
|
|
946
|
-
).
|
|
983
|
+
).orElse(() => okAsync2(void 0));
|
|
947
984
|
var makeInitCommand = ({
|
|
948
|
-
|
|
985
|
+
gitHubService: gitHubService2
|
|
949
986
|
}) => new Command4().name("init").description(
|
|
950
987
|
"Command to initialize resources (like projects, subscriptions, ...)"
|
|
951
988
|
).addCommand(
|
|
@@ -953,10 +990,10 @@ var makeInitCommand = ({
|
|
|
953
990
|
await checkPreconditions().andThen(initPlop).andTee(loadMonorepoScaffolder).andThen((plop) => getGenerator(plop)(PLOP_MONOREPO_GENERATOR_NAME)).andThen(
|
|
954
991
|
(generator) => (
|
|
955
992
|
// Ask the user the questions defined in the plop generator
|
|
956
|
-
getPrompts(generator).andThen(decode(answersSchema)).andThen(validateAnswers).andThen(runGeneratorActions(generator))
|
|
993
|
+
getPrompts(generator).andThen(decode(answersSchema)).andThen(validateAnswers(gitHubService2)).andThen(runGeneratorActions(generator))
|
|
957
994
|
)
|
|
958
995
|
).andThen(
|
|
959
|
-
(answers) => handleNewGitHubRepository(
|
|
996
|
+
(answers) => handleNewGitHubRepository(gitHubService2)(answers).map(
|
|
960
997
|
(repoPr) => makeInitResult(answers, repoPr)
|
|
961
998
|
)
|
|
962
999
|
).match(displaySummary, exitWithError(this));
|
|
@@ -996,7 +1033,7 @@ var makeSavemoneyCommand = () => new Command5("savemoney").description(
|
|
|
996
1033
|
// src/adapters/commander/index.ts
|
|
997
1034
|
var makeCli = (deps2, config2, cliDeps) => {
|
|
998
1035
|
const program2 = new Command6();
|
|
999
|
-
program2.name("dx").description("The CLI for DX-Platform").version("0.14.
|
|
1036
|
+
program2.name("dx").description("The CLI for DX-Platform").version("0.14.5");
|
|
1000
1037
|
program2.addCommand(makeDoctorCommand(deps2, config2));
|
|
1001
1038
|
program2.addCommand(makeCodemodCommand(cliDeps));
|
|
1002
1039
|
program2.addCommand(makeInitCommand(deps2));
|
|
@@ -1039,11 +1076,11 @@ var parseJson = Result2.fromThrowable(
|
|
|
1039
1076
|
);
|
|
1040
1077
|
|
|
1041
1078
|
// src/adapters/node/fs/file-reader.ts
|
|
1042
|
-
var
|
|
1079
|
+
var readFile = (filePath) => ResultAsync7.fromPromise(
|
|
1043
1080
|
fs3.readFile(filePath, "utf-8"),
|
|
1044
1081
|
(cause) => new Error(`Failed to read file: ${filePath}`, { cause })
|
|
1045
1082
|
);
|
|
1046
|
-
var readFileAndDecode = (filePath, schema) =>
|
|
1083
|
+
var readFileAndDecode = (filePath, schema) => readFile(filePath).andThen(parseJson).andThen(decode(schema));
|
|
1047
1084
|
var fileExists = (path3) => ResultAsync7.fromPromise(
|
|
1048
1085
|
fs3.stat(path3),
|
|
1049
1086
|
() => new Error(`${path3} not found.`)
|
|
@@ -1109,7 +1146,7 @@ var resolveWorkspacePattern = (repoRoot, pattern) => ResultAsync8.fromPromise(
|
|
|
1109
1146
|
subDirectories.map((directory) => path2.join(repoRoot, directory))
|
|
1110
1147
|
)
|
|
1111
1148
|
);
|
|
1112
|
-
var getWorkspaces = (repoRoot) =>
|
|
1149
|
+
var getWorkspaces = (repoRoot) => readFile(path2.join(repoRoot, "pnpm-workspace.yaml")).andThen(parseYaml).andThen(
|
|
1113
1150
|
(obj) => (
|
|
1114
1151
|
// If no packages are defined, go on with an empty array
|
|
1115
1152
|
decode(z3.object({ packages: z3.array(z3.string()) }))(obj).orElse(
|
|
@@ -1141,9 +1178,54 @@ var makeRepositoryReader = () => ({
|
|
|
1141
1178
|
fileExists,
|
|
1142
1179
|
findRepositoryRoot,
|
|
1143
1180
|
getWorkspaces,
|
|
1144
|
-
readFile
|
|
1181
|
+
readFile
|
|
1145
1182
|
});
|
|
1146
1183
|
|
|
1184
|
+
// src/adapters/octokit/index.ts
|
|
1185
|
+
import { RequestError } from "octokit";
|
|
1186
|
+
var OctokitGitHubService = class {
|
|
1187
|
+
#octokit;
|
|
1188
|
+
constructor(octokit2) {
|
|
1189
|
+
this.#octokit = octokit2;
|
|
1190
|
+
}
|
|
1191
|
+
async createPullRequest(params) {
|
|
1192
|
+
try {
|
|
1193
|
+
const { data } = await this.#octokit.rest.pulls.create({
|
|
1194
|
+
base: params.base,
|
|
1195
|
+
body: params.body,
|
|
1196
|
+
head: params.head,
|
|
1197
|
+
owner: params.owner,
|
|
1198
|
+
repo: params.repo,
|
|
1199
|
+
title: params.title
|
|
1200
|
+
});
|
|
1201
|
+
return new PullRequest(data.html_url);
|
|
1202
|
+
} catch (error) {
|
|
1203
|
+
throw new Error(
|
|
1204
|
+
`Failed to create pull request in ${params.owner}/${params.repo}`,
|
|
1205
|
+
{
|
|
1206
|
+
cause: error
|
|
1207
|
+
}
|
|
1208
|
+
);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
async getRepository(owner, name) {
|
|
1212
|
+
try {
|
|
1213
|
+
const { data } = await this.#octokit.rest.repos.get({
|
|
1214
|
+
owner,
|
|
1215
|
+
repo: name
|
|
1216
|
+
});
|
|
1217
|
+
return new Repository(data.name, data.owner.login);
|
|
1218
|
+
} catch (error) {
|
|
1219
|
+
if (error instanceof RequestError && error.status === 404) {
|
|
1220
|
+
throw new RepositoryNotFoundError(owner, name);
|
|
1221
|
+
}
|
|
1222
|
+
throw new Error(`Failed to fetch repository ${owner}/${name}`, {
|
|
1223
|
+
cause: error
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
};
|
|
1228
|
+
|
|
1147
1229
|
// src/config.ts
|
|
1148
1230
|
var getConfig = () => ({
|
|
1149
1231
|
minVersions: {
|
|
@@ -1152,9 +1234,9 @@ var getConfig = () => ({
|
|
|
1152
1234
|
});
|
|
1153
1235
|
|
|
1154
1236
|
// src/use-cases/apply-codemod.ts
|
|
1155
|
-
import { errAsync, okAsync as okAsync4, ResultAsync as ResultAsync9 } from "neverthrow";
|
|
1237
|
+
import { errAsync as errAsync2, okAsync as okAsync4, ResultAsync as ResultAsync9 } from "neverthrow";
|
|
1156
1238
|
var getCodemodById = (registry2, id) => registry2.getById(id).andThen(
|
|
1157
|
-
(codemod) => codemod ? okAsync4(codemod) :
|
|
1239
|
+
(codemod) => codemod ? okAsync4(codemod) : errAsync2(new Error(`Codemod with id ${id} not found`))
|
|
1158
1240
|
);
|
|
1159
1241
|
var safeGetInfo = (getInfo2) => ResultAsync9.fromPromise(
|
|
1160
1242
|
getInfo2(),
|
|
@@ -1195,11 +1277,12 @@ await configure({
|
|
|
1195
1277
|
var repositoryReader = makeRepositoryReader();
|
|
1196
1278
|
var packageJsonReader = makePackageJsonReader();
|
|
1197
1279
|
var validationReporter = makeValidationReporter();
|
|
1198
|
-
var octokit = new
|
|
1280
|
+
var octokit = new Octokit3({
|
|
1199
1281
|
auth: process.env.GITHUB_TOKEN
|
|
1200
1282
|
});
|
|
1283
|
+
var gitHubService = new OctokitGitHubService(octokit);
|
|
1201
1284
|
var deps = {
|
|
1202
|
-
|
|
1285
|
+
gitHubService,
|
|
1203
1286
|
packageJsonReader,
|
|
1204
1287
|
repositoryReader,
|
|
1205
1288
|
validationReporter
|