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
107 changes: 71 additions & 36 deletions cmd/release.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"io"
"log/slog"
"os"
"regexp"
"strings"
"time"

Expand Down Expand Up @@ -156,6 +157,10 @@ func newReleasePlanCmd() *cobra.Command {
packageFieldsRaw string
publishFieldsRaw string
logFile string

publishIncludeFoundTransitive bool
publishExcludeRegex string
publishExcludeFile string
)
cmd := &cobra.Command{
Use: "plan",
Expand Down Expand Up @@ -204,6 +209,26 @@ func newReleasePlanCmd() *cobra.Command {
}
slog.Info("release plan: exclude policy loaded", "patterns", len(excludes))

var publishExcludes []*regexp.Regexp
if publishExcludeFile != "" {
fileExcludes, loadErr := release.LoadExcludePatterns(publishExcludeFile)
if loadErr != nil {
return loadErr
}
publishExcludes = append(publishExcludes, fileExcludes...)
}
if publishExcludeRegex != "" {
re, compileErr := regexp.Compile(publishExcludeRegex)
if compileErr != nil {
return fmt.Errorf("invalid --publish-exclude-regex pattern: %w", compileErr)
}
publishExcludes = append(publishExcludes, re)
}
slog.Info("release plan: publish exclude policy loaded",
"publishIncludeFoundTransitive", publishIncludeFoundTransitive,
"publishExcludePatterns", len(publishExcludes),
)

packageFields := splitCSVFields(packageFieldsRaw, []string{"location", "type", "path", "dependency"})
publishFields := splitCSVFields(publishFieldsRaw, []string{"location", "type", "path", "dependency"})
logEnabled, logPath := manifestLogPath(cmd, logFile)
Expand Down Expand Up @@ -283,24 +308,26 @@ func newReleasePlanCmd() *cobra.Command {
}

planResult, buildErr := release.BuildPlan(context.Background(), release.PlanOptions{
Assets: fullAssets,
ConnectorSourceAssets: fullAssets,
Targets: opts.Targets,
FilterMissingTransitive: filterMissingTrans,
TargetResolutionOptions: targetResolutionOpts,
Validations: validationsByTarget,
OutputRoot: outputRoot,
PackageFileBaseName: "full_build.package",
PlanExt: planExt,
WriteAssets: writeAssets,
PackageFields: packageFields,
PublishFields: publishFields,
IncludeConnectors: opts.IncludeConnectors,
IncludeConnections: opts.IncludeConnections,
InfoEnabled: infoEnabled,
RenderPackageTotals: makeTypeCountRenderer(logWriter),
RenderPublishTotals: makeTypeCountRenderer(logWriter),
RenderConnectorTotals: makeTypeCountRenderer(logWriter),
Assets: fullAssets,
ConnectorSourceAssets: fullAssets,
Targets: opts.Targets,
FilterMissingTransitive: filterMissingTrans,
TargetResolutionOptions: targetResolutionOpts,
Validations: validationsByTarget,
OutputRoot: outputRoot,
PackageFileBaseName: "full_build.package",
PlanExt: planExt,
WriteAssets: writeAssets,
PackageFields: packageFields,
PublishFields: publishFields,
IncludeConnectors: opts.IncludeConnectors,
IncludeConnections: opts.IncludeConnections,
PublishIncludeFoundTransitive: publishIncludeFoundTransitive,
PublishExcludePatterns: publishExcludes,
InfoEnabled: infoEnabled,
RenderPackageTotals: makeTypeCountRenderer(logWriter),
RenderPublishTotals: makeTypeCountRenderer(logWriter),
RenderConnectorTotals: makeTypeCountRenderer(logWriter),
OnTargetFilesGenerated: func(env, packageFile, publishFile string, publishAssetCount int) {
slog.Info("release plan: full mode files generated",
"environment", env,
Expand Down Expand Up @@ -369,24 +396,26 @@ func newReleasePlanCmd() *cobra.Command {
connectorDependencyAssets := release.ApplyPolicies(assets, true, true, excludes)

planResult, buildErr := release.BuildPlan(context.Background(), release.PlanOptions{
Assets: allFiltered,
ConnectorSourceAssets: connectorDependencyAssets,
Targets: opts.Targets,
FilterMissingTransitive: filterMissingTrans,
TargetResolutionOptions: targetResolutionOpts,
Validations: validationsByTarget,
OutputRoot: outputRoot,
PackageFileBaseName: "tag_build.package",
PlanExt: planExt,
WriteAssets: writeAssets,
PackageFields: packageFields,
PublishFields: publishFields,
IncludeConnectors: opts.IncludeConnectors,
IncludeConnections: opts.IncludeConnections,
InfoEnabled: infoEnabled,
RenderPackageTotals: makeTypeCountRenderer(logWriter),
RenderPublishTotals: makeTypeCountRenderer(logWriter),
RenderConnectorTotals: makeTypeCountRenderer(logWriter),
Assets: allFiltered,
ConnectorSourceAssets: connectorDependencyAssets,
Targets: opts.Targets,
FilterMissingTransitive: filterMissingTrans,
TargetResolutionOptions: targetResolutionOpts,
Validations: validationsByTarget,
OutputRoot: outputRoot,
PackageFileBaseName: "tag_build.package",
PlanExt: planExt,
WriteAssets: writeAssets,
PackageFields: packageFields,
PublishFields: publishFields,
IncludeConnectors: opts.IncludeConnectors,
IncludeConnections: opts.IncludeConnections,
PublishIncludeFoundTransitive: publishIncludeFoundTransitive,
PublishExcludePatterns: publishExcludes,
InfoEnabled: infoEnabled,
RenderPackageTotals: makeTypeCountRenderer(logWriter),
RenderPublishTotals: makeTypeCountRenderer(logWriter),
RenderConnectorTotals: makeTypeCountRenderer(logWriter),
OnTargetProcessing: func(env string) {
slog.Info("release plan: processing target", "environment", env)
},
Expand Down Expand Up @@ -429,6 +458,12 @@ func newReleasePlanCmd() *cobra.Command {
cmd.Flags().StringVar(&planOutput, "output", "csv", "plan file output format: csv|json|yaml")
cmd.Flags().StringVar(&packageFieldsRaw, "package-fields", "location,type,path,dependency", "fields for generated package files")
cmd.Flags().StringVar(&publishFieldsRaw, "publish-fields", "location,type,path,dependency", "fields for generated publish files")
cmd.Flags().BoolVar(&publishIncludeFoundTransitive, "publish-include-found-transitive", false,
"include transitive dependencies already found in the target in the generated publish file (package file is unaffected; independent of --include-found-transitive)")
cmd.Flags().StringVar(&publishExcludeRegex, "publish-exclude-regex", "",
"regex matched against an asset's location to exclude it from the generated publish file only")
cmd.Flags().StringVar(&publishExcludeFile, "publish-exclude-file", "",
"path to regex patterns file (one per line, # comments) matched against location to exclude assets from the generated publish file only; same format as the release manifest's excludeFile")
bindManifestLogFlag(cmd, &logFile)
return cmd
}
Expand Down
10 changes: 10 additions & 0 deletions completions/iics.bash
Original file line number Diff line number Diff line change
Expand Up @@ -5127,10 +5127,20 @@ _iics_release_plan()
two_word_flags+=("--package-fields")
local_nonpersistent_flags+=("--package-fields")
local_nonpersistent_flags+=("--package-fields=")
flags+=("--publish-exclude-file=")
two_word_flags+=("--publish-exclude-file")
local_nonpersistent_flags+=("--publish-exclude-file")
local_nonpersistent_flags+=("--publish-exclude-file=")
flags+=("--publish-exclude-regex=")
two_word_flags+=("--publish-exclude-regex")
local_nonpersistent_flags+=("--publish-exclude-regex")
local_nonpersistent_flags+=("--publish-exclude-regex=")
flags+=("--publish-fields=")
two_word_flags+=("--publish-fields")
local_nonpersistent_flags+=("--publish-fields")
local_nonpersistent_flags+=("--publish-fields=")
flags+=("--publish-include-found-transitive")
local_nonpersistent_flags+=("--publish-include-found-transitive")
flags+=("--target-profile-map=")
two_word_flags+=("--target-profile-map")
local_nonpersistent_flags+=("--target-profile-map")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# CR-0039: `release plan` publish-only transitive inclusion and location excludes

## CR Type

- [x] Enhancement to existing command

## Problem

`release plan` generates a `package` file and a `publish` file per target,
both derived from the same asset list. `--include-found-transitive` controls
transitive-dependency inclusion for both files identically: by default,
transitive dependencies already found in the target are dropped from both;
explicit and missing-transitive assets are always kept.

There is no way to give the publish file a different transitive policy than
the package file, and no way to exclude specific assets from the publish file
only. Operators running `release plan` manually sometimes want the publish
file to include found-transitive assets (e.g. to force a republish) while
leaving the package file's deployment scope untouched, and to exclude a few
specific assets from publish by location pattern.

## Proposed Solution

Add three CLI-only flags to `release plan` (no manifest schema change - this
is for manual, ad hoc runs, not automated pipeline manifests):

- `--publish-include-found-transitive` (bool, default `false`): include
transitive dependencies already found in the target in the generated
publish file. Package file is unaffected; independent of
`--include-found-transitive`.
- `--publish-exclude-regex <pattern>`: regex matched against an asset's
`location`, excluding matches from the publish file only.
- `--publish-exclude-file <path>`: path to a regex-patterns file (one per
line, `#` comments), same format as the existing `release.LoadExcludePatterns`
used by the manifest's `excludeFile`. Combined (OR) with
`--publish-exclude-regex` when both are given.

Excludes always apply to the publish file regardless of whether
`--publish-include-found-transitive` is set. Default behavior (no new flags)
is byte-identical to today - this is strictly additive and non-breaking.

`iics publish` (the execution command) is untouched; it already has
`--from-file`/stdin to control exactly what gets published.

## Implementation

- `internal/release/plan.go` - new `ExcludeAssetsByLocation(assets, patterns)`
helper (`ApplyPolicies` refactored to reuse it, no behavior change).
- `internal/release/build.go` - `PlanOptions.PublishIncludeFoundTransitive`,
`PlanOptions.PublishExcludePatterns`; `BuildPlan` computes publish assets
from the unfiltered per-plan asset list when the flag is set, then applies
the exclude patterns.
- `cmd/release.go` - three new flags on `newReleasePlanCmd`, wired into
`PlanOptions` at both the full-deployment and selective/tag-mode call
sites.
- Tests: `internal/release/plan_test.go`, `internal/release/build_test.go`.
- Docs: `docs/documentation/release.md`.
24 changes: 24 additions & 0 deletions docs/documentation/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,27 @@ Two checks are performed against the resolved target client:
to be deployed); assets that **already exist** are **excluded** (assumed stable).
- Use `--include-found-transitive` to disable this filtering.

#### Publish-only transitive inclusion and excludes (CLI-only, ad hoc)

These flags affect the generated **publish file only**; the package file's deployment scope
and transitive policy are always governed by `--include-found-transitive` as described above.
They are independent of each other and of `--include-found-transitive`. There is no manifest
schema field for these; they exist for manual, ad hoc `release plan` runs, not automated
pipeline manifests.

- `--publish-include-found-transitive` (bool, default `false`): include transitive dependencies
already found in the target in the generated publish file, even when they are excluded from
the package file. Useful for forcing a republish of assets that already exist in the target.
- `--publish-exclude-regex <pattern>`: a regex matched against an asset's `location`; matches
are excluded from the publish file only.
- `--publish-exclude-file <path>`: path to a regex-patterns file (one pattern per line, `#`
comments), same format as the manifest's `excludeFile`, matched against `location` to exclude
assets from the publish file only. Combined with `--publish-exclude-regex` using OR semantics
when both are given.

Excludes always apply to the publish file regardless of whether
`--publish-include-found-transitive` is set. Default behavior (no new flags) is unchanged.

#### Per-asset validation (always runs)

- Checks every asset and annotates the output with `Status` and `Warning` values, written as a
Expand All @@ -191,6 +212,9 @@ Two checks are performed against the resolved target client:
| `--valid-targets` | string | | Comma-separated allowlist for valid targets (overrides `IICS_VALID_DEPLOY_TARGETS`) |
| `--target-profile-map` | string | | Comma-separated mapping `TARGET=profile` used for target org credential resolution (overrides `IICS_TARGET_PROFILE_MAP`) |
| `--include-found-transitive` | bool | `false` | Keep all resolved transitive dependencies, even when they already exist in the target environment |
| `--publish-include-found-transitive` | bool | `false` | Include transitive dependencies already found in the target in the generated publish file only (package file unaffected; independent of `--include-found-transitive`) |
| `--publish-exclude-regex` | string | | Regex matched against an asset's `location` to exclude it from the generated publish file only |
| `--publish-exclude-file` | string | | Path to regex patterns file (same format as `excludeFile`) to exclude assets from the generated publish file only |
| `--output` | string | `csv` | Plan file output format: `csv`, `json`, `yaml` |
| `--package-fields` | string | `location,type,path,dependency` | Fields for package files; `STATUS (<env>)` is auto-added per environment file |
| `--publish-fields` | string | `location,type,path,dependency` | Fields for publish files |
Expand Down
20 changes: 19 additions & 1 deletion internal/release/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"log/slog"
"os"
"path/filepath"
"regexp"
"strings"
)

Expand Down Expand Up @@ -57,6 +58,16 @@ type PlanOptions struct {
IncludeConnectors bool
IncludeConnections bool

// PublishIncludeFoundTransitive, when true, computes the publish file's
// assets from the unfiltered per-plan Assets (explicit + all transitive,
// found and missing) instead of the missing-transitive-filtered envAssets
// used for the package file. Package file output is unaffected.
PublishIncludeFoundTransitive bool
// PublishExcludePatterns, when non-empty, drops assets whose Location
// matches any pattern from the publish file only (package file output is
// unaffected).
PublishExcludePatterns []*regexp.Regexp

InfoEnabled bool
RenderPackageTotals TypeCountRenderer
RenderPublishTotals TypeCountRenderer
Expand Down Expand Up @@ -131,7 +142,14 @@ func BuildPlan(ctx context.Context, opts PlanOptions) (PlanResult, error) {
envPackageAssets = annotated
}
envPackageFields := EnsureCurrentTargetStatusField(opts.PackageFields, env)
publishAssets := PublishAssets(envAssets)
publishSource := envAssets
if opts.PublishIncludeFoundTransitive {
publishSource = opts.Assets // unfiltered per-plan input: explicit + all transitive (found and missing)
}
publishAssets := PublishAssets(publishSource)
if len(opts.PublishExcludePatterns) > 0 {
publishAssets = ExcludeAssetsByLocation(publishAssets, opts.PublishExcludePatterns)
}

if opts.InfoEnabled {
if opts.RenderPackageTotals != nil {
Expand Down
Loading