Files
flatrender/services/node-agent/internal/runner/scan.go
T
soroush.asadi 0c461ff841
Build backend images / build content-svc (push) Failing after 53s
Build backend images / build file-svc (push) Failing after 58s
Build backend images / build gateway (push) Failing after 1m1s
Build backend images / build identity-svc (push) Failing after 57s
Build backend images / build notification-svc (push) Failing after 59s
Build backend images / build render-svc (push) Failing after 49s
Build backend images / build studio-svc (push) Failing after 49s
fix(import): IgnoreQueryFilters so revive sees soft-deleted scenes; clear AE crash state
- AepImportService: the global Scene HasQueryFilter(DeletedAt==null) was hiding
  soft-deleted rows, so the revive never matched and the importer re-inserted →
  scenes_project_id_key violation. Add .IgnoreQueryFilters() to the load. (apply
  now revives + returns 200, verified.)
- node-agent: ClearAECrashState() deletes AE's SCRPriorState.json before each
  launch so the 'Crash Repair Options' dialog can't hang a headless scan/render.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 20:20:41 +03:30

72 lines
2.1 KiB
Go

package runner
import (
"context"
_ "embed"
"fmt"
"os"
"os/exec"
"path/filepath"
"time"
)
//go:embed scan.jsx
var scanScript []byte
// WriteScanScript writes the embedded scanner JSX into workDir and returns its path.
func WriteScanScript(workDir string) (string, error) {
dir := filepath.Join(workDir, "scripts")
if err := os.MkdirAll(dir, 0o755); err != nil {
return "", err
}
p := filepath.Join(dir, "scan.jsx")
if err := os.WriteFile(p, scanScript, 0o644); err != nil {
return "", err
}
return p, nil
}
// RunScan runs the After Effects template scanner against aepPath and returns the
// JSON the script writes to outPath. afterfxPath must be afterfx.exe (the AE app,
// not aerender.exe — only the app can run ScriptUI/ExtendScript).
//
// afterfx -r runs the script and the script calls app.quit(); we still poll for the
// output file because afterfx can return before the file is flushed.
func RunScan(ctx context.Context, afterfxPath, aepPath, workDir, outPath, mode string) ([]byte, error) {
ClearAECrashState() // avoid the "Crash Repair Options" dialog hanging a headless launch
scriptPath, err := WriteScanScript(workDir)
if err != nil {
return nil, fmt.Errorf("write scan script: %w", err)
}
if err := os.MkdirAll(filepath.Dir(outPath), 0o755); err != nil {
return nil, err
}
_ = os.Remove(outPath)
_ = os.Remove(outPath + ".error")
if mode == "" {
mode = "flexible"
}
cmd := exec.CommandContext(ctx, afterfxPath, "-r", scriptPath)
cmd.Env = append(os.Environ(), "FR_SCAN_AEP="+aepPath, "FR_SCAN_OUT="+outPath, "FR_SCAN_MODE="+mode)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
// afterfx may exit non-zero on app.quit() — don't treat the exit code as fatal;
// success is determined by the presence of the output JSON file.
_ = cmd.Run()
for {
if eb, rerr := os.ReadFile(outPath + ".error"); rerr == nil && len(eb) > 0 {
return nil, fmt.Errorf("scan script error: %s", string(eb))
}
if b, rerr := os.ReadFile(outPath); rerr == nil && len(b) > 0 {
return b, nil
}
select {
case <-ctx.Done():
return nil, fmt.Errorf("scan timed out waiting for %s", outPath)
case <-time.After(2 * time.Second):
}
}
}