Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions internal/scaffold/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,25 +73,23 @@ func thinStageName(content string) (string, error) {

func reusableWorkflowUses(stage string, opts RenderOptions) string {
if opts.Vendored {
if opts.PerRepo {
return "./.fullsend/.github/workflows/reusable-" + stage + ".yml"
}
return "./.github/workflows/reusable-" + stage + ".yml"
}
return config.DefaultUpstreamRepo + "/.github/workflows/reusable-" + stage + ".yml@" + config.DefaultUpstreamRef
}

func reusableDispatchUses(opts RenderOptions) string {
if opts.Vendored {
return "./.fullsend/.github/workflows/reusable-dispatch.yml"
return "./.github/workflows/reusable-dispatch.yml"
}
return config.DefaultUpstreamRepo + "/.github/workflows/reusable-dispatch.yml@" + config.DefaultUpstreamRef
}

// RenderDispatchPerRepoStagePaths rewrites stage workflow paths for vendored
// per-repo installs where reusable-dispatch.yml lives under .fullsend/.
// per-repo installs so reusable workflows reference .github/workflows/ (required
// by GitHub Actions for local reusable workflow references).
func RenderDispatchPerRepoStagePaths(content []byte) []byte {
return dispatchStageUses.ReplaceAll(content, []byte(`uses: ./.fullsend/.github/workflows/reusable-$1.yml`))
return dispatchStageUses.ReplaceAll(content, []byte(`uses: ./.github/workflows/reusable-$1.yml`))
}

var dispatchStageUses = regexp.MustCompile(`uses: fullsend-ai/fullsend/\.github/workflows/reusable-([a-z-]+)\.yml@[^\s]+`)
22 changes: 19 additions & 3 deletions internal/scaffold/render_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,26 @@ func TestRenderPerRepoShimVendored(t *testing.T) {
})
require.NoError(t, err)
out := string(rendered)
assert.Contains(t, out, "uses: ./.fullsend/.github/workflows/reusable-dispatch.yml")
assert.Contains(t, out, "uses: ./.github/workflows/reusable-dispatch.yml")
assert.NotContains(t, out, "distribution_mode")
}

func TestRenderThinCallerVendoredPerRepo(t *testing.T) {
raw, err := FullsendRepoFile(".github/workflows/triage.yml")
require.NoError(t, err)

rendered, err := RenderTemplate(".github/workflows/triage.yml", raw, RenderOptions{
Vendored: true,
PerRepo: true,
})
require.NoError(t, err)
out := string(rendered)
// GitHub Actions requires local reusable workflow references to be under .github/workflows/.
assert.Contains(t, out, "uses: ./.github/workflows/reusable-triage.yml")
assert.NotContains(t, out, ".fullsend/")
assertFreeOfRenderPlaceholders(t, out)
}

