Skip to content

Commit 57b4450

Browse files
authored
Merge branch 'main' into main
2 parents 740fe7c + 69f901c commit 57b4450

File tree

10 files changed

+210
-44
lines changed

10 files changed

+210
-44
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: Setup JFrog OIDC
2+
description: Obtain a JFrog access token via GitHub OIDC and configure npm to use JFrog registry proxy
3+
4+
runs:
5+
using: composite
6+
steps:
7+
- name: Get JFrog OIDC token
8+
shell: bash
9+
run: |
10+
set -euo pipefail
11+
ID_TOKEN=$(curl -sLS \
12+
-H "User-Agent: actions/oidc-client" \
13+
-H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
14+
"${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=jfrog-github" | jq .value | tr -d '"')
15+
echo "::add-mask::${ID_TOKEN}"
16+
ACCESS_TOKEN=$(curl -sLS -XPOST -H "Content-Type: application/json" \
17+
"https://databricks.jfrog.io/access/api/v1/oidc/token" \
18+
-d "{\"grant_type\": \"urn:ietf:params:oauth:grant-type:token-exchange\", \"subject_token_type\":\"urn:ietf:params:oauth:token-type:id_token\", \"subject_token\": \"${ID_TOKEN}\", \"provider_name\": \"github-actions\"}" | jq .access_token | tr -d '"')
19+
echo "::add-mask::${ACCESS_TOKEN}"
20+
if [ -z "$ACCESS_TOKEN" ] || [ "$ACCESS_TOKEN" = "null" ]; then
21+
echo "FAIL: Could not extract JFrog access token"
22+
exit 1
23+
fi
24+
echo "JFROG_ACCESS_TOKEN=${ACCESS_TOKEN}" >> "$GITHUB_ENV"
25+
echo "JFrog OIDC token obtained successfully"
26+
27+
- name: Configure npm for JFrog
28+
shell: bash
29+
run: |
30+
set -euo pipefail
31+
cat > ~/.npmrc << EOF
32+
registry=https://databricks.jfrog.io/artifactory/api/npm/db-npm/
33+
//databricks.jfrog.io/artifactory/api/npm/db-npm/:_authToken=${JFROG_ACCESS_TOKEN}
34+
always-auth=true
35+
EOF
36+
echo "npm configured to use JFrog registry"

.github/dependabot.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: npm
4+
directory: /
5+
schedule:
6+
interval: weekly
7+
- package-ecosystem: github-actions
8+
directory: /
9+
schedule:
10+
interval: weekly

.github/workflows/dco-check.yml

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,36 @@ name: DCO Check
22

33
on: [pull_request]
44

5+
permissions:
6+
contents: read
7+
pull-requests: write
8+
59
jobs:
610
check:
7-
runs-on: ubuntu-latest
11+
runs-on:
12+
group: databricks-protected-runner-group
13+
labels: linux-ubuntu-latest
814
steps:
9-
- name: Check for DCO
15+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
16+
with:
17+
fetch-depth: 0
18+
- name: Check for DCO sign-off
1019
id: dco-check
11-
uses: tisonkun/actions-dco@v1.1
20+
run: |
21+
base_sha="${{ github.event.pull_request.base.sha }}"
22+
head_sha="${{ github.event.pull_request.head.sha }}"
23+
failed=0
24+
for sha in $(git rev-list "$base_sha".."$head_sha"); do
25+
if ! git log -1 --format='%B' "$sha" | grep -qiE '^Signed-off-by: .+ <.+>'; then
26+
echo "::error::Commit $sha is missing a DCO sign-off"
27+
failed=1
28+
fi
29+
done
30+
if [ "$failed" -eq 1 ]; then
31+
exit 1
32+
fi
1233
- name: Comment about DCO status
13-
uses: actions/github-script@v6
34+
uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6
1435
if: ${{ failure() }}
1536
with:
1637
script: |

.github/workflows/main.yml

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,23 @@ on:
88
branches:
99
- main
1010

11+
permissions:
12+
contents: read
13+
id-token: write
14+
1115
jobs:
1216
lint:
13-
runs-on: ubuntu-latest
17+
runs-on:
18+
group: databricks-protected-runner-group
19+
labels: linux-ubuntu-latest
1420
steps:
15-
- uses: actions/checkout@v3
21+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
22+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
23+
with:
24+
node-version: 20
25+
- uses: ./.github/actions/setup-jfrog
1626
- name: Cache node modules
17-
uses: actions/cache@v3
27+
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
1828
env:
1929
cache-name: cache-node-modules
2030
with:
@@ -31,7 +41,9 @@ jobs:
3141
npm run lint
3242
3343
unit-test:
34-
runs-on: ubuntu-latest
44+
runs-on:
45+
group: databricks-protected-runner-group
46+
labels: linux-ubuntu-latest
3547
strategy:
3648
matrix:
3749
# only LTS versions starting from the lowest we support
@@ -41,17 +53,18 @@ jobs:
4153
NYC_REPORT_DIR: coverage_unit_node${{ matrix.node-version }}
4254

