hts/apps/migrant/lib/functions/deploy-contract/handle-imports.ts

96 lines
3.1 KiB
TypeScript

export default async function handleImports(
sourceCode: string,
sourcePath?: string
) {
const sources: { [fileName: string]: { content: any } } = {}
const importRegex = /import\s+(?:{[^}]+}\s+from\s+)?["']([^"']+)["'];/g
const matches = Array.from(sourceCode.matchAll(importRegex))
for (const match of matches) {
const importPath = match[1]
const { sources: importedSources, sourceCode: mainSourceCode } =
await fetchImport(importPath, sourcePath)
// Merge the imported sources into the main sources object
Object.assign(sources, importedSources)
let sourceFileName = importPath.split('/').pop() || importPath
// if sources[sourceFileName] already exists and the content is the same, then skip, otherwise change the sourceFileName to keep the folder structure but still be a relative path
if (
sources[sourceFileName] &&
sources[sourceFileName].content !== mainSourceCode
) {
sourceFileName = importPath.split('/').slice(-2).join('/')
}
sources[sourceFileName] = {
content: mainSourceCode
}
sourceCode = sourceCode.replace(match[0], `import "${sourceFileName}";`)
}
return { sources, sourceCode }
}
async function fetchImport(importPath: string, sourcePath?: string) {
// Determine the URL to fetch
let urlToFetch
if (importPath[0] === '.' && sourcePath) {
// If the import path starts with '.', it's a relative path, so resolve the path
let finalPath = resolveImportPath(importPath, sourcePath)
urlToFetch = finalPath
} else if (importPath[0] !== '@') {
// If the import path starts with anything other than '@', use it directly
urlToFetch = importPath
} else {
// Otherwise, convert the import path to an unpkg URL
urlToFetch = `https://unpkg.com/${importPath}`
}
// Convert GitHub URLs to raw content URLs
if (urlToFetch.includes('github.com')) {
urlToFetch = urlToFetch
.replace('github.com', 'raw.githubusercontent.com')
.replace('/blob/', '/')
}
// Fetch the imported file
const response = await fetch(urlToFetch)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
let importedSource = await response.text()
// Handle any imports within the fetched source code
const { sources, sourceCode } = await handleImports(
importedSource,
urlToFetch
)
return { sources, sourceCode }
}
/// utility function to handle relative paths
function resolveImportPath(importPath: string, sourcePath: string) {
let importSegments = importPath.split('/')
let sourceSegments = sourcePath.split('/')
// Remove the last segment (file name) from the source path
sourceSegments.pop()
// Process each segment of the import path
while (importSegments.length > 0 && importSegments[0] === '..') {
// Remove one directory level for each '..'
sourceSegments.pop()
importSegments.shift()
}
// Special handling for './'
if (importSegments.length > 0 && importSegments[0] === '.') {
importSegments.shift()
}
// Reconstruct the final path
return sourceSegments.concat(importSegments).join('/')
}