Skip to content

Commit 893801e

Browse files
committed
feat: continued v4.1 impl
Signed-off-by: Sam Gammon <sam@elide.ventures>
1 parent 759f9d5 commit 893801e

12 files changed

Lines changed: 505 additions & 235 deletions

File tree

.github/codecov.yml

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,31 @@
11
codecov:
2-
require_ci_to_pass: yes
2+
max_report_age: off
3+
require_ci_to_pass: true
4+
notify:
5+
wait_for_ci: true
36

4-
ignore:
5-
- "__tests__"
6-
- "dist"
7-
- "node_modules"
7+
coverage:
8+
precision: 2
9+
round: down
10+
range: "80...100"
11+
status:
12+
project:
13+
default:
14+
informational: true
15+
patch: off
816

9-
comment: # this is a top-level key
10-
layout: "diff, flags, files"
17+
comment:
18+
layout: "reach,diff,flags,files,footer"
1119
behavior: default
1220
require_changes: false
13-
require_base: false
14-
require_head: true
21+
22+
parsers:
23+
javascript:
24+
enable_partials: yes
25+
26+
github_checks:
27+
annotations: true
28+
29+
bundle_analysis:
30+
require_bundle_changes: "bundle_increase"
31+
bundle_change_threshold: "1Kb"

.github/workflows/ci.yml

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,37 @@ jobs:
4848

4949
- name: Test
5050
id: test
51-
run: bun run test
51+
run: bun run test:ci
5252

53-
- name: "Report: Coverage"
53+
- name: "Upload: Test Results"
54+
if: ${{ !cancelled() && hashFiles('test-results/**') != '' }}
5455
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
55-
env:
56-
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
56+
with:
57+
token: ${{ secrets.CODECOV_TOKEN }}
58+
report_type: test_results
59+
files: test-results/batch1.xml,test-results/batch2.xml,test-results/batch3.xml,test-results/batch4.xml,test-results/batch5.xml,test-results/batch6.xml
60+
fail_ci_if_error: false
61+
62+
- name: "Upload: Coverage"
63+
if: ${{ !cancelled() && hashFiles('coverage/**') != '' }}
64+
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
65+
with:
66+
token: ${{ secrets.CODECOV_TOKEN }}
67+
directory: ./coverage
68+
files: coverage/lcov.info
69+
fail_ci_if_error: false
70+
71+
- name: Build
72+
run: bun run build
73+
74+
- name: "Upload: Bundle Analysis"
75+
if: ${{ !cancelled() }}
76+
run: |
77+
bun x @codecov/bundle-analyzer ./dist \
78+
--bundle-name=setup-elide \
79+
--upload-token=${{ secrets.CODECOV_TOKEN }} \
80+
--config-file=codecov-bundle.json
81+
continue-on-error: true
5782

5883
test-action:
5984
name: "Test: ${{ matrix.name }}"

__tests__/main.test.ts

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,19 @@ mock.module('../src/command', () => ({
6969
ElideCommand: { RUN: 'run', INFO: 'info' },
7070
ElideArgument: { VERSION: '--version' }
7171
}))
72+
const withSpanMock = jest.fn(
73+
async (_name: string, _op: string, fn: () => Promise<any>) => fn()
74+
)
75+
const recordMetricMock = jest.fn()
76+
const logEventMock = jest.fn()
77+
7278
mock.module('../src/telemetry', () => ({
7379
initTelemetry: initTelemetryMock,
7480
reportError: reportErrorMock,
75-
flushTelemetry: flushTelemetryMock
81+
flushTelemetry: flushTelemetryMock,
82+
withSpan: withSpanMock,
83+
recordMetric: recordMetricMock,
84+
logEvent: logEventMock
7685
}))
7786

