@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.
@@ -0,0 +1,437 @@
1
+ #!/bin/bash
2
+
3
+ ###############################################################################
4
+ # Azure DevOps Setup Script
5
+ #
6
+ # This script configures branch policies and creates a pipeline for your
7
+ # Azure DevOps repository.
8
+ #
9
+ # Prerequisites:
10
+ # - Azure CLI installed and authenticated (run 'az login --allow-no-subscriptions')
11
+ # - Azure DevOps extension installed (az extension add --name azure-devops)
12
+ # - Appropriate permissions in Azure DevOps project
13
+ # - Repository already created with main branch
14
+ #
15
+ # Usage:
16
+ # 1. Configure azure-devops.env with your values
17
+ # 2. Run: bash setup.sh
18
+ ###############################################################################
19
+
20
+ set -e # Exit on error
21
+
22
+ # Colors for output
23
+ RED='\033[0;31m'
24
+ GREEN='\033[0;32m'
25
+ YELLOW='\033[1;33m'
26
+ BLUE='\033[0;34m'
27
+ NC='\033[0m' # No Color
28
+
29
+ # Get script directory
30
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
31
+ ENV_FILE="${SCRIPT_DIR}/azure-devops.env"
32
+
33
+ ###############################################################################
34
+ # Functions
35
+ ###############################################################################
36
+
37
+ log_info() {
38
+ echo -e "${BLUE}ℹ${NC} $1"
39
+ }
40
+
41
+ log_success() {
42
+ echo -e "${GREEN}✓${NC} $1"
43
+ }
44
+
45
+ log_warning() {
46
+ echo -e "${YELLOW}⚠${NC} $1"
47
+ }
48
+
49
+ log_error() {
50
+ echo -e "${RED}✗${NC} $1"
51
+ }
52
+
53
+ check_prerequisites() {
54
+ log_info "Checking prerequisites..."
55
+
56
+ # Check if Azure CLI is installed
57
+ if ! command -v az &> /dev/null; then
58
+ log_error "Azure CLI is not installed. Please install it first."
59
+ echo " Visit: https://docs.microsoft.com/cli/azure/install-azure-cli"
60
+ exit 1
61
+ fi
62
+ log_success "Azure CLI found"
63
+
64
+ # Check if Azure DevOps extension is installed
65
+ if ! az extension list --output tsv | grep -q "azure-devops"; then
66
+ log_warning "Azure DevOps extension not found. Installing..."
67
+ az extension add --name azure-devops
68
+ log_success "Azure DevOps extension installed"
69
+ else
70
+ log_success "Azure DevOps extension found"
71
+ fi
72
+
73
+ # Check if logged in
74
+ if ! az account show &> /dev/null; then
75
+ log_error "Not logged in to Azure. Please run 'az login --allow-no-subscriptions' first."
76
+ exit 1
77
+ fi
78
+ log_success "Authenticated to Azure"
79
+ }
80
+
81
+ load_config() {
82
+ log_info "Loading configuration from ${ENV_FILE}..."
83
+
84
+ if [ ! -f "${ENV_FILE}" ]; then
85
+ log_error "Configuration file not found: ${ENV_FILE}"
86
+ log_error "Please create azure-devops.env from the template and configure it."
87
+ exit 1
88
+ fi
89
+
90
+ # Source the env file
91
+ # shellcheck disable=SC1090
92
+ source "${ENV_FILE}"
93
+
94
+ # Validate required variables
95
+ if [ -z "${AZURE_DEVOPS_ORG_URL}" ]; then
96
+ log_error "AZURE_DEVOPS_ORG_URL is not set in ${ENV_FILE}"
97
+ exit 1
98
+ fi
99
+
100
+ if [ -z "${AZURE_PROJECT_NAME}" ]; then
101
+ log_error "AZURE_PROJECT_NAME is not set in ${ENV_FILE}"
102
+ exit 1
103
+ fi
104
+
105
+ if [ -z "${REPO_NAME}" ]; then
106
+ log_error "REPO_NAME is not set in ${ENV_FILE}"
107
+ exit 1
108
+ fi
109
+
110
+ log_success "Configuration loaded successfully"
111
+ log_info " Organization: ${AZURE_DEVOPS_ORG_URL}"
112
+ log_info " Project: ${AZURE_PROJECT_NAME}"
113
+ log_info " Repository: ${REPO_NAME}"
114
+ }
115
+
116
+ configure_azure_devops_defaults() {
117
+ log_info "Configuring Azure DevOps CLI defaults..."
118
+ az devops configure --defaults organization="${AZURE_DEVOPS_ORG_URL}" project="${AZURE_PROJECT_NAME}"
119
+ log_success "Defaults configured"
120
+ }
121
+
122
+ get_repo_id() {
123
+ log_info "Getting repository ID..."
124
+ REPO_ID=$(az repos show --repository "${REPO_NAME}" --query id --output tsv)
125
+ if [ -z "${REPO_ID}" ]; then
126
+ log_error "Failed to get repository ID. Does the repository '${REPO_NAME}' exist?"
127
+ exit 1
128
+ fi
129
+ log_success "Repository ID: ${REPO_ID}"
130
+ }
131
+
132
+ check_main_branch_exists() {
133
+ log_info "Checking if '${MAIN_BRANCH}' branch exists..."
134
+
135
+ if ! az repos ref list --repository "${REPO_NAME}" --query "[?name=='refs/heads/${MAIN_BRANCH}'].name" --output tsv | grep -q "refs/heads/${MAIN_BRANCH}"; then
136
+ log_warning "The '${MAIN_BRANCH}' branch does not exist in the repository."
137
+ log_info "This usually means the repository is empty with no initial commit."
138
+ echo ""
139
+
140
+ # Ask user if they want to create initial commit
141
+ read -p "$(echo -e ${BLUE}?${NC}) Would you like to create an initial commit automatically? (y/n): " -n 1 -r
142
+ echo ""
143
+
144
+ if [[ $REPLY =~ ^[Yy]$ ]]; then
145
+ create_initial_commit
146
+ else
147
+ echo ""
148
+ log_info "To manually create an initial commit:"
149
+ echo " 1. Clone the repository: git clone ${AZURE_DEVOPS_ORG_URL}/${AZURE_PROJECT_NAME}/_git/${REPO_NAME}"
150
+ echo " 2. Create an initial commit:"
151
+ echo " echo '# ${REPO_NAME}' > README.md"
152
+ echo " git add README.md"
153
+ echo " git commit -m 'Initial commit'"
154
+ echo " git push -u origin ${MAIN_BRANCH}"
155
+ echo " 3. Run this setup script again"
156
+ echo ""
157
+ exit 1
158
+ fi
159
+ else
160
+ log_success "'${MAIN_BRANCH}' branch exists"
161
+ fi
162
+ }
163
+
164
+ create_initial_commit() {
165
+ log_info "Creating initial commit..."
166
+
167
+ # Check if git is installed
168
+ if ! command -v git &> /dev/null; then
169
+ log_error "Git is not installed. Cannot create initial commit automatically."
170
+ exit 1
171
+ fi
172
+
173
+ # Create a temporary directory
174
+ TEMP_DIR=$(mktemp -d)
175
+ log_info "Using temporary directory: ${TEMP_DIR}"
176
+
177
+ # Clone the empty repository
178
+ log_info "Cloning repository..."
179
+ REPO_URL="${AZURE_DEVOPS_ORG_URL}/${AZURE_PROJECT_NAME}/_git/${REPO_NAME}"
180
+
181
+ if ! git clone "${REPO_URL}" "${TEMP_DIR}/${REPO_NAME}" 2>/dev/null; then
182
+ log_error "Failed to clone repository. Please check your credentials and permissions."
183
+ rm -rf "${TEMP_DIR}"
184
+ exit 1
185
+ fi
186
+
187
+ cd "${TEMP_DIR}/${REPO_NAME}"
188
+
189
+ # Configure git user if not set
190
+ if [ -z "$(git config user.email)" ]; then
191
+ git config user.email "azure-devops-setup@example.com"
192
+ git config user.name "Azure DevOps Setup Script"
193
+ fi
194
+
195
+ # Create README.md
196
+ log_info "Creating README.md..."
197
+ cat > README.md << EOF
198
+ # ${REPO_NAME}
199
+
200
+ This repository was initialized automatically by the Azure DevOps setup script.
201
+
202
+ ## Getting Started
203
+
204
+ Add your project documentation here.
205
+ EOF
206
+
207
+ # Commit and push
208
+ log_info "Committing and pushing to '${MAIN_BRANCH}'..."
209
+ git add README.md
210
+ git commit -m "Initial commit"
211
+ git push -u origin "${MAIN_BRANCH}"
212
+
213
+ # Clean up
214
+ cd - > /dev/null
215
+ rm -rf "${TEMP_DIR}"
216
+
217
+ log_success "Initial commit created and pushed to '${MAIN_BRANCH}'"
218
+ }
219
+
220
+ ensure_branch_exists() {
221
+ local branch_name=$1
222
+ log_info "Checking if branch '${branch_name}' exists..."
223
+
224
+ if az repos ref list --repository "${REPO_NAME}" --query "[?name=='refs/heads/${branch_name}'].name" --output tsv | grep -q "refs/heads/${branch_name}"; then
225
+ log_success "Branch '${branch_name}' exists"
226
+ else
227
+ log_warning "Branch '${branch_name}' does not exist"
228
+ log_info "Creating branch '${branch_name}' from main..."
229
+
230
+ # Get the commit SHA of the main branch
231
+ MAIN_COMMIT=$(az repos ref list --repository "${REPO_NAME}" --query "[?name=='refs/heads/${MAIN_BRANCH}'].objectId" --output tsv)
232
+
233
+ if [ -z "${MAIN_COMMIT}" ]; then
234
+ log_error "Failed to get commit SHA for '${MAIN_BRANCH}' branch"
235
+ exit 1
236
+ fi
237
+
238
+ az repos ref create --name "refs/heads/${branch_name}" --repository "${REPO_NAME}" --object-id "${MAIN_COMMIT}"
239
+ log_success "Branch '${branch_name}' created"
240
+ fi
241
+ }
242
+
243
+ set_default_branch() {
244
+ if [ "${SET_DEVELOPMENT_BRANCH_AS_DEFAULT}" = true ]; then
245
+ log_info "Setting '${DEV_BRANCH}' as the default branch..."
246
+ az repos update --repository "${REPO_NAME}" --default-branch "${DEV_BRANCH}"
247
+ log_success "'${DEV_BRANCH}' is now the default branch"
248
+ else
249
+ log_info "Keeping '${MAIN_BRANCH}' as the default branch"
250
+ fi
251
+ }
252
+
253
+ configure_main_branch_policies() {
254
+ log_info "Configuring branch policies for '${MAIN_BRANCH}'..."
255
+
256
+ # Minimum number of reviewers policy
257
+ log_info " Setting minimum approver count policy..."
258
+ az repos policy approver-count create \
259
+ --blocking true \
260
+ --enabled true \
261
+ --creator-vote-counts false \
262
+ --allow-downvotes false \
263
+ --reset-on-source-push "${MAIN_RESET_ON_SOURCE_PUSH}" \
264
+ --branch "${MAIN_BRANCH}" \
265
+ --minimum-approver-count "${MIN_REVIEWERS}" \
266
+ --repository-id "${REPO_ID}" \
267
+ --output none 2>/dev/null || log_warning "Approver count policy may already exist"
268
+
269
+ # Build validation policy
270
+ log_info " Setting build validation policy..."
271
+
272
+ # Check if build validation policy already exists for this branch and build definition
273
+ EXISTING_BUILD_POLICY=$(az repos policy list --repository-id "${REPO_ID}" --branch "${MAIN_BRANCH}" \
274
+ --query "[?type.displayName=='Build' && settings.buildDefinitionId==${BUILD_DEFINITION_ID}].id" \
275
+ --output tsv 2>/dev/null)
276
+
277
+ if [ -n "${EXISTING_BUILD_POLICY}" ]; then
278
+ log_warning "Build validation policy already exists for '${MAIN_BRANCH}' (Policy ID: ${EXISTING_BUILD_POLICY})"
279
+ else
280
+ az repos policy build create \
281
+ --blocking true \
282
+ --enabled true \
283
+ --manual-queue-only false \
284
+ --queue-on-source-update-only true \
285
+ --valid-duration 0 \
286
+ --branch "${MAIN_BRANCH}" \
287
+ --build-definition-id "${BUILD_DEFINITION_ID}" \
288
+ --display-name "PR Build Validation - ${MAIN_BRANCH}" \
289
+ --repository-id "${REPO_ID}" \
290
+ --output none
291
+ log_success "Build validation policy created"
292
+ fi
293
+
294
+ # Merge type restriction policy
295
+ log_info " Setting merge type restriction policy..."
296
+ az repos policy merge-strategy create \
297
+ --blocking true \
298
+ --branch "${MAIN_BRANCH}" \
299
+ --enabled true \
300
+ --repository-id "${REPO_ID}" \
301
+ --allow-no-fast-forward "${MAIN_ALLOW_NO_FAST_FORWARD}" \
302
+ --allow-rebase "${MAIN_ALLOW_REBASE}" \
303
+ --allow-rebase-merge "${MAIN_ALLOW_REBASE_MERGE}" \
304
+ --allow-squash "${MAIN_ALLOW_SQUASH_MERGE}" \
305
+ --output none 2>/dev/null || log_warning "Merge type policy may already exist"
306
+
307
+ log_success "Main branch policies configured"
308
+ }
309
+
310
+ configure_dev_branch_policies() {
311
+ log_info "Configuring branch policies for '${DEV_BRANCH}'..."
312
+
313
+ # Build validation policy
314
+ log_info " Setting build validation policy..."
315
+
316
+ # Check if build validation policy already exists for this branch and build definition
317
+ EXISTING_BUILD_POLICY=$(az repos policy list --repository-id "${REPO_ID}" --branch "${DEV_BRANCH}" \
318
+ --query "[?type.displayName=='Build' && settings.buildDefinitionId==${BUILD_DEFINITION_ID}].id" \
319
+ --output tsv 2>/dev/null)
320
+
321
+ if [ -n "${EXISTING_BUILD_POLICY}" ]; then
322
+ log_warning "Build validation policy already exists for '${DEV_BRANCH}' (Policy ID: ${EXISTING_BUILD_POLICY})"
323
+ else
324
+ az repos policy build create \
325
+ --blocking true \
326
+ --enabled true \
327
+ --manual-queue-only false \
328
+ --queue-on-source-update-only true \
329
+ --valid-duration 0 \
330
+ --branch "${DEV_BRANCH}" \
331
+ --build-definition-id "${BUILD_DEFINITION_ID}" \
332
+ --display-name "PR Build Validation - ${DEV_BRANCH}" \
333
+ --repository-id "${REPO_ID}" \
334
+ --output none
335
+ log_success "Build validation policy created"
336
+ fi
337
+
338
+ # Merge type restriction policy for development branch
339
+ log_info " Setting merge type restriction policy..."
340
+ az repos policy merge-strategy create \
341
+ --blocking true \
342
+ --branch "${DEV_BRANCH}" \
343
+ --enabled true \
344
+ --repository-id "${REPO_ID}" \
345
+ --allow-no-fast-forward "${DEV_ALLOW_NO_FAST_FORWARD}" \
346
+ --allow-rebase "${DEV_ALLOW_REBASE}" \
347
+ --allow-rebase-merge "${DEV_ALLOW_REBASE_MERGE}" \
348
+ --allow-squash "${DEV_ALLOW_SQUASH_MERGE}" \
349
+ --output none 2>/dev/null || log_warning "Merge type policy may already exist"
350
+
351
+ log_success "Development branch policies configured"
352
+ }
353
+
354
+ create_or_get_pipeline() {
355
+ log_info "Setting up pipeline '${PIPELINE_NAME}'..."
356
+
357
+ # Check if pipeline already exists
358
+ if az pipelines list --query "[?name=='${PIPELINE_NAME}'].id" --output tsv | grep -q .; then
359
+ BUILD_DEFINITION_ID=$(az pipelines list --query "[?name=='${PIPELINE_NAME}'].id" --output tsv)
360
+ log_warning "Pipeline '${PIPELINE_NAME}' already exists. Using existing pipeline."
361
+ log_info " Pipeline ID: ${BUILD_DEFINITION_ID}"
362
+ else
363
+ # Create pipeline (tfsgit = Azure Repos Git)
364
+ log_info "Creating pipeline..."
365
+ BUILD_DEFINITION_ID=$(az pipelines create \
366
+ --name "${PIPELINE_NAME}" \
367
+ --repository "${REPO_NAME}" \
368
+ --repository-type tfsgit \
369
+ --branch "${MAIN_BRANCH}" \
370
+ --yml-path "${PIPELINE_YAML_PATH}" \
371
+ --skip-first-run \
372
+ --query id \
373
+ --output tsv)
374
+
375
+ log_success "Pipeline '${PIPELINE_NAME}' created"
376
+ log_info " Pipeline ID: ${BUILD_DEFINITION_ID}"
377
+ fi
378
+
379
+ if [ -z "${BUILD_DEFINITION_ID}" ]; then
380
+ log_error "Failed to get pipeline ID"
381
+ exit 1
382
+ fi
383
+ }
384
+
385
+ ###############################################################################
386
+ # Main execution
387
+ ###############################################################################
388
+
389
+ main() {
390
+ echo ""
391
+ echo "╔════════════════════════════════════════════════════════════════╗"
392
+ echo "║ Azure DevOps Repository Setup Script ║"
393
+ echo "╚════════════════════════════════════════════════════════════════╝"
394
+ echo ""
395
+
396
+ check_prerequisites
397
+ load_config
398
+ configure_azure_devops_defaults
399
+ get_repo_id
400
+ check_main_branch_exists
401
+
402
+ # Ensure development branch exists
403
+ ensure_branch_exists "${DEV_BRANCH}"
404
+
405
+ # Set default branch
406
+ set_default_branch
407
+
408
+ # Create or get pipeline (needed for build validation policies)
409
+ create_or_get_pipeline
410
+
411
+ # Configure branch policies (including build validation)
412
+ configure_main_branch_policies
413
+ configure_dev_branch_policies
414
+
415
+ echo ""
416
+ log_success "Azure DevOps setup completed successfully!"
417
+ echo ""
418
+ echo "Summary:"
419
+ echo " • Default branch: ${DEV_BRANCH}"
420
+ echo " • Main branch policies: Min ${MIN_REVIEWERS} reviewer(s), build validation, merge restrictions"
421
+ echo " • Development branch policies: Build validation, merge restrictions"
422
+ echo " • Pipeline: ${PIPELINE_NAME} (ID: ${BUILD_DEFINITION_ID})"
423
+ echo ""
424
+ echo "Links:"
425
+ echo " • Pipeline: ${AZURE_DEVOPS_ORG_URL}/${AZURE_PROJECT_NAME}/_build?definitionId=${BUILD_DEFINITION_ID}"
426
+ echo " • Main branch policies: ${AZURE_DEVOPS_ORG_URL}/${AZURE_PROJECT_NAME}/_settings/repositories?repo=${REPO_ID}&_a=policiesMid&refs=refs%2Fheads%2F${MAIN_BRANCH}"
427
+ echo " • Dev branch policies: ${AZURE_DEVOPS_ORG_URL}/${AZURE_PROJECT_NAME}/_settings/repositories?repo=${REPO_ID}&_a=policiesMid&refs=refs%2Fheads%2F${DEV_BRANCH}"
428
+ echo ""
429
+ log_info "Next steps:"
430
+ echo " 1. Review the policies in Azure DevOps portal"
431
+ echo " 2. Configure additional settings as needed"
432
+ echo " 3. Start pushing your code and creating pull requests"
433
+ echo ""
434
+ }
435
+
436
+ # Run main function
437
+ main "$@"
@@ -4,7 +4,12 @@ import { join, dirname } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import chalk from "chalk";
6
6
  import mergewith from "lodash.mergewith";
