You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
293 lines
10 KiB
293 lines
10 KiB
#!/usr/bin/env php |
|
<?php |
|
/** |
|
* Copy packages from node_modules to assets/vendor and update importmap.php |
|
* This script replaces Symfony's importmap:install by using npm packages |
|
*/ |
|
|
|
$baseDir = __DIR__ . '/..'; |
|
$importmapFile = $baseDir . '/importmap.php'; |
|
$nodeModulesDir = $baseDir . '/node_modules'; |
|
$assetsVendorDir = $baseDir . '/assets/vendor'; |
|
|
|
if (!file_exists($importmapFile)) { |
|
fwrite(STDERR, "Error: importmap.php not found\n"); |
|
exit(1); |
|
} |
|
|
|
if (!is_dir($nodeModulesDir)) { |
|
fwrite(STDERR, "Error: node_modules directory not found. Run 'npm install' first.\n"); |
|
exit(1); |
|
} |
|
|
|
// Create assets/vendor directory |
|
if (!is_dir($assetsVendorDir)) { |
|
mkdir($assetsVendorDir, 0755, true); |
|
} |
|
|
|
$map = require $importmapFile; |
|
$updated = false; |
|
$errors = []; |
|
|
|
/** |
|
* Find the entry point file for a package |
|
*/ |
|
function findEntryPoint($packageDir) { |
|
if (!is_dir($packageDir)) { |
|
return null; |
|
} |
|
|
|
$packageJson = $packageDir . '/package.json'; |
|
if (file_exists($packageJson)) { |
|
$pkg = json_decode(file_get_contents($packageJson), true); |
|
|
|
// Check exports field (ESM) |
|
if (isset($pkg['exports'])) { |
|
$exports = $pkg['exports']; |
|
if (is_string($exports)) { |
|
$exports = ['.' => $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 = "<?php\n\n"; |
|
$content .= "/**\n"; |
|
$content .= " * Returns the importmap for this application.\n"; |
|
$content .= " *\n"; |
|
$content .= " * - \"path\" is a path inside the asset mapper system. Use the\n"; |
|
$content .= " * \"debug:asset-map\" command to see the full list of paths.\n"; |
|
$content .= " *\n"; |
|
$content .= " * - \"entrypoint\" (JavaScript only) set to true for any module that will\n"; |
|
$content .= " * be used as an \"entrypoint\" (and passed to the importmap() Twig function).\n"; |
|
$content .= " *\n"; |
|
$content .= " * The \"importmap:require\" command can be used to add new entries to this file.\n"; |
|
$content .= " *\n"; |
|
$content .= " * This file is auto-generated from npm packages. Run scripts/npm-to-importmap.php to update.\n"; |
|
$content .= " */\n"; |
|
$content .= "return [\n"; |
|
|
|
foreach ($map as $name => $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";
|
|
|