docopslab-dev 0.1.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.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.adoc +904 -0
  4. data/assets/config-packs/actionlint/base.yml +13 -0
  5. data/assets/config-packs/actionlint/project.yml +13 -0
  6. data/assets/config-packs/htmlproofer/base.yml +27 -0
  7. data/assets/config-packs/htmlproofer/project.yml +25 -0
  8. data/assets/config-packs/rubocop/base.yml +130 -0
  9. data/assets/config-packs/rubocop/project.yml +8 -0
  10. data/assets/config-packs/shellcheck/base.shellcheckrc +14 -0
  11. data/assets/config-packs/subtxt/ai-asciidoc-antipatterns.sub.txt +11 -0
  12. data/assets/config-packs/vale/asciidoc/ExplicitSectionIDs.yml +8 -0
  13. data/assets/config-packs/vale/asciidoc/ExtraLineBeforeLevel1.yml +7 -0
  14. data/assets/config-packs/vale/asciidoc/OneSentencePerLine.yml +8 -0
  15. data/assets/config-packs/vale/asciidoc/PreferSourceBlocks.yml +8 -0
  16. data/assets/config-packs/vale/asciidoc/ProperAdmonitions.yml +8 -0
  17. data/assets/config-packs/vale/asciidoc/ProperDLs.yml +7 -0
  18. data/assets/config-packs/vale/asciidoc/UncleanListStart.yml +8 -0
  19. data/assets/config-packs/vale/authoring/ButParagraph.yml +8 -0
  20. data/assets/config-packs/vale/authoring/ExNotEg.yml +8 -0
  21. data/assets/config-packs/vale/authoring/LiteralTerms.yml +20 -0
  22. data/assets/config-packs/vale/authoring/Spelling.yml +679 -0
  23. data/assets/config-packs/vale/base.ini +38 -0
  24. data/assets/config-packs/vale/config/scripts/ExplicitSectionIDs.tengo +56 -0
  25. data/assets/config-packs/vale/config/scripts/ExtraLineBeforeLevel1.tengo +121 -0
  26. data/assets/config-packs/vale/config/scripts/OneSentencePerLine.tengo +53 -0
  27. data/assets/config-packs/vale/project.ini +5 -0
  28. data/assets/hooks/pre-commit +63 -0
  29. data/assets/hooks/pre-push +72 -0
  30. data/assets/scripts/adoc_section_ids.rb +50 -0
  31. data/assets/scripts/build-common.sh +193 -0
  32. data/assets/scripts/build-docker.sh +64 -0
  33. data/assets/scripts/build.sh +56 -0
  34. data/assets/scripts/parse_jekyll_asciidoc_logs.rb +467 -0
  35. data/assets/templates/Gemfile +7 -0
  36. data/assets/templates/Rakefile +3 -0
  37. data/assets/templates/gitignore +69 -0
  38. data/assets/templates/jekyll-asciidoc-fix.prompt.yml +17 -0
  39. data/assets/templates/spellcheck.prompt.yml +16 -0
  40. data/docopslab-dev.gemspec +56 -0
  41. data/docs/agent/AGENTS.md +229 -0
  42. data/docs/agent/index.md +80 -0
  43. data/docs/agent/missions/conduct-release.md +224 -0
  44. data/docs/agent/missions/setup-new-project.md +250 -0
  45. data/docs/agent/roles/devops-release-engineer.md +152 -0
  46. data/docs/agent/roles/docops-engineer.md +193 -0
  47. data/docs/agent/roles/planner-architect.md +74 -0
  48. data/docs/agent/roles/product-engineer.md +153 -0
  49. data/docs/agent/roles/product-manager.md +130 -0
  50. data/docs/agent/roles/project-manager.md +139 -0
  51. data/docs/agent/roles/qa-testing-engineer.md +115 -0
  52. data/docs/agent/roles/tech-docs-manager.md +143 -0
  53. data/docs/agent/roles/tech-writer.md +163 -0
  54. data/docs/agent/skills/asciidoc.md +609 -0
  55. data/docs/agent/skills/code-commenting.md +347 -0
  56. data/docs/agent/skills/fix-broken-links.md +309 -0
  57. data/docs/agent/skills/fix-jekyll-asciidoc-build-errors.md +23 -0
  58. data/docs/agent/skills/fix-spelling-issues.md +13 -0
  59. data/docs/agent/skills/git.md +170 -0
  60. data/docs/agent/skills/github-issues.md +135 -0
  61. data/docs/agent/skills/product-release-rollback-and-patching.md +71 -0
  62. data/docs/agent/skills/rake-cli-dev.md +57 -0
  63. data/docs/agent/skills/readme-driven-dev.md +13 -0
  64. data/docs/agent/skills/release-history.md +29 -0
  65. data/docs/agent/skills/ruby.md +192 -0
  66. data/docs/agent/skills/schemagraphy-sgyml.md +18 -0
  67. data/docs/agent/skills/tests-running.md +25 -0
  68. data/docs/agent/skills/tests-writing.md +45 -0
  69. data/docs/agent/skills/write-the-docs.md +54 -0
  70. data/docs/agent/topics/common-project-paths.md +117 -0
  71. data/docs/agent/topics/dev-tooling-usage.md +202 -0
  72. data/docs/agent/topics/devops-ci-cd.md +55 -0
  73. data/docs/agent/topics/product-docs-deployment.md +25 -0
  74. data/lib/docopslab/dev/auto_fix_asciidoc.rb +46 -0
  75. data/lib/docopslab/dev/checkers.rb +108 -0
  76. data/lib/docopslab/dev/config_manager.rb +241 -0
  77. data/lib/docopslab/dev/file_utils.rb +140 -0
  78. data/lib/docopslab/dev/git_hooks.rb +140 -0
  79. data/lib/docopslab/dev/help.rb +121 -0
  80. data/lib/docopslab/dev/initializer.rb +95 -0
  81. data/lib/docopslab/dev/linters.rb +451 -0
  82. data/lib/docopslab/dev/log_parser.rb +31 -0
  83. data/lib/docopslab/dev/paths.rb +46 -0
  84. data/lib/docopslab/dev/script_manager.rb +136 -0
  85. data/lib/docopslab/dev/spell_check.rb +194 -0
  86. data/lib/docopslab/dev/sync_ops.rb +468 -0
  87. data/lib/docopslab/dev/tasks.rb +440 -0
  88. data/lib/docopslab/dev/tool_execution.rb +68 -0
  89. data/lib/docopslab/dev/version.rb +8 -0
  90. data/lib/docopslab/dev.rb +392 -0
  91. data/specs/data/default-manifest.yml +64 -0
  92. data/specs/data/manifest-schema.yaml +63 -0
  93. data/specs/data/tasks-def.yml +321 -0
  94. data/specs/data/tools.yml +60 -0
  95. metadata +362 -0