func TestRenderPrioritizeThinCallerVendored(t *testing.T) {
raw, err := FullsendRepoFile(".github/workflows/prioritize.yml")
require.NoError(t, err)
Expand Down Expand Up @@ -96,8 +112,8 @@ func TestRenderDispatchPerRepoStagePaths(t *testing.T) {
require.NotEmpty(t, raw)

rendered := RenderDispatchPerRepoStagePaths(raw)
assert.Contains(t, string(rendered), "uses: ./.fullsend/.github/workflows/reusable-triage.yml")
assert.Contains(t, string(rendered), "uses: ./.fullsend/.github/workflows/reusable-prioritize.yml")
assert.Contains(t, string(rendered), "uses: ./.github/workflows/reusable-triage.yml")
assert.Contains(t, string(rendered), "uses: ./.github/workflows/reusable-prioritize.yml")
assert.NotContains(t, string(rendered), "uses: fullsend-ai/fullsend/.github/workflows/reusable-triage.yml@v0")
}

Expand Down
8 changes: 5 additions & 3 deletions internal/scaffold/vendorcontent.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ const defaultsVendoredPrefix = ".defaults/"

// CollectVendoredAssets gathers files for --vendor installs.
// Upstream mirror content lives under .defaults/ (same layout as runtime sparse checkout).
// Reusable workflows are written under workflowPrefix (.fullsend/ for per-repo, "" for per-org).
// Reusable workflows are always written under .github/workflows/ because GitHub
// Actions requires local reusable workflow references (./path) to live there.
// Other vendored assets use workflowPrefix (.fullsend/ for per-repo, "" for per-org).
func CollectVendoredAssets(root, workflowPrefix string) (InstallFiles, error) {
var files InstallFiles

Expand All @@ -23,7 +25,7 @@ func CollectVendoredAssets(root, workflowPrefix string) (InstallFiles, error) {
rendered = RenderDispatchPerRepoStagePaths(content)
}
files = append(files, InstallFile{
Path: workflowPrefix + path,
Path: path,
Content: rendered,
Mode: "100644",
})
Expand Down Expand Up @@ -57,7 +59,7 @@ func CollectVendoredAssets(root, workflowPrefix string) (InstallFiles, error) {

// ManagedVendoredContentPaths returns embed-derived paths for the current vendor layout.
func ManagedVendoredContentPaths(workflowPrefix string) ([]string, error) {
return enumerateVendoredPaths(workflowPrefix)
return enumerateVendoredPaths()
}

// LegacyFlatVendoredPaths lists pre-.defaults flat layout paths for legacy cleanup.
Expand Down
5 changes: 3 additions & 2 deletions internal/scaffold/vendorcontent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@ func TestCollectVendoredAssets_PerRepoPrefix(t *testing.T) {
require.NoError(t, err)
require.NotEmpty(t, files)
for _, f := range files {
if strings.HasPrefix(f.Path, ".github/workflows/") {
assert.True(t, strings.HasPrefix(f.Path, ".fullsend/.github/workflows/"), "workflows should use per-repo prefix: %s", f.Path)
if isVendoredReusableWorkflow(f.Path) {
assert.True(t, strings.HasPrefix(f.Path, ".github/workflows/"), "reusable workflows must be under .github/workflows/ for GitHub Actions: %s", f.Path)
assert.False(t, strings.HasPrefix(f.Path, ".fullsend/"), "reusable workflows must not use .fullsend/ prefix: %s", f.Path)
}
}
}
Expand Down
15 changes: 11 additions & 4 deletions internal/scaffold/vendormanifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,8 @@ var vendoredDefaultsInfraPaths = []string{
}

// enumerateVendoredPaths returns embed-derived paths for a current --vendor install layout.
func enumerateVendoredPaths(workflowPrefix string) ([]string, error) {
// Reusable workflows are always under .github/workflows/ (GitHub Actions requirement).
func enumerateVendoredPaths() ([]string, error) {
seen := make(map[string]struct{})
add := func(p string) {
if p != "" {
Expand All @@ -164,7 +165,7 @@ func enumerateVendoredPaths(workflowPrefix string) ([]string, error) {
}

for _, name := range vendoredReusableWorkflows {
add(workflowPrefix + ".github/workflows/" + name)
add(".github/workflows/" + name)
}
for _, p := range vendoredDefaultsInfraPaths {
add(defaultsVendoredPrefix + p)
Expand All @@ -185,6 +186,8 @@ func enumerateVendoredPaths(workflowPrefix string) ([]string, error) {
}

// enumerateLegacyFlatVendoredPaths returns pre-.defaults flat layout paths from embed.
// Reusable workflows are always under .github/workflows/ (GitHub Actions requirement).
// Legacy per-repo paths (.fullsend/.github/workflows/...) are also included for cleanup.
func enumerateLegacyFlatVendoredPaths(workflowPrefix string) ([]string, error) {
seen := make(map[string]struct{})
add := func(p string) {
Expand All @@ -194,7 +197,11 @@ func enumerateLegacyFlatVendoredPaths(workflowPrefix string) ([]string, error) {
}

for _, name := range vendoredReusableWorkflows {
add(workflowPrefix + ".github/workflows/" + name)
add(".github/workflows/" + name)
// Include legacy per-repo paths for cleanup.
if workflowPrefix != "" {
add(workflowPrefix + ".github/workflows/" + name)
}
}
for _, p := range vendoredDefaultsInfraPaths {
add(p)
Expand Down Expand Up @@ -246,7 +253,7 @@ func ResolveVendoredCleanupPaths(ctx context.Context, client forge.Client, owner
return manifest.CleanupPaths(workflowPrefix), nil
}

paths, err := enumerateVendoredPaths(workflowPrefix)
paths, err := enumerateVendoredPaths()
if err != nil {
return nil, err
}
Expand Down
17 changes: 14 additions & 3 deletions internal/scaffold/vendormanifest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ func TestManagedVendoredContentPaths(t *testing.T) {
paths, err := ManagedVendoredContentPaths(".fullsend/")
require.NoError(t, err)
assert.Contains(t, paths, ".defaults/action.yml")
assert.Contains(t, paths, ".fullsend/.github/workflows/reusable-triage.yml")
assert.Contains(t, paths, ".github/workflows/reusable-triage.yml")
}

func TestLegacyFlatVendoredPaths(t *testing.T) {
Expand All @@ -121,6 +121,17 @@ func TestLegacyFlatVendoredPaths(t *testing.T) {
assert.Contains(t, paths, ".github/workflows/reusable-triage.yml")
}

func TestLegacyFlatVendoredPaths_PerRepoPrefix(t *testing.T) {
paths, err := LegacyFlatVendoredPaths(".fullsend/")
require.NoError(t, err)
// New canonical location for reusable workflows.
assert.Contains(t, paths, ".github/workflows/reusable-triage.yml")
// Legacy per-repo paths included for cleanup.
assert.Contains(t, paths, ".fullsend/.github/workflows/reusable-triage.yml")
// Per-repo action.yml marker.
assert.Contains(t, paths, ".fullsend/action.yml")
}

func TestVendoredDefaultsInfraPathsMatchPredicate(t *testing.T) {
for _, p := range vendoredDefaultsInfraPaths {
assert.True(t, isVendoredDefaultsInfra(p), "hardcoded path %q not matched by isVendoredDefaultsInfra", p)
Expand Down Expand Up @@ -174,7 +185,7 @@ func TestReadVendorManifest_ParseError(t *testing.T) {
}

func TestEnumerateVendoredPathsWithoutCheckout(t *testing.T) {
paths, err := enumerateVendoredPaths("")
paths, err := enumerateVendoredPaths()
require.NoError(t, err)
assert.Contains(t, paths, ".defaults/action.yml")
assert.Contains(t, paths, ".github/workflows/reusable-triage.yml")
Expand All @@ -187,7 +198,7 @@ func TestEnumerateVendoredPathsMatchesCollectInCheckout(t *testing.T) {
t.Skip("not in fullsend checkout")
}

embedPaths, err := enumerateVendoredPaths("")
embedPaths, err := enumerateVendoredPaths()
require.NoError(t, err)

files, err := CollectVendoredAssets(root, "")
Expand Down
Loading