4355
steps:
44-
- uses: actions/setup-node@v4
56+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
4557
with:
4658
node-version: ${{ matrix.node-version }}
47-
- uses: actions/checkout@v3
59+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
4860
- name: Set up Python 3.10 for Node 14
4961
if: ${{ matrix.node-version == '14' }}
50-
uses: actions/setup-python@v4
62+
uses: actions/setup-python@7f4fc3e22c37d6ff65e88745f38bd3157c663f7c # v4
5163
with:
5264
python-version: '3.10'
65+
- uses: ./.github/actions/setup-jfrog
5366
- name: Cache node modules
54-
uses: actions/cache@v3
67+
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
5568
with:
5669
path: ~/.npm
5770
key: ${{ runner.os }}-${{ matrix.node-version }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
@@ -65,14 +78,16 @@ jobs:
6578
npm run test
6679
- run: tar -cvf ${{ env.NYC_REPORT_DIR }}.tar ${{ env.NYC_REPORT_DIR }}
6780
- name: Store coverage report
68-
uses: actions/upload-artifact@v4
81+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
6982
with:
7083
name: ${{ env.NYC_REPORT_DIR }}
7184
path: ${{ env.NYC_REPORT_DIR }}.tar
7285
retention-days: 1
7386

7487
e2e-test:
75-
runs-on: ubuntu-latest
88+
runs-on:
89+
group: databricks-protected-runner-group
90+
labels: linux-ubuntu-latest
7691
environment: azure-prod
7792
env:
7893
E2E_HOST: ${{ secrets.DATABRICKS_HOST }}
@@ -86,9 +101,13 @@ jobs:
86101
NYC_REPORT_DIR: coverage_e2e
87102

88103
steps:
89-
- uses: actions/checkout@v3
104+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
105+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
106+
with:
107+
node-version: 20
108+
- uses: ./.github/actions/setup-jfrog
90109
- name: Cache node modules
91-
uses: actions/cache@v3
110+
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
92111
with:
93112
path: ~/.npm
94113
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
@@ -102,30 +121,32 @@ jobs:
102121
NODE_OPTIONS="--max-old-space-size=4096" npm run e2e
103122
- run: tar -cvf ${{ env.NYC_REPORT_DIR }}.tar ${{ env.NYC_REPORT_DIR }}
104123
- name: Store coverage report
105-
uses: actions/upload-artifact@v4
124+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
106125
with:
107126
name: ${{ env.NYC_REPORT_DIR }}
108127
path: ${{ env.NYC_REPORT_DIR }}.tar
109128
retention-days: 1
110129

111130
coverage:
112131
needs: [unit-test, e2e-test]
113-
runs-on: ubuntu-latest
132+
runs-on:
133+
group: databricks-protected-runner-group
134+
labels: linux-ubuntu-latest
114135
env:
115136
cache-name: cache-node-modules
116137

117138
steps:
118-
- uses: actions/checkout@v3
139+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
119140
- name: Cache node modules
120-
uses: actions/cache@v3
141+
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
121142
with:
122143
path: ~/.npm
123144
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
124145
restore-keys: |
125146
${{ runner.os }}-build-${{ env.cache-name }}-
126147
${{ runner.os }}-build-
127148
${{ runner.os }}-
128-
- uses: actions/download-artifact@v4
149+
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
129150
with:
130151
pattern: coverage_*
131152
merge-multiple: true
@@ -135,7 +156,7 @@ jobs:
135156
rm coverage_*.tar
136157
- run: ls -la
137158
- name: Coverage
138-
uses: codecov/codecov-action@v3
159+
uses: codecov/codecov-action@ab904c41d6ece82784817410c45d8b8c02684457 # v3
139160
with:
140161
token: ${{ secrets.CODECOV_TOKEN }}
141162
fail_ci_if_error: true

.github/workflows/release.yml

Lines changed: 0 additions & 19 deletions
This file was deleted.

.npmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
package-lock=false
1+
package-lock=true

lib/utils/agentDetector.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* Detects whether the Node.js SQL driver is being invoked by an AI coding agent
3+
* by checking for well-known environment variables that agents set in their
4+
* spawned shell processes.
5+
*
6+
* Detection only succeeds when exactly one agent environment variable is present,
7+
* to avoid ambiguous attribution when multiple agent environments overlap.
8+
*
9+
* Adding a new agent requires only a new entry in `knownAgents`.
10+
*
11+
* References for each environment variable:
12+
* - ANTIGRAVITY_AGENT: Closed source. Google Antigravity sets this variable.
13+
* - CLAUDECODE: https://github.com/anthropics/claude-code (sets CLAUDECODE=1)
14+
* - CLINE_ACTIVE: https://github.com/cline/cline (shipped in v3.24.0)
15+
* - CODEX_CI: https://github.com/openai/codex (part of UNIFIED_EXEC_ENV array in codex-rs)
16+
* - CURSOR_AGENT: Closed source. Referenced in a gist by johnlindquist.
17+
* - GEMINI_CLI: https://google-gemini.github.io/gemini-cli/docs/tools/shell.html (sets GEMINI_CLI=1)
18+
* - OPENCODE: https://github.com/opencode-ai/opencode (sets OPENCODE=1)
19+
*/
20+
21+
const knownAgents: Array<{ envVar: string; product: string }> = [
22+
{ envVar: 'ANTIGRAVITY_AGENT', product: 'antigravity' },
23+
{ envVar: 'CLAUDECODE', product: 'claude-code' },
24+
{ envVar: 'CLINE_ACTIVE', product: 'cline' },
25+
{ envVar: 'CODEX_CI', product: 'codex' },
26+
{ envVar: 'CURSOR_AGENT', product: 'cursor' },
27+
{ envVar: 'GEMINI_CLI', product: 'gemini-cli' },
28+
{ envVar: 'OPENCODE', product: 'opencode' },
29+
];
30+
31+
export default function detectAgent(env: Record<string, string | undefined> = process.env): string {
32+
const detected = knownAgents.filter((a) => env[a.envVar]).map((a) => a.product);
33+
34+
if (detected.length === 1) {
35+
return detected[0];
36+
}
37+
return '';
38+
}

lib/utils/buildUserAgentString.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os from 'os';
22
import packageVersion from '../version';
3+
import detectAgent from './agentDetector';
34

45
const productName = 'NodejsDatabricksSqlConnector';
56

@@ -27,5 +28,12 @@ export default function buildUserAgentString(userAgentEntry?: string): string {
2728
}
2829

2930
const extra = [userAgentEntry, getNodeVersion(), getOperatingSystemVersion()].filter(Boolean);
30-
return `${productName}/${packageVersion} (${extra.join('; ')})`;
31+
let ua = `${productName}/${packageVersion} (${extra.join('; ')})`;
32+
33+
const agentProduct = detectAgent();
34+
if (agentProduct) {
35+
ua += ` agent/${agentProduct}`;
36+
}
37+
38+
return ua;
3139
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { expect } from 'chai';
2+
import detectAgent from '../../../lib/utils/agentDetector';
3+
4+
describe('detectAgent', () => {
5+
const allAgents = [
6+
{ envVar: 'ANTIGRAVITY_AGENT', product: 'antigravity' },
7+
{ envVar: 'CLAUDECODE', product: 'claude-code' },
8+
{ envVar: 'CLINE_ACTIVE', product: 'cline' },
9+
{ envVar: 'CODEX_CI', product: 'codex' },
10+
{ envVar: 'CURSOR_AGENT', product: 'cursor' },
11+
{ envVar: 'GEMINI_CLI', product: 'gemini-cli' },
12+
{ envVar: 'OPENCODE', product: 'opencode' },
13+
];
14+
15+
for (const { envVar, product } of allAgents) {
16+
it(`detects ${product} when ${envVar} is set`, () => {
17+
expect(detectAgent({ [envVar]: '1' })).to.equal(product);
18+
});
19+
}
20+
21+
it('returns empty string when no agent is detected', () => {
22+
expect(detectAgent({})).to.equal('');
23+
});
24+
25+
it('returns empty string when multiple agents are detected', () => {
26+
expect(detectAgent({ CLAUDECODE: '1', CURSOR_AGENT: '1' })).to.equal('');
27+
});
28+
29+
it('ignores empty env var values', () => {
30+
expect(detectAgent({ CLAUDECODE: '' })).to.equal('');
31+
});
32+
33+
it('ignores undefined env var values', () => {
34+
expect(detectAgent({ CLAUDECODE: undefined })).to.equal('');
35+
});
36+
});

tests/unit/utils/utils.test.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ describe('buildUserAgentString', () => {
3232
// Prefix: 'NodejsDatabricksSqlConnector/'
3333
// Version: three period-separated digits and optional suffix
3434
const re =
35-
/^(?<productName>NodejsDatabricksSqlConnector)\/(?<productVersion>\d+\.\d+\.\d+(-[^(]+)?)\s*\((?<comment>[^)]+)\)$/i;
35+
/^(?<productName>NodejsDatabricksSqlConnector)\/(?<productVersion>\d+\.\d+\.\d+(-[^(]+)?)\s*\((?<comment>[^)]+)\)(\s+agent\/[a-z-]+)?$/i;
3636
const match = re.exec(ua);
3737
expect(match).to.not.be.eq(null);
3838

@@ -62,6 +62,21 @@ describe('buildUserAgentString', () => {
6262
const userAgentString = buildUserAgentString(userAgentEntry);
6363
expect(userAgentString).to.include('<REDACTED>');
6464
});
65+
66+
it('appends agent suffix when agent env var is set', () => {
67+
const orig = process.env.CLAUDECODE;
68+
try {
69+
process.env.CLAUDECODE = '1';
70+
const ua = buildUserAgentString();
71+
expect(ua).to.include('agent/claude-code');
72+
} finally {
73+
if (orig === undefined) {
74+
delete process.env.CLAUDECODE;
75+
} else {
76+
process.env.CLAUDECODE = orig;
77+
}
78+
}
79+
});
6580
});
6681

6782
describe('formatProgress', () => {

0 commit comments

Comments
 (0)