Skip to content

Commit 511c56d

Browse files
authored
feat: migrate patch command to @socketsecurity/socket-patch@1.2.0 (#1042)
1 parent f83017a commit 511c56d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+420
-7713
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
77
## [Unreleased]
88

99
### Changed
10+
- Updated to @socketsecurity/socket-patch@1.2.0.
1011
- Updated Coana CLI to v14.12.148.
1112

1213
### Fixed

packages/build-infra/lib/github-releases.mjs

Lines changed: 88 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* Shared utilities for fetching GitHub releases.
33
*/
44

5+
import { createTtlCache } from '@socketsecurity/lib/cache-with-ttl'
56
import { safeMkdir } from '@socketsecurity/lib/fs'
67
import { httpDownload, httpRequest } from '@socketsecurity/lib/http-request'
78
import { getDefaultLogger } from '@socketsecurity/lib/logger'
@@ -12,6 +13,13 @@ const logger = getDefaultLogger()
1213
const OWNER = 'SocketDev'
1314
const REPO = 'socket-btm'
1415

16+
// Cache GitHub API responses for 1 hour to avoid rate limiting.
17+
const cache = createTtlCache({
18+
memoize: true,
19+
prefix: 'github-releases',
20+
ttl: 60 * 60 * 1000, // 1 hour.
21+
})
22+
1523
/**
1624
* Get GitHub authentication headers if token is available.
1725
*
@@ -38,52 +46,56 @@ function getAuthHeaders() {
3846
* @returns {Promise<string|null>} - Latest release tag or null if not found.
3947
*/
4048
export async function getLatestRelease(tool, { quiet = false } = {}) {
41-
return await pRetry(
42-
async () => {
43-
const response = await httpRequest(
44-
`https://api.github.com/repos/${OWNER}/${REPO}/releases?per_page=100`,
45-
{
46-
headers: getAuthHeaders(),
47-
},
48-
)
49-
50-
if (!response.ok) {
51-
throw new Error(`Failed to fetch releases: ${response.status}`)
52-
}
49+
const cacheKey = `latest-release:${tool}`
50+
51+
return await cache.getOrFetch(cacheKey, async () => {
52+
return await pRetry(
53+
async () => {
54+
const response = await httpRequest(
55+
`https://api.github.com/repos/${OWNER}/${REPO}/releases?per_page=100`,
56+
{
57+
headers: getAuthHeaders(),
58+
},
59+
)
60+
61+
if (!response.ok) {
62+
throw new Error(`Failed to fetch releases: ${response.status}`)
63+
}
5364

54-
const releases = JSON.parse(response.body)
65+
const releases = JSON.parse(response.body)
5566

56-
// Find the first release matching the tool prefix.
57-
for (const release of releases) {
58-
const { tag_name: tag } = release
59-
if (tag.startsWith(`${tool}-`)) {
60-
if (!quiet) {
61-
logger.info(` Found release: ${tag}`)
67+
// Find the first release matching the tool prefix.
68+
for (const release of releases) {
69+
const { tag_name: tag } = release
70+
if (tag.startsWith(`${tool}-`)) {
71+
if (!quiet) {
72+
logger.info(` Found release: ${tag}`)
73+
}
74+
return tag
6275
}
63-
return tag
6476
}
65-
}
66-
67-
// No matching release found in the list.
68-
if (!quiet) {
69-
logger.info(` No ${tool} release found in latest 100 releases`)
70-
}
71-
return null
72-
},
73-
{
74-
backoffFactor: 1,
75-
baseDelayMs: 5000,
76-
onRetry: (attempt, error) => {
77+
78+
// No matching release found in the list.
7779
if (!quiet) {
78-
logger.info(
79-
` Retry attempt ${attempt + 1}/3 for ${tool} release list...`,
80-
)
81-
logger.warn(` Attempt ${attempt + 1}/3 failed: ${error.message}`)
80+
logger.info(` No ${tool} release found in latest 100 releases`)
8281
}
82+
return null
8383
},
84-
retries: 2,
85-
},
86-
)
84+
{
85+
backoffFactor: 1,
86+
baseDelayMs: 5000,
87+
onRetry: (attempt, error) => {
88+
if (!quiet) {
89+
logger.info(
90+
` Retry attempt ${attempt + 1}/3 for ${tool} release list...`,
91+
)
92+
logger.warn(` Attempt ${attempt + 1}/3 failed: ${error.message}`)
93+
}
94+
},
95+
retries: 2,
96+
},
97+
)
98+
})
8799
}
88100

89101
/**
@@ -103,46 +115,50 @@ export async function getReleaseAssetUrl(
103115
assetName,
104116
{ quiet = false } = {},
105117
) {
106-
return await pRetry(
107-
async () => {
108-
const response = await httpRequest(
109-
`https://api.github.com/repos/${OWNER}/${REPO}/releases/tags/${tag}`,
110-
{
111-
headers: getAuthHeaders(),
112-
},
113-
)
114-
115-
if (!response.ok) {
116-
throw new Error(`Failed to fetch release ${tag}: ${response.status}`)
117-
}
118-
119-
const release = JSON.parse(response.body)
118+
const cacheKey = `asset-url:${tag}:${assetName}`
119+
120+
return await cache.getOrFetch(cacheKey, async () => {
121+
return await pRetry(
122+
async () => {
123+
const response = await httpRequest(
124+
`https://api.github.com/repos/${OWNER}/${REPO}/releases/tags/${tag}`,
125+
{
126+
headers: getAuthHeaders(),
127+
},
128+
)
129+
130+
if (!response.ok) {
131+
throw new Error(`Failed to fetch release ${tag}: ${response.status}`)
132+
}
120133

121-
// Find the matching asset.
122-
const asset = release.assets.find(a => a.name === assetName)
134+
const release = JSON.parse(response.body)
123135

124-
if (!asset) {
125-
throw new Error(`Asset ${assetName} not found in release ${tag}`)
126-
}
136+
// Find the matching asset.
137+
const asset = release.assets.find(a => a.name === assetName)
127138

128-
if (!quiet) {
129-
logger.info(` Found asset: ${assetName}`)
130-
}
139+
if (!asset) {
140+
throw new Error(`Asset ${assetName} not found in release ${tag}`)
141+
}
131142

132-
return asset.browser_download_url
133-
},
134-
{
135-
backoffFactor: 1,
136-
baseDelayMs: 5000,
137-
onRetry: (attempt, error) => {
138143
if (!quiet) {
139-
logger.info(` Retry attempt ${attempt + 1}/3 for asset URL...`)
140-
logger.warn(` Attempt ${attempt + 1}/3 failed: ${error.message}`)
144+
logger.info(` Found asset: ${assetName}`)
141145
}
146+
147+
return asset.browser_download_url
142148
},
143-
retries: 2,
144-
},
145-
)
149+
{
150+
backoffFactor: 1,
151+
baseDelayMs: 5000,
152+
onRetry: (attempt, error) => {
153+
if (!quiet) {
154+
logger.info(` Retry attempt ${attempt + 1}/3 for asset URL...`)
155+
logger.warn(` Attempt ${attempt + 1}/3 failed: ${error.message}`)
156+
}
157+
},
158+
retries: 2,
159+
},
160+
)
161+
})
146162
}
147163

148164
/**

packages/cli/.config/esbuild.index.config.mjs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Builds the index loader that executes the CLI.
44
*/
55

6+
import { writeFileSync } from 'node:fs'
67
import path from 'node:path'
78
import { fileURLToPath } from 'node:url'
89

@@ -20,10 +21,19 @@ const config = createIndexConfig({
2021

2122
// Run build if invoked directly.
2223
if (fileURLToPath(import.meta.url) === process.argv[1]) {
23-
build(config).catch(error => {
24-
console.error('Index loader build failed:', error)
25-
process.exitCode = 1
26-
})
24+
build(config)
25+
.then(result => {
26+
// Write the transformed output (build had write: false).
27+
if (result.outputFiles && result.outputFiles.length > 0) {
28+
for (const output of result.outputFiles) {
29+
writeFileSync(output.path, output.contents)
30+
}
31+
}
32+
})
33+
.catch(error => {
34+
console.error('Index loader build failed:', error)
35+
process.exitCode = 1
36+
})
2737
}
2838

2939
export default config

packages/cli/.env.test

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,8 @@ SOCKET_CLI_BIN_PATH="./build/cli.js"
66
SOCKET_CLI_JS_PATH="./dist/cli.js"
77
# RUN_E2E_TESTS=1
88
# SOCKET_CLI_BIN_PATH="./dist/sea/socket-macos-arm64"
9+
10+
# External tool versions (from external-tools.json)
11+
INLINED_SOCKET_CLI_COANA_VERSION="14.12.148"
12+
INLINED_SOCKET_CLI_SFW_VERSION="2.0.4"
13+
INLINED_SOCKET_CLI_SOCKET_PATCH_VERSION="1.2.0"

packages/cli/external-tools.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@
2424
"package": "socketsecurity",
2525
"version": "^2.2.15"
2626
},
27+
"socket-patch": {
28+
"description": "Socket Patch CLI for applying security patches",
29+
"type": "npm",
30+
"package": "@socketsecurity/socket-patch",
31+
"version": "1.2.0"
32+
},
2733
"sfw": {
2834
"description": "Socket Firewall (sfw)",
2935
"type": "standalone",

packages/cli/scripts/build-js.mjs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
* Orchestrates extraction, building, and validation.
44
*/
55

6+
import { copyFileSync } from 'node:fs'
7+
68
import { getDefaultLogger } from '@socketsecurity/lib/logger'
79
import { spawn } from '@socketsecurity/lib/spawn'
810

@@ -34,7 +36,10 @@ async function main() {
3436
return
3537
}
3638

37-
// Step 3: Validate bundle.
39+
// Step 3: Copy bundle to dist/.
40+
copyFileSync('build/cli.js', 'dist/cli.js')
41+
42+
// Step 4: Validate bundle.
3843
logger.step('Validating bundle')
3944
const validateResult = await spawn(
4045
'node',

packages/cli/scripts/build.mjs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
* Options: --quiet, --verbose, --force, --watch
44
*/
55

6-
import { copyFileSync } from 'node:fs'
7-
import { promises as fs } from 'node:fs'
6+
import { copyFileSync, promises as fs } from 'node:fs'
87
import path from 'node:path'
98
import { fileURLToPath } from 'node:url'
109

packages/cli/scripts/esbuild-shared.mjs

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ const rootPath = path.join(__dirname, '..')
2121
* @returns {Object} esbuild configuration object
2222
*/
2323
export function createIndexConfig({ entryPoint, minify = false, outfile }) {
24+
// Get inlined environment variables for build-time constant replacement.
25+
const inlinedEnvVars = getInlinedEnvVars()
26+
2427
const config = {
2528
banner: {
2629
js: '#!/usr/bin/env node',
@@ -33,6 +36,15 @@ export function createIndexConfig({ entryPoint, minify = false, outfile }) {
3336
platform: 'node',
3437
target: 'node18',
3538
treeShaking: true,
39+
// Define environment variables for inlining.
40+
define: {
41+
'process.env.NODE_ENV': '"production"',
42+
...createDefineEntries(inlinedEnvVars),
43+
},
44+
// Add plugin for post-bundle env var replacement.
45+
plugins: [envVarReplacementPlugin(inlinedEnvVars)],
46+
// Plugin needs to transform output.
47+
write: false,
3648
}
3749

3850
if (minify) {
@@ -137,12 +149,30 @@ export function getInlinedEnvVars() {
137149
const externalTools = JSON.parse(
138150
readFileSync(path.join(rootPath, 'external-tools.json'), 'utf-8'),
139151
)
140-
const cdxgenVersion = externalTools['@cyclonedx/cdxgen']?.version || ''
141-
const coanaVersion = externalTools['@coana-tech/cli']?.version || ''
142-
const pyCliVersion = externalTools['socketsecurity']?.version || ''
143-
const pythonBuildTag = externalTools['python']?.buildTag || ''
144-
const pythonVersion = externalTools['python']?.version || ''
145-
const sfwVersion = externalTools['sfw']?.version || ''
152+
153+
function getExternalToolVersion(key, field = 'version') {
154+
const tool = externalTools[key]
155+
if (!tool) {
156+
throw new Error(
157+
`External tool "${key}" not found in external-tools.json. Please add it to the configuration.`,
158+
)
159+
}
160+
const value = tool[field]
161+
if (!value) {
162+
throw new Error(
163+
`External tool "${key}" is missing required field "${field}" in external-tools.json.`,
164+
)
165+
}
166+
return value
167+
}
168+
169+
const cdxgenVersion = getExternalToolVersion('@cyclonedx/cdxgen')
170+
const coanaVersion = getExternalToolVersion('@coana-tech/cli')
171+
const pyCliVersion = getExternalToolVersion('socketsecurity')
172+
const pythonBuildTag = getExternalToolVersion('python', 'buildTag')
173+
const pythonVersion = getExternalToolVersion('python')
174+
const sfwVersion = getExternalToolVersion('sfw')
175+
const socketPatchVersion = getExternalToolVersion('socket-patch')
146176

147177
// Build-time constants that can be overridden by environment variables.
148178
const publishedBuild =
@@ -166,6 +196,7 @@ export function getInlinedEnvVars() {
166196
INLINED_SOCKET_CLI_CYCLONEDX_CDXGEN_VERSION: JSON.stringify(cdxgenVersion),
167197
INLINED_SOCKET_CLI_PYCLI_VERSION: JSON.stringify(pyCliVersion),
168198
INLINED_SOCKET_CLI_SFW_VERSION: JSON.stringify(sfwVersion),
199+
INLINED_SOCKET_CLI_SOCKET_PATCH_VERSION: JSON.stringify(socketPatchVersion),
169200
INLINED_SOCKET_CLI_SYNP_VERSION: JSON.stringify(synpVersion),
170201
INLINED_SOCKET_CLI_PUBLISHED_BUILD: JSON.stringify(
171202
publishedBuild ? '1' : '',

0 commit comments

Comments
 (0)