Skip to content

Commit e725cbe

Browse files
committed
Add standardized update.mjs script
- Copied update.mjs from socket-registry - Added "update" script to package.json - Provides standardized dependency update workflow with taze - Includes provenance downgrade detection - Supports Socket package updates and project-specific update scripts
1 parent 6d08787 commit e725cbe

File tree

2 files changed

+315
-1
lines changed

2 files changed

+315
-1
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,8 @@
732732
"prepublishOnly": "pnpm run build",
733733
"test": "node scripts/test.mjs",
734734
"test-ci": "vitest run",
735-
"type-ci": "pnpm run check"
735+
"type-ci": "pnpm run check",
736+
"update": "node scripts/update.mjs"
736737
},
737738
"devDependencies": {
738739
"@babel/core": "7.28.4",

scripts/update.mjs

Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
/**
2+
* @fileoverview Standardized update runner that manages dependency updates.
3+
* Handles taze updates, Socket package updates, and project-specific tasks.
4+
*/
5+
6+
import { spawn } from 'node:child_process'
7+
import { existsSync } from 'node:fs'
8+
import path from 'node:path'
9+
import { fileURLToPath } from 'node:url'
10+
11+
import { parseArgs } from '@socketsecurity/lib/argv/parse'
12+
13+
import { log, printFooter, printHeader } from './utils/cli-helpers.mjs'
14+
15+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
16+
const rootPath = path.join(__dirname, '..')
17+
const WIN32 = process.platform === 'win32'
18+
19+
function includesProvenanceDowngradeWarning(output) {
20+
const lowered = output.toString().toLowerCase()
21+
return (
22+
lowered.includes('provenance') &&
23+
(lowered.includes('downgrade') || lowered.includes('warn'))
24+
)
25+
}
26+
27+
async function runCommand(command, args = [], options = {}) {
28+
return new Promise((resolve, reject) => {
29+
const child = spawn(command, args, {
30+
stdio: 'inherit',
31+
cwd: rootPath,
32+
...(WIN32 && { shell: true }),
33+
...options,
34+
})
35+
36+
child.on('exit', code => {
37+
resolve(code || 0)
38+
})
39+
40+
child.on('error', error => {
41+
reject(error)
42+
})
43+
})
44+
}
45+
46+
async function runCommandWithOutput(command, args = [], options = {}) {
47+
return new Promise((resolve, reject) => {
48+
let stdout = ''
49+
let stderr = ''
50+
let hasProvenanceDowngrade = false
51+
52+
const child = spawn(command, args, {
53+
cwd: rootPath,
54+
...(WIN32 && { shell: true }),
55+
...options,
56+
})
57+
58+
if (child.stdout) {
59+
child.stdout.on('data', chunk => {
60+
stdout += chunk
61+
process.stdout.write(chunk)
62+
if (includesProvenanceDowngradeWarning(chunk)) {
63+
hasProvenanceDowngrade = true
64+
}
65+
})
66+
}
67+
68+
if (child.stderr) {
69+
child.stderr.on('data', chunk => {
70+
stderr += chunk
71+
process.stderr.write(chunk)
72+
if (includesProvenanceDowngradeWarning(chunk)) {
73+
hasProvenanceDowngrade = true
74+
}
75+
})
76+
}
77+
78+
child.on('exit', code => {
79+
resolve({ exitCode: code || 0, stdout, stderr, hasProvenanceDowngrade })
80+
})
81+
82+
child.on('error', error => {
83+
reject(error)
84+
})
85+
})
86+
}
87+
88+
/**
89+
* Run taze to update dependencies.
90+
*/
91+
async function updateDependencies(options = {}) {
92+
const { check = false, write = false } = options
93+
94+
log.progress('Checking for dependency updates')
95+
96+
const args = ['exec', 'taze']
97+
98+
// Add taze options.
99+
if (check) {
100+
args.push('--check')
101+
}
102+
if (write) {
103+
args.push('--write')
104+
}
105+
106+
// Pass through any additional arguments.
107+
if (options.args && options.args.length > 0) {
108+
args.push(...options.args)
109+
}
110+
111+
const result = await runCommandWithOutput('pnpm', args)
112+
113+
if (result.hasProvenanceDowngrade) {
114+
log.failed('Provenance downgrade detected!')
115+
log.error(
116+
'ERROR: Provenance downgrade detected! Failing to maintain security.',
117+
)
118+
log.error(
119+
'Configure your dependencies to maintain provenance or exclude problematic packages.',
120+
)
121+
return 1
122+
}
123+
124+
if (result.exitCode !== 0) {
125+
log.failed('Dependency update failed')
126+
return result.exitCode
127+
}
128+
129+
log.done(write ? 'Dependencies updated' : 'Dependency check complete')
130+
return 0
131+
}
132+
133+
/**
134+
* Update Socket packages to latest versions.
135+
*/
136+
async function updateSocketPackages() {
137+
log.progress('Updating Socket packages')
138+
139+
const exitCode = await runCommand('pnpm', [
140+
'-r',
141+
'update',
142+
'@socketsecurity/*',
143+
'@socketregistry/*',
144+
'--latest',
145+
])
146+
147+
if (exitCode !== 0) {
148+
log.failed('Socket package update failed')
149+
return exitCode
150+
}
151+
152+
log.done('Socket packages updated')
153+
return 0
154+
}
155+
156+
/**
157+
* Run project-specific update scripts.
158+
*/
159+
async function runProjectUpdates() {
160+
const updates = []
161+
162+
// Check for project-specific update scripts.
163+
const projectScripts = [
164+
'update-empty-dirs.mjs',
165+
'update-empty-files.mjs',
166+
'update-licenses.mjs',
167+
'update-manifest.mjs',
168+
'update-package-json.mjs',
169+
'update-npm-package-json.mjs',
170+
'update-npm-readmes.mjs',
171+
'update-data-npm.mjs',
172+
]
173+
174+
for (const script of projectScripts) {
175+
const scriptPath = path.join(rootPath, 'scripts', script)
176+
if (existsSync(scriptPath)) {
177+
updates.push({
178+
name: script.replace(/^update-/, '').replace(/\.mjs$/, ''),
179+
script: scriptPath,
180+
})
181+
}
182+
}
183+
184+
if (updates.length === 0) {
185+
return 0
186+
}
187+
188+
log.step('Running project-specific updates')
189+
190+
for (const { name, script } of updates) {
191+
log.progress(`Updating ${name}`)
192+
193+
const exitCode = await runCommand('node', [script], {
194+
stdio: 'pipe',
195+
})
196+
197+
if (exitCode !== 0) {
198+
log.failed(`Failed to update ${name}`)
199+
return exitCode
200+
}
201+
202+
log.done(`Updated ${name}`)
203+
}
204+
205+
return 0
206+
}
207+
208+
async function main() {
209+
try {
210+
// Parse arguments.
211+
const { positionals, values } = parseArgs({
212+
options: {
213+
help: {
214+
type: 'boolean',
215+
default: false,
216+
},
217+
check: {
218+
type: 'boolean',
219+
default: false,
220+
},
221+
write: {
222+
type: 'boolean',
223+
default: false,
224+
},
225+
deps: {
226+
type: 'boolean',
227+
default: false,
228+
},
229+
socket: {
230+
type: 'boolean',
231+
default: false,
232+
},
233+
project: {
234+
type: 'boolean',
235+
default: false,
236+
},
237+
},
238+
allowPositionals: true,
239+
strict: false,
240+
})
241+
242+
// Show help if requested.
243+
if (values.help) {
244+
console.log('\nUsage: pnpm update [options]')
245+
console.log('\nOptions:')
246+
console.log(' --help Show this help message')
247+
console.log(' --check Check for updates without modifying files')
248+
console.log(' --write Write updates to package.json')
249+
console.log(' --deps Update dependencies only')
250+
console.log(' --socket Update Socket packages only')
251+
console.log(' --project Run project-specific updates only')
252+
console.log('\nExamples:')
253+
console.log(' pnpm update # Run all updates')
254+
console.log(' pnpm update --check # Check for dependency updates')
255+
console.log(
256+
' pnpm update --write # Update dependencies in package.json',
257+
)
258+
console.log(' pnpm update --deps # Update dependencies only')
259+
console.log(' pnpm update --socket # Update Socket packages only')
260+
process.exitCode = 0
261+
return
262+
}
263+
264+
printHeader('Update Runner')
265+
266+
let exitCode = 0
267+
const runAll = !values.deps && !values.socket && !values.project
268+
269+
// Update dependencies.
270+
if (runAll || values.deps) {
271+
log.step('Updating dependencies')
272+
exitCode = await updateDependencies({
273+
check: values.check,
274+
write: values.write,
275+
args: positionals,
276+
})
277+
if (exitCode !== 0) {
278+
log.error('Dependency update failed')
279+
process.exitCode = exitCode
280+
return
281+
}
282+
}
283+
284+
// Update Socket packages.
285+
if ((runAll || values.socket) && !values.check) {
286+
log.step('Updating Socket packages')
287+
exitCode = await updateSocketPackages()
288+
if (exitCode !== 0) {
289+
log.error('Socket package update failed')
290+
process.exitCode = exitCode
291+
return
292+
}
293+
}
294+
295+
// Run project-specific updates.
296+
if ((runAll || values.project) && !values.check) {
297+
exitCode = await runProjectUpdates()
298+
if (exitCode !== 0) {
299+
log.error('Project updates failed')
300+
process.exitCode = exitCode
301+
return
302+
}
303+
}
304+
305+
printFooter('All updates completed successfully!')
306+
process.exitCode = 0
307+
} catch (error) {
308+
log.error(`Update runner failed: ${error.message}`)
309+
process.exitCode = 1
310+
}
311+
}
312+
313+
main().catch(console.error)

0 commit comments

Comments
 (0)