@hed-hog/cli 0.0.109 → 0.0.111
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/dist/package.json +1 -1
- package/dist/src/modules/developer/developer.service.d.ts +2 -0
- package/dist/src/modules/developer/developer.service.js +52 -2
- package/dist/src/modules/developer/developer.service.js.map +1 -1
- package/dist/src/modules/hedhog/services/diff.service.d.ts +5 -2
- package/dist/src/modules/hedhog/services/diff.service.js +46 -12
- package/dist/src/modules/hedhog/services/diff.service.js.map +1 -1
- package/dist/src/modules/hedhog/services/migration.service.d.ts +3 -0
- package/dist/src/modules/hedhog/services/migration.service.js +61 -10
- package/dist/src/modules/hedhog/services/migration.service.js.map +1 -1
- package/dist/src/templates/deployment/scripts.promote-production.js.ejs +277 -0
- package/dist/src/templates/deployment/workflow.deploy.yml.ejs +174 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -4,6 +4,11 @@ on:
|
|
|
4
4
|
branches:
|
|
5
5
|
- <%= config.deployBranch %>
|
|
6
6
|
workflow_dispatch:
|
|
7
|
+
inputs:
|
|
8
|
+
apps:
|
|
9
|
+
description: 'Apps para deploy (ex: api, admin, api,admin) ou "all" para todos'
|
|
10
|
+
required: false
|
|
11
|
+
default: 'all'
|
|
7
12
|
env:
|
|
8
13
|
REGISTRY: <%= config.containerRegistry %>
|
|
9
14
|
NAMESPACE: <%= config.namespace %>
|
|
@@ -18,6 +23,10 @@ env:
|
|
|
18
23
|
NEXT_PUBLIC_API_BASE_URL: <%= config.apiDomain || '' %>
|
|
19
24
|
INTERNAL_API_URL: <%= config.apiDomain || '' %>
|
|
20
25
|
<% } %>
|
|
26
|
+
<% (config.apps || []).filter(function(a) { return a !== 'api' && a !== 'admin'; }).forEach(function(app) { %>
|
|
27
|
+
# <%= app %> environment
|
|
28
|
+
<%= app.toUpperCase() %>_API_BASE_URL: <%= config.apiDomain || '' %>
|
|
29
|
+
<% }); %>
|
|
21
30
|
jobs:
|
|
22
31
|
apply-cluster-config:
|
|
23
32
|
name: Apply Kubernetes and Helm configs
|
|
@@ -53,6 +62,14 @@ jobs:
|
|
|
53
62
|
name: Deploy API
|
|
54
63
|
runs-on: ubuntu-latest
|
|
55
64
|
needs: apply-cluster-config
|
|
65
|
+
if: >-
|
|
66
|
+
(github.event_name == 'push' &&
|
|
67
|
+
(!contains(github.event.head_commit.message, '[deploy:') ||
|
|
68
|
+
contains(github.event.head_commit.message, '[deploy:api]'))) ||
|
|
69
|
+
(github.event_name == 'workflow_dispatch' &&
|
|
70
|
+
(github.event.inputs.apps == '' ||
|
|
71
|
+
github.event.inputs.apps == 'all' ||
|
|
72
|
+
contains(github.event.inputs.apps, 'api')))
|
|
56
73
|
# Required GitHub Secrets for API deployment:
|
|
57
74
|
# - DATABASE_URL: PostgreSQL connection string (format: postgresql://user:pass@host:port/dbname)
|
|
58
75
|
# - JWT_SECRET: 64-char hex string or base64 key for JWT signing
|
|
@@ -361,6 +378,19 @@ jobs:
|
|
|
361
378
|
<% if ((config.apps || []).includes('api')) { %>
|
|
362
379
|
- deploy-api
|
|
363
380
|
<% } %>
|
|
381
|
+
if: >-
|
|
382
|
+
always() &&
|
|
383
|
+
needs.apply-cluster-config.result == 'success' &&
|
|
384
|
+
<% if ((config.apps || []).includes('api')) { %>
|
|
385
|
+
(needs.deploy-api.result == 'success' || needs.deploy-api.result == 'skipped') &&
|
|
386
|
+
<% } %>
|
|
387
|
+
((github.event_name == 'push' &&
|
|
388
|
+
(!contains(github.event.head_commit.message, '[deploy:') ||
|
|
389
|
+
contains(github.event.head_commit.message, '[deploy:admin]'))) ||
|
|
390
|
+
(github.event_name == 'workflow_dispatch' &&
|
|
391
|
+
(github.event.inputs.apps == '' ||
|
|
392
|
+
github.event.inputs.apps == 'all' ||
|
|
393
|
+
contains(github.event.inputs.apps, 'admin'))))
|
|
364
394
|
steps:
|
|
365
395
|
- name: Checkout code
|
|
366
396
|
uses: actions/checkout@v6
|
|
@@ -534,3 +564,147 @@ jobs:
|
|
|
534
564
|
doctl registry repository delete-manifest "${REGISTRY_NAME}/${REPOSITORY}" "${DIGEST}" --force
|
|
535
565
|
done
|
|
536
566
|
<% } %>
|
|
567
|
+
<% (config.apps || []).filter(function(a) { return a !== 'api' && a !== 'admin'; }).forEach(function(app) { var appUpper = app.toUpperCase(); var appCapitalized = app.charAt(0).toUpperCase() + app.slice(1); %>
|
|
568
|
+
deploy-<%= app %>:
|
|
569
|
+
name: Deploy <%= appCapitalized %>
|
|
570
|
+
runs-on: ubuntu-latest
|
|
571
|
+
needs:
|
|
572
|
+
- apply-cluster-config
|
|
573
|
+
<% if ((config.apps || []).includes('api')) { %>
|
|
574
|
+
- deploy-api
|
|
575
|
+
<% } %>
|
|
576
|
+
if: >-
|
|
577
|
+
always() &&
|
|
578
|
+
needs.apply-cluster-config.result == 'success' &&
|
|
579
|
+
<% if ((config.apps || []).includes('api')) { %>
|
|
580
|
+
(needs.deploy-api.result == 'success' || needs.deploy-api.result == 'skipped') &&
|
|
581
|
+
<% } %>
|
|
582
|
+
((github.event_name == 'push' &&
|
|
583
|
+
(!contains(github.event.head_commit.message, '[deploy:') ||
|
|
584
|
+
contains(github.event.head_commit.message, '[deploy:<%= app %>]'))) ||
|
|
585
|
+
(github.event_name == 'workflow_dispatch' &&
|
|
586
|
+
(github.event.inputs.apps == '' ||
|
|
587
|
+
github.event.inputs.apps == 'all' ||
|
|
588
|
+
contains(github.event.inputs.apps, '<%= app %>'))))
|
|
589
|
+
steps:
|
|
590
|
+
- name: Checkout code
|
|
591
|
+
uses: actions/checkout@v6
|
|
592
|
+
- name: Install doctl
|
|
593
|
+
run: |
|
|
594
|
+
set -eu
|
|
595
|
+
DOCTL_VERSION="1.119.1"
|
|
596
|
+
curl -fsSL "https://github.com/digitalocean/doctl/releases/download/v${DOCTL_VERSION}/doctl-${DOCTL_VERSION}-linux-amd64.tar.gz" -o /tmp/doctl.tar.gz
|
|
597
|
+
tar -xzf /tmp/doctl.tar.gz -C /tmp
|
|
598
|
+
sudo mv /tmp/doctl /usr/local/bin/doctl
|
|
599
|
+
doctl version
|
|
600
|
+
- name: Authenticate doctl
|
|
601
|
+
run: doctl auth init -t '${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}'
|
|
602
|
+
- name: Validate DigitalOcean token
|
|
603
|
+
run: |
|
|
604
|
+
doctl auth validate
|
|
605
|
+
doctl account get
|
|
606
|
+
- name: Log in to Container Registry
|
|
607
|
+
run: doctl registry login --expiry-seconds 1200
|
|
608
|
+
- name: Build Docker image
|
|
609
|
+
run: |
|
|
610
|
+
docker build -t ${{ env.REGISTRY }}/<%= config.appName %>-<%= app %>:${{ github.sha }} \
|
|
611
|
+
--build-arg <%= appUpper %>_API_BASE_URL='${{ env.<%= appUpper %>_API_BASE_URL }}' \
|
|
612
|
+
-f apps/<%= app %>/Dockerfile apps/<%= app %>/
|
|
613
|
+
- name: Push Docker image (sha)
|
|
614
|
+
run: |
|
|
615
|
+
docker push ${{ env.REGISTRY }}/<%= config.appName %>-<%= app %>:${{ github.sha }}
|
|
616
|
+
- name: Push Docker image (latest)
|
|
617
|
+
run: |
|
|
618
|
+
docker tag ${{ env.REGISTRY }}/<%= config.appName %>-<%= app %>:${{ github.sha }} \
|
|
619
|
+
${{ env.REGISTRY }}/<%= config.appName %>-<%= app %>:latest
|
|
620
|
+
docker push ${{ env.REGISTRY }}/<%= config.appName %>-<%= app %>:latest
|
|
621
|
+
- name: Save DigitalOcean kubeconfig
|
|
622
|
+
run: doctl kubernetes cluster kubeconfig save ${{ env.K8S_CLUSTER_ID }}
|
|
623
|
+
- name: Ensure <%= app %> config exists
|
|
624
|
+
run: |
|
|
625
|
+
kubectl create configmap <%= config.appName %>-<%= app %>-config \
|
|
626
|
+
-n ${{ env.NAMESPACE }} \
|
|
627
|
+
--from-literal=<%= appUpper %>_API_BASE_URL='${{ env.<%= appUpper %>_API_BASE_URL }}' \
|
|
628
|
+
--dry-run=client -o yaml | kubectl apply -f -
|
|
629
|
+
- name: Deploy to Kubernetes
|
|
630
|
+
run: |
|
|
631
|
+
kubectl set image deployment/<%= config.appName %>-<%= app %> \
|
|
632
|
+
<%= config.appName %>-<%= app %>=${{ env.REGISTRY }}/<%= config.appName %>-<%= app %>:${{ github.sha }} \
|
|
633
|
+
-n ${{ env.NAMESPACE }}
|
|
634
|
+
kubectl rollout status deployment/<%= config.appName %>-<%= app %> -n ${{ env.NAMESPACE }} --timeout=5m || (
|
|
635
|
+
echo "--- Rollout timed out. Pod status:"
|
|
636
|
+
kubectl get pods -l app=<%= config.appName %>-<%= app %> -n ${{ env.NAMESPACE }}
|
|
637
|
+
echo "--- Recent events:"
|
|
638
|
+
kubectl describe deployment/<%= config.appName %>-<%= app %> -n ${{ env.NAMESPACE }} | tail -20
|
|
639
|
+
POD_NAME=$(kubectl get pods -l app=<%= config.appName %>-<%= app %> -n ${{ env.NAMESPACE }} --sort-by=.metadata.creationTimestamp -o name | tail -n 1 | cut -d/ -f2)
|
|
640
|
+
if [ -n "$POD_NAME" ]; then
|
|
641
|
+
echo "--- Describe newest pod: $POD_NAME"
|
|
642
|
+
kubectl describe pod "$POD_NAME" -n ${{ env.NAMESPACE }}
|
|
643
|
+
echo "--- Current logs from $POD_NAME:"
|
|
644
|
+
kubectl logs "$POD_NAME" -n ${{ env.NAMESPACE }} --tail=120 || true
|
|
645
|
+
fi
|
|
646
|
+
exit 1
|
|
647
|
+
)
|
|
648
|
+
- name: Cleanup old registry manifests (<%= appCapitalized %>)
|
|
649
|
+
run: |
|
|
650
|
+
set -eu
|
|
651
|
+
REGISTRY_NAME="${REGISTRY##*/}"
|
|
652
|
+
REPOSITORY="<%= config.appName %>-<%= app %>"
|
|
653
|
+
DIGEST_REGEX='^sha256:[a-f0-9]{64}$'
|
|
654
|
+
|
|
655
|
+
echo "Inspecting repository ${REPOSITORY} in registry ${REGISTRY_NAME}"
|
|
656
|
+
|
|
657
|
+
TAG_ROWS="$(doctl registry repository list-tags "${REGISTRY_NAME}/${REPOSITORY}" --format Tag,ManifestDigest --no-header || true)"
|
|
658
|
+
|
|
659
|
+
if [ -z "${TAG_ROWS}" ]; then
|
|
660
|
+
echo "No tags found for ${REPOSITORY}; skipping cleanup."
|
|
661
|
+
exit 0
|
|
662
|
+
fi
|
|
663
|
+
|
|
664
|
+
CURRENT_DIGEST="$(printf '%s\n' "${TAG_ROWS}" | awk -v target="${{ github.sha }}" -v regex="${DIGEST_REGEX}" '
|
|
665
|
+
$1 == target {
|
|
666
|
+
for (i = 1; i <= NF; i++) {
|
|
667
|
+
if ($i ~ regex) {
|
|
668
|
+
print $i;
|
|
669
|
+
exit;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
')"
|
|
674
|
+
|
|
675
|
+
if [ -z "${CURRENT_DIGEST}" ]; then
|
|
676
|
+
echo "::error::Unable to determine current manifest digest for ${REPOSITORY}:${{ github.sha }}"
|
|
677
|
+
exit 1
|
|
678
|
+
fi
|
|
679
|
+
|
|
680
|
+
OLD_DIGESTS="$(printf '%s\n' "${TAG_ROWS}" | awk -v keep="${CURRENT_DIGEST}" -v regex="${DIGEST_REGEX}" '
|
|
681
|
+
{
|
|
682
|
+
for (i = 1; i <= NF; i++) {
|
|
683
|
+
if ($i ~ regex && $i != keep) {
|
|
684
|
+
print $i;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
' | sort -u)"
|
|
689
|
+
|
|
690
|
+
if [ -z "${OLD_DIGESTS}" ]; then
|
|
691
|
+
echo "No old manifests to delete for ${REPOSITORY}."
|
|
692
|
+
exit 0
|
|
693
|
+
fi
|
|
694
|
+
|
|
695
|
+
echo "Deleting old manifests from ${REPOSITORY}:"
|
|
696
|
+
printf '%s\n' "${OLD_DIGESTS}"
|
|
697
|
+
|
|
698
|
+
printf '%s\n' "${OLD_DIGESTS}" | while IFS= read -r DIGEST; do
|
|
699
|
+
if [ -z "${DIGEST}" ]; then
|
|
700
|
+
continue
|
|
701
|
+
fi
|
|
702
|
+
|
|
703
|
+
if ! printf '%s\n' "${DIGEST}" | grep -Eq "${DIGEST_REGEX}"; then
|
|
704
|
+
echo "Skipping invalid digest candidate: ${DIGEST}"
|
|
705
|
+
continue
|
|
706
|
+
fi
|
|
707
|
+
|
|
708
|
+
doctl registry repository delete-manifest "${REGISTRY_NAME}/${REPOSITORY}" "${DIGEST}" --force
|
|
709
|
+
done
|
|
710
|
+
<% }); %>
|