@cbs-consulting/generator-btp 1.2.9 ā 1.3.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/README.md +240 -61
- package/generators/app/__tests__/index.test.js +91 -4
- package/generators/app/index.js +11 -0
- package/generators/cap/__tests__/index.test.js +155 -7
- package/generators/cap/additions/package.json +1 -1
- package/generators/cap/additions/tsconfig.json +19 -0
- package/generators/cap/dependencies.json +1 -2
- package/generators/cap/index.js +273 -22
- package/generators/cap/templates/_.gitconfig.aliases +1 -0
- package/generators/cap/templates/_.gitignore +1 -0
- package/generators/cap/templates/jest.config.json +25 -0
- package/generators/cap/templates/srv/cat-service.ts +7 -0
- package/generators/cap/templates/test/CatalogService.test.ts +15 -0
- package/generators/devcontainer/__tests__/index.test.js +37 -0
- package/generators/devcontainer/index.js +22 -0
- package/generators/setup-azure-devops/__tests__/index.test.js +267 -0
- package/generators/setup-azure-devops/index.js +233 -0
- package/generators/setup-azure-devops/templates/scripts/setup-azure-devops/README.md +158 -0
- package/generators/setup-azure-devops/templates/scripts/setup-azure-devops/azure-devops.env +113 -0
- package/generators/setup-azure-devops/templates/scripts/setup-azure-devops/setup.sh +437 -0
- package/generators/ui5/index.js +7 -2
- package/package.json +2 -2
- package/utils/jsonFile.js +30 -1
- package/generators/cap/templates/base.tsconfig.json +0 -14
|
@@ -20,6 +20,20 @@ describe("generator-btp:cap", () => {
|
|
|
20
20
|
customerName: "Test Customer",
|
|
21
21
|
dbSchema: "TEST_SCHEMA",
|
|
22
22
|
useAzureDevOps: true,
|
|
23
|
+
azureRepoUrl:
|
|
24
|
+
"https://dev.azure.com/myorg/myproject/_git/test-cap-project",
|
|
25
|
+
useBestPractices: true,
|
|
26
|
+
cdsFeatures: [
|
|
27
|
+
"typescript",
|
|
28
|
+
"mta",
|
|
29
|
+
"xsuaa",
|
|
30
|
+
"hana",
|
|
31
|
+
"sqlite",
|
|
32
|
+
"html5-repo",
|
|
33
|
+
"tiny-sample",
|
|
34
|
+
"http",
|
|
35
|
+
"test",
|
|
36
|
+
],
|
|
23
37
|
});
|
|
24
38
|
}, 120000);
|
|
25
39
|
|
|
@@ -34,6 +48,18 @@ describe("generator-btp:cap", () => {
|
|
|
34
48
|
});
|
|
35
49
|
});
|
|
36
50
|
|
|
51
|
+
const defaultCdsFeatures = [
|
|
52
|
+
"typescript",
|
|
53
|
+
"mta",
|
|
54
|
+
"xsuaa",
|
|
55
|
+
"hana",
|
|
56
|
+
"sqlite",
|
|
57
|
+
"html5-repo",
|
|
58
|
+
"tiny-sample",
|
|
59
|
+
"http",
|
|
60
|
+
"test",
|
|
61
|
+
];
|
|
62
|
+
|
|
37
63
|
describe.each([
|
|
38
64
|
{
|
|
39
65
|
scenario: "with Azure DevOps enabled",
|
|
@@ -41,6 +67,10 @@ describe("generator-btp:cap", () => {
|
|
|
41
67
|
customerName: "Test Customer",
|
|
42
68
|
dbSchema: "TEST_CAP_PROJECT",
|
|
43
69
|
useAzureDevOps: true,
|
|
70
|
+
azureRepoUrl:
|
|
71
|
+
"https://dev.azure.com/myorg/myproject/_git/test-cap-project",
|
|
72
|
+
useBestPractices: true,
|
|
73
|
+
cdsFeatures: defaultCdsFeatures,
|
|
44
74
|
expectedProjectNormalized: "test-cap-project",
|
|
45
75
|
expectedCustomerNormalized: "test-customer",
|
|
46
76
|
expectedDbSchema: "TEST_CAP_PROJECT",
|
|
@@ -51,6 +81,7 @@ describe("generator-btp:cap", () => {
|
|
|
51
81
|
customerName: "My Customer",
|
|
52
82
|
dbSchema: null,
|
|
53
83
|
useAzureDevOps: false,
|
|
84
|
+
cdsFeatures: defaultCdsFeatures,
|
|
54
85
|
expectedProjectNormalized: "no-azure-project",
|
|
55
86
|
expectedCustomerNormalized: "my-customer",
|
|
56
87
|
expectedDbSchema: "NO_AZURE_PROJECT",
|
|
@@ -61,6 +92,7 @@ describe("generator-btp:cap", () => {
|
|
|
61
92
|
customerName: "UPPERCASE CUSTOMER",
|
|
62
93
|
dbSchema: null,
|
|
63
94
|
useAzureDevOps: false,
|
|
95
|
+
cdsFeatures: defaultCdsFeatures,
|
|
64
96
|
expectedProjectNormalized: "uppercase-project",
|
|
65
97
|
expectedCustomerNormalized: "uppercase-customer",
|
|
66
98
|
expectedDbSchema: "UPPERCASE_PROJECT",
|
|
@@ -71,10 +103,25 @@ describe("generator-btp:cap", () => {
|
|
|
71
103
|
customerName: "Multi Space Customer",
|
|
72
104
|
dbSchema: null,
|
|
73
105
|
useAzureDevOps: true,
|
|
106
|
+
azureRepoUrl:
|
|
107
|
+
"https://dev.azure.com/myorg/myproject/_git/multi-space-project",
|
|
108
|
+
useBestPractices: true,
|
|
109
|
+
cdsFeatures: defaultCdsFeatures,
|
|
74
110
|
expectedProjectNormalized: "multi-space-project",
|
|
75
111
|
expectedCustomerNormalized: "multi-space-customer",
|
|
76
112
|
expectedDbSchema: "MULTI_SPACE_PROJECT",
|
|
77
113
|
},
|
|
114
|
+
{
|
|
115
|
+
scenario: "with custom CDS features",
|
|
116
|
+
projectName: "Custom Features Project",
|
|
117
|
+
customerName: "Custom Customer",
|
|
118
|
+
dbSchema: null,
|
|
119
|
+
useAzureDevOps: false,
|
|
120
|
+
cdsFeatures: ["typescript", "hana", "xsuaa"],
|
|
121
|
+
expectedProjectNormalized: "custom-features-project",
|
|
122
|
+
expectedCustomerNormalized: "custom-customer",
|
|
123
|
+
expectedDbSchema: "CUSTOM_FEATURES_PROJECT",
|
|
124
|
+
},
|
|
78
125
|
])(
|
|
79
126
|
"$scenario",
|
|
80
127
|
({
|
|
@@ -82,6 +129,9 @@ describe("generator-btp:cap", () => {
|
|
|
82
129
|
customerName,
|
|
83
130
|
dbSchema,
|
|
84
131
|
useAzureDevOps,
|
|
132
|
+
azureRepoUrl,
|
|
133
|
+
useBestPractices,
|
|
134
|
+
cdsFeatures,
|
|
85
135
|
expectedProjectNormalized,
|
|
86
136
|
expectedCustomerNormalized,
|
|
87
137
|
expectedDbSchema,
|
|
@@ -89,14 +139,25 @@ describe("generator-btp:cap", () => {
|
|
|
89
139
|
let runResult;
|
|
90
140
|
|
|
91
141
|
beforeAll(async () => {
|
|
142
|
+
const prompts = {
|
|
143
|
+
projectName,
|
|
144
|
+
customerName,
|
|
145
|
+
dbSchema,
|
|
146
|
+
useAzureDevOps,
|
|
147
|
+
cdsFeatures,
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// Add Azure DevOps-specific prompts if enabled
|
|
151
|
+
if (useAzureDevOps) {
|
|
152
|
+
Object.assign(prompts, {
|
|
153
|
+
azureRepoUrl,
|
|
154
|
+
useBestPractices,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
92
158
|
runResult = await helpers
|
|
93
159
|
.run(join(__dirname, "../index.js"))
|
|
94
|
-
.withPrompts(
|
|
95
|
-
projectName,
|
|
96
|
-
customerName,
|
|
97
|
-
dbSchema,
|
|
98
|
-
useAzureDevOps,
|
|
99
|
-
})
|
|
160
|
+
.withPrompts(prompts)
|
|
100
161
|
.withOptions({
|
|
101
162
|
skipInstall: true,
|
|
102
163
|
})
|
|
@@ -134,6 +195,7 @@ describe("generator-btp:cap", () => {
|
|
|
134
195
|
expect(runResult.generator.answers.customerNameNormalized).toBe(
|
|
135
196
|
expectedCustomerNormalized,
|
|
136
197
|
);
|
|
198
|
+
expect(runResult.generator.answers.cdsFeatures).toEqual(cdsFeatures);
|
|
137
199
|
});
|
|
138
200
|
|
|
139
201
|
it("should create base configuration files", () => {
|
|
@@ -142,7 +204,6 @@ describe("generator-btp:cap", () => {
|
|
|
142
204
|
".npmrc",
|
|
143
205
|
".gitattributes",
|
|
144
206
|
".gitconfig.aliases",
|
|
145
|
-
"base.tsconfig.json",
|
|
146
207
|
"eslint.config.mjs",
|
|
147
208
|
"prettier.config.mjs",
|
|
148
209
|
"mta.yaml",
|
|
@@ -173,6 +234,22 @@ describe("generator-btp:cap", () => {
|
|
|
173
234
|
}
|
|
174
235
|
});
|
|
175
236
|
|
|
237
|
+
it("should conditionally create Azure DevOps setup scripts", () => {
|
|
238
|
+
if (useAzureDevOps) {
|
|
239
|
+
assert.file([
|
|
240
|
+
"scripts/setup-azure-devops/README.md",
|
|
241
|
+
"scripts/setup-azure-devops/azure-devops.env",
|
|
242
|
+
"scripts/setup-azure-devops/setup.sh",
|
|
243
|
+
]);
|
|
244
|
+
} else {
|
|
245
|
+
assert.noFile([
|
|
246
|
+
"scripts/setup-azure-devops/README.md",
|
|
247
|
+
"scripts/setup-azure-devops/azure-devops.env",
|
|
248
|
+
"scripts/setup-azure-devops/setup.sh",
|
|
249
|
+
]);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
|
|
176
253
|
it("should not create undeploy.json", () => {
|
|
177
254
|
assert.noFile(["db/undeploy.json"]);
|
|
178
255
|
});
|
|
@@ -212,4 +289,75 @@ describe("generator-btp:cap", () => {
|
|
|
212
289
|
});
|
|
213
290
|
},
|
|
214
291
|
);
|
|
292
|
+
|
|
293
|
+
describe("with custom target directory", () => {
|
|
294
|
+
let runResult;
|
|
295
|
+
|
|
296
|
+
beforeAll(async () => {
|
|
297
|
+
runResult = await helpers
|
|
298
|
+
.run(join(__dirname, "../index.js"))
|
|
299
|
+
.withPrompts({
|
|
300
|
+
targetDirectory: "my-new-cap-project",
|
|
301
|
+
projectName: "My New CAP Project",
|
|
302
|
+
customerName: "Test Customer",
|
|
303
|
+
dbSchema: "MY_NEW_CAP_PROJECT",
|
|
304
|
+
useAzureDevOps: false,
|
|
305
|
+
cdsFeatures: [
|
|
306
|
+
"typescript",
|
|
307
|
+
"mta",
|
|
308
|
+
"xsuaa",
|
|
309
|
+
"hana",
|
|
310
|
+
"sqlite",
|
|
311
|
+
"html5-repo",
|
|
312
|
+
"tiny-sample",
|
|
313
|
+
"http",
|
|
314
|
+
"test",
|
|
315
|
+
],
|
|
316
|
+
})
|
|
317
|
+
.withOptions({
|
|
318
|
+
skipInstall: true,
|
|
319
|
+
})
|
|
320
|
+
.withSpawnMock((command, args) => {
|
|
321
|
+
if (command === "git" && args[0] === "init") {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
if (command === "cds" && args[0] === "init") {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (command === "npm") {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (command === "npx" && args[0] === "simple-git-hooks") {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
}, 30000);
|
|
335
|
+
|
|
336
|
+
afterAll(() => {
|
|
337
|
+
if (runResult) {
|
|
338
|
+
runResult.restore();
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it("should set the destination root to the target directory", () => {
|
|
343
|
+
expect(runResult.generator.destinationRoot()).toMatch(
|
|
344
|
+
/[\\/]my-new-cap-project$/,
|
|
345
|
+
);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it("should generate files inside the target directory", () => {
|
|
349
|
+
assert.file([
|
|
350
|
+
"my-new-cap-project/.gitignore",
|
|
351
|
+
"my-new-cap-project/mta.yaml",
|
|
352
|
+
"my-new-cap-project/.devcontainer/devcontainer.json",
|
|
353
|
+
]);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it("should template package.json with normalized name inside target directory", () => {
|
|
357
|
+
assert.fileContent(
|
|
358
|
+
"my-new-cap-project/package.json",
|
|
359
|
+
/"name":\s*"my-new-cap-project"/,
|
|
360
|
+
);
|
|
361
|
+
});
|
|
362
|
+
});
|
|
215
363
|
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"noUnusedLocals": true,
|
|
4
|
+
"noUnusedParameters": true,
|
|
5
|
+
"allowUnusedLabels": false,
|
|
6
|
+
"allowUnreachableCode": false,
|
|
7
|
+
"noImplicitOverride": true,
|
|
8
|
+
"noImplicitReturns": true,
|
|
9
|
+
"noPropertyAccessFromIndexSignature": true,
|
|
10
|
+
"noFallthroughCasesInSwitch": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"exactOptionalPropertyTypes": true
|
|
13
|
+
},
|
|
14
|
+
"exclude": [
|
|
15
|
+
"gen",
|
|
16
|
+
"**/*.config.mjs",
|
|
17
|
+
"**/*.config.js"
|
|
18
|
+
]
|
|
19
|
+
}
|
package/generators/cap/index.js
CHANGED
|
@@ -5,7 +5,12 @@ import fs from "node:fs";
|
|
|
5
5
|
import { dirname, join, resolve } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import Generator from "yeoman-generator";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
mergeArray,
|
|
10
|
+
readJsonC,
|
|
11
|
+
readJsonCSafe,
|
|
12
|
+
sortPackageJson,
|
|
13
|
+
} from "../../utils/jsonFile.js";
|
|
9
14
|
|
|
10
15
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
16
|
const __dirname = dirname(__filename);
|
|
@@ -19,6 +24,19 @@ export default class extends Generator {
|
|
|
19
24
|
const aPrompt = [];
|
|
20
25
|
|
|
21
26
|
aPrompt.push(
|
|
27
|
+
{
|
|
28
|
+
type: "input",
|
|
29
|
+
name: "targetDirectory",
|
|
30
|
+
message:
|
|
31
|
+
"Enter the target directory for the project (use '.' to generate in the current directory):",
|
|
32
|
+
default: ".",
|
|
33
|
+
validate(sInput) {
|
|
34
|
+
if (sInput.trim().length > 0) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
return "Target directory must not be empty.";
|
|
38
|
+
},
|
|
39
|
+
},
|
|
22
40
|
{
|
|
23
41
|
type: "input",
|
|
24
42
|
name: "projectName",
|
|
@@ -57,9 +75,9 @@ export default class extends Generator {
|
|
|
57
75
|
// Use project name in uppercase with underscores instead of special characters
|
|
58
76
|
return answers.projectName
|
|
59
77
|
.toUpperCase()
|
|
60
|
-
.replace(/[^A-Z0-9]/g,
|
|
61
|
-
.replace(/_+/g,
|
|
62
|
-
.replace(/^_|_$/g,
|
|
78
|
+
.replace(/[^A-Z0-9]/g, "_")
|
|
79
|
+
.replace(/_+/g, "_") // Replace multiple underscores with single underscore
|
|
80
|
+
.replace(/^_|_$/g, ""); // Remove leading/trailing underscores
|
|
63
81
|
},
|
|
64
82
|
},
|
|
65
83
|
{
|
|
@@ -68,10 +86,219 @@ export default class extends Generator {
|
|
|
68
86
|
message: "Is Azure DevOps used for version control and CI/CD?",
|
|
69
87
|
default: true,
|
|
70
88
|
},
|
|
89
|
+
{
|
|
90
|
+
type: "checkbox",
|
|
91
|
+
name: "cdsFeatures",
|
|
92
|
+
message: "Select the CDS features to add (via cds add ...):",
|
|
93
|
+
choices: [
|
|
94
|
+
{ name: "typescript", value: "typescript", checked: true },
|
|
95
|
+
{ name: "mta", value: "mta", checked: true },
|
|
96
|
+
{ name: "xsuaa", value: "xsuaa", checked: true },
|
|
97
|
+
{ name: "hana", value: "hana", checked: true },
|
|
98
|
+
{ name: "sqlite", value: "sqlite", checked: true },
|
|
99
|
+
{ name: "html5-repo", value: "html5-repo", checked: true },
|
|
100
|
+
{ name: "tiny-sample", value: "tiny-sample", checked: true },
|
|
101
|
+
{ name: "http", value: "http", checked: true },
|
|
102
|
+
{ name: "test", value: "test", checked: true },
|
|
103
|
+
],
|
|
104
|
+
validate(selected) {
|
|
105
|
+
if (selected.length > 0) {
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
return "At least one CDS feature must be selected.";
|
|
109
|
+
},
|
|
110
|
+
},
|
|
71
111
|
);
|
|
72
112
|
|
|
73
113
|
const answers = await this.prompt(aPrompt);
|
|
74
114
|
|
|
115
|
+
// Normalize names for use in subsequent prompts and templates
|
|
116
|
+
answers.projectNameNormalized = answers.projectName
|
|
117
|
+
.toLowerCase()
|
|
118
|
+
.replace(/\s+/g, "-");
|
|
119
|
+
answers.customerNameNormalized = answers.customerName
|
|
120
|
+
.toLowerCase()
|
|
121
|
+
.replace(/\s+/g, "-");
|
|
122
|
+
|
|
123
|
+
// Change the destination root when a custom target directory is specified
|
|
124
|
+
const targetDir = answers.targetDirectory.trim();
|
|
125
|
+
if (targetDir !== ".") {
|
|
126
|
+
const resolvedTarget = resolve(this.destinationRoot(), targetDir);
|
|
127
|
+
fs.mkdirSync(resolvedTarget, { recursive: true });
|
|
128
|
+
this.destinationRoot(resolvedTarget);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// If using Azure DevOps, prompt for additional configuration
|
|
132
|
+
if (answers.useAzureDevOps) {
|
|
133
|
+
this.log(chalk.bold("\nāļø Configuring Azure DevOps settings..."));
|
|
134
|
+
|
|
135
|
+
// First, ask for repository URL
|
|
136
|
+
const initialAzurePrompts = [
|
|
137
|
+
{
|
|
138
|
+
type: "input",
|
|
139
|
+
name: "azureRepoUrl",
|
|
140
|
+
message:
|
|
141
|
+
"Enter your Azure DevOps repository URL:\n (Example: https://dev.azure.com/ORG_NAME/PROJECT_NAME/_git/REPO_NAME)\n Your repository URL:",
|
|
142
|
+
validate(input) {
|
|
143
|
+
const regex =
|
|
144
|
+
/^https:\/\/dev\.azure\.com\/([^\/]+)\/([^\/]+)\/_git\/([^\/]+)\/?$/;
|
|
145
|
+
if (regex.test(input.trim())) {
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
return "Invalid Azure DevOps repository URL. Expected format: https://dev.azure.com/ORG_NAME/PROJECT_NAME/_git/REPO_NAME";
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
type: "confirm",
|
|
153
|
+
name: "useBestPractices",
|
|
154
|
+
message:
|
|
155
|
+
"Use recommended best practices for branch policies and merge strategies?\n Choose no to customize settings in detail with best practices as defaults.",
|
|
156
|
+
default: true,
|
|
157
|
+
},
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
const initialAzureAnswers = await this.prompt(initialAzurePrompts);
|
|
161
|
+
Object.assign(answers, initialAzureAnswers);
|
|
162
|
+
|
|
163
|
+
// Parse Azure DevOps repository URL to extract organization, project, and repo names
|
|
164
|
+
const urlRegex =
|
|
165
|
+
/^https:\/\/dev\.azure\.com\/([^\/]+)\/([^\/]+)\/_git\/([^\/]+)\/?$/;
|
|
166
|
+
const match = answers.azureRepoUrl.trim().match(urlRegex);
|
|
167
|
+
|
|
168
|
+
if (match) {
|
|
169
|
+
answers.azureDevOpsOrgUrl = `https://dev.azure.com/${match[1]}`;
|
|
170
|
+
answers.azureProjectName = match[2];
|
|
171
|
+
answers.repoName = match[3];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// If not using best practices, ask for detailed configuration
|
|
175
|
+
if (!answers.useBestPractices) {
|
|
176
|
+
const detailedAzurePrompts = [
|
|
177
|
+
{
|
|
178
|
+
type: "input",
|
|
179
|
+
name: "mainBranch",
|
|
180
|
+
message: "Enter the name of the main/production branch:",
|
|
181
|
+
default: "main",
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
type: "input",
|
|
185
|
+
name: "devBranch",
|
|
186
|
+
message: "Enter the name of the development branch:",
|
|
187
|
+
default: "development",
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
type: "confirm",
|
|
191
|
+
name: "setDevBranchAsDefault",
|
|
192
|
+
message: "Set development branch as the default branch?",
|
|
193
|
+
default: true,
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
type: "number",
|
|
197
|
+
name: "minReviewers",
|
|
198
|
+
message: "Minimum number of reviewers required for PRs to main:",
|
|
199
|
+
default: 1,
|
|
200
|
+
validate(input) {
|
|
201
|
+
return input >= 0 ? true : "Must be 0 or greater";
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
type: "confirm",
|
|
206
|
+
name: "mainResetOnSourcePush",
|
|
207
|
+
message:
|
|
208
|
+
"Reset approvals when new commits are pushed to PRs (main branch)?",
|
|
209
|
+
default: true,
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
type: "confirm",
|
|
213
|
+
name: "mainAllowSquashMerge",
|
|
214
|
+
message: "Allow squash merge on main branch?",
|
|
215
|
+
default: true,
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
type: "confirm",
|
|
219
|
+
name: "mainAllowRebaseMerge",
|
|
220
|
+
message: "Allow rebase with merge commit on main branch?",
|
|
221
|
+
default: true,
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
type: "confirm",
|
|
225
|
+
name: "mainAllowRebase",
|
|
226
|
+
message: "Allow rebase and fast-forward on main branch?",
|
|
227
|
+
default: false,
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
type: "confirm",
|
|
231
|
+
name: "mainAllowNoFastForward",
|
|
232
|
+
message: "Allow basic merge (no fast-forward) on main branch?",
|
|
233
|
+
default: false,
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
type: "confirm",
|
|
237
|
+
name: "devAllowSquashMerge",
|
|
238
|
+
message: "Allow squash merge on development branch?",
|
|
239
|
+
default: true,
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
type: "confirm",
|
|
243
|
+
name: "devAllowRebaseMerge",
|
|
244
|
+
message: "Allow rebase with merge commit on development branch?",
|
|
245
|
+
default: true,
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
type: "confirm",
|
|
249
|
+
name: "devAllowRebase",
|
|
250
|
+
message: "Allow rebase and fast-forward on development branch?",
|
|
251
|
+
default: false,
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
type: "confirm",
|
|
255
|
+
name: "devAllowNoFastForward",
|
|
256
|
+
message:
|
|
257
|
+
"Allow basic merge (no fast-forward) on development branch?",
|
|
258
|
+
default: false,
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
type: "input",
|
|
262
|
+
name: "pipelineName",
|
|
263
|
+
message: "Enter the Azure Pipeline name:",
|
|
264
|
+
default: answers.projectNameNormalized,
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
type: "input",
|
|
268
|
+
name: "pipelineYamlPath",
|
|
269
|
+
message: "Enter the path to the pipeline YAML file:",
|
|
270
|
+
default: "azure-pipelines.yml",
|
|
271
|
+
},
|
|
272
|
+
];
|
|
273
|
+
|
|
274
|
+
const detailedAzureAnswers = await this.prompt(detailedAzurePrompts);
|
|
275
|
+
Object.assign(answers, detailedAzureAnswers);
|
|
276
|
+
} else {
|
|
277
|
+
// Use best practice defaults
|
|
278
|
+
answers.mainBranch = "main";
|
|
279
|
+
answers.devBranch = "development";
|
|
280
|
+
answers.setDevBranchAsDefault = true;
|
|
281
|
+
answers.minReviewers = 1;
|
|
282
|
+
answers.mainResetOnSourcePush = true;
|
|
283
|
+
answers.mainAllowSquashMerge = true;
|
|
284
|
+
answers.mainAllowRebaseMerge = true;
|
|
285
|
+
answers.mainAllowRebase = false;
|
|
286
|
+
answers.mainAllowNoFastForward = false;
|
|
287
|
+
answers.devAllowSquashMerge = true;
|
|
288
|
+
answers.devAllowRebaseMerge = true;
|
|
289
|
+
answers.devAllowRebase = false;
|
|
290
|
+
answers.devAllowNoFastForward = false;
|
|
291
|
+
answers.pipelineName = answers.projectNameNormalized;
|
|
292
|
+
answers.pipelineYamlPath = "azure-pipelines.yml";
|
|
293
|
+
|
|
294
|
+
this.log(
|
|
295
|
+
chalk.green(
|
|
296
|
+
"ā Using best practice defaults for Azure DevOps configuration",
|
|
297
|
+
),
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
75
302
|
// Check if .git directory exists, if not run git init
|
|
76
303
|
if (!fs.existsSync(this.destinationPath(".git"))) {
|
|
77
304
|
this.log(chalk.bold("\nš§ Initializing git repository..."));
|
|
@@ -82,22 +309,12 @@ export default class extends Generator {
|
|
|
82
309
|
this.log(chalk.bold("\nš Initializing CAP project..."));
|
|
83
310
|
this.spawnCommandSync(
|
|
84
311
|
"cds",
|
|
85
|
-
[
|
|
86
|
-
"init",
|
|
87
|
-
"--add",
|
|
88
|
-
"typescript,mta,xsuaa,hana,sqlite,html5-repo,tiny-sample,http",
|
|
89
|
-
],
|
|
312
|
+
["init", "--add", answers.cdsFeatures.join(",")],
|
|
90
313
|
{
|
|
91
314
|
cwd: this.destinationPath(),
|
|
92
315
|
},
|
|
93
316
|
);
|
|
94
317
|
|
|
95
|
-
answers.projectNameNormalized = answers.projectName
|
|
96
|
-
.toLowerCase()
|
|
97
|
-
.replace(/\s+/g, "-");
|
|
98
|
-
answers.customerNameNormalized = answers.customerName
|
|
99
|
-
.toLowerCase()
|
|
100
|
-
.replace(/\s+/g, "-");
|
|
101
318
|
this.answers = answers;
|
|
102
319
|
}
|
|
103
320
|
|
|
@@ -148,7 +365,25 @@ export default class extends Generator {
|
|
|
148
365
|
this.fs.copyTpl(sOrigin, sTarget, this.answers);
|
|
149
366
|
});
|
|
150
367
|
|
|
151
|
-
// 3)
|
|
368
|
+
// 3) Copy Azure DevOps setup files from the setup-azure-devops generator
|
|
369
|
+
if (this.answers.useAzureDevOps) {
|
|
370
|
+
const azureDevOpsTemplatesPath = resolve(
|
|
371
|
+
__dirname,
|
|
372
|
+
"../setup-azure-devops/templates",
|
|
373
|
+
);
|
|
374
|
+
this.sourceRoot(azureDevOpsTemplatesPath);
|
|
375
|
+
globSync("**", {
|
|
376
|
+
cwd: this.sourceRoot(),
|
|
377
|
+
nodir: true,
|
|
378
|
+
}).forEach((fileName) => {
|
|
379
|
+
const sOrigin = join(azureDevOpsTemplatesPath, fileName);
|
|
380
|
+
const sTarget = this.destinationPath(fileName);
|
|
381
|
+
|
|
382
|
+
this.fs.copyTpl(sOrigin, sTarget, this.answers);
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// 4) Merge additions into existing files (scripting.txt is excluded by design)
|
|
152
387
|
// package.json
|
|
153
388
|
const destPkgPath = this.destinationPath("package.json");
|
|
154
389
|
const srcPkgPath = join(this._addRoot, "package.json");
|
|
@@ -156,15 +391,15 @@ export default class extends Generator {
|
|
|
156
391
|
const addPkg = readJsonC(srcPkgPath, {});
|
|
157
392
|
const mergedPkg = mergewith({}, destPkg, addPkg, mergeArray);
|
|
158
393
|
mergedPkg.name = this.answers.projectNameNormalized;
|
|
159
|
-
this.fs.writeJSON(destPkgPath, mergedPkg);
|
|
394
|
+
this.fs.writeJSON(destPkgPath, sortPackageJson(mergedPkg));
|
|
160
395
|
|
|
161
|
-
// tsconfig.json
|
|
396
|
+
// tsconfig.json
|
|
162
397
|
const destTsPath = this.destinationPath("tsconfig.json");
|
|
398
|
+
const srcTsPath = join(this._addRoot, "tsconfig.json");
|
|
163
399
|
const destTs = readJsonCSafe(this, destTsPath, {});
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
this.fs.writeJSON(destTsPath, destTs);
|
|
400
|
+
const addTs = readJsonC(srcTsPath, {});
|
|
401
|
+
const mergedTs = mergewith({}, destTs, addTs, mergeArray);
|
|
402
|
+
this.fs.writeJSON(destTsPath, mergedTs);
|
|
168
403
|
|
|
169
404
|
// .vscode/settings.json
|
|
170
405
|
const destVsSettingsPath = this.destinationPath(".vscode/settings.json");
|
|
@@ -220,5 +455,21 @@ export default class extends Generator {
|
|
|
220
455
|
this.log(chalk.bold.green("\nā
CAP best practices applied."));
|
|
221
456
|
this.log("Next steps:");
|
|
222
457
|
this.log("1) Review/commit the changes.");
|
|
458
|
+
|
|
459
|
+
if (this.answers.useAzureDevOps) {
|
|
460
|
+
this.log("2) Configure Azure DevOps branch policies and pipeline:");
|
|
461
|
+
this.log(
|
|
462
|
+
" a) Review/edit scripts/setup-azure-devops/azure-devops.env if needed",
|
|
463
|
+
);
|
|
464
|
+
this.log(
|
|
465
|
+
" b) Ensure you're logged in to Azure CLI: az login --allow-no-subscriptions",
|
|
466
|
+
);
|
|
467
|
+
this.log(
|
|
468
|
+
" c) Run the setup script: bash scripts/setup-azure-devops/setup.sh",
|
|
469
|
+
);
|
|
470
|
+
this.log(
|
|
471
|
+
" d) See scripts/setup-azure-devops/README.md for detailed instructions",
|
|
472
|
+
);
|
|
473
|
+
}
|
|
223
474
|
}
|
|
224
475
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
[alias]
|
|
2
2
|
# Helper aliases for ticket workflow
|
|
3
|
+
cleanup-branches = "!f() {\n current_branch=$(git symbolic-ref --short HEAD);\n default_branch=$(git default-branch);\n if [ $? -ne 0 ]; then\n echo \"Error: Could not determine default branch\" >&2;\n return 1;\n fi;\n deleted_any=false;\n\n for branch in $(git branch --format=\"%(refname:short)\"); do\n if [ \"$branch\" = \"$current_branch\" ] || [ \"$branch\" = \"$default_branch\" ]; then\n continue;\n fi\n\n upstream=$(git for-each-ref --format=\"%(upstream:short)\" refs/heads/\"$branch\")\n if [ -z \"$upstream\" ]; then\n echo \" Skipping $branch - No upstream tracking branch\"\n continue;\n fi\n\n if git branch -d \"$branch\" >/dev/null 2>&1; then\n if [ \"$deleted_any\" = false ]; then\n echo \"Deleting branches:\";\n deleted_any=true;\n fi\n echo \" $branch - Remote: $upstream\"\n else\n echo \" Not deleting $branch - Remote: $upstream (not merged or has unpushed commits)\"\n fi\n done\n\n if [ \"$deleted_any\" = false ]; then\n echo \"No branches to delete.\"\n fi\n}; f"
|
|
3
4
|
extract-branch-parts = "!f() { current_branch=$(git branch --show-current); case \"$current_branch\" in */*) prefix=\"${current_branch%%/*}\"; ticketRef=\"${current_branch#*/}\"; echo \"$prefix|$ticketRef\" ;; *) echo \"Error: Branch name must contain a prefix (e.g., feature/ABC-123)\" >&2; return 1 ;; esac; }; f"
|
|
4
5
|
capitalize-prefix = "!f() { first_char=$(printf '%s' \"$1\" | cut -c1 | tr '[:lower:]' '[:upper:]'); rest_chars=$(printf '%s' \"$1\" | cut -c2-); echo \"$first_char$rest_chars\"; }; f"
|
|
5
6
|
format-pr-url = "!f() { pr_id=\"$1\"; remote_url=$(git config --get remote.origin.url | sed 's|https://[^@]*@|https://|'); base_url=$(echo \"$remote_url\" | sed 's|\\.git$||'); echo \"${base_url}/pullrequest/${pr_id}\"; }; f"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"clearMocks": true,
|
|
3
|
+
"coverageDirectory": "coverage",
|
|
4
|
+
"collectCoverageFrom": ["./srv/**", "!./srv/types/**"],
|
|
5
|
+
"collectCoverage": true,
|
|
6
|
+
"coverageProvider": "v8",
|
|
7
|
+
"coverageThreshold": {
|
|
8
|
+
"global": {
|
|
9
|
+
"branches": 50,
|
|
10
|
+
"functions": 50,
|
|
11
|
+
"lines": 70,
|
|
12
|
+
"statements": 70
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"roots": ["<rootDir>"],
|
|
16
|
+
"preset": "ts-jest",
|
|
17
|
+
"globals": {},
|
|
18
|
+
"testEnvironment": "node",
|
|
19
|
+
"testMatch": ["**/?(*.)+(spec|test).[tj]s?(x)"],
|
|
20
|
+
"testPathIgnorePatterns": ["<rootDir>/dist/", "<rootDir>/__archive/", "<rootDir>/node_modules/", "<rootDir>/config/", "<rootDir>/gen/"],
|
|
21
|
+
"modulePathIgnorePatterns": ["<rootDir>/gen"],
|
|
22
|
+
"transform": {
|
|
23
|
+
"^.+\\.(ts|tsx)$": ["ts-jest"]
|
|
24
|
+
}
|
|
25
|
+
}
|