#!/usr/bin/env php $exports]; } if (isset($exports['.']['import']) || isset($exports['.']['default'])) { $exportPath = $exports['.']['import'] ?? $exports['.']['default']; if ($exportPath && file_exists($packageDir . '/' . $exportPath)) { return $packageDir . '/' . $exportPath; } } } // Check module field (ESM) if (isset($pkg['module']) && file_exists($packageDir . '/' . $pkg['module'])) { return $packageDir . '/' . $pkg['module']; } // Check main field if (isset($pkg['main']) && file_exists($packageDir . '/' . $pkg['main'])) { return $packageDir . '/' . $pkg['main']; } } // Fallback: try common locations $commonPaths = [ '/dist/index.js', '/index.js', '/esm/index.js', '/es/index.js', '/lib/index.js', '/src/index.js', '/main.js', ]; foreach ($commonPaths as $path) { if (file_exists($packageDir . $path)) { return $packageDir . $path; } } // Last resort: find any .js file in root $files = glob($packageDir . '/*.js'); if (!empty($files)) { return $files[0]; } // Try dist directory $distFiles = glob($packageDir . '/dist/*.js'); if (!empty($distFiles)) { return $distFiles[0]; } return null; } /** * Copy package to assets/vendor */ function copyPackage($packageName, $nodeModulesDir, $assetsVendorDir) { // Handle scoped packages $parts = explode('/', $packageName); if ($packageName[0] === '@') { $sourceDir = $nodeModulesDir . '/' . $parts[0] . '/' . $parts[1]; $targetDir = $assetsVendorDir . '/' . $parts[0] . '/' . $parts[1]; } else { $sourceDir = $nodeModulesDir . '/' . $parts[0]; $targetDir = $assetsVendorDir . '/' . $parts[0]; } if (!is_dir($sourceDir)) { return ['success' => false, 'error' => "Package not found in node_modules: $packageName"]; } $entryPoint = findEntryPoint($sourceDir); if (!$entryPoint) { return ['success' => false, 'error' => "Could not find entry point for: $packageName"]; } // Create target directory if (!is_dir($targetDir)) { mkdir($targetDir, 0755, true); } // Copy entry point to index.js $targetFile = $targetDir . '/index.js'; if (!copy($entryPoint, $targetFile)) { return ['success' => false, 'error' => "Failed to copy file: $entryPoint"]; } // Copy package.json for reference (optional) $pkgJson = $sourceDir . '/package.json'; if (file_exists($pkgJson)) { copy($pkgJson, $targetDir . '/package.json'); } return ['success' => true, 'path' => './assets/vendor/' . str_replace($assetsVendorDir . '/', '', $targetFile)]; } // Process each entry in importmap foreach ($map as $name => &$config) { // Only process entries with version but no path (need to be installed) if (isset($config['version']) && !isset($config['path'])) { // Extract package name (handle subpaths like 'quill/dist/quill.core.css') $parts = explode('/', $name); if ($name[0] === '@') { // Scoped package: @noble/curves/secp256k1 -> @noble/curves $packageName = $parts[0] . '/' . $parts[1]; } else { // Regular package: quill/dist/quill.core.css -> quill $packageName = $parts[0]; } $result = copyPackage($packageName, $nodeModulesDir, $assetsVendorDir); if ($result['success']) { // Update importmap.php entry - remove version when adding path $config['path'] = $result['path']; unset($config['version']); // Remove version when path is set $updated = true; echo "✓ Installed: $name -> {$result['path']}\n"; } else { $errors[] = "$name: {$result['error']}"; echo "✗ Failed: $name - {$result['error']}\n"; } } } // Handle CSS files and subpaths - copy the full package structure foreach ($map as $name => &$config) { if (isset($config['version']) && isset($config['type']) && $config['type'] === 'css') { // For CSS files, we need to copy from node_modules/dist $parts = explode('/', $name); $packageName = $parts[0]; $subPath = implode('/', array_slice($parts, 1)); if ($packageName[0] === '@') { $sourceFile = $nodeModulesDir . '/' . $parts[0] . '/' . $parts[1] . '/' . implode('/', array_slice($parts, 2)); $targetDir = $assetsVendorDir . '/' . $parts[0] . '/' . $parts[1]; } else { $sourceFile = $nodeModulesDir . '/' . $packageName . '/' . $subPath; $targetDir = $assetsVendorDir . '/' . $packageName; } if (file_exists($sourceFile)) { $targetFile = $targetDir . '/' . $subPath; $targetFileDir = dirname($targetFile); if (!is_dir($targetFileDir)) { mkdir($targetFileDir, 0755, true); } copy($sourceFile, $targetFile); $config['path'] = './assets/vendor/' . str_replace($assetsVendorDir . '/', '', $targetFile); $updated = true; echo "✓ Installed CSS: $name -> {$config['path']}\n"; } } } // Handle subpaths (like 'nostr-tools/nip46') foreach ($map as $name => &$config) { if (isset($config['version']) && !isset($config['path']) && strpos($name, '/') !== false && !isset($config['type'])) { $parts = explode('/', $name); $packageName = $parts[0]; $subPath = $parts[1]; if ($packageName[0] === '@') { $sourceFile = $nodeModulesDir . '/' . $parts[0] . '/' . $parts[1] . '/' . $subPath . '.js'; $targetDir = $assetsVendorDir . '/' . $parts[0] . '/' . $parts[1]; } else { $sourceFile = $nodeModulesDir . '/' . $packageName . '/' . $subPath . '.js'; $targetDir = $assetsVendorDir . '/' . $packageName; } // Try .mjs extension too if (!file_exists($sourceFile)) { $sourceFile = str_replace('.js', '.mjs', $sourceFile); } if (file_exists($sourceFile)) { $targetFile = $targetDir . '/' . $subPath . '.js'; $targetFileDir = dirname($targetFile); if (!is_dir($targetFileDir)) { mkdir($targetFileDir, 0755, true); } copy($sourceFile, $targetFile); $config['path'] = './assets/vendor/' . str_replace($assetsVendorDir . '/', '', $targetFile); unset($config['version']); // Remove version when path is set $updated = true; echo "✓ Installed subpath: $name -> {$config['path']}\n"; } } } // Write updated importmap.php if ($updated) { $content = " $config) { $nameEscaped = str_replace("'", "\\'", $name); $content .= " '$nameEscaped' => [\n"; if (isset($config['path'])) { $pathEscaped = str_replace("'", "\\'", $config['path']); $content .= " 'path' => '$pathEscaped',\n"; } if (isset($config['version'])) { $versionEscaped = str_replace("'", "\\'", $config['version']); $content .= " 'version' => '$versionEscaped',\n"; } if (isset($config['type'])) { $typeEscaped = str_replace("'", "\\'", $config['type']); $content .= " 'type' => '$typeEscaped',\n"; } if (isset($config['entrypoint'])) { $content .= " 'entrypoint' => " . ($config['entrypoint'] ? 'true' : 'false') . ",\n"; } $content .= " ],\n"; } $content .= "];\n"; file_put_contents($importmapFile, $content); echo "\n✓ Updated importmap.php\n"; } if (!empty($errors)) { echo "\n⚠ Errors encountered:\n"; foreach ($errors as $error) { echo " - $error\n"; } exit(1); } echo "\n✓ All packages installed successfully!\n";