diff --git a/.github/workflows/generate.yml b/.github/workflows/generate.yml index fe926ba9..779676ec 100644 --- a/.github/workflows/generate.yml +++ b/.github/workflows/generate.yml @@ -6,9 +6,9 @@ on: - main paths: - '.github/workflows/generate.yml' - - 'openapi.json' + - 'scripts/generate-sdk.mjs' - 'scripts/generate-types.mjs' - - 'scripts/prettify-base-json.mjs' + - 'scripts/generate-strict-types.mjs' schedule: # At 07:23 on every day-of-week from Monday through Friday. - cron: '23 7 * * 1-5' @@ -46,34 +46,20 @@ jobs: - uses: SocketDev/socket-registry/.github/actions/setup-and-install@4709a2443e5a036bb0cd94e5d1559f138f05994c # main - - name: Fetch latest OpenAPI definition - id: fetch + - name: Generate SDK + run: pnpm run generate-sdk + + - name: Check for changes + id: check run: | - echo "Fetching latest OpenAPI definition..." - curl -sSL https://api.socket.dev/v0/openapi -o openapi-new.json - - # Check if file changed - if [ -f openapi.json ]; then - if diff -q openapi.json openapi-new.json > /dev/null; then - echo "No changes detected" - echo "changed=false" >> $GITHUB_OUTPUT - else - echo "Changes detected" - echo "changed=true" >> $GITHUB_OUTPUT - mv openapi-new.json openapi.json - fi + if [ -n "$(git status --porcelain)" ]; then + echo "has_changes=true" >> $GITHUB_OUTPUT else - echo "OpenAPI file doesn't exist, creating new one" - echo "changed=true" >> $GITHUB_OUTPUT - mv openapi-new.json openapi.json + echo "has_changes=false" >> $GITHUB_OUTPUT fi - - name: Run post-sync script - if: steps.fetch.outputs.changed == 'true' || github.event.inputs.force == 'true' - run: pnpm run generate-sdk - - name: Configure git - if: steps.fetch.outputs.changed == 'true' || github.event.inputs.force == 'true' + if: steps.check.outputs.has_changes == 'true' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | @@ -82,16 +68,6 @@ jobs: # Configure git to use the GitHub token for authentication git config --global url."https://x-access-token:${GH_TOKEN}@github.com/".insteadOf "https://github.com/" - - name: Check for changes - id: check - if: steps.fetch.outputs.changed == 'true' || github.event.inputs.force == 'true' - run: | - if [ -n "$(git status --porcelain)" ]; then - echo "has_changes=true" >> $GITHUB_OUTPUT - else - echo "has_changes=false" >> $GITHUB_OUTPUT - fi - - name: Commit and push changes if: steps.check.outputs.has_changes == 'true' run: | diff --git a/package.json b/package.json index ae3e0763..19154613 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,8 @@ "@types/node": "24.9.2", "@typescript/native-preview": "7.0.0-dev.20250926.1", "@vitest/coverage-v8": "4.0.3", + "@sveltejs/acorn-typescript": "1.0.8", + "acorn": "8.15.0", "del": "8.0.1", "dev-null-cli": "2.0.0", "esbuild": "0.25.11", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d250c216..0557556a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,6 +45,9 @@ importers: '@eslint/js': specifier: 9.35.0 version: 9.35.0 + '@sveltejs/acorn-typescript': + specifier: 1.0.8 + version: 1.0.8(acorn@8.15.0) '@types/babel__traverse': specifier: 7.28.0 version: 7.28.0 @@ -57,6 +60,9 @@ importers: '@vitest/coverage-v8': specifier: 4.0.3 version: 4.0.3(vitest@4.0.3(@types/node@24.9.2)(jiti@2.6.1)(yaml@2.8.2)) + acorn: + specifier: 8.15.0 + version: 8.15.0 del: specifier: 8.0.1 version: 8.0.1 @@ -701,6 +707,11 @@ packages: '@standard-schema/spec@1.0.0': resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + '@sveltejs/acorn-typescript@1.0.8': + resolution: {integrity: sha512-esgN+54+q0NjB0Y/4BomT9samII7jGwNy/2a3wNZbT2A2RpmXsXwUt24LvLhx6jUq2gVk4cWEvcRO6MFQbOfNA==} + peerDependencies: + acorn: ^8.9.0 + '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} @@ -2764,6 +2775,10 @@ snapshots: '@standard-schema/spec@1.0.0': {} + '@sveltejs/acorn-typescript@1.0.8(acorn@8.15.0)': + dependencies: + acorn: 8.15.0 + '@tybys/wasm-util@0.10.1': dependencies: tslib: 2.8.1 diff --git a/scripts/generate-sdk.mjs b/scripts/generate-sdk.mjs index 2945fa28..9521db4d 100644 --- a/scripts/generate-sdk.mjs +++ b/scripts/generate-sdk.mjs @@ -2,67 +2,83 @@ /** * @fileoverview SDK generation script. * Orchestrates the complete SDK generation process: - * 1. Prettifies the OpenAPI JSON + * 1. Fetches and formats OpenAPI JSON * 2. Generates TypeScript types from OpenAPI - * 3. Formats and lints the generated code + * 3. Generates strict types from OpenAPI * * Usage: * node scripts/generate-sdk.mjs */ -import { spawn } from 'node:child_process' -import { readFileSync, writeFileSync } from 'node:fs' -import { resolve } from 'node:path' +import { promises as fs } from 'node:fs' +import path from 'node:path' import { parse } from '@babel/parser' import { default as traverse } from '@babel/traverse' import * as t from '@babel/types' import MagicString from 'magic-string' +import { httpGetJson } from '@socketsecurity/lib/http-request' import { getDefaultLogger } from '@socketsecurity/lib/logger' +import { spawn } from '@socketsecurity/lib/spawn' import { getRootPath } from './utils/path-helpers.mjs' import { runCommand } from './utils/run-command.mjs' +const OPENAPI_URL = 'https://api.socket.dev/v0/openapi' + const rootPath = getRootPath(import.meta.url) -const typesPath = resolve(rootPath, 'types/api.d.ts') +const openApiPath = path.resolve(rootPath, 'openapi.json') +const typesPath = path.resolve(rootPath, 'types/api.d.ts') // Initialize logger const logger = getDefaultLogger() -async function generateTypes() { - return new Promise((resolve, reject) => { - const child = spawn('node', ['scripts/generate-types.mjs'], { - cwd: rootPath, - stdio: ['inherit', 'pipe', 'inherit'], - }) - - let output = '' - - child.stdout.on('data', data => { - output += data.toString() - }) - - child.on('exit', code => { - if (code !== 0) { - reject(new Error(`Type generation failed with exit code ${code}`)) - return - } +async function fetchOpenApi() { + const data = await httpGetJson(OPENAPI_URL) + await fs.writeFile(openApiPath, JSON.stringify(data, null, 2), 'utf8') + logger.log(`Downloaded from ${OPENAPI_URL}`) +} - try { - writeFileSync(typesPath, output, 'utf8') - // Fix array syntax after writing to disk - fixArraySyntax(typesPath) - // Add SDK v3 method name aliases - addSdkMethodAliases(typesPath) - resolve() - } catch (error) { - reject(error) - } - }) +async function generateStrictTypes() { + await spawn('node', ['scripts/generate-strict-types.mjs'], { + cwd: rootPath, + stdio: 'inherit', + }) + const exitCode = await runCommand('pnpm', [ + 'exec', + 'biome', + 'format', + '--log-level=none', + '--fix', + 'src/types-strict.ts', + ]) + if (exitCode !== 0) { + throw new Error(`Formatting strict types failed with exit code ${exitCode}`) + } +} - child.on('error', reject) +async function generateTypes() { + await spawn('node', ['scripts/generate-types.mjs'], { + cwd: rootPath, + stdio: 'inherit', }) + // Fix array syntax after writing to disk + await fixArraySyntax(typesPath) + // Add SDK v3 method name aliases + await addSdkMethodAliases(typesPath) + // Format generated types + const exitCode = await runCommand('pnpm', [ + 'exec', + 'biome', + 'format', + '--log-level=none', + '--fix', + 'types/api.d.ts', + ]) + if (exitCode !== 0) { + throw new Error(`Formatting types failed with exit code ${exitCode}`) + } } /** @@ -70,14 +86,14 @@ async function generateTypes() { * These aliases map the new SDK method names to their underlying OpenAPI operation names. * @param {string} filePath - The path to the TypeScript file to update */ -function addSdkMethodAliases(filePath) { - const content = readFileSync(filePath, 'utf8') +async function addSdkMethodAliases(filePath) { + const content = await fs.readFile(filePath, 'utf8') // Find the closing brace of the operations interface const operationsInterfaceEnd = content.lastIndexOf('\n}') if (operationsInterfaceEnd === -1) { - logger.error(' Could not find operations interface closing brace') + logger.error('Could not find operations interface closing brace') return } @@ -101,8 +117,8 @@ function addSdkMethodAliases(filePath) { content.slice(0, operationsInterfaceEnd) + aliases + content.slice(operationsInterfaceEnd) - writeFileSync(filePath, updated, 'utf8') - logger.log(' Added SDK v3 method name aliases') + await fs.writeFile(filePath, updated, 'utf8') + logger.log('Added SDK v3 method name aliases') } /** @@ -111,8 +127,8 @@ function addSdkMethodAliases(filePath) { * Complex types use Array syntax. * @param {string} filePath - The path to the TypeScript file to fix */ -function fixArraySyntax(filePath) { - const content = readFileSync(filePath, 'utf8') +async function fixArraySyntax(filePath) { + const content = await fs.readFile(filePath, 'utf8') const magicString = new MagicString(content) // Parse the TypeScript file @@ -186,12 +202,8 @@ function fixArraySyntax(filePath) { }, }) - logger.log( - ` Found ${transformCount + skipCount} complex arrays to transform`, - ) - logger.log( - ` Transformed ${transformCount}, skipped ${skipCount} (overlaps)`, - ) + logger.log(`Found ${transformCount + skipCount} complex arrays to transform`) + logger.log(`Transformed ${transformCount}, skipped ${skipCount} (overlaps)`) if (transformCount > 0) { const transformed = magicString.toString() @@ -200,65 +212,33 @@ function fixArraySyntax(filePath) { const objectArrayCount = (transformed.match(/\}\[\]/g) || []).length const arrayGenericCount = (transformed.match(/Array a.name.localeCompare(b.name)) + return properties +} + +/** + * Extract query parameters from operation. + */ +function extractQueryParams(operationsNode, operationId, source, config) { + const opProp = findProperty(operationsNode, operationId) + if (!opProp) { + return null + } + + const opType = opProp.typeAnnotation?.typeAnnotation + const paramsProp = findProperty(opType, 'parameters') + if (!paramsProp) { + return null + } + + const paramsType = paramsProp.typeAnnotation?.typeAnnotation + const queryProp = findProperty(paramsType, 'query') + if (!queryProp) { + return null + } + + const queryType = queryProp.typeAnnotation?.typeAnnotation + const properties = [] + const members = queryType?.members || [] + const requiredParams = new Set(config.requiredParams || []) + + for (const member of members) { + if (member.type === 'TSPropertySignature' && member.key?.name) { + const name = member.key.name + const isRequired = requiredParams.has(name) + let typeStr = typeNodeToString( + member.typeAnnotation?.typeAnnotation, + source, + ) + // Add | undefined for optional params only + if (!isRequired && !typeStr.includes('| undefined')) { + typeStr = `${typeStr} | undefined` + } + properties.push({ + name, + optional: !isRequired, + type: typeStr, + }) + } + } + + // Add additional fields from config + if (config.additionalFields) { + for (const field of config.additionalFields) { + properties.push({ + name: field.name, + optional: field.optional !== false, + type: field.type, + }) + } + } + + // Sort properties alphabetically + properties.sort((a, b) => a.name.localeCompare(b.name)) + return properties +} + +/** + * Extract response type from operation. + */ +function extractResponseType( + operationsNode, + operationId, + responseCode, + sourcePath, + source, + config, +) { + const opProp = findProperty(operationsNode, operationId) + if (!opProp) { + return null + } + + const opType = opProp.typeAnnotation?.typeAnnotation + const responsesProp = findProperty(opType, 'responses') + if (!responsesProp) { + return null + } + + const responsesType = responsesProp.typeAnnotation?.typeAnnotation + const codeProp = findProperty(responsesType, responseCode) + if (!codeProp) { + return null + } + + const codeType = codeProp.typeAnnotation?.typeAnnotation + const contentProp = findProperty(codeType, 'content') + if (!contentProp) { + return null + } + + const contentType = contentProp.typeAnnotation?.typeAnnotation + const jsonProp = findProperty(contentType, 'application/json') + if (!jsonProp) { + return null + } + + let targetType = jsonProp.typeAnnotation?.typeAnnotation + + // Navigate to nested path if specified + if (sourcePath && sourcePath.length > 0) { + targetType = navigateToPath(targetType, sourcePath) + } + + if (!targetType) { + return null + } + + return extractProperties(targetType, source, config) +} + +/** + * Find an export declaration by name in the AST. + */ +function findExportByName(ast, name) { + for (const node of ast.body) { + if ( + node.type === 'ExportNamedDeclaration' && + node.declaration?.type === 'TSInterfaceDeclaration' && + node.declaration.id?.name === name + ) { + return node.declaration + } + if ( + node.type === 'ExportNamedDeclaration' && + node.declaration?.type === 'TSTypeAliasDeclaration' && + node.declaration.id?.name === name + ) { + return node.declaration + } + } + return null +} + +/** + * Find a property in a type literal or interface body. + */ +function findProperty(node, propName) { + // TSInterfaceBody has .body array, TSTypeLiteral has .members array + const members = node.body || node.members || [] + for (const member of members) { + if (member.type === 'TSPropertySignature') { + // Key can be Identifier (name) or Literal (value for numbers/strings) + const keyName = member.key?.name ?? member.key?.value + if (keyName === propName) { + return member + } + } + } + return null +} + +/** + * Generate type definition string from properties. + */ +function generateTypeDefinition(typeName, properties, description) { + const lines = [] + lines.push('/**') + lines.push(` * ${description}`) + lines.push(' */') + lines.push(`export type ${typeName} = {`) + + for (const prop of properties) { + const opt = prop.optional ? '?' : '' + lines.push(` ${prop.name}${opt}: ${prop.type}`) + } + + lines.push('}') + return lines.join('\n') +} + +/** + * Generate wrapper result types. + */ +function generateWrapperTypes() { + return ` +/** + * Error result type for all SDK operations. + */ +export type StrictErrorResult = { + cause?: string | undefined + data?: undefined | undefined + error: string + status: number + success: false +} + +/** + * Generic strict result type combining success and error. + */ +export type StrictResult = + | { + cause?: undefined | undefined + data: T + error?: undefined | undefined + status: number + success: true + } + | StrictErrorResult + +/** + * Strict type for full scan list result. + */ +export type FullScanListResult = { + cause?: undefined | undefined + data: FullScanListData + error?: undefined | undefined + status: number + success: true +} + +/** + * Strict type for single full scan result. + */ +export type FullScanResult = { + cause?: undefined | undefined + data: FullScanItem + error?: undefined | undefined + status: number + success: true +} + +/** + * Options for streaming a full scan. + */ +export type StreamFullScanOptions = { + output?: boolean | string | undefined +} + +/** + * Strict type for organizations list result. + */ +export type OrganizationsResult = { + cause?: undefined | undefined + data: { + organizations: OrganizationItem[] + } + error?: undefined | undefined + status: number + success: true +} + +/** + * Strict type for repositories list result. + */ +export type RepositoriesListResult = { + cause?: undefined | undefined + data: RepositoriesListData + error?: undefined | undefined + status: number + success: true +} + +/** + * Strict type for delete operation result. + */ +export type DeleteResult = { + cause?: undefined | undefined + data: { success: boolean } + error?: undefined | undefined + status: number + success: true +} + +/** + * Strict type for single repository result. + */ +export type RepositoryResult = { + cause?: undefined | undefined + data: RepositoryItem + error?: undefined | undefined + status: number + success: true +} + +/** + * Strict type for repository labels list result. + */ +export type RepositoryLabelsListResult = { + cause?: undefined | undefined + data: RepositoryLabelsListData + error?: undefined | undefined + status: number + success: true +} + +/** + * Strict type for single repository label result. + */ +export type RepositoryLabelResult = { + cause?: undefined | undefined + data: RepositoryLabelItem + error?: undefined | undefined + status: number + success: true +} + +/** + * Strict type for delete repository label result. + */ +export type DeleteRepositoryLabelResult = { + cause?: undefined | undefined + data: { status: string } + error?: undefined | undefined + status: number + success: true +} +` +} + +/** + * Main generation function. + */ +async function main() { + try { + logger.log('Generating strict types from OpenAPI schema using AST...') + + // Step 1: Generate TypeScript using openapi-typescript + logger.log(' Running openapi-typescript...') + const generatedTS = await openapiTS(openApiPath, { + transform(schemaObject) { + if ('format' in schemaObject && schemaObject.format === 'binary') { + return 'never' + } + }, + }) + + // Step 2: Parse the generated TypeScript with acorn + logger.log(' Parsing generated TypeScript with acorn...') + const ast = parseTypeScript(generatedTS) + + // Step 3: Find the operations interface + const operationsDecl = findExportByName(ast, 'operations') + if (!operationsDecl) { + throw new Error('Could not find operations interface in generated types') + } + + const operationsNode = operationsDecl.body || operationsDecl.typeAnnotation + + // Step 4: Generate each configured type + const generatedTypes = [] + + for (const [key, config] of Object.entries(STRICT_TYPE_CONFIG)) { + if (config.extractType === 'queryParams') { + // Extract query parameters + const properties = extractQueryParams( + operationsNode, + config.operationId, + generatedTS, + config, + ) + + if (!properties) { + logger.log(` Warning: Could not extract query params for ${key}`) + continue + } + + const description = `Options for ${config.typeName + .replace(/Options$/, '') + .replace(/([A-Z])/g, ' $1') + .toLowerCase() + .trim()}.` + + const typeCode = generateTypeDefinition( + config.typeName, + properties, + description, + ) + generatedTypes.push(typeCode) + logger.log( + ` Generated ${config.typeName} with ${properties.length} params`, + ) + } else { + // Extract response type + const properties = extractResponseType( + operationsNode, + config.operationId, + config.responseCode, + config.sourcePath || [], + generatedTS, + config, + ) + + if (!properties) { + logger.log(` Warning: Could not extract response type for ${key}`) + continue + } + + const description = `Strict type for ${config.typeName + .replace(/([A-Z])/g, ' $1') + .toLowerCase() + .trim()}.` + + const typeCode = generateTypeDefinition( + config.typeName, + properties, + description, + ) + generatedTypes.push(typeCode) + logger.log( + ` Generated ${config.typeName} with ${properties.length} fields`, + ) + } + } + + // Step 5: Build the output file + const output = `/** + * @fileoverview Strict type definitions for Socket SDK v3. + * AUTO-GENERATED from OpenAPI definitions using AST parsing - DO NOT EDIT MANUALLY. + * These types provide better TypeScript DX by marking guaranteed fields as required + * and only keeping truly optional fields as optional. + * + * Generated by: scripts/generate-strict-types.mjs + */ +/* c8 ignore start - Type definitions only, no runtime code to test. */ + +${generatedTypes.join('\n\n')} +${generateWrapperTypes()} +/* c8 ignore stop */ +` + + // Step 6: Write the output file + await fs.writeFile(strictTypesPath, output, 'utf8') + logger.log(` Written to ${strictTypesPath}`) + logger.log('Strict type generation complete') + } catch (error) { + logger.error('Strict type generation failed:', error.message) + logger.error(error.stack) + process.exitCode = 1 + } +} + +/** + * Navigate to a nested type following a path. + */ +function navigateToPath(node, path) { + let current = unwrapType(node) + for (const segment of path) { + if (!current) { + return null + } + current = unwrapType(current) + + if (segment === 'Array' && current.type === 'TSArrayType') { + current = unwrapType(current.elementType) + continue + } + if (segment === 'items' && current.type === 'TSTypeLiteral') { + // Already at the array element type + continue + } + if (segment === 'Record' && current.type === 'TSTypeReference') { + // For Record, get T + if (current.typeParameters?.params?.[1]) { + current = unwrapType(current.typeParameters.params[1]) + continue + } + } + if (segment === 'Record' && current.type === 'TSTypeLiteral') { + // For { [key: string]: T }, get T via index signature + const indexSig = current.members?.find(m => m.type === 'TSIndexSignature') + if (indexSig?.typeAnnotation?.typeAnnotation) { + current = unwrapType(indexSig.typeAnnotation.typeAnnotation) + continue + } + } + if (segment === 'value') { + // Already navigated via Record + continue + } + + // Navigate to property + const prop = findProperty(current, segment) + if (prop?.typeAnnotation?.typeAnnotation) { + current = unwrapType(prop.typeAnnotation.typeAnnotation) + } else { + return null + } + } + return current +} + +/** + * Parse TypeScript source into AST. + */ +function parseTypeScript(source) { + return TSParser.parse(source, { + ecmaVersion: 'latest', + sourceType: 'module', + locations: true, + }) +} + +/** + * Convert AST type node to TypeScript string. + */ +function typeNodeToString(node, source) { + if (!node) { + return 'unknown' + } + return source.slice(node.start, node.end) +} + +/** + * Unwrap parenthesized types to get the inner type. + */ +function unwrapType(node) { + if (!node) { + return null + } + // Unwrap parenthesized types: (T) -> T + if (node.type === 'TSParenthesizedType') { + return unwrapType(node.typeAnnotation) + } + return node +} + +main().catch(e => { + logger.error(e) + process.exitCode = 1 +}) diff --git a/scripts/generate-types.mjs b/scripts/generate-types.mjs index bb4e7e46..bb64ecd7 100644 --- a/scripts/generate-types.mjs +++ b/scripts/generate-types.mjs @@ -2,6 +2,7 @@ * @fileoverview TypeScript type generation script for Socket API. * Generates type definitions from OpenAPI schema for Socket SDK. */ +import { promises as fs } from 'node:fs' import path from 'node:path' import openapiTS from 'openapi-typescript' @@ -14,6 +15,7 @@ const logger = getDefaultLogger() const rootPath = getRootPath(import.meta.url) const openApiJsonPath = path.join(rootPath, 'openapi.json') +const typesPath = path.join(rootPath, 'types/api.d.ts') async function main() { try { @@ -24,7 +26,8 @@ async function main() { } }, }) - logger.log(output) + await fs.writeFile(typesPath, output, 'utf8') + logger.log(` Written to ${typesPath}`) } catch (e) { process.exitCode = 1 logger.error('Failed with error:', e.message) diff --git a/scripts/prettify-base-json.mjs b/scripts/prettify-base-json.mjs deleted file mode 100644 index 5fb202fd..00000000 --- a/scripts/prettify-base-json.mjs +++ /dev/null @@ -1,33 +0,0 @@ -/** - * @fileoverview JSON prettification script for Socket API base files. - * Formats and prettifies JSON configuration files for better readability. - */ -import fs from 'node:fs/promises' -import path from 'node:path' - -import { getDefaultLogger } from '@socketsecurity/lib/logger' - -import { getRootPath } from './utils/path-helpers.mjs' - -const logger = getDefaultLogger() - -const rootPath = getRootPath(import.meta.url) -const openApiJsonPath = path.join(rootPath, 'openapi.json') - -async function main() { - try { - const openApiData = await fs.readFile(openApiJsonPath, 'utf8') - await fs.writeFile( - openApiJsonPath, - JSON.stringify(JSON.parse(openApiData), null, 2), - ) - } catch (e) { - process.exitCode = 1 - logger.error('Failed with error:', e.message) - } -} - -main().catch(e => { - logger.error(e) - process.exitCode = 1 -}) diff --git a/src/types-strict.ts b/src/types-strict.ts index 94bab1e1..eba85d36 100644 --- a/src/types-strict.ts +++ b/src/types-strict.ts @@ -1,76 +1,87 @@ /** * @fileoverview Strict type definitions for Socket SDK v3. + * AUTO-GENERATED from OpenAPI definitions using AST parsing - DO NOT EDIT MANUALLY. * These types provide better TypeScript DX by marking guaranteed fields as required - * and only keeping truly optional fields as optional. This improves IntelliSense autocomplete. + * and only keeping truly optional fields as optional. + * + * Generated by: scripts/generate-strict-types.mjs */ /* c8 ignore start - Type definitions only, no runtime code to test. */ /** - * Strict type for full scan metadata item. - * Represents a single full scan with guaranteed fields marked as required. + * Options for create full scan. + */ +export type CreateFullScanOptions = { + branch?: string | undefined + commit_hash?: string | undefined + commit_message?: string | undefined + committers?: string | undefined + integration_org_slug?: string | undefined + integration_type?: + | 'api' + | 'github' + | 'gitlab' + | 'bitbucket' + | 'azure' + | 'web' + | undefined + make_default_branch?: boolean | undefined + pathsRelativeTo?: string | undefined + pull_request?: number | undefined + repo: string + scan_type?: string | undefined + set_as_pending_head?: boolean | undefined + tmp?: boolean | undefined + workspace?: string | undefined +} + +/** + * Strict type for full scan item. */ export type FullScanItem = { - // Guaranteed fields (always returned by API) - id: string + api_url: string | null + branch?: string | null | undefined + commit_hash?: string | null | undefined + commit_message?: string | null | undefined + committers?: string[] | undefined created_at: string - updated_at: string + html_report_url: string + html_url?: string | null | undefined + id: string + integration_branch_url?: string | null | undefined + integration_commit_url?: string | null | undefined + integration_pull_request_url?: string | null | undefined + integration_repo_url: string + integration_type: string | null organization_id: string organization_slug: string + pull_request?: number | null | undefined + repo: string repository_id: string repository_slug: string - repo: string - html_report_url: string - api_url: string - integration_type: string - integration_repo_url: string - - // Truly optional/nullable fields - branch: string | null - commit_message: string | null - commit_hash: string | null - pull_request: number | null - committers: string[] - html_url: string | null - integration_branch_url: string | null - integration_commit_url: string | null - integration_pull_request_url: string | null - scan_state: 'pending' | 'precrawl' | 'resolve' | 'scan' | null - unmatchedFiles?: string[] | undefined + scan_state?: 'pending' | 'precrawl' | 'resolve' | 'scan' | null | undefined + updated_at: string + workspace?: string | undefined } /** - * Strict type for full scan list response. + * Strict type for full scan list data. */ export type FullScanListData = { + nextPage?: number | null | undefined + nextPageCursor?: string | null | undefined results: FullScanItem[] - nextPageCursor: string | null - nextPage: number | null } /** - * Strict type for full scan list result. + * Options for get repository. */ -export type FullScanListResult = { - cause?: undefined | undefined - data: FullScanListData - error?: undefined | undefined - status: number - success: true -} - -/** - * Strict type for single full scan result. - */ -export type FullScanResult = { - cause?: undefined | undefined - data: FullScanItem - error?: undefined | undefined - status: number - success: true +export type GetRepositoryOptions = { + workspace?: string | undefined } /** - * Options for listing full scans. + * Options for list full scans. */ export type ListFullScansOptions = { branch?: string | undefined @@ -81,43 +92,104 @@ export type ListFullScansOptions = { per_page?: number | undefined pull_request?: string | undefined repo?: string | undefined - sort?: 'created_at' | 'name' | undefined + sort?: 'name' | 'created_at' | undefined startAfterCursor?: string | undefined use_cursor?: boolean | undefined workspace?: string | undefined } /** - * Options for creating a full scan. + * Options for list repositories. */ -export type CreateFullScanOptions = { - branch?: string | undefined - commit_hash?: string | undefined - commit_message?: string | undefined - committers?: string | undefined - integration_org_slug?: string | undefined - integration_type?: - | 'api' - | 'azure' - | 'bitbucket' - | 'github' - | 'gitlab' - | undefined - make_default_branch?: boolean | undefined - pathsRelativeTo?: string | undefined - pull_request?: number | undefined - repo: string - scan_type?: string | undefined - set_as_pending_head?: boolean | undefined - tmp?: boolean | undefined - workspace?: string | undefined +export type ListRepositoriesOptions = { + direction?: string | undefined + include_archived?: boolean | undefined + page?: number | undefined + per_page?: number | undefined + sort?: string | undefined } /** - * Options for streaming a full scan. + * Strict type for organization item. */ -export type StreamFullScanOptions = { - output?: boolean | string | undefined +export type OrganizationItem = { + id: string + image?: string | null | undefined + name?: string | null | undefined + plan: string + slug: string +} + +/** + * Strict type for repositories list data. + */ +export type RepositoriesListData = { + nextPage?: number | null | undefined + results: RepositoryItem[] +} + +/** + * Strict type for repository item. + */ +export type RepositoryItem = { + archived: boolean + created_at: string + default_branch: string | null + description: string | null + head_full_scan_id: string | null + homepage: string | null + id: string + integration_meta: { + /** @enum {string} */ + type?: 'github' + value?: { + /** + * @description The GitHub installation_id of the active associated Socket GitHub App + * @default + */ + installation_id: string + /** + * @description The GitHub login name that the active Socket GitHub App installation is installed to + * @default + */ + installation_login: string + /** + * @description The name of the associated GitHub repo. + * @default + */ + repo_name: string | null + /** + * @description The id of the associated GitHub repo. + * @default + */ + repo_id: string | null + } + } | null + name: string + slig?: string | undefined + slug: string + updated_at: string + visibility: 'public' | 'private' + workspace: string +} + +/** + * Strict type for repository label item. + */ +export type RepositoryLabelItem = { + has_license_policy?: boolean | undefined + has_security_policy?: boolean | undefined + id: string + name: string + repository_ids?: string[] | undefined +} + +/** + * Strict type for repository labels list data. + */ +export type RepositoryLabelsListData = { + nextPage?: number | null | undefined + results: RepositoryLabelItem[] } /** @@ -145,53 +217,45 @@ export type StrictResult = | StrictErrorResult /** - * Strict type for organization item. + * Strict type for full scan list result. */ -export type OrganizationItem = { - id: string - name: string - slug: string - created_at: string - updated_at: string - plan: string +export type FullScanListResult = { + cause?: undefined | undefined + data: FullScanListData + error?: undefined | undefined + status: number + success: true } /** - * Strict type for organizations list result. + * Strict type for single full scan result. */ -export type OrganizationsResult = { +export type FullScanResult = { cause?: undefined | undefined - data: { - organizations: OrganizationItem[] - } + data: FullScanItem error?: undefined | undefined status: number success: true } /** - * Strict type for repository item. + * Options for streaming a full scan. */ -export type RepositoryItem = { - id: string - created_at: string - updated_at: string - name: string - organization_id: string - organization_slug: string - default_branch: string | null - homepage: string | null - archived: boolean - visibility: 'public' | 'private' | 'internal' +export type StreamFullScanOptions = { + output?: boolean | string | undefined } /** - * Strict type for repositories list data. + * Strict type for organizations list result. */ -export type RepositoriesListData = { - results: RepositoryItem[] - nextPageCursor: string | null - nextPage: number | null +export type OrganizationsResult = { + cause?: undefined | undefined + data: { + organizations: OrganizationItem[] + } + error?: undefined | undefined + status: number + success: true } /** @@ -205,25 +269,6 @@ export type RepositoriesListResult = { success: true } -/** - * Options for getting a single repository. - */ -export type GetRepositoryOptions = { - workspace?: string | undefined -} - -/** - * Options for listing repositories. - */ -export type ListRepositoriesOptions = { - direction?: 'asc' | 'desc' | undefined - page?: number | undefined - per_page?: number | undefined - sort?: 'created_at' | 'name' | undefined - startAfterCursor?: string | undefined - use_cursor?: boolean | undefined -} - /** * Strict type for delete operation result. */ @@ -246,28 +291,6 @@ export type RepositoryResult = { success: true } -/** - * Strict type for repository label item. - */ -export type RepositoryLabelItem = { - // Guaranteed fields (always returned by API) - id: string - name: string - - // Optional fields - repository_ids?: string[] | undefined - has_security_policy?: boolean | undefined - has_license_policy?: boolean | undefined -} - -/** - * Strict type for repository labels list data. - */ -export type RepositoryLabelsListData = { - results: RepositoryLabelItem[] - nextPage: number | null -} - /** * Strict type for repository labels list result. */ diff --git a/test/unit/socket-sdk-strict-types.test.mts b/test/unit/socket-sdk-strict-types.test.mts index 931ca3d1..a73e6b37 100644 --- a/test/unit/socket-sdk-strict-types.test.mts +++ b/test/unit/socket-sdk-strict-types.test.mts @@ -243,16 +243,12 @@ describe.sequential('Strict Types - v3.0', () => { id: 'org-1', name: 'Test Org', slug: 'test-org', - created_at: '2025-01-01T00:00:00Z', - updated_at: '2025-01-01T00:00:00Z', plan: 'pro', }, { id: 'org-2', name: 'Another Org', slug: 'another-org', - created_at: '2025-01-01T00:00:00Z', - updated_at: '2025-01-01T00:00:00Z', plan: 'free', }, ], @@ -277,8 +273,6 @@ describe.sequential('Strict Types - v3.0', () => { expect(typeof org.id).toBe('string') expect(typeof org.name).toBe('string') expect(typeof org.slug).toBe('string') - expect(typeof org.created_at).toBe('string') - expect(typeof org.updated_at).toBe('string') expect(typeof org.plan).toBe('string') })