1+ #!/usr/bin/env node
2+
3+ /**
4+ * @fileoverview Verifies that trusted publisher is configured correctly for @socketbin packages.
5+ * Checks for provenance attestations and publisher information.
6+ */
7+
8+ import https from 'node:https'
9+ import { spawn } from 'node:child_process'
10+
11+ const packages = [
12+ '@socketbin/cli-darwin-arm64' ,
13+ '@socketbin/cli-darwin-x64' ,
14+ '@socketbin/cli-linux-arm64' ,
15+ '@socketbin/cli-linux-x64' ,
16+ '@socketbin/cli-win32-arm64' ,
17+ '@socketbin/cli-win32-x64'
18+ ]
19+
20+ /**
21+ * Fetch package metadata from npm registry
22+ */
23+ async function getPackageInfo ( packageName ) {
24+ return new Promise ( ( resolve , reject ) => {
25+ const url = `https://registry.npmjs.org/${ packageName } `
26+
27+ https . get ( url , res => {
28+ let data = ''
29+ res . on ( 'data' , chunk => data += chunk )
30+ res . on ( 'end' , ( ) => {
31+ try {
32+ resolve ( JSON . parse ( data ) )
33+ } catch ( error ) {
34+ reject ( error )
35+ }
36+ } )
37+ } ) . on ( 'error' , reject )
38+ } )
39+ }
40+
41+ /**
42+ * Check npm attestations for a package
43+ */
44+ async function checkAttestations ( packageName , version = 'latest' ) {
45+ return new Promise ( ( resolve ) => {
46+ const child = spawn ( 'npm' , [ 'view' , `${ packageName } @${ version } ` , '--json' ] , {
47+ stdio : [ 'ignore' , 'pipe' , 'pipe' ]
48+ } )
49+
50+ let stdout = ''
51+ let stderr = ''
52+
53+ child . stdout . on ( 'data' , data => stdout += data )
54+ child . stderr . on ( 'data' , data => stderr += data )
55+
56+ child . on ( 'exit' , ( ) => {
57+ try {
58+ const data = JSON . parse ( stdout )
59+ resolve ( {
60+ package : packageName ,
61+ version : data . version || version ,
62+ hasProvenance : ! ! data . dist ?. attestations ?. provenance ,
63+ publishedBy : data . dist ?. attestations ?. provenance ?. predicateType || null ,
64+ npmUser : data . _npmUser ?. name || 'unknown'
65+ } )
66+ } catch {
67+ resolve ( {
68+ package : packageName ,
69+ version,
70+ hasProvenance : false ,
71+ publishedBy : null ,
72+ npmUser : 'unknown'
73+ } )
74+ }
75+ } )
76+ } )
77+ }
78+
79+ /**
80+ * Check GitHub workflow configuration
81+ */
82+ async function checkWorkflow ( ) {
83+ return new Promise ( ( resolve ) => {
84+ const child = spawn ( 'gh' , [ 'workflow' , 'view' , 'publish-socketbin.yml' , '--json' , 'name,state' ] , {
85+ stdio : [ 'ignore' , 'pipe' , 'pipe' ]
86+ } )
87+
88+ let stdout = ''
89+ child . stdout . on ( 'data' , data => stdout += data )
90+
91+ child . on ( 'exit' , code => {
92+ if ( code === 0 ) {
93+ try {
94+ const data = JSON . parse ( stdout )
95+ resolve ( {
96+ exists : true ,
97+ name : data . name ,
98+ state : data . state
99+ } )
100+ } catch {
101+ resolve ( { exists : false } )
102+ }
103+ } else {
104+ resolve ( { exists : false } )
105+ }
106+ } )
107+ } )
108+ }
109+
110+ /**
111+ * Check if NPM_TOKEN is configured in GitHub secrets
112+ */
113+ async function checkSecrets ( ) {
114+ return new Promise ( ( resolve ) => {
115+ const child = spawn ( 'gh' , [ 'secret' , 'list' , '--json' , 'name' ] , {
116+ stdio : [ 'ignore' , 'pipe' , 'pipe' ]
117+ } )
118+
119+ let stdout = ''
120+ child . stdout . on ( 'data' , data => stdout += data )
121+
122+ child . on ( 'exit' , code => {
123+ if ( code === 0 ) {
124+ try {
125+ const secrets = JSON . parse ( stdout )
126+ const hasNpmToken = secrets . some ( s => s . name === 'NPM_TOKEN' )
127+ resolve ( { hasNpmToken } )
128+ } catch {
129+ resolve ( { hasNpmToken : false } )
130+ }
131+ } else {
132+ resolve ( { hasNpmToken : false } )
133+ }
134+ } )
135+ } )
136+ }
137+
138+ async function main ( ) {
139+ console . log ( '🔍 Verifying Trusted Publisher Setup for @socketbin\n' )
140+ console . log ( '=' . repeat ( 60 ) )
141+
142+ // Check if packages exist
143+ console . log ( '\n📦 Checking package status...\n' )
144+
145+ let allPublished = true
146+ const packageResults = [ ]
147+
148+ for ( const pkg of packages ) {
149+ try {
150+ const info = await getPackageInfo ( pkg )
151+ const latest = info [ 'dist-tags' ] ?. latest
152+ if ( latest ) {
153+ console . log ( `✅ ${ pkg } @${ latest } ` )
154+ packageResults . push ( { name : pkg , version : latest , exists : true } )
155+ } else {
156+ console . log ( `❌ ${ pkg } - not published` )
157+ allPublished = false
158+ packageResults . push ( { name : pkg , exists : false } )
159+ }
160+ } catch {
161+ console . log ( `❌ ${ pkg } - not found` )
162+ allPublished = false
163+ packageResults . push ( { name : pkg , exists : false } )
164+ }
165+ }
166+
167+ if ( ! allPublished ) {
168+ console . log ( '\n⚠️ Not all packages are published yet.' )
169+ console . log ( 'Run: node scripts/publish-placeholders.mjs' )
170+ console . log ( '\nTrusted publisher cannot be configured until packages exist.' )
171+ process . exit ( 1 )
172+ }
173+
174+ // Check for provenance on existing packages
175+ console . log ( '\n🔐 Checking provenance attestations...\n' )
176+
177+ const attestationResults = await Promise . all (
178+ packageResults
179+ . filter ( p => p . exists )
180+ . map ( p => checkAttestations ( p . name , p . version ) )
181+ )
182+
183+ let hasProvenance = false
184+ for ( const result of attestationResults ) {
185+ if ( result . hasProvenance ) {
186+ console . log ( `✅ ${ result . package } @${ result . version } - has provenance (published by ${ result . npmUser } )` )
187+ hasProvenance = true
188+ } else {
189+ console . log ( `⚠️ ${ result . package } @${ result . version } - no provenance (manual publish by ${ result . npmUser } )` )
190+ }
191+ }
192+
193+ // Check GitHub workflow
194+ console . log ( '\n🔧 Checking GitHub configuration...\n' )
195+
196+ const workflow = await checkWorkflow ( )
197+ if ( workflow . exists ) {
198+ console . log ( `✅ Workflow exists: ${ workflow . name } ` )
199+ console . log ( ` State: ${ workflow . state } ` )
200+ } else {
201+ console . log ( '❌ Workflow not found or gh CLI not configured' )
202+ console . log ( ' Expected: .github/workflows/publish-socketbin.yml' )
203+ }
204+
205+ // Check secrets
206+ const secrets = await checkSecrets ( )
207+ if ( secrets . hasNpmToken ) {
208+ console . log ( '✅ NPM_TOKEN secret configured' )
209+ } else {
210+ console . log ( '⚠️ NPM_TOKEN secret not found (or no access to check)' )
211+ }
212+
213+ // Summary
214+ console . log ( '\n' + '=' . repeat ( 60 ) )
215+ console . log ( '\n📊 SUMMARY\n' )
216+
217+ if ( ! hasProvenance ) {
218+ console . log ( '📝 Trusted Publisher Status: NOT CONFIGURED\n' )
219+ console . log ( 'This is expected for placeholder packages (v0.0.0).' )
220+ console . log ( '\nTo enable trusted publisher:' )
221+ console . log ( '1. Go to: https://www.npmjs.com/settings/socketbin/integrations' )
222+ console . log ( '2. Click "Add Trusted Publisher"' )
223+ console . log ( '3. Configure:' )
224+ console . log ( ' - Repository: SocketDev/socket-cli' )
225+ console . log ( ' - Workflow: .github/workflows/publish-socketbin.yml' )
226+ console . log ( ' - Environment: (leave blank)\n' )
227+ console . log ( 'After configuration, the next publish from GitHub Actions will have provenance.' )
228+ } else {
229+ console . log ( '✅ Trusted Publisher Status: CONFIGURED\n' )
230+ console . log ( 'Packages published from GitHub Actions will have provenance attestations.' )
231+ }
232+
233+ // Test command
234+ console . log ( '\n🧪 Test Commands:\n' )
235+ console . log ( 'Test the workflow (dry run):' )
236+ console . log ( ' gh workflow run publish-socketbin.yml -f version=0.0.1-test -f dry-run=true\n' )
237+ console . log ( 'Publish with provenance (after trusted publisher is set):' )
238+ console . log ( ' gh workflow run publish-socketbin.yml -f version=0.0.1\n' )
239+ console . log ( 'Check specific package provenance:' )
240+ console . log ( ' npm view @socketbin/cli-darwin-arm64@latest dist.attestations.provenance' )
241+ }
242+
243+ // Run the script
244+ main ( ) . catch ( error => {
245+ console . error ( 'Error:' , error )
246+ process . exit ( 1 )
247+ } )
0 commit comments