Merge remote-tracking branch 'upstream/main' into codex/schema-compat-float-image-one-of
This commit is contained in:
@@ -21,6 +21,7 @@ const (
|
||||
|
||||
CommandMigrationMove = "command_move"
|
||||
CommandMigrationFlagExtraction = "flag_extraction"
|
||||
CommandMigrationAvailability = "schema_availability_hardening"
|
||||
)
|
||||
|
||||
// CommandMigrationManifest governs compatibility-preserving surface moves that
|
||||
@@ -64,6 +65,12 @@ type CommandMigrationSchema struct {
|
||||
SourceToolID string `json:"source_tool_id"`
|
||||
ReplacementToolID string `json:"replacement_tool_id"`
|
||||
Parameters []CommandParameterMigration `json:"parameters"`
|
||||
Availability *CommandAvailabilityChange `json:"availability,omitempty"`
|
||||
}
|
||||
|
||||
type CommandAvailabilityChange struct {
|
||||
Before string `json:"before"`
|
||||
After string `json:"after"`
|
||||
}
|
||||
|
||||
type CommandParameterMigration struct {
|
||||
@@ -126,14 +133,23 @@ func (m CommandMigrationManifest) Validate() error {
|
||||
}
|
||||
|
||||
func (m CommandMigration) validate() error {
|
||||
if m.Kind != CommandMigrationMove && m.Kind != CommandMigrationFlagExtraction {
|
||||
if m.Kind != CommandMigrationMove && m.Kind != CommandMigrationFlagExtraction && m.Kind != CommandMigrationAvailability {
|
||||
return fmt.Errorf("invalid kind %q", m.Kind)
|
||||
}
|
||||
if !isExactCommandPath(m.Legacy.Command) || !isExactCommandPath(m.Replacement.Command) {
|
||||
return fmt.Errorf("legacy and replacement must be exact command paths rooted at dws")
|
||||
}
|
||||
if m.Legacy.Command == m.Replacement.Command {
|
||||
return fmt.Errorf("legacy and replacement command paths must differ")
|
||||
if m.Kind == CommandMigrationAvailability {
|
||||
if !isExactCommandPath(m.Legacy.Command) {
|
||||
return fmt.Errorf("legacy must be an exact command path rooted at dws")
|
||||
}
|
||||
if m.Replacement != (CommandMigrationSide{}) {
|
||||
return fmt.Errorf("schema_availability_hardening must not declare replacement")
|
||||
}
|
||||
} else {
|
||||
if !isExactCommandPath(m.Legacy.Command) || !isExactCommandPath(m.Replacement.Command) {
|
||||
return fmt.Errorf("legacy and replacement must be exact command paths rooted at dws")
|
||||
}
|
||||
if m.Legacy.Command == m.Replacement.Command {
|
||||
return fmt.Errorf("legacy and replacement command paths must differ")
|
||||
}
|
||||
}
|
||||
if strings.TrimSpace(m.Reason) == "" || m.Reason != strings.TrimSpace(m.Reason) {
|
||||
return fmt.Errorf("migration must include a non-empty trimmed reason")
|
||||
@@ -141,12 +157,15 @@ func (m CommandMigration) validate() error {
|
||||
if m.State != CommandMigrationPending && m.State != CommandMigrationConsumed {
|
||||
return fmt.Errorf("invalid state %q", m.State)
|
||||
}
|
||||
for label, state := range map[string]CommandMigrationState{
|
||||
"legacy before": m.Legacy.Before,
|
||||
"legacy after": m.Legacy.After,
|
||||
"replacement before": m.Replacement.Before,
|
||||
"replacement after": m.Replacement.After,
|
||||
} {
|
||||
states := map[string]CommandMigrationState{
|
||||
"legacy before": m.Legacy.Before,
|
||||
"legacy after": m.Legacy.After,
|
||||
}
|
||||
if m.Kind != CommandMigrationAvailability {
|
||||
states["replacement before"] = m.Replacement.Before
|
||||
states["replacement after"] = m.Replacement.After
|
||||
}
|
||||
for label, state := range states {
|
||||
if err := state.validate(label); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -155,7 +174,8 @@ func (m CommandMigration) validate() error {
|
||||
!m.Legacy.After.Present || !m.Legacy.After.Runnable {
|
||||
return fmt.Errorf("legacy command must remain runnable and start visible")
|
||||
}
|
||||
if m.Replacement.Before.Present || !m.Replacement.After.Present || !m.Replacement.After.Runnable || m.Replacement.After.Hidden {
|
||||
if m.Kind != CommandMigrationAvailability &&
|
||||
(m.Replacement.Before.Present || !m.Replacement.After.Present || !m.Replacement.After.Runnable || m.Replacement.After.Hidden) {
|
||||
return fmt.Errorf("replacement command must migrate exactly from absent to visible runnable")
|
||||
}
|
||||
if err := m.Schema.validate(m.Kind); err != nil {
|
||||
@@ -214,6 +234,13 @@ func (m CommandMigration) validate() error {
|
||||
if m.LegacyFlag.Before.NoOpt != wantNoOpt || m.LegacyFlag.After.NoOpt != wantNoOpt {
|
||||
return fmt.Errorf("flag_extraction legacy_flag no_opt must equal replacement constant %q", wantNoOpt)
|
||||
}
|
||||
case CommandMigrationAvailability:
|
||||
if !m.Legacy.After.Hidden {
|
||||
return fmt.Errorf("schema_availability_hardening legacy command must migrate exactly from visible to hidden")
|
||||
}
|
||||
if m.LegacyFlag != (CommandMigrationFlag{}) {
|
||||
return fmt.Errorf("schema_availability_hardening must not declare legacy_flag")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -259,9 +286,8 @@ func (f CommandMigrationFlag) validate() error {
|
||||
|
||||
func (s CommandMigrationSchema) validate(kind string) error {
|
||||
for label, value := range map[string]string{
|
||||
"product_id": s.ProductID,
|
||||
"source_tool_id": s.SourceToolID,
|
||||
"replacement_tool_id": s.ReplacementToolID,
|
||||
"product_id": s.ProductID,
|
||||
"source_tool_id": s.SourceToolID,
|
||||
} {
|
||||
if !isExactSchemaIdentifier(value) {
|
||||
return fmt.Errorf("schema %s must be an exact identifier", label)
|
||||
@@ -270,6 +296,21 @@ func (s CommandMigrationSchema) validate(kind string) error {
|
||||
if s.Parameters == nil {
|
||||
return fmt.Errorf("schema parameters must be an array")
|
||||
}
|
||||
if kind == CommandMigrationAvailability {
|
||||
if s.ReplacementToolID != "" || len(s.Parameters) != 0 {
|
||||
return fmt.Errorf("schema_availability_hardening must not declare a replacement tool or parameter mappings")
|
||||
}
|
||||
if s.Availability == nil || s.Availability.Before != "available" || s.Availability.After != "unavailable" {
|
||||
return fmt.Errorf("schema_availability_hardening requires availability available to unavailable")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !isExactSchemaIdentifier(s.ReplacementToolID) {
|
||||
return fmt.Errorf("schema replacement_tool_id must be an exact identifier")
|
||||
}
|
||||
if s.Availability != nil {
|
||||
return fmt.Errorf("%s must not declare schema availability", kind)
|
||||
}
|
||||
seenFrom := map[string]bool{}
|
||||
seenTo := map[string]bool{}
|
||||
seenConstantProperty := map[string]bool{}
|
||||
@@ -344,6 +385,9 @@ func (m CommandMigration) key() string {
|
||||
}
|
||||
|
||||
func (m CommandMigration) displayKey() string {
|
||||
if m.Kind == CommandMigrationAvailability {
|
||||
return fmt.Sprintf("%s %q", m.Kind, m.Legacy.Command)
|
||||
}
|
||||
return fmt.Sprintf("%s %q -> %q", m.Kind, m.Legacy.Command, m.Replacement.Command)
|
||||
}
|
||||
|
||||
@@ -401,7 +445,7 @@ func AuthorizeCommandMigrations(
|
||||
func validateCommandMoveLegacyLeaves(snapshot Snapshot, manifest CommandMigrationManifest) error {
|
||||
commands := commandIndex(snapshot)
|
||||
for _, migration := range manifest.Migrations {
|
||||
if migration.Kind != CommandMigrationMove {
|
||||
if migration.Kind != CommandMigrationMove && migration.Kind != CommandMigrationAvailability {
|
||||
continue
|
||||
}
|
||||
if _, present := commands[migration.Legacy.Command]; !present {
|
||||
@@ -409,7 +453,7 @@ func validateCommandMoveLegacyLeaves(snapshot Snapshot, manifest CommandMigratio
|
||||
}
|
||||
for _, command := range snapshot.Commands {
|
||||
if strings.HasPrefix(command.Path, migration.Legacy.Command+" ") {
|
||||
return fmt.Errorf("command_move legacy command %q must be a leaf", migration.Legacy.Command)
|
||||
return fmt.Errorf("%s legacy command %q must be a leaf", migration.Kind, migration.Legacy.Command)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -524,6 +568,15 @@ func sameCommandMigrationApproval(left, right CommandMigration) bool {
|
||||
func matchCommandMigrationPhase(snapshot Snapshot, migration CommandMigration) commandMigrationPhase {
|
||||
commands := commandIndex(snapshot)
|
||||
legacy := commandMigrationStateForCommand(commands, migration.Legacy.Command)
|
||||
if migration.Kind == CommandMigrationAvailability {
|
||||
if legacy == migration.Legacy.Before {
|
||||
return commandMigrationBefore
|
||||
}
|
||||
if legacy == migration.Legacy.After {
|
||||
return commandMigrationAfter
|
||||
}
|
||||
return commandMigrationPartial
|
||||
}
|
||||
replacement := commandMigrationStateForCommand(commands, migration.Replacement.Command)
|
||||
before := legacy == migration.Legacy.Before && replacement == migration.Replacement.Before
|
||||
after := legacy == migration.Legacy.After && replacement == migration.Replacement.After
|
||||
@@ -629,6 +682,10 @@ func commandMigrationAuthorizesChange(current, reference Snapshot, change Change
|
||||
if change.Kind == "command_became_hidden" && change.Flag == "" {
|
||||
return true
|
||||
}
|
||||
case CommandMigrationAvailability:
|
||||
if change.Kind == "command_became_hidden" && change.Flag == "" {
|
||||
return true
|
||||
}
|
||||
case CommandMigrationFlagExtraction:
|
||||
if change.Kind == "flag_became_hidden" && change.Flag == migration.LegacyFlag.Name {
|
||||
return true
|
||||
|
||||
@@ -186,6 +186,7 @@ func TestCrossPlatformCoverageCommandMigrationValidationEdges(t *testing.T) {
|
||||
want string
|
||||
}{
|
||||
{"identifier", CommandMigrationMove, func(s *CommandMigrationSchema) { s.ProductID = "bad/id" }, "exact identifier"},
|
||||
{"replacement identifier", CommandMigrationMove, func(s *CommandMigrationSchema) { s.ReplacementToolID = "bad/id" }, "replacement_tool_id"},
|
||||
{"nil parameters", CommandMigrationMove, func(s *CommandMigrationSchema) { s.Parameters = nil }, "must be an array"},
|
||||
{"bad from", CommandMigrationMove, func(s *CommandMigrationSchema) { s.Parameters[0].From = "bad name" }, "exact parameter"},
|
||||
{"duplicate from", CommandMigrationMove, func(s *CommandMigrationSchema) { s.Parameters = append(s.Parameters, s.Parameters[0]) }, "duplicates from"},
|
||||
@@ -241,6 +242,96 @@ func TestCrossPlatformCoverageCommandMigrationValidationEdges(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrossPlatformCoverageSchemaAvailabilityMigrationLifecycle(t *testing.T) {
|
||||
pending := availabilityCommandMigrationManifest(CommandMigrationPending)
|
||||
consumed := availabilityCommandMigrationManifest(CommandMigrationConsumed)
|
||||
empty := CommandMigrationManifest{Version: CommandMigrationManifestVersion, Migrations: []CommandMigration{}}
|
||||
before := testSnapshot(
|
||||
testCommand("dws"),
|
||||
testCommand("dws devapp"),
|
||||
testCommand("dws devapp +event-list"),
|
||||
)
|
||||
after := testSnapshot(
|
||||
testCommand("dws"),
|
||||
testCommand("dws devapp"),
|
||||
Command{Path: "dws devapp +event-list", Runnable: true, Hidden: true},
|
||||
)
|
||||
|
||||
got, err := AuthorizeCommandMigrations(
|
||||
after,
|
||||
map[string]Snapshot{"merge-base": before, "stable": before},
|
||||
pending,
|
||||
consumed,
|
||||
)
|
||||
if err != nil || len(got) != 1 {
|
||||
t.Fatalf("availability lifecycle authorizations=%#v error=%v", got, err)
|
||||
}
|
||||
report, err := CompareAllWithInterfaceMigrations(
|
||||
after,
|
||||
map[string]Snapshot{"merge-base": before, "stable": before},
|
||||
FlagMigrationManifest{Version: FlagMigrationManifestVersion, Migrations: []FlagMigration{}},
|
||||
FlagMigrationManifest{Version: FlagMigrationManifestVersion, Migrations: []FlagMigration{}},
|
||||
pending,
|
||||
consumed,
|
||||
)
|
||||
if err != nil || !report.Compatible {
|
||||
t.Fatalf("availability interface report compatible=%v error=%v report=%#v", report.Compatible, err, report)
|
||||
}
|
||||
if got, err := AuthorizeCommandMigrations(
|
||||
before,
|
||||
map[string]Snapshot{"merge-base": before, "stable": before},
|
||||
empty,
|
||||
pending,
|
||||
); err != nil || len(got) != 0 {
|
||||
t.Fatalf("candidate-added pending migration must remain inert: %#v, %v", got, err)
|
||||
}
|
||||
|
||||
valid := pending.Migrations[0]
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
mutate func(*CommandMigration)
|
||||
want string
|
||||
}{
|
||||
{"legacy path", func(m *CommandMigration) { m.Legacy.Command = "dws devapp *" }, "legacy must be an exact"},
|
||||
{"replacement", func(m *CommandMigration) { m.Replacement.Command = "dws devapp other" }, "must not declare replacement"},
|
||||
{"visible after", func(m *CommandMigration) { m.Legacy.After.Hidden = false }, "visible to hidden"},
|
||||
{"legacy flag", func(m *CommandMigration) { m.LegacyFlag.Name = "legacy" }, "must not declare legacy_flag"},
|
||||
{"missing availability", func(m *CommandMigration) { m.Schema.Availability = nil }, "requires availability"},
|
||||
{"wrong availability", func(m *CommandMigration) { m.Schema.Availability.After = "available" }, "requires availability"},
|
||||
{"parameter mapping", func(m *CommandMigration) {
|
||||
m.Schema.Parameters = []CommandParameterMigration{{From: "cursor", To: "next-token"}}
|
||||
}, "must not declare"},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
migration := cloneCommandMigration(valid)
|
||||
if valid.Schema.Availability != nil {
|
||||
change := *valid.Schema.Availability
|
||||
migration.Schema.Availability = &change
|
||||
}
|
||||
test.mutate(&migration)
|
||||
if err := migration.validate(); err == nil || !strings.Contains(err.Error(), test.want) {
|
||||
t.Fatalf("availability migration validation error=%v, want %q", err, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
if got := valid.displayKey(); !strings.Contains(got, CommandMigrationAvailability) {
|
||||
t.Fatalf("availability display key = %q", got)
|
||||
}
|
||||
if got := matchCommandMigrationPhase(testSnapshot(testCommand("dws")), valid); got != commandMigrationPartial {
|
||||
t.Fatalf("missing availability command phase = %q", got)
|
||||
}
|
||||
parent := before
|
||||
parent.Commands = append(append([]Command(nil), before.Commands...), testCommand("dws devapp +event-list detail"))
|
||||
if err := validateCommandMoveLegacyLeaves(parent, pending); err == nil || !strings.Contains(err.Error(), "must be a leaf") {
|
||||
t.Fatalf("availability parent command error = %v", err)
|
||||
}
|
||||
moveSchema := commandMigrationManifest(CommandMigrationPending).Migrations[0].Schema
|
||||
moveSchema.Availability = &CommandAvailabilityChange{Before: "available", After: "unavailable"}
|
||||
if err := moveSchema.validate(CommandMigrationMove); err == nil || !strings.Contains(err.Error(), "must not declare schema availability") {
|
||||
t.Fatalf("ordinary move availability error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrossPlatformCoverageFlagExtractionRejectsRequiredLegacyFlag(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
@@ -686,7 +777,10 @@ func flagExtractionCommandMigrationManifest(state string) CommandMigrationManife
|
||||
|
||||
func cloneCommandMigration(source CommandMigration) CommandMigration {
|
||||
cloned := source
|
||||
cloned.Schema.Parameters = append([]CommandParameterMigration(nil), source.Schema.Parameters...)
|
||||
if source.Schema.Parameters != nil {
|
||||
cloned.Schema.Parameters = make([]CommandParameterMigration, len(source.Schema.Parameters))
|
||||
copy(cloned.Schema.Parameters, source.Schema.Parameters)
|
||||
}
|
||||
for index, parameter := range source.Schema.Parameters {
|
||||
if parameter.ReplacementConstant == nil {
|
||||
continue
|
||||
@@ -703,6 +797,28 @@ func singleCommandMigrationManifest(state string) CommandMigrationManifest {
|
||||
return manifest
|
||||
}
|
||||
|
||||
func availabilityCommandMigrationManifest(state string) CommandMigrationManifest {
|
||||
return CommandMigrationManifest{
|
||||
Version: CommandMigrationManifestVersion,
|
||||
Migrations: []CommandMigration{{
|
||||
Kind: CommandMigrationAvailability,
|
||||
Legacy: CommandMigrationSide{
|
||||
Command: "dws devapp +event-list",
|
||||
Before: CommandMigrationState{Present: true, Runnable: true},
|
||||
After: CommandMigrationState{Present: true, Runnable: true, Hidden: true},
|
||||
},
|
||||
Schema: CommandMigrationSchema{
|
||||
ProductID: "devapp",
|
||||
SourceToolID: "devapp.shortcut_event_list",
|
||||
Parameters: []CommandParameterMigration{},
|
||||
Availability: &CommandAvailabilityChange{Before: "available", After: "unavailable"},
|
||||
},
|
||||
State: state,
|
||||
Reason: "Fail closed until terminal pagination evidence is available.",
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
func modifiedCommandManifest(source CommandMigrationManifest) CommandMigrationManifest {
|
||||
modified := source
|
||||
modified.Migrations = append([]CommandMigration(nil), source.Migrations...)
|
||||
|
||||
@@ -13,6 +13,69 @@ import (
|
||||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/interfacesnapshot"
|
||||
)
|
||||
|
||||
func TestCrossPlatformCoverageSchemaAvailabilityMigrationNormalizesOnlyAvailability(t *testing.T) {
|
||||
baseline := baselineContract()
|
||||
current := cloneContract(baseline)
|
||||
mutateTool(¤t, func(tool *toolSchema) { tool.Availability = "unavailable" })
|
||||
migration := interfacesnapshot.CommandMigration{
|
||||
Kind: interfacesnapshot.CommandMigrationAvailability,
|
||||
Legacy: interfacesnapshot.CommandMigrationSide{
|
||||
Command: "dws doc create",
|
||||
Before: interfacesnapshot.CommandMigrationState{Present: true, Runnable: true},
|
||||
After: interfacesnapshot.CommandMigrationState{Present: true, Runnable: true, Hidden: true},
|
||||
},
|
||||
Schema: interfacesnapshot.CommandMigrationSchema{
|
||||
ProductID: "doc",
|
||||
SourceToolID: "doc.create",
|
||||
Parameters: []interfacesnapshot.CommandParameterMigration{},
|
||||
Availability: &interfacesnapshot.CommandAvailabilityChange{Before: "available", After: "unavailable"},
|
||||
},
|
||||
State: interfacesnapshot.CommandMigrationPending,
|
||||
Reason: "Reviewed fail-closed availability hardening.",
|
||||
}
|
||||
|
||||
normalized, err := normalizeSchemaCommandMigrations(baseline, current, []interfacesnapshot.CommandMigration{migration})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := normalized.Products["doc"].Tools["doc.create"].Availability; got != "unavailable" {
|
||||
t.Fatalf("normalized availability = %q", got)
|
||||
}
|
||||
if failures := checkCompatibility(normalized, current); len(failures) != 0 {
|
||||
t.Fatalf("availability hardening failures = %v", failures)
|
||||
}
|
||||
|
||||
unrelated := cloneContract(current)
|
||||
mutateTool(&unrelated, func(tool *toolSchema) { tool.Risk = "high" })
|
||||
normalized, err = normalizeSchemaCommandMigrations(baseline, unrelated, []interfacesnapshot.CommandMigration{migration})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if failures := strings.Join(checkCompatibility(normalized, unrelated), "\n"); !strings.Contains(failures, "changed risk") {
|
||||
t.Fatalf("unrelated risk drift was hidden: %q", failures)
|
||||
}
|
||||
|
||||
alreadyAfter := cloneContract(current)
|
||||
if _, err := normalizeSchemaCommandMigrations(alreadyAfter, current, []interfacesnapshot.CommandMigration{migration}); err != nil {
|
||||
t.Fatalf("consumed merge-base availability should remain inert: %v", err)
|
||||
}
|
||||
wrongPath := cloneContract(current)
|
||||
mutateTool(&wrongPath, func(tool *toolSchema) { tool.PrimaryCLIPath = "doc other" })
|
||||
normalized, err = normalizeSchemaCommandMigrations(baseline, wrongPath, []interfacesnapshot.CommandMigration{migration})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := normalized.Products["doc"].Tools["doc.create"].Availability; got != "available" {
|
||||
t.Fatalf("wrong-path migration changed availability to %q", got)
|
||||
}
|
||||
wrong := cloneContract(current)
|
||||
mutateTool(&wrong, func(tool *toolSchema) { tool.Availability = "available" })
|
||||
if _, err := normalizeSchemaCommandMigrations(baseline, wrong, []interfacesnapshot.CommandMigration{migration}); err == nil ||
|
||||
!strings.Contains(err.Error(), "does not match Schema availability") {
|
||||
t.Fatalf("wrong availability error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrossPlatformCoverageSchemaCommandMigrationsAuthorizeOnlyExactProjection(t *testing.T) {
|
||||
baseline := schemaCommandMigrationContract(false)
|
||||
current := schemaCommandMigrationContract(true)
|
||||
|
||||
@@ -1875,6 +1875,24 @@ func normalizeSchemaCommandMigrations(
|
||||
normalizedProduct := normalized.Products[migration.Schema.ProductID]
|
||||
normalizedTool := normalizedProduct.Tools[migration.Schema.SourceToolID]
|
||||
switch migration.Kind {
|
||||
case interfacesnapshot.CommandMigrationAvailability:
|
||||
change := migration.Schema.Availability
|
||||
if change != nil && oldTool.Availability == change.After && newSource.Availability == change.After {
|
||||
continue
|
||||
}
|
||||
if change == nil || oldTool.Availability != change.Before || newSource.Availability != change.After {
|
||||
return schemaContract{}, fmt.Errorf(
|
||||
"approved availability hardening %q does not match Schema availability %q -> %q",
|
||||
migration.Legacy.Command,
|
||||
oldTool.Availability,
|
||||
newSource.Availability,
|
||||
)
|
||||
}
|
||||
if newSource.PrimaryCLIPath != legacyPath {
|
||||
continue
|
||||
}
|
||||
normalizedTool.Availability = newSource.Availability
|
||||
|
||||
case interfacesnapshot.CommandMigrationMove:
|
||||
if newSource.PrimaryCLIPath != replacementPath {
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user