Tea.Deploy.Cli 0.2.0

tea.deploy.cli

Interactive .NET CLI for the TEA SCM-Instructions release workflow.

Picks the right release tag for each environment, fetches delta SQL filenames automatically from Jenkins or the ApplicationStaging share, optionally triggers the tag-creation Jenkins job, and renders the DOS ticket body — without clicking through Jira / Bitbucket / Jenkins by hand.

  • Tool command: tea-deploy
  • Package id: Tea.Deploy.Cli
  • Source path: C:\TEA\tea.deploy.cli
  • Direction / spec: C:\App\AI\Deploy\DIRECTIONS.md

Install

# Install
dotnet tool install --global --source "TEA Local" Tea.Deploy.Cli

# Update later
dotnet tool update  --global --source "TEA Local" Tea.Deploy.Cli

# Uninstall
dotnet tool uninstall --global Tea.Deploy.Cli

The TEA Local NuGet source (https://nuget.tea.noctusoft.com) is already registered on TEA machines. Confirm with dotnet nuget list source.

From source

# Build + pack into nupkg\
dotnet pack C:\TEA\tea.deploy.cli -c Release

# Install the global tool from the local nupkg folder
dotnet tool install --global --add-source C:\TEA\tea.deploy.cli\nupkg Tea.Deploy.Cli

# Update later
dotnet tool update  --global --add-source C:\TEA\tea.deploy.cli\nupkg Tea.Deploy.Cli

Or run straight from source without installing:

dotnet run --project C:\TEA\tea.deploy.cli -- <args>

Requires the .NET 10 SDK.


Quick start

# 1. Put your TEA credentials in this file:
#    C:\Users\admin\.config\tea.env.json   (see "Configuration" below)

# 2. Verify the tool can reach everything:
tea-deploy --check

# 3. Get a tag-math overview of the whole workspace:
tea-deploy --tagmath

# 4. Walk through a deploy:
tea-deploy spedcdrms        # (or any other repo slug)

Run tea-deploy with no arguments at any time to get the help screen.


Commands

Command What it does
tea-deploy Show help (no parameters = help).
tea-deploy --help, -h, /? Show help.
tea-deploy --version, -v Print the tool version and exit.
tea-deploy --check Probe Bitbucket Cloud, Jira Cloud, and the internal Jenkins. Prints OK/FAIL with friendly messages. Exit 0 = all OK, 2 = something unreachable.
tea-deploy --tagmath Print a single table for every active app in the workspace: latest tag, currently in test / prod / training, hotfix bump, minor bump.
tea-deploy --pick Open the interactive app drop-down without naming a slug.
tea-deploy <app-slug> Walk through a deploy for that app (e.g. tea-deploy spedcdrms).

Deploy flags (combine with <app-slug>)

Flag What it does
--jenkins-job "<name>" Override the Jenkins job used for delta file discovery (default: "{JiraKey} Pipeline", e.g. "CDRMS Pipeline").
--no-trigger Skip the "trigger tag creation?" prompt even when the selected tag doesn't exist in Bitbucket yet.
--trigger-only Trigger the tag-creation Jenkins job and exit — no ticket rendered.
--staging-share <path> Override the UNC path used as a fallback for delta file discovery (default: \\TEA4AFSHR02\ApplicationStaging).

--check

─── tea-deploy --check ───
Verifying connectivity to required services...

  Bitbucket Cloud            https://api.bitbucket.org/2.0/                   OK
  Jira Cloud                 https://tea-applications.atlassian.net           OK
  Jenkins (TEA internal)     http://tea4svdragonet.tea.state.tx.us:8080      FAIL
    Cannot reach Jenkins (TEA internal) (http://tea4svdragonet.tea.state.tx.us:8080).
    DNS lookup failed — the host name could not be resolved.
    This is the expected behavior when you are NOT connected to the TEA VPN.
    Connect to the TEA VPN and try again.

Bitbucket and Jira are reachable; Jenkins is not.
Tag math, lints, and ticket rendering will still work without Jenkins.
Connect to the TEA VPN if you need to trigger builds or harvest deltas.

When JENKINS_TOKEN is configured, --check additionally verifies that the MA job folder is accessible (not just that Jenkins is reachable).

--tagmath

Tag-math overview for every active app (updated in the last 30 days):

─── tea-deploy --tagmath ───
Active in last 30 days: 25 of 39

╭─────────────────┬───────┬─────────────────┬─────────────────┬─────────────────┬─────────────────┬─────────────────┬─────────────────╮
│ slug            │ jira  │ latest          │ in test         │ in prod         │ in training     │ hotfix bump     │ minor bump      │
├─────────────────┼───────┼─────────────────┼─────────────────┼─────────────────┼─────────────────┼─────────────────┼─────────────────┤
│ apex            │ APEX  │ Rel_6.2.0.10    │ ?               │ ?               │ ?               │ Rel_6.2.0.11    │ Rel_6.2.1.0     │
│ spedcdrms       │ CDRMS │ Rel_8.43.6.0    │ Rel_8.43.5.2    │ Rel_8.43.5.1    │ ?               │ Rel_8.43.6.1    │ Rel_8.43.7.0    │
│ timeandeffort   │ TE    │ Rel_3.42.1.0    │ ?               │ ?               │ ?               │ Rel_3.42.1.1    │ Rel_3.42.2.0    │
│ ... (more)                                                                                                                          │
╰─────────────────┴───────┴─────────────────┴─────────────────┴─────────────────┴─────────────────┴─────────────────┴─────────────────╯
  • ? = unknown (no entry in state.yaml).
  • An env cell turns green when the deployed tag matches the latest from Bitbucket.
  • The jira column is blank until you set the Jira key for that slug in apps.yaml.

The walkthrough (tea-deploy <app> or --pick)

─── tea-deploy spedcdrms ───

OK config loaded (ricardo.vega@tea.texas.gov)
Selected: spedcdrms

Recent Rel_* tags (top 5):
  Rel_8.43.6.0    2026-05-04   <- latest
  Rel_8.43.5.2    2026-04-24
  Rel_8.43.5.1    2026-04-13
  ...

State:
  test:      Rel_8.43.5.2
  prod:      Rel_8.43.5.1
  training:  Rel_8.43.5.1

? Which environment are you deploying to?
  > test

? Which tag do you want to deploy to test?
  > Rel_8.43.6.0   (latest, ready to deploy)
    Rel_8.43.6.1   (hotfix bump from latest)
    Rel_8.43.7.0   (minor bump from latest)
    Custom tag…
    Cancel

# If the selected tag doesn't exist in Bitbucket yet (e.g. a hotfix bump),
# and JENKINS_TOKEN is configured:
Note: Rel_8.43.6.1 does not exist in Bitbucket yet.
? Trigger "Create Tag (Git) MultiApps" to push Rel_8.43.6.1 to Bitbucket?
  > Yes — trigger the tag creation job now
    No — I'll push the tag manually then re-run
    Cancel

Build #42 queued. Polling for completion...
  Create Tag (Git) MultiApps #42  [====================] SUCCESS

? Contact phone (will be saved to apps.yaml):  > 512-555-1234
? Business contact (Jira email or username):    > ramya.kasaraneni@tea.texas.gov
? Save to apps.yaml so I don't ask next time?   [Y/n]: Y

? Deployment date (YYYY-MM-DD):  > 2026-05-06
Timed for 2026-05-06 18:00 CT (per the standing 6 PM rule).

Looking for delta files in Jenkins workspace (CDRMS Pipeline)...
Found 2 delta file(s) from Jenkins workspace.

─── DOS ticket preview ───
# [spedcdrms] TEST deploy of Rel_8.43.6.1
**Issue Type:** SCM Test Deployment
...
Delta files for Rel_8.43.6.1:
- Cdrms_Stored Procedures_Delta_Rel_8.43.6.1_vs_Rel_8.43.6.0
- Cdrms_Tables_Delta_Rel_8.43.6.1_vs_Rel_8.43.6.0

? What now?
  > Save to file (spedcdrms-test-Rel_8.43.6.1.md)
    Copy to clipboard
    Save and copy
    Submit to Jira (not yet implemented)
    Cancel

Delta file discovery

The tool resolves delta files automatically in this order:

  1. Jenkins workspace API — browses the {JiraKey} Pipeline workspace for a Database/ subfolder matching the release tag. Requires VPN + JENKINS_TOKEN.
  2. UNC file share — falls back to \\TEA4AFSHR02\ApplicationStaging\{slug}\{tag}\Database\. Also requires VPN.
  3. Placeholder — if neither source is reachable, the ticket body includes a placeholder you fill in manually.

Both VPN-gated sources are tried automatically; the fallback chain is invisible to the user unless neither works.

Tag-trigger step

When you pick a tag that doesn't yet exist in Bitbucket (a hotfix or minor bump) and JENKINS_TOKEN is configured, the tool offers to trigger the "Create Tag (Git) MultiApps" Jenkins job before continuing:

  • Yes — triggers the job (parameters: Repository, Branch, Release, Email), then polls with a progress bar until SUCCESS or FAILURE.
  • No — skips; a reminder is shown to push the tag manually and re-run.
  • Cancel — exits cleanly.

The tool never triggers silently. --no-trigger suppresses the prompt entirely; --trigger-only triggers then exits without rendering a ticket.

Design rules:

  • Read-only until you confirm. The tool never mutates Bitbucket / Jira / Jenkins / state.yaml without an explicit pick.
  • It asks for missing config (Contact Phone, Business Contact) once per app — answers persist to apps.yaml.
  • It stops at every irreversible step (env, tag, trigger, ticket action) and lets you cancel.

Configuration files

tea.env.json (credentials)

Default location: C:\Users\admin\.config\tea.env.json (override with the TEA_DEPLOY_CONFIG env var).

{
  "JIRA_BASE_URL":  "https://tea-applications.atlassian.net",
  "JIRA_EMAIL":     "your.email@tea.texas.gov",
  "JIRA_API_TOKEN": "...atlassian API token...",
  "BB_PASS":        "...bitbucket app password...",
  "TEA_UID":        "TEA\\rvega",
  "TEA_PWD":        "...AD password...",
  "JENKINS_TOKEN":  "...Jenkins API token..."
}
Key What it's for
JIRA_BASE_URL Your Jira Cloud base URL (no trailing slash needed).
JIRA_EMAIL The email tied to your Atlassian account (used for Jira, Bitbucket, and Jenkins email notifications).
JIRA_API_TOKEN Atlassian API token, from id.atlassian.com/manage-profile/security/api-tokens.
BB_PASS Bitbucket app password with Repositories: Read and Workspaces: Read scopes.
TEA_UID TEA domain username in TEA\username format (the domain prefix is stripped automatically for Jenkins).
TEA_PWD TEA AD password (reserved for future use).
JENKINS_TOKEN Jenkins API token — generate at http://tea4svdragonet.tea.state.tx.us:8080/user/<username>/configure. Enables delta file discovery and tag-trigger. Optional — the tool works without it, but those steps are skipped.

apps.yaml (per-app overrides)

Default location: same directory as tea.env.json. The tool creates and updates it as you answer prompts.

overrides:
  spedcdrms:
    jira: CDRMS
    contact_phone: 512-555-1234
    business_contact: ramya.kasaraneni@tea.texas.gov
    jenkins_job: CDRMS Pipeline     # optional — defaults to "{jira} Pipeline"
  timeandeffort:
    jira: TE
  charterfirst:
    jira: CF
Field What it's for
jira Jira project key — used for the picker annotation, ticket linking, and deriving the Jenkins job name.
contact_phone / business_contact Pre-fill the required DOS-ticket fields.
jenkins_job Override the Jenkins job name for delta file discovery and tag triggering. Defaults to "{jira} Pipeline" if omitted.

state.yaml (currently-deployed tags per env)

Default location: same directory as tea.env.json.

spedcdrms:
  test:     Rel_8.43.5.2
  prod:     Rel_8.43.5.1
  training: Rel_8.43.5.1
hrrs:
  test:     Rel_6.57.1.0

This is what fills the "in test / in prod / in training" columns of --tagmath and lights up the "promote" choice in the walkthrough. Edit by hand after each successful deploy.


Common workflows

Deploy a new release of spedcdrms to test

tea-deploy --check                # confirm all services reachable
tea-deploy spedcdrms              # env=test, tag=latest
                                  # delta files auto-fetched from Jenkins if on VPN
                                  # paste the saved .md into the SCM Instructions Board

Promote spedcdrms to prod after test passes

tea-deploy spedcdrms              # env=prod; picker offers the test tag as "promote"
                                  # ticket saves with Deployment Type = "Database and Atos"

Hotfix scenario (tag doesn't exist yet)

tea-deploy spedcdrms              # env=test; pick "Rel_X.Y.Z.N+1 (hotfix bump)"
                                  # tool offers to trigger "Create Tag (Git) MultiApps"
                                  # confirm → tag pushed to Bitbucket → delta files fetched → ticket rendered

Trigger tag only, no ticket

tea-deploy spedcdrms --trigger-only   # triggers the Jenkins tag job and exits

Skip the trigger prompt

tea-deploy spedcdrms --no-trigger     # walks through normally; no tag-creation offer

Override the Jenkins job name for one run

tea-deploy spedcdrms --jenkins-job "CDRMS Pipeline"

Daily situational awareness

tea-deploy --tagmath              # one-pane view: latest, what's where, what bumps next

Network behavior

  • Bitbucket Cloud and Jira Cloud need only the public internet. They auth with your Atlassian email + API token.
  • Jenkins (tea4svdragonet:8080) and the UNC file share (\\TEA4AFSHR02\ApplicationStaging) both live on the internal TEA network. Both require the TEA VPN. Tag math, lints, and ticket rendering still work without VPN.
  • When JENKINS_TOKEN is configured and Jenkins is reachable, delta files are fetched automatically from the Jenkins workspace. The UNC share is used as a fallback. If neither is reachable the ticket gets a placeholder.
  • When something is unreachable, the tool prints plain English about why (DNS lookup failure, expired token, timeout, connection refused) and a VPN hint when appropriate.
  • Run tea-deploy --check any time to confirm what you can reach.

Tests

77 tests covering tag math, the picker UX, --check, --tagmath, friendly error messages, delta file discovery, tag triggering, and the Jira schema.

# Fast suite — no network, ~5 s
dotnet test C:\TEA\tea.deploy.cli --filter "Category!=Live"

# Full suite — fast + live (hits real Bitbucket and Jira)
dotnet test C:\TEA\tea.deploy.cli

# Live tag math with verbose output:
dotnet test C:\TEA\tea.deploy.cli `
  --filter "Category=Live&FullyQualifiedName~LiveTagMath" `
  --logger "console;verbosity=detailed"

# Live tag math for a custom set of apps:
$env:TEA_TEST_APPS = "spedcdrms,charterfirst,timeandeffort"
dotnet test C:\TEA\tea.deploy.cli `
  --filter "Category=Live&FullyQualifiedName~LiveTagMath" `
  --logger "console;verbosity=detailed"

Live tests pass with a [skipped] note on machines without tea.env.json — safe to run anywhere.

Writing a new scenario

Tests for the walkthrough use a fluent ScenarioBuilder:

// Basic walkthrough — existing tag, no Jenkins
using var result = await new ScenarioBuilder()
    .WithArgs("spedcdrms")
    .WithRepo("spedcdrms")
    .WithTags("spedcdrms", "Rel_8.43.6.0", "Rel_8.43.5.2")
    .WithState("spedcdrms", test: "Rel_8.43.5.2", prod: "Rel_8.43.5.1")
    .WithAppsOverride("spedcdrms", phone: "512", contact: "x@y.com")
    .Pick(0)              // env: test
    .Pick(0)              // tag: promote (Rel_8.43.6.0)
    .PressEnter()         // date: accept default
    .Pick(0)              // action: Save to file
    .RunAsync();

Assert.Equal(0, result.ExitCode);

// With a fake Jenkins client (delta files + tag trigger)
var fakeJenkins = new FakeJenkinsClient
{
    WorkspaceDeltaFiles = new List<string> { "Cdrms_Stored Procedures_Delta_Rel_8.43.6.1_vs_Rel_8.43.6.0" },
    TriggerBuildNumber = 42,
    TriggerResult = JenkinsBuildStatus.Success
};

using var result = await new ScenarioBuilder()
    .WithArgs("spedcdrms")
    .WithRepo("spedcdrms")
    .WithTags("spedcdrms", "Rel_8.43.6.0")        // hotfix bump won't exist in Bitbucket
    .WithState("spedcdrms", test: "Rel_8.43.6.0")
    .WithAppsOverride("spedcdrms", phone: "512", contact: "x@y.com")
    .WithJenkins(fakeJenkins)
    .Pick(0)              // env: test
    .Pick(1)              // tag: hotfix bump (doesn't exist → trigger prompt appears)
    .Pick(0)              // trigger: Yes
    .PressEnter()         // date
    .Pick(0)              // Save to file
    .RunAsync();

Pick(N) = press Down N times then Enter on a SelectionPrompt. Type(text) = TextPrompt input. PressEnter() = accept default. WithNoTrigger() / WithTriggerOnly() / WithStagingShare(path) = set the corresponding walkthrough flags.


Publishing a new version

# 1. Run the fast tests
dotnet test C:\TEA\tea.deploy.cli --filter "Category!=Live"

# 2. Pack
dotnet pack C:\TEA\tea.deploy.cli -c Release

# 3. Push to the TEA NuGet server
dotnet nuget push "C:\TEA\tea.deploy.cli\nupkg\Tea.Deploy.Cli.*.nupkg" `
  --source "TEA Local" --api-key BAGET-API-KEY

Bump the version in tea.deploy.cli.csproj (<Version>) before step 2 if this is a new release.


Troubleshooting

Install / launch

Symptom Likely cause / fix
dotnet pack fails with "SDK 10.0 not found" Install the .NET 10 SDK from https://dot.net/download. Confirm with dotnet --list-sdks.
tea-deploy: command not found after dotnet tool install succeeded Close and re-open the terminal so PATH refreshes. Run dotnet tool list -g to confirm the install, then add $env:USERPROFILE\.dotnet\tools to your PATH if needed.
dotnet tool install says "already installed" Use dotnet tool update instead.
Spectre prompts don't accept arrow keys Use a real Windows Terminal / PowerShell 7 console — VS Code's "Output" panel and some restricted shells don't render Spectre menus correctly.

Configuration

Symptom Likely cause / fix
Config file not found: ...tea.env.json Create the file at the default path or set TEA_DEPLOY_CONFIG to its location. See INSTALL.md.
Config is missing JIRA_EMAIL or BB_PASS One or more required keys are blank. Open tea.env.json and fill all required fields.
Config file is not valid JSON Usually a missing comma or unescaped backslash. JSON requires \\ for a single \TEA_UID must be "TEA\\rvega".
Delta files never appear in the ticket JENKINS_TOKEN is missing from tea.env.json, or you're off VPN. Both Jenkins and the UNC share require VPN.

Network and auth

Symptom Likely cause / fix
Authentication failed for Bitbucket Cloud App password expired — issue a new one with Repositories: Read and Workspaces: Read scopes.
Authentication failed for Jira Cloud Atlassian API token expired. Issue a new one at id.atlassian.com/manage-profile/security/api-tokens.
Cannot reach Jenkins ... DNS lookup failed ... Connect to the TEA VPN You're not on the TEA VPN. Connect; the rest of the tool still works without it.
Jenkins reachable but delta files still missing Check apps.yaml — the jenkins_job field (or jira key for auto-derivation) must match the actual Jenkins job name under MA/.
Tag trigger fails with auth error The JENKINS_TOKEN may be wrong or expired — regenerate at http://tea4svdragonet.tea.state.tx.us:8080/user/<username>/configure.

Tag math and pickers

Symptom Likely cause / fix
--tagmath columns are all ? state.yaml doesn't have entries yet. Edit it by hand at <config-dir>/state.yaml.
--tagmath jira column is blank Add jira: <KEY> under overrides.<slug> in apps.yaml.
Tool exits with Unknown repo: <slug> The slug isn't in the teaapplications workspace. Run --tagmath to see the live list.
Custom tag… rejects your input The pattern is case-sensitive: Rel_8.43.6.0 works, rel_8.43.6.0 does not. Four numeric segments required.

See also

  • C:\App\AI\Deploy\DIRECTIONS.md — design spec, conceptual model, phasing, open questions.
  • C:\App\AI\Deploy\Deploy.txt.txt — original transcript of the workflow walkthrough with Ramya.
  • INSTALL.md — two-minute setup guide for new users.

This package has no dependencies.

Version Downloads Last updated
0.4.6 2 5/8/2026
0.4.5 2 5/8/2026
0.4.4 2 5/8/2026
0.4.3 2 5/8/2026
0.4.2 2 5/8/2026
0.4.1 2 5/8/2026
0.4.0 2 5/8/2026
0.3.0 2 5/6/2026
0.2.0 1 5/6/2026
0.1.0 1 5/5/2026