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
69 changes: 29 additions & 40 deletions lib/eval/import-eval-path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { importSnippet } from "./import-snippet"
import { resolveFilePath } from "lib/runner/resolveFilePath"
import { resolveNodeModule } from "lib/utils/resolve-node-module"
import { importNodeModule } from "./import-node-module"
import { importNpmPackage } from "./import-npm-package"
import { importNpmPackageFromCdn } from "./import-npm-package-from-cdn"
import {
getTsConfig,
matchesTsconfigPathPattern,
Expand All @@ -24,7 +24,6 @@ export async function importEvalPath(
depth = 0,
opts: {
cwd?: string
fromJsDelivr?: boolean
} = {},
) {
debug("importEvalPath called with:", {
Expand Down Expand Up @@ -97,12 +96,8 @@ export async function importEvalPath(
`Cannot find module "${pkgName}". The package is not available in the local environment.\n\n${ctx.logger.stringifyLogs()}`,
)
}
ctx.logger.info(`importNpmPackage("${pkgName}")`)
// /npm/ paths are always transitive dependencies from jsDelivr
await importNpmPackage(
{ importName: pkgName, depth, fromJsDelivr: true },
ctx,
)
ctx.logger.info(`importNpmPackageFromCdn("${pkgName}")`)
await importNpmPackageFromCdn({ importName: pkgName, depth }, ctx)
const pkg = preSuppliedImports[pkgName]
if (pkg) {
preSuppliedImports[importName] = pkg
Expand Down Expand Up @@ -198,41 +193,38 @@ export async function importEvalPath(
}

if (!importName.startsWith(".") && !importName.startsWith("/")) {
// Validation steps for node modules (before jsDelivr fallback)
if (!opts.fromJsDelivr) {
// Step 1: Check if package is declared in package.json
if (!isPackageDeclaredInPackageJson(importName, ctx.fsMap)) {
throw new Error(
`Node module imported but not in package.json "${importName}"\n\n${ctx.logger.stringifyLogs()}`,
)
}
// Step 1: Check if package is declared in package.json
if (!isPackageDeclaredInPackageJson(importName, ctx.fsMap)) {
throw new Error(
`Node module imported but not in package.json "${importName}"\n\n${ctx.logger.stringifyLogs()}`,
)
}

// Step 2: Check if node_modules directory exists (only if not found locally yet)
// Only validate if CDN loading is disabled (i.e., no fallback to jsDelivr available)
const nodeModuleDir = getNodeModuleDirectory(importName, ctx.fsMap)
if (!nodeModuleDir && disableCdnLoading) {
// Step 2: Check if node_modules directory exists (only if not found locally yet)
// Only validate if CDN loading is disabled (i.e., no fallback to jsDelivr available)
const nodeModuleDir = getNodeModuleDirectory(importName, ctx.fsMap)
if (!nodeModuleDir && disableCdnLoading) {
throw new Error(
`Node module "${importName}" has no files in the node_modules directory\n\n${ctx.logger.stringifyLogs()}`,
)
}

// Step 3: Check if main entrypoint is a TypeScript file (only if dir exists)
if (nodeModuleDir) {
const entrypoint = getPackageJsonEntrypoint(importName, ctx.fsMap)
if (isTypeScriptEntrypoint(entrypoint)) {
throw new Error(
`Node module "${importName}" has no files in the node_modules directory\n\n${ctx.logger.stringifyLogs()}`,
`Node module "${importName}" has a typescript entrypoint that is unsupported\n\n${ctx.logger.stringifyLogs()}`,
)
}

// Step 3: Check if main entrypoint is a TypeScript file (only if dir exists)
if (nodeModuleDir) {
const entrypoint = getPackageJsonEntrypoint(importName, ctx.fsMap)
if (isTypeScriptEntrypoint(entrypoint)) {
// Step 4: Check if dist directory is empty when main points to dist
if (entrypoint?.startsWith("dist/")) {
if (isDistDirEmpty(importName, ctx.fsMap)) {
throw new Error(
`Node module "${importName}" has a typescript entrypoint that is unsupported\n\n${ctx.logger.stringifyLogs()}`,
`Node module "${importName}" has no files in dist, did you forget to transpile?\n\n${ctx.logger.stringifyLogs()}`,
)
}

// Step 4: Check if dist directory is empty when main points to dist
if (entrypoint && entrypoint.startsWith("dist/")) {
if (isDistDirEmpty(importName, ctx.fsMap)) {
throw new Error(
`Node module "${importName}" has no files in dist, did you forget to transpile?\n\n${ctx.logger.stringifyLogs()}`,
)
}
}
}
}

Expand All @@ -241,11 +233,8 @@ export async function importEvalPath(
`Cannot find module "${importName}". The package is not available in the local environment.\n\n${ctx.logger.stringifyLogs()}`,
)
}
ctx.logger.info(`importNpmPackage("${importName}")`)
return importNpmPackage(
{ importName, depth, fromJsDelivr: opts.fromJsDelivr },
ctx,
)
ctx.logger.info(`importNpmPackageFromCdn("${importName}")`)
return importNpmPackageFromCdn({ importName, depth }, ctx)
}

throw new Error(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import Debug from "debug"
import { getImportsFromCode } from "lib/utils/get-imports-from-code"
import { importEvalPath } from "./import-eval-path"
import { transformWithSucrase } from "lib/transpile/transform-with-sucrase"
import { isPackageDeclaredInPackageJson } from "./isPackageDeclaredInPackageJson"

const debug = Debug("tsci:eval:import-npm-package")

Expand All @@ -17,28 +16,15 @@ function extractPackagePathFromJSDelivr(url: string) {
return url
}

export async function importNpmPackage(
{
importName,
depth = 0,
fromJsDelivr = false,
}: { importName: string; depth?: number; fromJsDelivr?: boolean },
export async function importNpmPackageFromCdn(
{ importName, depth = 0 }: { importName: string; depth?: number },
ctx: ExecutionContext,
) {
debug(`importing npm package: ${importName}`)
const { preSuppliedImports, fsMap } = ctx
debug(`importing npm package from CDN: ${importName}`)
const { preSuppliedImports } = ctx

if (preSuppliedImports[importName]) return

// Check if the package is declared in package.json before fetching from jsDelivr
// Skip this check for transitive dependencies (sub-imports from jsDelivr packages)
if (!fromJsDelivr && !isPackageDeclaredInPackageJson(importName, fsMap)) {
throw new Error(
`Package "${importName}" is not declared in package.json. ` +
`Add it to dependencies or devDependencies before importing.\n\n${ctx.logger.stringifyLogs()}`,
)
}

const npmCdnUrl = `https://cdn.jsdelivr.net/npm/${importName}/+esm`

let finalUrl: string | undefined
Expand Down Expand Up @@ -67,7 +53,6 @@ export async function importNpmPackage(
if (!preSuppliedImports[subImportName]) {
await importEvalPath(subImportName, ctx, depth + 1, {
cwd,
fromJsDelivr: true,
})
}
}
Expand Down
2 changes: 1 addition & 1 deletion lib/eval/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export { importEvalPath } from "./import-eval-path"
export { importLocalFile } from "./import-local-file"
export { importSnippet } from "./import-snippet"
export { importNodeModule } from "./import-node-module"
export { importNpmPackage } from "./import-npm-package"
export { importNpmPackageFromCdn as importNpmPackage } from "./import-npm-package-from-cdn"
export { evalCompiledJs } from "./eval-compiled-js"
export { transformWithSucrase } from "lib/transpile/transform-with-sucrase"
export { extractBasePackageName } from "./extractBasePackageName"
Expand Down