Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .config/rollup.base.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
normalizeId,
resolveId
} from '../scripts/utils/packages.js'
import { envAsBoolean } from '@socketsecurity/registry/lib/env'

const require = createRequire(import.meta.url)

Expand All @@ -45,6 +46,11 @@ const {
tsconfigPath
} = constants

const IS_SENTRY_BUILD = envAsBoolean(process.env['SOCKET_WITH_SENTRY']);
console.log('IS_SENTRY_BUILD:', IS_SENTRY_BUILD);
const IS_PUBLISH = envAsBoolean(process.env['SOCKET_IS_PUBLISHED'])
console.log('IS_PUBLISH:', IS_PUBLISH);

const SOCKET_INTEROP = '_socketInterop'

const constantsSrcPath = path.join(rootSrcPath, `${CONSTANTS}.ts`)
Expand Down Expand Up @@ -125,6 +131,33 @@ function isAncestorsExternal(id, depStats) {
return true
}


function sentryAliasingPlugin() {
return {
name: 'sentry-alias-plugin',
order: 'post',
resolveId(source, importer) {
// By default use the noop file for crash handler.
// When at build-time the `SOCKET_WITH_SENTRY` flag is set, route to use
// the Sentry specific files instead.
if (source === './initialize-crash-handler') {
return IS_SENTRY_BUILD
? `${rootSrcPath}/initialize-sentry.ts`
: `${rootSrcPath}/initialize-crash-handler.ts`;
}

if (source === './handle-crash') {
return IS_SENTRY_BUILD
? `${rootSrcPath}/handle-crash-with-sentry.ts`
: `${rootSrcPath}/handle-crash.ts`;
}

return null;
}
};
}