@@ -0,0 +1,56 @@
1
+ text := import("text")
2
+
3
+ matches := []
4
+ lines := text.split(scope, "\n")
5
+
6
+ // byte starts (CRLF-safe)
7
+ starts := []
8
+ run := 0
9
+ i := 0
10
+ for i < len(lines) {
11
+ starts = append(starts, run)
12
+ run = run + len(lines[i]) + 1
13
+ i = i + 1
14
+ }
15
+
16
+ idRe := `^\[\[[A-Za-z0-9_-]+\]\]$`
17
+ h2plusRe := `^={2,}\s+.+$`
18
+ commentRe := `^\s*//`
19
+ condRe := `^\s*(ifdef|ifndef|ifeval)::`
20
+
21
+ j := 0
22
+ for j < len(lines) {
23
+ line := lines[j]
24
+
25
+ // Check if this is a section heading (level 2+)
26
+ if text.re_match(h2plusRe, line) {
27
+ // Walk backward from j-1 to find the ID, skipping comments/conditionals
28
+ hasID := false
29
+ k := j - 1
30
+
31
+ for k >= 0 {
32
+ candidate := text.trim_space(lines[k])
33
+
34
+ // Skip empty lines, comments, and conditionals
35
+ if candidate == "" || text.re_match(commentRe, candidate) || text.re_match(condRe, candidate) {
36
+ k = k - 1
37
+ continue
38
+ }
39
+
40
+ // Check if this line is an ID
41
+ if text.re_match(idRe, candidate) {
42
+ hasID = true
43
+ }
44
+
45
+ // Stop at first non-skippable line
46
+ break
47
+ }
48
+
49
+ if !hasID {
50
+ begin := starts[j]
51
+ end := begin + len(line)
52
+ matches = append(matches, {"begin": begin, "end": end})
53
+ }
54
+ }
55
+ j = j + 1
56
+ }
@@ -0,0 +1,121 @@
1
+ text := import("text")
2
+
3
+ matches := []
4
+ lines := text.split(scope, "\n")
5
+
6
+ // byte starts (CRLF-safe)
7
+ starts := []
8
+ run := 0
9
+ i := 0
10
+ for i < len(lines) {
11
+ starts = append(starts, run)
12
+ run = run + len(lines[i]) + 1
13
+ i = i + 1
14
+ }
15
+
16
+ commentRe := `^\s*//`
17
+ condRe := `^\s*(ifdef|ifndef|ifeval)::`
18
+ idRe := `^\[\[[A-Za-z0-9_-]+\]\]$`
19
+ h1Re := `^==\s+.+$`
20
+
21
+ j := 0
22
+ for j < len(lines) {
23
+ line := lines[j]
24
+
25
+ // Check if this is a level-1 heading
26
+ if text.re_match(h1Re, line) {
27
+ // Step 1: Walk backward from heading to find ID (required)
28
+ k := j - 1
29
+ idLineNum := -1
30
+
31
+ // Skip backwards over comments/conditionals between ID and heading
32
+ for k >= 0 {
33
+ candidate := text.trim_space(lines[k])
34
+
35
+ // Skip comments and conditionals
36
+ if text.re_match(commentRe, candidate) || text.re_match(condRe, candidate) {
37
+ k = k - 1
38
+ continue
39
+ }
40
+
41
+ // If blank, no ID found
42
+ if candidate == "" {
43
+ break
44
+ }
45
+
46
+ // Check if this is the ID
47
+ if text.re_match(idRe, candidate) {
48
+ idLineNum = k
49
+ }
50
+
51
+ // Stop at first non-comment/conditional line
52
+ break
53
+ }
54
+
55
+ // If no ID was found, flag this heading
56
+ if idLineNum == -1 {
57
+ begin := starts[j]
58
+ end := begin + len(line)
59
+ matches = append(matches, {"begin": begin, "end": end})
60
+ j = j + 1
61
+ continue
62
+ }
63
+
64
+ // Step 2: Walk backward from ID to find start of section header block
65
+ // Section header includes: opening conditionals/comments that are adjacent to ID (no blanks between)
66
+ m := idLineNum - 1
67
+ sectionStart := idLineNum
68
+
69
+ for m >= 0 {
70
+ candidate := text.trim_space(lines[m])
71
+
72
+ // If blank, stop - section header block ends here
73
+ if candidate == "" {
74
+ break
75
+ }
76
+
77
+ // If comment or conditional, it's part of section header block
78
+ if text.re_match(commentRe, candidate) || text.re_match(condRe, candidate) {
79
+ sectionStart = m
80
+ m = m - 1
81
+ continue
82
+ }
83
+
84
+ // Hit other content, stop
85
+ break
86
+ }
87
+
88
+ // Step 3: Count blank lines immediately before section header block start
89
+ n := sectionStart - 1
90
+ blanks := 0
91
+
92
+ for n >= 0 {
93
+ candidate := text.trim_space(lines[n])
94
+
95
+ // Count consecutive blank lines
96
+ if candidate == "" {
97
+ blanks = blanks + 1
98
+ n = n - 1
99
+ continue
100
+ }
101
+
102
+ // Hit non-blank, stop counting
103
+ break
104
+ }
105
+
106
+ // If we reached the start of file without hitting content, don't require blanks
107
+ if n < 0 {
108
+ j = j + 1
109
+ continue
110
+ }
111
+
112
+ // Require exactly 2 blank lines before the section header block
113
+ if blanks != 2 {
114
+ begin := starts[j]
115
+ end := begin + len(line)
116
+ matches = append(matches, {"begin": begin, "end": end})
117
+ }
118
+ }
119
+
120
+ j = j + 1
121
+ }
@@ -0,0 +1,53 @@
1
+ text := import("text")
2
+
3
+ matches := []
4
+
5
+ lines := text.split(scope, "\n")
6
+
7
+ starts := []
8
+ run := 0
9
+ i := 0
10
+ for i < len(lines) {
11
+ line := lines[i]
12
+ starts = append(starts, run)
13
+ run = run + len(line) + 1
14
+ i = i + 1
15
+ }
16
+
17
+ // require an alnum/closer BEFORE .!? to avoid leading "." list markers
18
+ boundary := `[A-Za-z0-9\)\]"'][.!?]['")\]]*[ \t]+[A-Z]`
19
+
20
+ j := 0
21
+ for j < len(lines) {
22
+ line := lines[j]
23
+
24
+ // skip attribute/table/heading lines
25
+ if text.re_match(`^[:|=]`, line) {
26
+ j = j + 1
27
+ continue
28
+ }
29
+
30
+ // skip numbered list items (e.g., "1. Something") unless prefaced by comment chars
31
+ // Allow: // 1. Something or # 1. Another thing
32
+ // Don't allow: 1. Something
33
+ if text.re_match(`^\d+\.\s+\w`, line) && !text.re_match(`^[/#]+\s*\d+\.\s+\w`, line) {
34
+ j = j + 1
35
+ continue
36
+ }
37
+
38
+ // skip ALL commented lines that start with number and period (like "# 1. Something")
39
+ if text.re_match(`^[/#]+\s*\d+\.\s`, line) {
40
+ j = j + 1
41
+ continue
42
+ }
43
+
44
+ if text.re_match(boundary, line) {
45
+ begin := starts[j]
46
+ end := begin + len(line)
47
+ matches = append(matches, {"begin": begin, "end": end})
48
+ }
49
+
50
+ j = j + 1
51
+ }
52
+
53
+ // no explicit return
@@ -0,0 +1,5 @@
1
+ # DocOps Lab Vale Configuration
2
+ # Combined base and project configuration for consistent Vale linting
3
+
4
+ MinAlertLevel = error
5
+ StylesPath = .vendor/vale/styles
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env bash
2
+ # DocOps Lab pre-commit hook template
3
+ # Managed by docopslab-dev gem
4
+ # Developer-friendly: warns about issues but doesn't block commits
5
+
6
+ set -e
7
+
8
+ echo "๐Ÿช DocOps Lab pre-commit advisory checks..."
9
+
10
+ # Get list of staged files
11
+ STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)
12
+
13
+ if [[ -z "$STAGED_FILES" ]]; then
14
+ echo "โ„น๏ธ No files staged for commit."
15
+ exit 0
16
+ fi
17
+
18
+ # Advisory config sync check (non-blocking)
19
+ if [[ -f ".config/docopslab-dev.yml" ]] && command -v bundle &> /dev/null; then
20
+ echo "๐Ÿ”„ Checking config sync status..."
21
+ if bundle exec rake labdev:config:sync --dry-run &> /dev/null; then
22
+ echo "โœ… Config sync up to date"
23
+ else
24
+ echo "๐Ÿ’ก Config sync available. Run after commit: bundle exec rake labdev:config:sync"
25
+ fi
26
+ fi
27
+
28
+ # Quick syntax check on staged Ruby files (non-blocking)
29
+ RUBY_FILES=$(echo "$STAGED_FILES" | grep -E '\.(rb|rake)$|^Rakefile$' || true)
30
+ if [[ -n "$RUBY_FILES" ]]; then
31
+ echo "๐Ÿ” Quick syntax check on staged Ruby files..."
32
+ for file in $RUBY_FILES; do
33
+ if ! ruby -c "$file" &> /dev/null; then
34
+ echo "โš ๏ธ Syntax error in $file - consider fixing before push"
35
+ fi
36
+ done
37
+ fi
38
+
39
+ # For Bash files, ensure proper shebang
40
+ # Look for .sh files recursively but also search for files with 1. no extensions AND 2. a shebang on the first line that includes "bash" or "sh"
41
+ BASH_FILES=$(echo "$STAGED_FILES" | grep -E '\.sh$|^([^./]+)$' | while read -r file; do
42
+ if head -n 1 "$file" | grep -qE '^#!.*\b(bash|sh)\b'; then
43
+ echo "$file"
44
+ fi
45
+ done)
46
+ if [[ -n "$BASH_FILES" ]]; then
47
+ for file in $BASH_FILES; do
48
+ if head -n 1 "$file" | grep -qv '^#!/usr/bin/env bash'; then
49
+ echo "โŒ Incorrect shebang in $file. Please use '#!/usr/bin/env bash'."
50
+ exit 1
51
+ fi
52
+ done
53
+ fi
54
+
55
+ # Show helpful next steps
56
+ echo ""
57
+ echo "๐Ÿ’ก After committing, consider running:"
58
+ echo " โ€ข bundle exec rake labdev:heal # Auto-fix linting issues"
59
+ echo " โ€ข bundle exec rake labdev:assess # Check environment"
60
+ echo " โ€ข bundle exec rake labdev:lint:all # Full quality check"
61
+ echo ""
62
+ echo "โœ… Commit proceeding (full quality gate runs on push)"
63
+ exit 0
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env bash
2
+ # DocOps Lab pre-push hook template
3
+ # Managed by docopslab-dev gem
4
+ # Runs comprehensive linting before pushing to prevent issues in CI
5
+
6
+ set -e
7
+
8
+ echo "๐Ÿš€ DocOps Lab pre-push quality gate..."
9
+
10
+ # Check if we have any commits to push
11
+ LOCAL=$(git rev-parse @)
12
+ REMOTE=$(git rev-parse '@{u}' 2>/dev/null || echo "")
13
+ BASE=$(git merge-base @ '@{u}' 2>/dev/null || echo "")
14
+
15
+ if [[ -z "$REMOTE" ]]; then
16
+ echo "โ„น๏ธ No remote tracking branch found, skipping pre-push checks."
17
+ exit 0
18
+ fi
19
+
20
+ if [[ "$LOCAL" = "$REMOTE" ]]; then
21
+ echo "โ„น๏ธ No new commits to push."
22
+ exit 0
23
+ fi
24
+
25
+ if git diff --cached --name-only | grep -q 'Gemfile'; then
26
+ if git diff --cached -U0 | grep -E '^\+.*gem [a-zA-Z0-9_-]+ .+ path:\s*['"'"'"].+['"'"'"]'; then
27
+ echo ""
28
+ echo "โš ๏ธ Warning: Detected gems with local path: parameters in Gemfile changes."
29
+ echo "๐Ÿ’ก Consider using published gems from RubyGems.org or Git hosting services instead."
30
+ echo ""
31
+ fi
32
+ fi
33
+
34
+ echo "๐Ÿ“‹ Checking commits from $BASE to $LOCAL..."
35
+
36
+ # Run all linters on the codebase
37
+ echo "๐Ÿ” Running comprehensive linting..."
38
+
39
+ # Check config sync status first
40
+ if [[ -f ".config/docopslab-dev.yml" ]]; then
41
+ echo "๐Ÿ”„ Verifying config sync..."
42
+ if command -v bundle &> /dev/null; then
43
+ if ! bundle exec rake labdev:config:sync --dry-run &> /dev/null; then
44
+ echo "โŒ Config sync needed. Run: bundle exec rake labdev:config:sync"
45
+ exit 1
46
+ fi
47
+ echo "โœ… Config sync verified"
48
+ fi
49
+ fi
50
+
51
+ # Run all linters
52
+ if command -v bundle &> /dev/null; then
53
+ echo "๐Ÿงน Running all linters..."
54
+ if ! bundle exec rake labdev:lint:all; then
55
+ echo ""
56
+ echo "โŒ Quality gate failed!"
57
+ echo "๐Ÿ’ก Suggested workflow:"
58
+ echo " 1. Run: bundle exec rake labdev:heal"
59
+ echo " 2. Review changes with: git diff"
60
+ echo " 3. Commit auto-fixes: git commit -am 'Auto-fix linting issues'"
61
+ echo " 4. Fix remaining issues manually"
62
+ echo " 5. Try pushing again"
63
+ echo ""
64
+ echo "๐Ÿšซ Or bypass with: git push --no-verify"
65
+ exit 1
66
+ fi
67
+ else
68
+ echo "โš ๏ธ Bundle not available, skipping linting checks"
69
+ fi
70
+
71
+ echo "โœ… Pre-push quality gate passed"
72
+ exit 0
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ def generate_id_from_heading heading
4
+ heading.gsub(/\b(a|an|the|and|or|but|for|nor|on|at|to|from|by|in|of|is|are|was|were|be|being|been)\b/i, '')
5
+ .gsub(/[^\w\s-]/, '').strip
6
+ .squeeze(' ')
7
+ .gsub(' ', '-').downcase
8
+ heading.strip.downcase.gsub(/[^a-z0-9]+/, '-').gsub(/^-|-$/, '')
9
+ end
10
+
11
+ def process_adoc_file file_path
12
+ lines = File.readlines(file_path)
13
+ updated_lines = []
14
+ i = 0
15
+
16
+ while i < lines.size
17
+ line = lines[i]
18
+ if line =~ /^(={2,})\s([A-Z].*)$/ # Match AsciiDoc headings (2+ = chars)
19
+ Regexp.last_match(1)
20
+ heading_text = Regexp.last_match(2)
21
+
22
+ # Check if previous line is an explicit ID
23
+ if i.zero? || lines[i - 1] !~ /^\[\[.*\]\]$/
24
+ generated_id = generate_id_from_heading(heading_text)
25
+ updated_lines << "[[#{generated_id}]]\n"
26
+ end
27
+ end
28
+ updated_lines << line
29
+ i += 1
30
+ end
31
+
32
+ File.write(file_path, updated_lines.join)
33
+ puts "Processed file: #{file_path}"
34
+ end
35
+
36
+ # We need an alternative to optparse, hopefully using no dependencies
37
+
38
+ if ARGV.empty?
39
+ puts 'Usage: ruby adoc_section_ids.rb <asciidoc_file1> [<asciidoc_file2> ...]'
40
+ exit 1
41
+ end
42
+
43
+ ARGV.each do |file_path|
44
+ unless File.exist?(file_path)
45
+ puts "File not found: #{file_path}"
46
+ next
47
+ end
48
+
49
+ process_adoc_file(file_path)
50
+ end
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env bash
2
+ # Common build functions for DocOps Lab Ruby gem projects
3
+ # This library provides reusable functions for building gems and Docker images
4
+
5
+ # Colors for output
6
+ RED='\033[0;31m'
7
+ GREEN='\033[0;32m'
8
+ YELLOW='\033[1;33m'
9
+ NC='\033[0m' # No Color
10
+
11
+ # Project configuration - these should be set by the calling script
12
+ PROJECT_NAME="${PROJECT_NAME:-$(basename "$(pwd)")}"
13
+ DOCKER_ORG="${DOCKER_ORG:-docopslab}"
14
+ GEMSPEC_FILE="${GEMSPEC_FILE:-${PROJECT_NAME}.gemspec}"
15
+ CLI_EXECUTABLE="${CLI_EXECUTABLE:-exe/${PROJECT_NAME}}"
16
+ EXAMPLE_FILE="${EXAMPLE_FILE:-examples/minimal-example.yml}"
17
+ TEST_SPEC_PATH="${TEST_SPEC_PATH:-specs/tests/rspec/}"
18
+
19
+ # Common validation functions
20
+ check_project_root() {
21
+ if [ ! -f "$GEMSPEC_FILE" ]; then
22
+ echo -e "${RED}โŒ Error: $GEMSPEC_FILE not found. Run this script from the project root.${NC}"
23
+ exit 1
24
+ fi
25
+ }
26
+
27
+ check_docker_available() {
28
+ if ! command -v docker &> /dev/null; then
29
+ echo -e "${RED}โŒ Error: Docker is not installed or not in PATH${NC}"
30
+ exit 1
31
+ fi
32
+ }
33
+
34
+ check_git_clean() {
35
+ if [ -n "$(git status --porcelain)" ]; then
36
+ echo -e "${RED}โŒ Error: Working directory is not clean. Commit or stash changes first.${NC}"
37
+ git status --short
38
+ exit 1
39
+ fi
40
+ }
41
+
42
+ check_main_branch() {
43
+ current_branch=$(git branch --show-current)
44
+ if [ "$current_branch" != "main" ]; then
45
+ echo -e "${YELLOW}โš ๏ธ Warning: Not on main branch (currently on: $current_branch)${NC}"
46
+ read -p "Continue anyway? (y/N): " -n 1 -r
47
+ echo
48
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
49
+ echo "Aborted."
50
+ exit 1
51
+ fi
52
+ fi
53
+ }
54
+
55
+ check_bundle_installed() {
56
+ if [ ! -f "Gemfile" ]; then
57
+ echo -e "${YELLOW}โš ๏ธ Warning: No Gemfile found. Some operations may require dependencies.${NC}"
58
+ return
59
+ fi
60
+
61
+ if ! bundle check > /dev/null 2>&1; then
62
+ echo -e "${YELLOW}๐Ÿ“ฆ Installing gem dependencies...${NC}"
63
+ bundle install
64
+ fi
65
+ }
66
+
67
+ # Get current version from README.adoc by parsing directly
68
+ get_current_version() {
69
+ grep '^:this_prod_vrsn:' README.adoc | sed 's/^:this_prod_vrsn:[[:space:]]*//' | tr -d '\r'
70
+ }
71
+
72
+ # Get next version from README.adoc by parsing directly
73
+ get_next_version() {
74
+ grep '^:next_prod_vrsn:' README.adoc | sed 's/^:next_prod_vrsn:[[:space:]]*//' | tr -d '\r'
75
+ }
76
+
77
+ # Docker build and test functions
78
+ build_docker_image() {
79
+ local version=$1
80
+ local docker_args="${2:-}"
81
+
82
+ echo -e "${YELLOW}๐Ÿณ Building Docker image...${NC}"
83
+ # shellcheck disable=SC2086
84
+ docker build ${docker_args} -t "${DOCKER_ORG}/${PROJECT_NAME}:${version}" .
85
+ docker tag "${DOCKER_ORG}/${PROJECT_NAME}:${version}" "${DOCKER_ORG}/${PROJECT_NAME}:latest"
86
+ }
87
+
88
+ test_docker_image() {
89
+ local version=$1
90
+ local image_name="${DOCKER_ORG}/${PROJECT_NAME}:${version}"
91
+
92
+ echo -e "${YELLOW}๐Ÿงช Testing Docker image...${NC}"
93
+ docker run --rm -v "$(pwd):/workdir" "${image_name}" --version
94
+
95
+ if [ -f "$EXAMPLE_FILE" ]; then
96
+ docker run --rm -v "$(pwd):/workdir" "${image_name}" "${EXAMPLE_FILE}" --dry
97
+ fi
98
+ }
99
+
100
+ # Test functions
101
+ run_rspec_tests() {
102
+ if [ -d "$TEST_SPEC_PATH" ]; then
103
+ echo -e "${YELLOW}๐Ÿงช Running RSpec tests...${NC}"
104
+ bundle exec rspec "$TEST_SPEC_PATH" --format documentation
105
+ else
106
+ echo -e "${YELLOW}โš ๏ธ No RSpec tests found at $TEST_SPEC_PATH${NC}"
107
+ fi
108
+ }
109
+
110
+ test_cli_functionality() {
111
+ if [ -x "$CLI_EXECUTABLE" ]; then
112
+ echo -e "${YELLOW}๐Ÿงช Testing CLI functionality...${NC}"
113
+ $CLI_EXECUTABLE --version
114
+
115
+ if [ -f "$EXAMPLE_FILE" ]; then
116
+ $CLI_EXECUTABLE "$EXAMPLE_FILE" --dry
117
+ fi
118
+ else
119
+ echo -e "${YELLOW}โš ๏ธ CLI executable not found at $CLI_EXECUTABLE${NC}"
120
+ fi
121
+ }
122
+
123
+ # Gem build functions
124
+ build_gem() {
125
+ echo -e "${YELLOW}๐Ÿ’Ž Building gem...${NC}"
126
+ bundle exec rake build
127
+ }
128
+
129
+ test_built_gem() {
130
+ local current_version
131
+ current_version=$(get_current_version)
132
+ local gem_file="pkg/${PROJECT_NAME}-${current_version}.gem"
133
+
134
+ if [ ! -f "$gem_file" ]; then
135
+ echo -e "${RED}โŒ Error: Expected gem file not found: $gem_file${NC}"
136
+ exit 1
137
+ fi
138
+
139
+ echo -e "${GREEN}โœ… Built gem: $gem_file${NC}"
140
+ echo "$gem_file"
141
+ }
142
+
143
+ # Success message functions
144
+ show_build_success() {
145
+ local version=$1
146
+ local gem_file=$2
147
+
148
+ echo
149
+ echo -e "${GREEN}๐ŸŽ‰ Build completed successfully!${NC}"
150
+ echo "=================================="
151
+ echo -e "${GREEN}๐Ÿ“‹ Version: $version${NC}"
152
+ echo -e "${GREEN}๐Ÿ’Ž Gem: $gem_file${NC}"
153
+ echo -e "${GREEN}๐Ÿณ Docker: ${DOCKER_ORG}/${PROJECT_NAME}:$version${NC}"
154
+ echo
155
+ }
156
+
157
+ show_docker_success() {
158
+ local version=$1
159
+
160
+ echo
161
+ echo -e "${GREEN}๐ŸŽ‰ Docker build completed successfully!${NC}"
162
+ echo "=================================="
163
+ echo -e "${GREEN}๐Ÿ“‹ Version: $version${NC}"
164
+ echo -e "${GREEN}๐Ÿณ Images built:${NC}"
165
+ echo " ${DOCKER_ORG}/${PROJECT_NAME}:$version"
166
+ echo " ${DOCKER_ORG}/${PROJECT_NAME}:latest"
167
+ }
168
+
169
+ # Version bump functions
170
+ bump_version() {
171
+ local current_version
172
+ local next_version
173
+ current_version=$(get_current_version)
174
+ next_version=$(get_next_version)
175
+
176
+ if [ "$current_version" = "$next_version" ]; then
177
+ echo -e "${RED}โŒ Error: Current and next versions are the same: $current_version${NC}"
178
+ echo "Update :next_prod_vrsn: in README.adoc first"
179
+ exit 1
180
+ fi
181
+
182
+ echo -e "${YELLOW}๐Ÿ“ Bumping version from $current_version to $next_version...${NC}"
183
+
184
+ # Update the current version to match next version
185
+ sed -i "s/^:this_prod_vrsn: $current_version/:this_prod_vrsn: $next_version/" README.adoc
186
+
187
+ # Commit the version bump
188
+ git add README.adoc
189
+ git commit -m "Release v$next_version"
190
+ git tag "v$next_version"
191
+
192
+ echo -e "${GREEN}โœ… Version bumped and tagged: v$next_version${NC}"
193
+ }
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env bash
2
+ # Generic Docker build script for DocOps Lab projects
3
+ # Usage: Set PROJECT_NAME and DOCKER_ORG, then run this script
4
+
5
+ set -e
6
+
7
+ # Load common build functions from centrally managed location
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+
10
+ # Try to find build-common.sh in various locations
11
+ if [ -f "$SCRIPT_DIR/build-common.sh" ]; then
12
+ # shellcheck source=build-common.sh
13
+ source "$SCRIPT_DIR/build-common.sh"
14
+ elif [ -f "$SCRIPT_DIR/lib/build-common.sh" ]; then
15
+ # shellcheck source=build-common.sh
16
+ source "$SCRIPT_DIR/lib/build-common.sh"
17
+ elif [ -f "scripts/.vendor/docopslab/build-common.sh" ]; then
18
+ # shellcheck source=build-common.sh
19
+ source "scripts/.vendor/docopslab/build-common.sh"
20
+ else
21
+ echo "โŒ Error: build-common.sh not found. Run 'rake labdev:config:sync' to get centrally managed scripts."
22
+ exit 1
23
+ fi
24
+
25
+ # Project configuration - override these in your calling script or environment
26
+ PROJECT_NAME="${PROJECT_NAME:-$(basename "$(pwd)")}"
27
+ DOCKER_ORG="${DOCKER_ORG:-docopslab}"
28
+
29
+ echo -e "${GREEN}๐Ÿณ ${PROJECT_NAME} Docker Build Script${NC}"
30
+ echo "=================================="
31
+
32
+ # Validation
33
+ check_project_root
34
+ check_docker_available
35
+
36
+ # Get current version
37
+ current_version=$(get_current_version)
38
+ echo -e "${GREEN}๐Ÿ“‹ Current version: $current_version${NC}"
39
+
40
+ # Check if gem exists in pkg/, if not build it
41
+ gem_file="pkg/${PROJECT_NAME}-$current_version.gem"
42
+ if [ ! -f "$gem_file" ]; then
43
+ echo -e "${YELLOW}๐Ÿ”จ Gem not found in pkg/. Building gem first...${NC}"
44
+ check_bundle_installed
45
+ build_gem
46
+ echo -e "${GREEN}โœ… Gem built: $gem_file${NC}"
47
+ else
48
+ echo -e "${GREEN}๐Ÿ“‹ Using existing gem: $gem_file${NC}"
49
+ fi
50
+
51
+ # Build and test Docker image
52
+ build_docker_image "$current_version"
53
+ test_docker_image "$current_version"
54
+
55
+ # Show success message
56
+ show_docker_success "$current_version"
57
+
58
+ echo
59
+ echo "Test the image with:"
60
+ echo " docker run --rm -v \$(pwd):/workdir ${DOCKER_ORG}/${PROJECT_NAME}:$current_version --version"
61
+
62
+ if [ -f "$EXAMPLE_FILE" ]; then
63
+ echo " docker run --rm -v \$(pwd):/workdir ${DOCKER_ORG}/${PROJECT_NAME}:$current_version $EXAMPLE_FILE --dry"
64
+ fi