7
- import { readJsonC, readJsonCSafe, mergeArray } from "../../utils/jsonFile.js";
7
+ import {
8
+ readJsonC,
9
+ readJsonCSafe,
10
+ mergeArray,
11
+ sortPackageJson,
12
+ } from "../../utils/jsonFile.js";
8
13
 
9
14
  const __filename = fileURLToPath(import.meta.url);
10
15
  const __dirname = dirname(__filename);
@@ -62,7 +67,7 @@ export default class extends Generator {
62
67
  const destPkg = readJsonCSafe(this, destPkgPath, {});
63
68
  const addPkg = readJsonC(srcPkgPath, {});
64
69
  const mergedPkg = mergewith({}, destPkg, addPkg, mergeArray);
65
- this.fs.writeJSON(destPkgPath, mergedPkg);
70
+ this.fs.writeJSON(destPkgPath, sortPackageJson(mergedPkg));
66
71
 
67
72
  // tsconfig.json - handle rootDir sentinel and union rootDirs
68
73
  const destTsPath = join(appDir, "tsconfig.json");
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@cbs-consulting/generator-btp",
3
- "version": "1.2.9",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
- "description": "Yeoman generator for bootstrapping CAP/UI5 projects with TypeScript, ESLint, and other essential configurations",
5
+ "description": "CBS Best Practice Generator - Yeoman generator for bootstrapping CAP/UI5 projects with TypeScript, ESLint, and other essential configurations",
6
6
  "main": "generators/app/index.js",
7
7
  "scripts": {
8
8
  "link": "npm link",
package/utils/jsonFile.js CHANGED
@@ -34,4 +34,33 @@ function mergeArray(objValue, srcValue) {
34
34
  }
35
35
  }
36
36
 
37
- export { readJsonC, readJsonCSafe, mergeArray };
37
+ const PKG_KEY_ORDER = [
38
+ "name",
39
+ "version",
40
+ "description",
41
+ "main",
42
+ "module",
43
+ "scripts",
44
+ "keywords",
45
+ "author",
46
+ "license",
47
+ "dependencies",
48
+ "devDependencies",
49
+ ];
50
+
51
+ function sortPackageJson(pkg) {
52
+ const sorted = {};
53
+ for (const key of PKG_KEY_ORDER) {
54
+ if (Object.prototype.hasOwnProperty.call(pkg, key)) {
55
+ sorted[key] = pkg[key];
56
+ }
57
+ }
58
+ for (const key of Object.keys(pkg)) {
59
+ if (!PKG_KEY_ORDER.includes(key)) {
60
+ sorted[key] = pkg[key];
61
+ }
62
+ }
63
+ return sorted;
64
+ }
65
+
66
+ export { readJsonC, readJsonCSafe, mergeArray, sortPackageJson };
@@ -1,14 +0,0 @@
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
- }