Bundle Puppeteer Dependency
This commit is contained in:
15
README.md
15
README.md
@@ -1,2 +1,17 @@
|
||||
# streamed-tui
|
||||
TUI Application for launching streamed.pk feeds
|
||||
|
||||
## Bundled Puppeteer dependencies
|
||||
|
||||
The extractor relies on `puppeteer-extra`, `puppeteer-extra-plugin-stealth`, and `puppeteer`. These Node.js packages are
|
||||
bundled into the final binary via `internal/assets/node_modules.tar.gz`. To refresh the archive (for example after updating
|
||||
dependency versions), run:
|
||||
|
||||
```
|
||||
scripts/build_node_modules.sh
|
||||
```
|
||||
|
||||
The script installs the dependencies into a temporary directory and regenerates the tarball so the Go binary can extract
|
||||
them at runtime without requiring `npm install` on the target system. When the binary starts it will automatically unpack the
|
||||
archive into the user's cache directory (or `$TMPDIR` fallback) and point Puppeteer at that cached `node_modules` tree, so the
|
||||
program can run as a single self-contained executable even when no dependencies exist alongside it.
|
||||
|
||||
BIN
internal/assets/node_modules.tar.gz
Normal file
BIN
internal/assets/node_modules.tar.gz
Normal file
Binary file not shown.
104
internal/dependencies.go
Normal file
104
internal/dependencies.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/sha256"
|
||||
_ "embed"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
//go:embed assets/node_modules.tar.gz
|
||||
var embeddedNodeModules []byte
|
||||
|
||||
// ensureEmbeddedNodeModules extracts the bundled Node.js dependencies into a
|
||||
// deterministic cache directory derived from the archive hash and returns the
|
||||
// path that contains the resulting node_modules directory.
|
||||
func ensureEmbeddedNodeModules() (string, error) {
|
||||
if len(embeddedNodeModules) == 0 {
|
||||
return "", errors.New("no embedded node modules archive available")
|
||||
}
|
||||
|
||||
sum := sha256.Sum256(embeddedNodeModules)
|
||||
hashPrefix := hex.EncodeToString(sum[:8])
|
||||
|
||||
cacheRoot, err := os.UserCacheDir()
|
||||
if err != nil {
|
||||
cacheRoot = os.TempDir()
|
||||
}
|
||||
baseDir := filepath.Join(cacheRoot, "streamed-tui", "node_modules", hashPrefix)
|
||||
|
||||
marker := filepath.Join(baseDir, ".complete")
|
||||
if _, err := os.Stat(marker); err == nil {
|
||||
return baseDir, nil
|
||||
}
|
||||
|
||||
if err := os.RemoveAll(baseDir); err != nil {
|
||||
return "", fmt.Errorf("failed to clear embedded node cache: %w", err)
|
||||
}
|
||||
if err := os.MkdirAll(baseDir, 0o755); err != nil {
|
||||
return "", fmt.Errorf("failed to create embedded node cache: %w", err)
|
||||
}
|
||||
|
||||
if err := untarGzip(bytes.NewReader(embeddedNodeModules), baseDir); err != nil {
|
||||
return "", fmt.Errorf("failed to extract embedded node modules: %w", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(marker, []byte(time.Now().Format(time.RFC3339)), 0o644); err != nil {
|
||||
return "", fmt.Errorf("failed to mark embedded node modules ready: %w", err)
|
||||
}
|
||||
|
||||
return baseDir, nil
|
||||
}
|
||||
|
||||
func untarGzip(r io.Reader, dest string) error {
|
||||
gz, err := gzip.NewReader(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer gz.Close()
|
||||
|
||||
tr := tar.NewReader(gz)
|
||||
for {
|
||||
hdr, err := tr.Next()
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
target := filepath.Join(dest, hdr.Name)
|
||||
switch hdr.Typeflag {
|
||||
case tar.TypeDir:
|
||||
if err := os.MkdirAll(target, os.FileMode(hdr.Mode)); err != nil {
|
||||
return err
|
||||
}
|
||||
case tar.TypeReg:
|
||||
if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(hdr.Mode))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(f, tr); err != nil {
|
||||
f.Close()
|
||||
return err
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
// Ignore unsupported entries to keep extraction simple.
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -71,7 +71,11 @@ func findNodeModuleBase() (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return "", errors.New("puppeteer-extra not found; install dependencies with npm in the project directory")
|
||||
if extracted, err := ensureEmbeddedNodeModules(); err == nil {
|
||||
return extracted, nil
|
||||
}
|
||||
|
||||
return "", errors.New("puppeteer-extra not found; install dependencies with npm in the project directory or rebuild the embedded archive")
|
||||
}
|
||||
|
||||
func (l *logBuffer) Write(p []byte) (int, error) {
|
||||
@@ -134,7 +138,11 @@ func ensurePuppeteerAvailable(baseDir string) error {
|
||||
check.Env = append(os.Environ(), fmt.Sprintf("STREAMED_TUI_NODE_BASE=%s", baseDir))
|
||||
|
||||
if err := check.Run(); err != nil {
|
||||
return fmt.Errorf("puppeteer-extra or stealth plugin missing in %s. Run `npm install puppeteer-extra puppeteer-extra-plugin-stealth puppeteer` there: %w", baseDir, err)
|
||||
if embedded, embErr := ensureEmbeddedNodeModules(); embErr == nil && embedded != baseDir {
|
||||
return ensurePuppeteerAvailable(embedded)
|
||||
}
|
||||
|
||||
return fmt.Errorf("puppeteer-extra or stealth plugin missing in %s. Run `npm install puppeteer-extra puppeteer-extra-plugin-stealth puppeteer` there or rebuild the embedded archive with scripts/build_node_modules.sh: %w", baseDir, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
22
scripts/build_node_modules.sh
Executable file
22
scripts/build_node_modules.sh
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
ASSETS_DIR="$ROOT_DIR/internal/assets"
|
||||
ARCHIVE="$ASSETS_DIR/node_modules.tar.gz"
|
||||
|
||||
if ! command -v npm >/dev/null 2>&1; then
|
||||
echo "npm is required to bundle node_modules" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||
|
||||
pushd "$TMP_DIR" >/dev/null
|
||||
npm install puppeteer-extra puppeteer-extra-plugin-stealth puppeteer
|
||||
mkdir -p "$ASSETS_DIR"
|
||||
tar -czf "$ARCHIVE" node_modules
|
||||
popd >/dev/null
|
||||
|
||||
echo "Bundled node_modules into $ARCHIVE"
|
||||
Reference in New Issue
Block a user