Bundle Puppeteer Dependency
This commit is contained in:
15
README.md
15
README.md
@@ -1,2 +1,17 @@
|
|||||||
# streamed-tui
|
# streamed-tui
|
||||||
TUI Application for launching streamed.pk feeds
|
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) {
|
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))
|
check.Env = append(os.Environ(), fmt.Sprintf("STREAMED_TUI_NODE_BASE=%s", baseDir))
|
||||||
|
|
||||||
if err := check.Run(); err != nil {
|
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
|
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