export default function baseConfig(extendConfig = {}) {
const depStats = {
dependencies: { __proto__: null },
Expand Down Expand Up @@ -215,6 +248,7 @@ export default function baseConfig(extendConfig = {}) {
},
...extendConfig,
plugins: [
sentryAliasingPlugin(), // Should go real early.
customResolver,
jsonPlugin(),
tsPlugin({
Expand Down
86 changes: 83 additions & 3 deletions .config/rollup.dist.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ import {
existsSync,
mkdirSync,
rmSync,
writeFileSync
writeFileSync,
readFileSync
} from 'node:fs'
import path from 'node:path'
import { spawnSync } from 'node:child_process'
import { randomUUID } from 'node:crypto'

import { globSync as tinyGlobSync } from 'tinyglobby'

Expand All @@ -26,6 +29,8 @@ import {
isBuiltin,
normalizeId
} from '../scripts/utils/packages.js'
import { envAsBoolean } from '@socketsecurity/registry/lib/env'
import assert from 'node:assert'

const {
BABEL_RUNTIME,
Expand All @@ -44,6 +49,9 @@ const CONSTANTS_JS = `${CONSTANTS}.js`
const CONSTANTS_STUB_CODE = createStubCode(`../${CONSTANTS_JS}`)
const VENDOR_JS = `${VENDOR}.js`

const IS_SENTRY_BUILD = envAsBoolean(process.env['SOCKET_WITH_SENTRY']);
const IS_PUBLISH = envAsBoolean(process.env['SOCKET_IS_PUBLISHED'])

const distConstantsPath = path.join(rootDistPath, CONSTANTS_JS)
const distModuleSyncPath = path.join(rootDistPath, MODULE_SYNC)
const distRequirePath = path.join(rootDistPath, REQUIRE)
Expand All @@ -52,6 +60,10 @@ const editablePkgJson = readPackageJsonSync(rootPath, { editable: true })

const processEnvTapRegExp =
/\bprocess\.env(?:\.TAP|\[['"]TAP['"]\])(\s*\?[^:]+:\s*)?/g
const processEnvSocketIsPublishedRegExp =
/\bprocess\.env(?:\.SOCKET_IS_PUBLISHED|\[['"]SOCKET_IS_PUBLISHED['"]\])/g
const processEnvSocketCliVersionRegExp =
/\bprocess\.env(?:\.SOCKET_CLI_VERSION|\[['"]SOCKET_CLI_VERSION['"]\])/g

function createStubCode(relFilepath) {
return `'use strict'\n\nmodule.exports = require('${relFilepath}')\n`
Expand Down Expand Up @@ -104,13 +116,28 @@ function updateDepStatsSync(depStats) {
delete depStats.dependencies[key]
}
}

assert(Object.keys(editablePkgJson?.content?.bin).join(',') === 'socket,socket-npm,socket-npx', 'If this fails, make sure to update the rollup sentry override for .bin to match the regular build!');
if (IS_SENTRY_BUILD) {
editablePkgJson.content['name'] = '@socketsecurity/socket-with-sentry'
editablePkgJson.content['description'] = "CLI tool for Socket.dev, includes Sentry error handling, otherwise identical to the regular `socket` package"
editablePkgJson.content['bin'] = {
"socket-with-sentry": "bin/cli.js",
"socket-npm-with-sentry": "bin/npm-cli.js",
"socket-npx-with-sentry": "bin/npx-cli.js"
}
// Add Sentry as a regular dep for this build
depStats.dependencies['@sentry/node'] = '9.1.0';
}

depStats.dependencies = toSortedObject(depStats.dependencies)
depStats.devDependencies = toSortedObject(depStats.devDependencies)
depStats.esm = toSortedObject(depStats.esm)
depStats.external = toSortedObject(depStats.external)
depStats.transitives = toSortedObject(depStats.transitives)
// Write dep stats.
writeFileSync(depStatsPath, `${formatObject(depStats)}\n`, 'utf8')

// Update dependencies with additional inlined modules.
editablePkgJson
.update({
Expand All @@ -120,6 +147,40 @@ function updateDepStatsSync(depStats) {
}
})
.saveSync()

if (IS_SENTRY_BUILD) {
// Replace the name in the package lock too, just in case.
const lock = readFileSync('package-lock.json', 'utf8');
// Note: this should just replace the first occurrence, even if there are more
const lock2 = lock.replace('"name": "socket",', '"name": "@socketsecurity/socket-with-sentry",')
writeFileSync('package-lock.json', lock2)
}
}

function versionBanner(_chunk) {
let pkgJsonVersion = 'unknown';
try { pkgJsonVersion = JSON.parse(readFileSync('package.json', 'utf8'))?.version ?? 'unknown' } catch {}

let gitHash = ''
try {
const obj = spawnSync('git', ['rev-parse','--short', 'HEAD']);
if (obj.stdout) {
gitHash = obj.stdout.toString('utf8').trim()
}
} catch {}

// Make each build generate a unique version id, regardless
// Mostly for development: confirms the build refreshed. For prod
// builds the git hash should suffice to identify the build.
const rng = randomUUID().split('-')[0];

return `
var SOCKET_CLI_PKG_JSON_VERSION = "${pkgJsonVersion}"
var SOCKET_CLI_GIT_HASH = "${gitHash}"
var SOCKET_CLI_BUILD_RNG = "${rng}"
var SOCKET_PUB = ${IS_PUBLISH}
var SOCKET_CLI_VERSION = "${pkgJsonVersion}:${gitHash}:${rng}${IS_PUBLISH ? ':pub':''}"
`.trim().split('\n').map(s => s.trim()).join('\n')
}

export default () => {
Expand All @@ -132,12 +193,15 @@ export default () => {
},
output: [
{
intro: versionBanner, // Note: "banner" would defeat "use strict"
dir: path.relative(rootPath, distModuleSyncPath),
entryFileNames: '[name].js',
exports: 'auto',
externalLiveBindings: false,
format: 'cjs',
freeze: false
freeze: false,
sourcemap: true,
sourcemapDebugIds: true,
}
],
external(id_) {
Expand Down Expand Up @@ -182,12 +246,15 @@ export default () => {
},
output: [
{
intro: versionBanner, // Note: "banner" would defeat "use strict"
dir: path.relative(rootPath, distRequirePath),
entryFileNames: '[name].js',
exports: 'auto',
externalLiveBindings: false,
format: 'cjs',
freeze: false
freeze: false,
sourcemap: true,
sourcemapDebugIds: true,
}
],
plugins: [
Expand All @@ -197,6 +264,19 @@ export default () => {
find: processEnvTapRegExp,
replace: (_match, ternary) => (ternary ? '' : 'false')
}),
// Replace `process.env.SOCKET_IS_PUBLISHED` with a boolean
socketModifyPlugin({
find: processEnvSocketIsPublishedRegExp,
// Note: these are going to be bools in JS, not strings
replace: () => (IS_PUBLISH ? 'true' : 'false')
}),
// Replace `process.env.SOCKET_CLI_VERSION` with var ref that rollup
// adds to the top of each file.
socketModifyPlugin({
find: processEnvSocketCliVersionRegExp,
replace: 'SOCKET_CLI_VERSION'
}),

{
generateBundle(_options, bundle) {
for (const basename of Object.keys(bundle)) {
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/provenance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ jobs:
scope: "@socketsecurity"
- run: npm install -g npm@latest
- run: npm ci
- run: npm run build:dist
- run: SOCKET_IS_PUBLISHED=1 npm run build:dist
- run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- run: SOCKET_IS_PUBLISHED=1 SOCKET_WITH_SENTRY=1 npm run build:dist
- run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
11 changes: 10 additions & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#!/usr/bin/env node

// Keep this on top, no `from`, just init:
import './initialize-crash-handler'

import process from 'node:process'
import { pathToFileURL } from 'node:url'

Expand All @@ -20,6 +23,7 @@ import { cmdLogout } from './commands/logout/cmd-logout'
import { cmdManifest } from './commands/manifest/cmd-manifest'
import { cmdNpm } from './commands/npm/cmd-npm'
import { cmdNpx } from './commands/npx/cmd-npx'
import { cmdOops } from './commands/oops/cmd-oops'
import { cmdOptimize } from './commands/optimize/cmd-optimize'
import { cmdOrganizations } from './commands/organizations/cmd-organizations'
import { cmdRawNpm } from './commands/raw-npm/cmd-raw-npm'
Expand All @@ -30,6 +34,7 @@ import { cmdScan } from './commands/scan/cmd-scan'
import { cmdThreatFeed } from './commands/threat-feed/cmd-threat-feed'
import { cmdWrapper } from './commands/wrapper/cmd-wrapper'
import constants from './constants'
import { handle } from './handle-crash'
import { AuthError, InputError } from './utils/errors'
import { logSymbols } from './utils/logging'
import { meowWithSubcommands } from './utils/meow-with-subcommands'
Expand All @@ -55,6 +60,7 @@ void (async () => {
logout: cmdLogout,
npm: cmdNpm,
npx: cmdNpx,
oops: cmdOops,
optimize: cmdOptimize,
organization: cmdOrganizations,
'raw-npm': cmdRawNpm,
Expand Down Expand Up @@ -106,6 +112,9 @@ void (async () => {
if (errorBody) {
console.error(`\n${errorBody}`)
}
process.exit(1)

process.exitCode = 1

await handle(err)
}
})()
37 changes: 37 additions & 0 deletions src/commands/oops/cmd-oops.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import meowOrExit from 'meow'

import { CliCommandConfig } from '../../utils/meow-with-subcommands.ts'

const config: CliCommandConfig = {
commandName: 'oops',
description: 'Trigger an intentional error (for development)',
hidden: true,
flags: {},
help: (parentName, config) => `
Usage
$ ${parentName} ${config.commandName}

Don't run me.
`
}

export const cmdOops = {
description: config.description,
hidden: config.hidden,
run
}

async function run(
argv: readonly string[],
importMeta: ImportMeta,
{ parentName }: { parentName: string }
): Promise<void> {
meowOrExit(config.help(parentName, config), {
argv,
description: config.description,
importMeta,
flags: config.flags
})

throw new Error('This error was intentionally left blank')
}
3 changes: 3 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ type Constants = Omit<
readonly ENV: ENV
readonly DIST_TYPE: 'module-sync' | 'require'
readonly IPC: IPC
readonly IS_PUBLISHED: boolean
readonly LOCK_EXT: '.lock'
readonly MODULE_SYNC: 'module-sync'
readonly NPM_REGISTRY_URL: 'https://registry.npmjs.org'
Expand Down Expand Up @@ -95,6 +96,7 @@ const BUN = 'bun'
const CVE_ALERT_PROPS_FIRST_PATCHED_VERSION_IDENTIFIER =
'firstPatchedVersionIdentifier'
const CVE_ALERT_PROPS_VULNERABLE_VERSION_RANGE = 'vulnerableVersionRange'
const IS_PUBLISHED = process.env['SOCKET_IS_PUBLISHED']
const LOCK_EXT = '.lock'
const MODULE_SYNC = 'module-sync'
const NPM_REGISTRY_URL = 'https://registry.npmjs.org'
Expand Down Expand Up @@ -178,6 +180,7 @@ const constants = <Constants>createConstantsObject(
// Lazily defined values are initialized as `undefined` to keep their key order.
DIST_TYPE: undefined,
ENV: undefined,
IS_PUBLISHED,
LOCK_EXT,
MODULE_SYNC,
NPM_REGISTRY_URL,
Expand Down
25 changes: 25 additions & 0 deletions src/handle-crash-with-sentry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// In a Sentry build, this file will replace the `handle_crash.ts`, see rollup.
//
// This is intended to send a caught-but-unexpected exception to Sentry
// It only works in a special @socketsecurity/cli-with-sentry build.
//
// The regular build will not have the Sentry dependency at all because we
// don't want to give people the idea that we're using it to gather telemetry.

// @ts-ignore
import * as sentry from '@sentry/node'

// Note: Make sure not to exit() explicitly after calling this command. Sentry
// needs some time to finish the fetch() but it doesn't return a promise.
export async function handle(err: unknown) {
if (process.env['SOCKET_CLI_DEBUG'] === '1') {
console.log('Sending to Sentry...')
}
sentry.captureException(err)
if (process.env['SOCKET_CLI_DEBUG'] === '1') {
console.log('Request to Sentry initiated.')
}

// "Sleep" for a second, just in case, hopefully enough time to initiate fetch
return await new Promise(r => setTimeout(r, 1000))
}
11 changes: 11 additions & 0 deletions src/handle-crash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// By default this doesn't do anything.
// There's a special cli package in the @socketsecurity scope that is identical
// to this package, except it actually handles error crash reporting.

import { envAsBoolean } from '@socketsecurity/registry/lib/env'

export async function handle(err: unknown) {
if (envAsBoolean(process.env['SOCKET_CLI_DEBUG'])) {
console.error('An unexpected but caught error happened:', err)
}
}
3 changes: 3 additions & 0 deletions src/initialize-crash-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This is a placeholder
// In a special @socketsecurity scoped build this will hold crash handler init
// See the rollup config for details.
Loading
Loading