7887
const main = await import('../src/main')
@@ -117,6 +126,9 @@ describe('action', () => {
117126
initTelemetryMock.mockClear()
118127
reportErrorMock.mockClear()
119128
flushTelemetryMock.mockClear()
129+
withSpanMock.mockClear()
130+
recordMetricMock.mockClear()
131+
logEventMock.mockClear()
120132
summaryMock.addHeading.mockClear()
121133
summaryMock.addTable.mockClear()
122134
summaryMock.write.mockClear()
@@ -138,6 +150,9 @@ describe('action', () => {
138150
elideInfoMock.mockResolvedValue(undefined)
139151
obtainVersionMock.mockResolvedValue('1.0.0')
140152
flushTelemetryMock.mockResolvedValue(undefined)
153+
withSpanMock.mockImplementation(
154+
async (_name: string, _op: string, fn: () => Promise<any>) => fn()
155+
)
141156
})
142157

143158
it('reads option inputs', async () => {
@@ -198,6 +213,50 @@ describe('action', () => {
198213
expect(setFailedMock).toHaveBeenCalled()
199214
})
200215

216+
it('should emit start and exit log events', async () => {
217+
setupMocks()
218+
await main.run({ force: true, installer: 'shell' })
219+
expect(logEventMock).toHaveBeenCalledWith(
220+
'setup-elide.start',
221+
expect.objectContaining({ installer: 'shell' })
222+
)
223+
expect(logEventMock).toHaveBeenCalledWith(
224+
'setup-elide.exit',
225+
expect.objectContaining({ status: 'success' })
226+
)
227+
})
228+
229+
it('should record install duration metric', async () => {
230+
setupMocks()
231+
await main.run({ force: true, installer: 'shell' })
232+
expect(recordMetricMock).toHaveBeenCalledWith(
233+
'setup_elide.duration_ms',
234+
expect.any(Number),
235+
'millisecond',
236+
expect.objectContaining({ installer: 'shell' })
237+
)
238+
})
239+
240+
it('should wrap install in tracing spans', async () => {
241+
setupMocks()
242+
await main.run({ force: true, installer: 'shell' })
243+
expect(withSpanMock).toHaveBeenCalledWith(
244+
'setup-elide',
245+
'setup',
246+
expect.any(Function)
247+
)
248+
expect(withSpanMock).toHaveBeenCalledWith(
249+
'install.shell',
250+
'install',
251+
expect.any(Function)
252+
)
253+
expect(withSpanMock).toHaveBeenCalledWith(
254+
'verify',
255+
'verify',
256+
expect.any(Function)
257+
)
258+
})
259+
201260
it('should use grouped output', async () => {
202261
setupMocks()
203262
await main.run({ force: true, installer: 'shell' })

__tests__/telemetry.test.ts

Lines changed: 93 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,44 @@ import { describe, it, expect, beforeEach, jest, mock } from 'bun:test'
33
const initMock = jest.fn()
44
const setTagsMock = jest.fn()
55
const captureExceptionMock = jest.fn()
6+
const captureMessageMock = jest.fn()
67
const withScopeMock = jest.fn((cb: (scope: any) => void) => {
78
cb({ setTag: jest.fn() })
89
})
10+
const startSpanMock = jest.fn((_opts: any, fn: () => Promise<any>) => fn())
11+
const metricsGaugeMock = jest.fn()
912
const flushMock = jest.fn().mockResolvedValue(true)
1013

1114
mock.module('@sentry/node', () => ({
1215
init: initMock,
1316
setTags: setTagsMock,
1417
captureException: captureExceptionMock,
18+
captureMessage: captureMessageMock,
1519
withScope: withScopeMock,
20+
startSpan: startSpanMock,
21+
metrics: { gauge: metricsGaugeMock },
1622
flush: flushMock
1723
}))
1824

19-
const { initTelemetry, reportError, flushTelemetry } = await import(
20-
'../src/telemetry'
21-
)
25+
const {
26+
initTelemetry,
27+
reportError,
28+
flushTelemetry,
29+
withSpan,
30+
recordMetric,
31+
logEvent
32+
} = await import('../src/telemetry')
2233
const { default: buildOptions } = await import('../src/options')
2334

2435
describe('telemetry', () => {
2536
beforeEach(() => {
2637
initMock.mockClear()
2738
setTagsMock.mockClear()
2839
captureExceptionMock.mockClear()
40+
captureMessageMock.mockClear()
2941
withScopeMock.mockClear()
42+
startSpanMock.mockClear()
43+
metricsGaugeMock.mockClear()
3044
flushMock.mockClear()
3145
})
3246

@@ -42,7 +56,8 @@ describe('telemetry', () => {
4256
expect(initMock).toHaveBeenCalledWith(
4357
expect.objectContaining({
4458
defaultIntegrations: false,
45-
environment: 'nightly'
59+
environment: 'nightly',
60+
tracesSampleRate: 1.0
4661
})
4762
)
4863
})
@@ -90,8 +105,25 @@ describe('telemetry', () => {
90105
expect(scrubbed.contexts).toEqual({})
91106
})
92107

108+
it('should strip sensitive data in beforeSendTransaction', () => {
109+
const options = buildOptions({})
110+
initTelemetry(true, options)
111+
112+
const beforeSendTransaction =
113+
initMock.mock.calls[0][0].beforeSendTransaction
114+
const event = {
115+
server_name: 'runner-abc',
116+
extra: { leak: true },
117+
contexts: { something: {} }
118+
}
119+
120+
const scrubbed = beforeSendTransaction(event)
121+
expect(scrubbed.server_name).toBeUndefined()
122+
expect(scrubbed.extra).toBeUndefined()
123+
expect(scrubbed.contexts).toEqual({})
124+
})
125+
93126
it('should scrub exception values of env var secrets', () => {
94-
// Set a fake sensitive env var
95127
const originalToken = process.env.GITHUB_TOKEN
96128
process.env.GITHUB_TOKEN = 'ghp_superSecretTokenValue123'
97129
try {
@@ -127,10 +159,7 @@ describe('telemetry', () => {
127159
it('should report errors via captureException', () => {
128160
const options = buildOptions({})
129161
initTelemetry(true, options)
130-
131-
const err = new Error('install failed')
132-
reportError(err)
133-
162+
reportError(new Error('install failed'))
134163
expect(withScopeMock).toHaveBeenCalled()
135164
})
136165

@@ -144,8 +173,6 @@ describe('telemetry', () => {
144173
})
145174

146175
reportError(new Error('fail'), { phase: 'download' })
147-
148-
expect(withScopeMock).toHaveBeenCalled()
149176
expect(setTagMock).toHaveBeenCalledWith('phase', 'download')
150177
})
151178

@@ -155,6 +182,61 @@ describe('telemetry', () => {
155182
expect(withScopeMock).not.toHaveBeenCalled()
156183
})
157184

185+
// --- Tracing ---
186+
187+
it('withSpan should call Sentry.startSpan when enabled', async () => {
188+
initTelemetry(true, buildOptions({}))
189+
const result = await withSpan('test-op', 'test', async () => 42)
190+
expect(result).toBe(42)
191+
expect(startSpanMock).toHaveBeenCalledWith(
192+
expect.objectContaining({ name: 'test-op', op: 'test' }),
193+
expect.any(Function)
194+
)
195+
})
196+
197+
it('withSpan should run function directly when disabled', async () => {
198+
initTelemetry(false, buildOptions({}))
199+
const result = await withSpan('test-op', 'test', async () => 99)
200+
expect(result).toBe(99)
201+
expect(startSpanMock).not.toHaveBeenCalled()
202+
})
203+
204+
// --- Metrics ---
205+
206+
it('recordMetric should call metrics.gauge when enabled', () => {
207+
initTelemetry(true, buildOptions({}))
208+
recordMetric('install_duration', 1500, 'millisecond', { os: 'linux' })
209+
expect(metricsGaugeMock).toHaveBeenCalledWith('install_duration', 1500, {
210+
unit: 'millisecond',
211+
tags: { os: 'linux' }
212+
})
213+
})
214+
215+
it('recordMetric should no-op when disabled', () => {
216+
initTelemetry(false, buildOptions({}))
217+
recordMetric('install_duration', 1500, 'millisecond')
218+
expect(metricsGaugeMock).not.toHaveBeenCalled()
219+
})
220+
221+
// --- Logging ---
222+
223+
it('logEvent should call captureMessage when enabled', () => {
224+
initTelemetry(true, buildOptions({}))
225+
logEvent('setup-elide.start', { installer: 'shell' })
226+
expect(captureMessageMock).toHaveBeenCalledWith('setup-elide.start', {
227+
level: 'info',
228+
tags: { installer: 'shell' }
229+
})
230+
})
231+
232+
it('logEvent should no-op when disabled', () => {
233+
initTelemetry(false, buildOptions({}))
234+
logEvent('setup-elide.start')
235+
expect(captureMessageMock).not.toHaveBeenCalled()
236+
})
237+
238+
// --- Flush ---
239+
158240
it('should flush pending events', async () => {
159241
initTelemetry(true, buildOptions({}))
160242
await flushTelemetry()

codecov-bundle.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"ignorePatterns": ["*.map"],
3+
"normalizeAssetsPattern": "[name].js"
4+
}

dist/index.js

Lines changed: 57 additions & 57 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/index.js.map

Lines changed: 8 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/post.js

Lines changed: 31 additions & 31 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/post.js.map

Lines changed: 7 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@
6060
"lint": "oxlint src __tests__",
6161
"package": "bun build src/index.ts src/post.ts --outdir=dist --target=node --minify --sourcemap=linked",
6262
"package:watch": "bun build src/index.ts src/post.ts --outdir=dist --target=node --sourcemap=linked --watch",
63-
"test": "bun test __tests__/options.test.ts __tests__/platform.test.ts __tests__/releases.test.ts __tests__/index.test.ts && bun test __tests__/command.test.ts && bun test __tests__/releases-download.test.ts && bun test __tests__/telemetry.test.ts && bun test __tests__/install-apt.test.ts __tests__/install-shell.test.ts __tests__/install-msi.test.ts __tests__/install-pkg.test.ts __tests__/install-rpm.test.ts && bun test __tests__/main.test.ts",
63+
"test": "rm -rf coverage && bun test __tests__/options.test.ts __tests__/platform.test.ts __tests__/releases.test.ts __tests__/index.test.ts && cp coverage/lcov.info coverage/batch1.info && bun test __tests__/command.test.ts && cp coverage/lcov.info coverage/batch2.info && bun test __tests__/releases-download.test.ts && cp coverage/lcov.info coverage/batch3.info && bun test __tests__/telemetry.test.ts && cp coverage/lcov.info coverage/batch4.info && bun test __tests__/install-apt.test.ts __tests__/install-shell.test.ts __tests__/install-msi.test.ts __tests__/install-pkg.test.ts __tests__/install-rpm.test.ts && cp coverage/lcov.info coverage/batch5.info && bun test __tests__/main.test.ts && cp coverage/lcov.info coverage/batch6.info && cat coverage/batch*.info > coverage/lcov.info",
64+
"test:ci": "rm -rf coverage test-results && mkdir -p test-results && bun test --reporter=junit --reporter-outfile=test-results/batch1.xml __tests__/options.test.ts __tests__/platform.test.ts __tests__/releases.test.ts __tests__/index.test.ts && cp coverage/lcov.info coverage/batch1.info && bun test --reporter=junit --reporter-outfile=test-results/batch2.xml __tests__/command.test.ts && cp coverage/lcov.info coverage/batch2.info && bun test --reporter=junit --reporter-outfile=test-results/batch3.xml __tests__/releases-download.test.ts && cp coverage/lcov.info coverage/batch3.info && bun test --reporter=junit --reporter-outfile=test-results/batch4.xml __tests__/telemetry.test.ts && cp coverage/lcov.info coverage/batch4.info && bun test --reporter=junit --reporter-outfile=test-results/batch5.xml __tests__/install-apt.test.ts __tests__/install-shell.test.ts __tests__/install-msi.test.ts __tests__/install-pkg.test.ts __tests__/install-rpm.test.ts && cp coverage/lcov.info coverage/batch5.info && bun test --reporter=junit --reporter-outfile=test-results/batch6.xml __tests__/main.test.ts && cp coverage/lcov.info coverage/batch6.info && cat coverage/batch*.info > coverage/lcov.info",
6465
"test:docker": "docker build -t setup-elide-test . && docker run --rm setup-elide-test sh -c 'bun run build && bun run test'",
6566
"all": "bun run format:write && bun run build && bun run test"
6667
},

0 commit comments

Comments
 (0)