import { tmpdir } from 'node:os'; import { dirname, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { existsSync } from 'node:fs'; import { realpath } from 'node:fs/promises'; import { bundlePlayFile as bundlePlayFileCore, type BundlePlayFileOptions, type BundledPlayFileSuccess, type BundledPlayFileResult, type ImportedPlayDependency, type PlayBundlingAdapter, type PlayLocalFileDiscoveryError, type PlayLocalFileReference, } from '../../../shared_libs/plays/bundling/index.js'; import { PLAY_ARTIFACT_KINDS, type PlayArtifactKind, } from '../../../shared_libs/play-runtime/backend.js'; import { resolveExecutionProfile } from '../../../shared_libs/play-runtime/profiles.js'; import { validatePlaySourceFilesHaveNoInlineSecrets } from '../../../shared_libs/plays/secret-guardrails.js'; import { discoverPackagedLocalFiles } from './local-file-discovery.js'; export type { BundlePlayFileOptions, BundledPlayFileSuccess, BundledPlayFileResult, ImportedPlayDependency, PlayLocalFileDiscoveryError, PlayLocalFileReference, }; export type { PlayArtifactCompatibility, PlayBundleArtifact, PlayImportPolicy, PlayPackageImport, PlayRuntimeFeature, } from '../../../shared_libs/plays/bundling/index.js'; export { extractDefinedPlayName } from '../../../shared_libs/plays/bundling/index.js'; const PLAY_BUNDLE_CACHE_VERSION = 35; const MODULE_DIR = dirname(fileURLToPath(import.meta.url)); const SDK_PACKAGE_ROOT = resolve(MODULE_DIR, '..', '..'); const SOURCE_REPO_ROOT = resolve(SDK_PACKAGE_ROOT, '..'); const HAS_SOURCE_BUNDLING_SOURCES = existsSync( resolve(SOURCE_REPO_ROOT, 'shared_libs', 'plays', 'bundling', 'index.ts'), ); const PACKAGED_BUNDLING_SOURCE_ROOT = resolve( SDK_PACKAGE_ROOT, 'dist', 'bundling-sources', ); const HAS_PACKAGED_BUNDLING_SOURCES = existsSync( resolve( PACKAGED_BUNDLING_SOURCE_ROOT, 'shared_libs', 'plays', 'bundling', 'index.ts', ), ); const PROJECT_ROOT = HAS_SOURCE_BUNDLING_SOURCES ? SOURCE_REPO_ROOT : HAS_PACKAGED_BUNDLING_SOURCES ? PACKAGED_BUNDLING_SOURCE_ROOT : resolve(SDK_PACKAGE_ROOT, '..'); const SDK_SOURCE_ROOT = HAS_SOURCE_BUNDLING_SOURCES ? resolve(SOURCE_REPO_ROOT, 'sdk', 'src') : HAS_PACKAGED_BUNDLING_SOURCES ? resolve(PACKAGED_BUNDLING_SOURCE_ROOT, 'sdk', 'src') : resolve(SDK_PACKAGE_ROOT, 'src'); const SDK_PACKAGE_JSON = resolve(SDK_PACKAGE_ROOT, 'package.json'); const SDK_ENTRY_FILE = resolve(SDK_SOURCE_ROOT, 'index.ts'); const SDK_TYPES_ENTRY_FILE = HAS_SOURCE_BUNDLING_SOURCES ? SDK_ENTRY_FILE : resolve(SDK_PACKAGE_ROOT, 'dist', 'index.d.ts'); let hasWarnedAboutNonDevelopmentBundling = false; /** * SDK/local bundling deliberately stays tool-metadata agnostic. * * The SDK's job is to turn a local play file into a portable artifact using * only SDK/runtime sources. It must work for installed packages, source * checkouts, CI smoke jobs, and clean worktrees without app-generated files * such as `src/lib/generated/tool-typecheck-catalog.jsonl`. * * Tool-aware validation belongs to the Deepline API after upload: * `compilePlayManifest` / server preflight runs the cloud typechecker inside * the app deployment, where the generated catalog is a normal build artifact. * Do not import `src/lib/plays/cloud-tool-typecheck` or any generated app * catalog from this SDK adapter. */ function warnAboutNonDevelopmentBundling(filePath: string): void { if (hasWarnedAboutNonDevelopmentBundling) { return; } const nodeEnv = String(process.env.NODE_ENV ?? '') .trim() .toLowerCase(); if (!nodeEnv || nodeEnv === 'development' || nodeEnv === 'test') { return; } hasWarnedAboutNonDevelopmentBundling = true; console.warn( `[deepline] Warning: live play bundling was invoked while NODE_ENV=${nodeEnv} for ${filePath}. ` + 'This source-first SDK path is intended for local development. ' + 'For preview/production, run a published or prebuilt play reference instead of bundling source at runtime.', ); console.warn( '[deepline] Preferred production call pattern: client.play("person-to-email").run(...) ' + 'or run a previously registered/published org play.', ); } function defaultPlayBundleTarget(): PlayArtifactKind { return resolveExecutionProfile(null).artifactKind; } export function createSdkPlayBundlingAdapter(): PlayBundlingAdapter { return { projectRoot: PROJECT_ROOT, nodeModulesDir: HAS_PACKAGED_BUNDLING_SOURCES ? resolve(SDK_PACKAGE_ROOT, 'node_modules') : resolve(PROJECT_ROOT, 'node_modules'), cacheDir: join( tmpdir(), `deepline-play-artifacts-v${PLAY_BUNDLE_CACHE_VERSION}`, ), sdkSourceRoot: SDK_SOURCE_ROOT, sdkPackageJson: SDK_PACKAGE_JSON, sdkEntryFile: SDK_ENTRY_FILE, sdkTypesEntryFile: HAS_SOURCE_BUNDLING_SOURCES || !existsSync(SDK_TYPES_ENTRY_FILE) ? SDK_ENTRY_FILE : SDK_TYPES_ENTRY_FILE, discoverPackagedLocalFiles, warnAboutNonDevelopmentBundling, }; } export async function bundlePlayFile( filePath: string, options: BundlePlayFileOptions = {}, ): Promise { // The SDK sends this graph to a remote checker/runtime. Its file identities // therefore must describe the authoring workspace, rather than the local // machine paths used while building it. This is especially important on // Windows: a raw `C:\\...` key cannot be reconciled beneath `/var/task`. // Match the bundler's physical-path normalization. On macOS, for example, // os.tmpdir() can report /var while the source graph resolves /private/var. const localWorkspaceRoot = dirname(resolve(filePath)); const sourceIdentityRoot = await realpath(localWorkspaceRoot).catch( () => localWorkspaceRoot, ); const result = await bundlePlayFileCore(filePath, { target: options.target ?? defaultPlayBundleTarget(), exportName: options.exportName, adapter: { ...createSdkPlayBundlingAdapter(), sourceIdentityRoot, }, }); if (result.success) validatePlaySourceFilesHaveNoInlineSecrets(result.sourceFiles); return result; } export { PLAY_ARTIFACT_KINDS };