From 300f4dfa83e9f1812b64eaf62cd1f0b6910af9dd Mon Sep 17 00:00:00 2001 From: Fiachra Corcoran Date: Thu, 7 May 2026 00:12:28 +0100 Subject: [PATCH 1/8] feat(fn): add --help, --doc, and standalone file mode to AsMain Extend fn.AsMain with functional options to support: - --help: renders human-readable docs from embedded README markers - --doc: outputs machine-readable JSON for catalog tooling - Standalone file mode: accepts positional file args for local debugging New fn.WithDocs(readme, meta) option registers embedded content. Existing callers with no options are unaffected (backward-compatible). Adds internal/docs package with: - ParseMarkers: extracts mdtogo Short/Long/Examples sections - ParseMetadata: parses metadata.yaml content - RenderHelp/RenderDoc: formats output for --help and --doc Includes property-based tests (rapid) and unit tests for all new code. Signed-off-by: Fiachra Corcoran --- go/fn/go.mod | 7 +- go/fn/go.sum | 2 + go/fn/internal/docs/markers.go | 82 +++++ go/fn/internal/docs/markers_test.go | 119 +++++++ go/fn/internal/docs/metadata.go | 39 +++ go/fn/internal/docs/metadata_test.go | 301 +++++++++++++++++ go/fn/internal/docs/render.go | 102 ++++++ go/fn/internal/docs/render_test.go | 467 +++++++++++++++++++++++++++ go/fn/run.go | 150 ++++++++- go/fn/run_filemode_property_test.go | 171 ++++++++++ go/fn/run_filemode_test.go | 428 ++++++++++++++++++++++++ go/fn/run_flags_test.go | 312 ++++++++++++++++++ 12 files changed, 2176 insertions(+), 4 deletions(-) create mode 100644 go/fn/internal/docs/markers.go create mode 100644 go/fn/internal/docs/markers_test.go create mode 100644 go/fn/internal/docs/metadata.go create mode 100644 go/fn/internal/docs/metadata_test.go create mode 100644 go/fn/internal/docs/render.go create mode 100644 go/fn/internal/docs/render_test.go create mode 100644 go/fn/run_filemode_property_test.go create mode 100644 go/fn/run_filemode_test.go create mode 100644 go/fn/run_flags_test.go diff --git a/go/fn/go.mod b/go/fn/go.mod index f14b0f39..48eba0a4 100644 --- a/go/fn/go.mod +++ b/go/fn/go.mod @@ -15,7 +15,11 @@ require ( sigs.k8s.io/kustomize/kyaml v0.21.1 ) -require github.com/pkg/errors v0.9.1 +require ( + github.com/pkg/errors v0.9.1 + go.yaml.in/yaml/v3 v3.0.4 + pgregory.net/rapid v1.3.0 +) require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -39,7 +43,6 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/xlab/treeprint v1.2.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/sys v0.44.0 // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go/fn/go.sum b/go/fn/go.sum index 7c41123e..870150a3 100644 --- a/go/fn/go.sum +++ b/go/fn/go.sum @@ -79,6 +79,8 @@ k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= k8s.io/kube-openapi v0.0.0-20260520065146-aa012df4f4af h1:zLXA2Irn14q2/06WMkxViyr7YCPUO2lJ0QYE9Juy5vA= k8s.io/kube-openapi v0.0.0-20260520065146-aa012df4f4af/go.mod h1:V/QaCUYDa+0QpcHhVVc5l99Uz56wEMEXBSj9oCDkNDY= +pgregory.net/rapid v1.3.0 h1:vBvO0VSqti75J1jjYqpgPNBLKMd1+gxa9fYo7vk/Exc= +pgregory.net/rapid v1.3.0/go.mod h1:dPlE4OBBxgXPqkP79flB6sJL1dx5azpI7HQ9MY9Z7uk= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7fI= diff --git a/go/fn/internal/docs/markers.go b/go/fn/internal/docs/markers.go new file mode 100644 index 00000000..0c6eab24 --- /dev/null +++ b/go/fn/internal/docs/markers.go @@ -0,0 +1,82 @@ +// Copyright 2025 The kpt Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package docs + +import "strings" + +// Sections holds parsed README marker content. +type Sections struct { + Short string + Long string + Examples string +} + +const ( + markerShort = "" + markerLong = "" + markerExamples = "" + markerEnd = "" +) + +// ParseMarkers extracts mdtogo marker sections from README content. +// Missing markers result in empty strings for the corresponding sections. +// When no markers are present at all, the full content (trimmed) is returned +// as the Long description. +func ParseMarkers(readme []byte) Sections { + content := string(readme) + + short := extractSection(content, markerShort) + long := extractSection(content, markerLong) + examples := extractSection(content, markerExamples) + + // Fallback: if no markers are present at all, use full content as Long. + if !hasAnyMarker(content) { + return Sections{ + Long: strings.TrimSpace(content), + } + } + + return Sections{ + Short: short, + Long: long, + Examples: examples, + } +} + +// extractSection finds text between the given start marker and the next +// marker. Returns empty string if either marker is missing. +func extractSection(content, startMarker string) string { + startIdx := strings.Index(content, startMarker) + if startIdx < 0 { + return "" + } + afterStart := startIdx + len(startMarker) + remaining := content[afterStart:] + + endIdx := strings.Index(remaining, markerEnd) + if endIdx < 0 { + return "" + } + + return strings.TrimSpace(remaining[:endIdx]) +} + +// hasAnyMarker reports whether the content contains any mdtogo marker. +func hasAnyMarker(content string) bool { + return strings.Contains(content, markerShort) || + strings.Contains(content, markerLong) || + strings.Contains(content, markerExamples) || + strings.Contains(content, markerEnd) +} diff --git a/go/fn/internal/docs/markers_test.go b/go/fn/internal/docs/markers_test.go new file mode 100644 index 00000000..bc830748 --- /dev/null +++ b/go/fn/internal/docs/markers_test.go @@ -0,0 +1,119 @@ +// Copyright 2025 The kpt Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package docs + +import ( + "fmt" + "strings" + "testing" + + "pgregory.net/rapid" +) + +// Feature: sdk-alignment, Property 1: Marker parser round-trip +// +// For any three strings (short, long, examples), formatting them into a README +// with mdtogo markers and then parsing that README with ParseMarkers SHALL +// produce a Sections struct with fields equal to the original strings (after trimming). +// +// Validates: Requirements 6.1, 6.2, 6.3, 6.5, 5.2 + +// genSectionContent generates arbitrary non-empty strings that do not contain +// mdtogo markers (which would confuse the parser). +func genSectionContent() *rapid.Generator[string] { + return rapid.Custom(func(t *rapid.T) string { + s := rapid.StringMatching(`[a-zA-Z0-9 \t\n.,;:!?(){}\[\]'"/_-]{1,200}`).Draw(t, "content") + // Ensure the generated content does not accidentally contain marker strings. + s = strings.ReplaceAll(s, " +%s + + + +%s + + + +%s + +`, short, long, examples) +} + +// Feature: sdk-alignment, Property 7: Missing markers fallback +// +// For any README content that does NOT contain mdtogo markers, ParseMarkers +// SHALL return empty strings for Short and Examples, and the full content +// (trimmed) as Long. +// +// Validates: Requirements 5.4, 6.4 + +// genNoMarkerContent generates arbitrary strings guaranteed not to contain +// any mdtogo marker substrings. +func genNoMarkerContent() *rapid.Generator[string] { + return rapid.Custom(func(t *rapid.T) string { + s := rapid.StringMatching(`[a-zA-Z0-9 \t\n.,;:!?(){}\[\]'"/_-]{0,300}`).Draw(t, "content") + // Strip anything that could form a marker. + s = strings.ReplaceAll(s, " +%s + + + +%s + + + +%s + +`, short, long, examples) + + // Parse the README to get sections. + sections := ParseMarkers([]byte(readme)) + + // Generate non-empty metadata fields to ensure all appear in output. + meta := Metadata{ + Image: rapid.StringMatching(`gcr\.io/[a-z0-9-]{3,20}/[a-z0-9-]{3,20}:v[0-9]+\.[0-9]+`).Draw(t, "image"), + Description: genNonEmptySectionContent().Draw(t, "description"), + Tags: rapid.SliceOfN( + rapid.StringMatching(`[a-z]{3,10}`), 1, 5, + ).Draw(t, "tags"), + SourceURL: rapid.StringMatching(`https://github\.com/[a-z0-9-]{3,20}/[a-z0-9-]{3,20}`).Draw(t, "sourceURL"), + ExamplePackageURLs: rapid.SliceOfN(rapid.StringMatching(`https://github\.com/[a-z0-9-]{3,20}/[a-z0-9-]{3,20}`), 1, 3).Draw(t, "exampleURLs"), + License: rapid.SampledFrom([]string{"Apache-2.0", "MIT", "BSD-3-Clause"}).Draw(t, "license"), + Hidden: rapid.Bool().Draw(t, "hidden"), + } + + // Render doc JSON output. + var buf bytes.Buffer + err := RenderDoc(&buf, sections, meta) + if err != nil { + t.Fatalf("RenderDoc returned error: %v", err) + } + + // Decode the JSON output. + var output DocOutput + if err := json.Unmarshal(buf.Bytes(), &output); err != nil { + t.Fatalf("failed to decode doc JSON: %v\n Raw: %s", err, buf.String()) + } + + // Assert all non-empty source values from README appear in output. + if output.Short != sections.Short { + t.Fatalf("doc JSON 'short' mismatch\n expected: %q\n got: %q", sections.Short, output.Short) + } + if output.Long != sections.Long { + t.Fatalf("doc JSON 'long' mismatch\n expected: %q\n got: %q", sections.Long, output.Long) + } + if output.Examples != sections.Examples { + t.Fatalf("doc JSON 'examples' mismatch\n expected: %q\n got: %q", sections.Examples, output.Examples) + } + + // Assert all non-empty source values from metadata appear in output. + if output.Image != meta.Image { + t.Fatalf("doc JSON 'image' mismatch\n expected: %q\n got: %q", meta.Image, output.Image) + } + if output.Description != meta.Description { + t.Fatalf("doc JSON 'description' mismatch\n expected: %q\n got: %q", meta.Description, output.Description) + } + if len(output.Tags) != len(meta.Tags) { + t.Fatalf("doc JSON 'tags' length mismatch\n expected: %v\n got: %v", meta.Tags, output.Tags) + } + for i, tag := range meta.Tags { + if output.Tags[i] != tag { + t.Fatalf("doc JSON 'tags[%d]' mismatch\n expected: %q\n got: %q", i, tag, output.Tags[i]) + } + } + if output.SourceURL != meta.SourceURL { + t.Fatalf("doc JSON 'sourceURL' mismatch\n expected: %q\n got: %q", meta.SourceURL, output.SourceURL) + } + if len(output.ExamplePackageURLs) != len(meta.ExamplePackageURLs) { + t.Fatalf("doc JSON 'examplePackageURLs' length mismatch\n expected: %v\n got: %v", meta.ExamplePackageURLs, output.ExamplePackageURLs) + } + for i, url := range meta.ExamplePackageURLs { + if output.ExamplePackageURLs[i] != url { + t.Fatalf("doc JSON 'examplePackageURLs[%d]' mismatch\n expected: %q\n got: %q", i, url, output.ExamplePackageURLs[i]) + } + } + if output.License != meta.License { + t.Fatalf("doc JSON 'license' mismatch\n expected: %q\n got: %q", meta.License, output.License) + } + if output.Hidden != meta.Hidden { + t.Fatalf("doc JSON 'hidden' mismatch\n expected: %v\n got: %v", meta.Hidden, output.Hidden) + } + }) +} + +func TestProperty3_HelpOutputContainsParsedSections(t *testing.T) { + rapid.Check(t, func(t *rapid.T) { + short := genNonEmptySectionContent().Draw(t, "short") + long := genNonEmptySectionContent().Draw(t, "long") + examples := genNonEmptySectionContent().Draw(t, "examples") + + // Format a README with valid mdtogo markers. + readme := fmt.Sprintf(` +%s + + + +%s + + + +%s + +`, short, long, examples) + + // Parse the README to get sections (same as runtime would). + sections := ParseMarkers([]byte(readme)) + + // Render help output. + var buf bytes.Buffer + RenderHelp(&buf, sections, Metadata{}) + output := buf.String() + + // Assert that the help output contains each parsed section. + if !strings.Contains(output, sections.Short) { + t.Fatalf("help output does not contain Short section\n Short: %q\n Output: %q", sections.Short, output) + } + if !strings.Contains(output, sections.Long) { + t.Fatalf("help output does not contain Long section\n Long: %q\n Output: %q", sections.Long, output) + } + if !strings.Contains(output, sections.Examples) { + t.Fatalf("help output does not contain Examples section\n Examples: %q\n Output: %q", sections.Examples, output) + } + }) +} + +// --- Unit Tests for Renderers --- +// Validates: Requirements 2.3, 3.3, 3.6 + +func TestRenderHelp_FullSectionsAndMetadata(t *testing.T) { + sections := Sections{ + Short: "Set labels on all resources", + Long: "The set-labels function adds or updates labels on all resources in the package.", + Examples: " kpt fn eval --image gcr.io/kpt-fn/set-labels:v0.1 -- label_name=label_value", + } + meta := Metadata{ + Image: "gcr.io/kpt-fn/set-labels:v0.1", + Description: "Set labels on all resources", + Tags: []string{"mutator", "labels"}, + } + + var buf bytes.Buffer + RenderHelp(&buf, sections, meta) + output := buf.String() + + // Verify output contains the Short description. + if !strings.Contains(output, sections.Short) { + t.Errorf("expected output to contain Short %q, got:\n%s", sections.Short, output) + } + // Verify output contains the Long description. + if !strings.Contains(output, sections.Long) { + t.Errorf("expected output to contain Long %q, got:\n%s", sections.Long, output) + } + // Verify output contains the Examples content. + if !strings.Contains(output, sections.Examples) { + t.Errorf("expected output to contain Examples %q, got:\n%s", sections.Examples, output) + } + // Verify the "Examples:" header is present. + if !strings.Contains(output, "Examples:") { + t.Errorf("expected output to contain 'Examples:' header, got:\n%s", output) + } + // Verify no cobra boilerplate. + if strings.Contains(output, "Usage:") { + t.Errorf("output should not contain 'Usage:', got:\n%s", output) + } + if strings.Contains(output, "Flags:") { + t.Errorf("output should not contain 'Flags:', got:\n%s", output) + } +} + +func TestRenderHelp_EmptySections(t *testing.T) { + sections := Sections{} + meta := Metadata{} + + var buf bytes.Buffer + RenderHelp(&buf, sections, meta) + output := buf.String() + + expected := "No documentation available. Pass fn.WithDocs to fn.AsMain to enable --help.\n" + if output != expected { + t.Errorf("expected minimal message %q, got %q", expected, output) + } +} + +func TestRenderDoc_ValidJSON_AllFields(t *testing.T) { + sections := Sections{ + Short: "Set labels on all resources", + Long: "The set-labels function adds or updates labels.", + Examples: " kpt fn eval --image gcr.io/kpt-fn/set-labels:v0.1", + } + meta := Metadata{ + Image: "gcr.io/kpt-fn/set-labels:v0.1", + Description: "Set labels on all resources", + Tags: []string{"mutator", "labels"}, + SourceURL: "https://github.com/kptdev/krm-functions/tree/main/functions/go/set-labels", + ExamplePackageURLs: []string{"https://github.com/kptdev/krm-functions/tree/main/examples/set-labels-simple"}, + License: "Apache-2.0", + Hidden: false, + } + + var buf bytes.Buffer + err := RenderDoc(&buf, sections, meta) + if err != nil { + t.Fatalf("RenderDoc returned error: %v", err) + } + + // Verify output is valid JSON. + var output DocOutput + if err := json.Unmarshal(buf.Bytes(), &output); err != nil { + t.Fatalf("output is not valid JSON: %v\nRaw: %s", err, buf.String()) + } + + // Verify all fields are correctly populated. + if output.Short != sections.Short { + t.Errorf("Short: got %q, want %q", output.Short, sections.Short) + } + if output.Long != sections.Long { + t.Errorf("Long: got %q, want %q", output.Long, sections.Long) + } + if output.Examples != sections.Examples { + t.Errorf("Examples: got %q, want %q", output.Examples, sections.Examples) + } + if output.Image != meta.Image { + t.Errorf("Image: got %q, want %q", output.Image, meta.Image) + } + if output.Description != meta.Description { + t.Errorf("Description: got %q, want %q", output.Description, meta.Description) + } + if len(output.Tags) != len(meta.Tags) { + t.Errorf("Tags length: got %d, want %d", len(output.Tags), len(meta.Tags)) + } else { + for i, tag := range meta.Tags { + if output.Tags[i] != tag { + t.Errorf("Tags[%d]: got %q, want %q", i, output.Tags[i], tag) + } + } + } + if output.SourceURL != meta.SourceURL { + t.Errorf("SourceURL: got %q, want %q", output.SourceURL, meta.SourceURL) + } + if len(output.ExamplePackageURLs) != len(meta.ExamplePackageURLs) { + t.Errorf("ExamplePackageURLs length: got %d, want %d", len(output.ExamplePackageURLs), len(meta.ExamplePackageURLs)) + } else { + for i, url := range meta.ExamplePackageURLs { + if output.ExamplePackageURLs[i] != url { + t.Errorf("ExamplePackageURLs[%d]: got %q, want %q", i, output.ExamplePackageURLs[i], url) + } + } + } + if output.License != meta.License { + t.Errorf("License: got %q, want %q", output.License, meta.License) + } + if output.Hidden != meta.Hidden { + t.Errorf("Hidden: got %v, want %v", output.Hidden, meta.Hidden) + } +} + +func TestRenderDoc_EmptyInputs(t *testing.T) { + sections := Sections{} + meta := Metadata{} + + var buf bytes.Buffer + err := RenderDoc(&buf, sections, meta) + if err != nil { + t.Fatalf("RenderDoc returned error: %v", err) + } + + // Verify output is valid JSON. + var raw map[string]interface{} + if err := json.Unmarshal(buf.Bytes(), &raw); err != nil { + t.Fatalf("output is not valid JSON: %v\nRaw: %s", err, buf.String()) + } + + // Verify it decodes to a DocOutput with zero-value fields. + var output DocOutput + if err := json.Unmarshal(buf.Bytes(), &output); err != nil { + t.Fatalf("failed to decode into DocOutput: %v", err) + } + + if output.Short != "" { + t.Errorf("Short: got %q, want empty", output.Short) + } + if output.Long != "" { + t.Errorf("Long: got %q, want empty", output.Long) + } + if output.Examples != "" { + t.Errorf("Examples: got %q, want empty", output.Examples) + } + if output.Image != "" { + t.Errorf("Image: got %q, want empty", output.Image) + } + if output.Hidden != false { + t.Errorf("Hidden: got %v, want false", output.Hidden) + } +} + +func TestRenderDoc_HiddenFieldSerialization(t *testing.T) { + sections := Sections{ + Short: "A hidden function", + } + meta := Metadata{ + Hidden: true, + } + + var buf bytes.Buffer + err := RenderDoc(&buf, sections, meta) + if err != nil { + t.Fatalf("RenderDoc returned error: %v", err) + } + + // Verify the raw JSON contains "hidden": true. + var raw map[string]interface{} + if err := json.Unmarshal(buf.Bytes(), &raw); err != nil { + t.Fatalf("output is not valid JSON: %v\nRaw: %s", err, buf.String()) + } + + hiddenVal, ok := raw["hidden"] + if !ok { + t.Fatal("JSON output does not contain 'hidden' field") + } + if hiddenVal != true { + t.Errorf("hidden field: got %v, want true", hiddenVal) + } + + // Also verify via struct decoding. + var output DocOutput + if err := json.Unmarshal(buf.Bytes(), &output); err != nil { + t.Fatalf("failed to decode into DocOutput: %v", err) + } + if !output.Hidden { + t.Errorf("DocOutput.Hidden: got false, want true") + } +} diff --git a/go/fn/run.go b/go/fn/run.go index c38ebfbd..b2100199 100644 --- a/go/fn/run.go +++ b/go/fn/run.go @@ -18,16 +18,70 @@ import ( "fmt" "io" "os" + "slices" + "strings" + "github.com/kptdev/krm-functions-sdk/go/fn/internal/docs" "sigs.k8s.io/kustomize/kyaml/errors" "sigs.k8s.io/kustomize/kyaml/kio" ) -// AsMain evaluates the ResourceList from STDIN to STDOUT. +// Option configures fn.AsMain behavior. +type Option func(*mainConfig) + +// mainConfig holds configuration gathered from Options. +type mainConfig struct { + readme []byte // raw embedded README.md content + metadata []byte // raw embedded metadata.yaml content +} + +// WithDocs registers embedded README and metadata content for --help and --doc. +func WithDocs(readme []byte, meta []byte) Option { + return func(c *mainConfig) { + c.readme = readme + c.metadata = meta + } +} + +// AsMain evaluates a KRM function. By default it reads a ResourceList from +// STDIN, processes it, and writes the result to STDOUT. +// // `input` can be // - a `ResourceListProcessor` which implements `Process` method // - a function `Runner` which implements `Run` method -func AsMain(input any) error { +// +// Invocation modes (checked in this order): +// - --help: prints human-readable documentation to STDOUT and returns nil. +// - --doc: prints machine-readable JSON documentation to STDOUT and returns nil. +// - positional file args: reads KRM resources from files instead of STDIN. +// - no args: reads ResourceList from STDIN (default behavior). +// +// Options configure additional behavior such as documentation support +// via WithDocs. Existing callers with no options continue to work unchanged. +func AsMain(input any, opts ...Option) error { + // Apply options to build configuration. + var cfg mainConfig + for _, opt := range opts { + opt(&cfg) + } + + // Check for --help and --doc flags before reading STDIN. + // --help always takes precedence over --doc regardless of argument order. + if slices.Contains(os.Args[1:], "--help") { + return handleHelp(&cfg) + } + if slices.Contains(os.Args[1:], "--doc") { + return handleDoc(&cfg) + } + + // Collect non-flag positional arguments (file paths). + var filePaths []string + for _, arg := range os.Args[1:] { + if !strings.HasPrefix(arg, "--") { + filePaths = append(filePaths, arg) + } + } + err := func() error { var p ResourceListProcessor switch input := input.(type) { @@ -38,6 +92,31 @@ func AsMain(input any) error { default: return fmt.Errorf("unknown input type %T", input) } + + // If file paths are provided, use file mode instead of STDIN. + if len(filePaths) > 0 { + rl, err := readFilesAsResourceList(filePaths) + if err != nil { + return err + } + success, fnErr := p.Process(rl) + out, yamlErr := rl.ToYAML() + if yamlErr != nil { + return yamlErr + } + _, outErr := os.Stdout.Write(out) + if outErr != nil { + return outErr + } + if fnErr != nil { + return fnErr + } + if !success { + return fmt.Errorf("error: function failure") + } + return nil + } + in, err := io.ReadAll(os.Stdin) if err != nil { return fmt.Errorf("unable to read from stdin: %v", err) @@ -57,6 +136,73 @@ func AsMain(input any) error { return err } +// handleHelp renders help text to STDOUT based on registered docs. +func handleHelp(cfg *mainConfig) error { + if cfg.readme == nil && cfg.metadata == nil { + fmt.Fprint(os.Stdout, "No documentation available. Pass fn.WithDocs to fn.AsMain to enable --help.\n") + return nil + } + + sections := docs.ParseMarkers(cfg.readme) + meta, err := docs.ParseMetadata(cfg.metadata) + if err != nil { + Logf("warning: invalid metadata YAML: %v", err) + meta = docs.Metadata{} + } + + docs.RenderHelp(os.Stdout, sections, meta) + return nil +} + +// handleDoc renders JSON documentation to STDOUT based on registered docs. +func handleDoc(cfg *mainConfig) error { + if cfg.readme == nil && cfg.metadata == nil { + fmt.Fprint(os.Stdout, "{}") + return nil + } + + sections := docs.ParseMarkers(cfg.readme) + meta, err := docs.ParseMetadata(cfg.metadata) + if err != nil { + Logf("warning: invalid metadata YAML: %v", err) + meta = docs.Metadata{} + } + + return docs.RenderDoc(os.Stdout, sections, meta) +} + +// readFilesAsResourceList reads KRM YAML from the given file paths, +// assembles them into a ResourceList with an empty FunctionConfig. +// Each file is parsed as one or more KRM YAML documents (separated by ---). +// Empty files are valid (no items added). Returns a descriptive error if a +// file does not exist or contains invalid YAML. +func readFilesAsResourceList(paths []string) (*ResourceList, error) { + rl := &ResourceList{ + FunctionConfig: NewEmptyKubeObject(), + } + for _, path := range paths { + data, err := os.ReadFile(path) + if err != nil { + if os.IsNotExist(err) { + return nil, fmt.Errorf("file not found: %s", path) + } + return nil, fmt.Errorf("failed to read file %s: %v", path, err) + } + // Empty files are valid — proceed with no items from this file. + if len(strings.TrimSpace(string(data))) == 0 { + continue + } + objects, err := ParseKubeObjects(data) + if err != nil { + return nil, fmt.Errorf("failed to parse KRM resources from %s: %v", path, err) + } + for _, obj := range objects { + rl.Items = append(rl.Items, obj) + } + } + return rl, nil +} + // Run evaluates the function. input must be a resourceList in yaml format. An // updated resourceList will be returned. func Run(p ResourceListProcessor, input []byte) ([]byte, error) { diff --git a/go/fn/run_filemode_property_test.go b/go/fn/run_filemode_property_test.go new file mode 100644 index 00000000..5439c043 --- /dev/null +++ b/go/fn/run_filemode_property_test.go @@ -0,0 +1,171 @@ +// Copyright 2025 The kpt Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fn + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "pgregory.net/rapid" +) + +// Feature: sdk-alignment, Property 6: File mode equivalence +// +// For any valid set of KRM YAML resources, processing them via standalone file +// mode SHALL produce identical output to processing the same resources assembled +// into a ResourceList via STDIN mode. +// +// Validates: Requirements 4.4 + +// genKRMResource generates a valid KRM YAML resource (a ConfigMap) with random +// name and namespace. ConfigMaps are used because they are simple, always valid +// KRM resources that don't require complex schemas. +func genKRMResource() *rapid.Generator[string] { + return rapid.Custom(func(t *rapid.T) string { + name := rapid.StringMatching(`[a-z][a-z0-9]{2,12}`).Draw(t, "name") + namespace := rapid.StringMatching(`[a-z][a-z0-9]{2,8}`).Draw(t, "namespace") + // Generate 1-3 data entries with YAML-safe values (alphanumeric only, + // no special characters that could be misinterpreted by the YAML parser). + numEntries := rapid.IntRange(1, 3).Draw(t, "numEntries") + dataLines := "" + for i := 0; i < numEntries; i++ { + key := rapid.StringMatching(`[a-z][a-z0-9]{1,8}`).Draw(t, fmt.Sprintf("key%d", i)) + value := rapid.StringMatching(`[a-zA-Z0-9]{1,15}`).Draw(t, fmt.Sprintf("value%d", i)) + dataLines += fmt.Sprintf(" %s: %s\n", key, value) + } + return fmt.Sprintf(`apiVersion: v1 +kind: ConfigMap +metadata: + name: %s + namespace: %s +data: +%s`, name, namespace, dataLines) + }) +} + +// genKRMResourceList generates a slice of 1-5 valid KRM YAML resource strings. +func genKRMResourceList() *rapid.Generator[[]string] { + return rapid.SliceOfN(genKRMResource(), 1, 5) +} + +func TestProperty6_FileModeEquivalence(t *testing.T) { + rapid.Check(t, func(t *rapid.T) { + resources := genKRMResourceList().Draw(t, "resources") + + // --- File mode path --- + // Write each resource to a temp file. + tmpDir, err := os.MkdirTemp("", "property6-*") + if err != nil { + t.Fatalf("failed to create temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + + var filePaths []string + for i, res := range resources { + path := filepath.Join(tmpDir, fmt.Sprintf("resource-%d.yaml", i)) + if err := os.WriteFile(path, []byte(res), 0644); err != nil { + t.Fatalf("failed to write temp file: %v", err) + } + filePaths = append(filePaths, path) + } + + // Process via file mode: readFilesAsResourceList → Process → ToYAML. + // Use a no-op processor that passes items through unchanged. + noopProc := ResourceListProcessorFunc(func(rl *ResourceList) (bool, error) { + return true, nil + }) + + fileRL, err := readFilesAsResourceList(filePaths) + if err != nil { + t.Fatalf("readFilesAsResourceList failed: %v", err) + } + _, fnErr := noopProc.Process(fileRL) + if fnErr != nil { + t.Fatalf("file mode Process failed: %v", fnErr) + } + fileOutput, err := fileRL.ToYAML() + if err != nil { + t.Fatalf("file mode ToYAML failed: %v", err) + } + + // --- STDIN mode path --- + // Assemble the same resources into a ResourceList YAML (as STDIN would provide). + stdinInput := "apiVersion: config.kubernetes.io/v1\nkind: ResourceList\nitems:\n" + for _, res := range resources { + // Indent each resource line under items as a YAML list element. + stdinInput += "- " + first := true + for _, line := range splitLines(res) { + if first { + stdinInput += line + "\n" + first = false + } else { + stdinInput += " " + line + "\n" + } + } + } + + stdinOutput, err := Run(noopProc, []byte(stdinInput)) + if err != nil { + t.Fatalf("STDIN mode Run failed: %v\n Input:\n%s", err, stdinInput) + } + + // --- Compare outputs --- + // Parse both outputs as ResourceLists and compare items. + fileResultRL, err := ParseResourceList(fileOutput) + if err != nil { + t.Fatalf("failed to parse file mode output: %v\n Output:\n%s", err, string(fileOutput)) + } + stdinResultRL, err := ParseResourceList(stdinOutput) + if err != nil { + t.Fatalf("failed to parse STDIN mode output: %v\n Output:\n%s", err, string(stdinOutput)) + } + + // Compare item counts. + if len(fileResultRL.Items) != len(stdinResultRL.Items) { + t.Fatalf("item count mismatch: file mode has %d items, STDIN mode has %d items", + len(fileResultRL.Items), len(stdinResultRL.Items)) + } + + // Compare each item by its string representation (after sorting, which + // ToYAML does automatically). + for i := range fileResultRL.Items { + fileItem := fileResultRL.Items[i].String() + stdinItem := stdinResultRL.Items[i].String() + if fileItem != stdinItem { + t.Fatalf("item %d mismatch:\n File mode:\n%s\n STDIN mode:\n%s", + i, fileItem, stdinItem) + } + } + }) +} + +// splitLines splits a string into lines, preserving empty lines. +func splitLines(s string) []string { + var lines []string + start := 0 + for i := 0; i < len(s); i++ { + if s[i] == '\n' { + lines = append(lines, s[start:i]) + start = i + 1 + } + } + if start < len(s) { + lines = append(lines, s[start:]) + } + return lines +} diff --git a/go/fn/run_filemode_test.go b/go/fn/run_filemode_test.go new file mode 100644 index 00000000..7906eaaf --- /dev/null +++ b/go/fn/run_filemode_test.go @@ -0,0 +1,428 @@ +// Copyright 2025 The kpt Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fn + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestFileMode_ValidInput verifies that valid input files produce correct output +// on STDOUT when processed via file mode. +// Requirements: 4.1, 4.2 +func TestFileMode_ValidInput(t *testing.T) { + tmpDir := t.TempDir() + + // Write a valid ConfigMap resource to a temp file. + configMap := `apiVersion: v1 +kind: ConfigMap +metadata: + name: my-config + namespace: default +data: + key1: value1 +` + filePath := filepath.Join(tmpDir, "configmap.yaml") + require.NoError(t, os.WriteFile(filePath, []byte(configMap), 0644)) + + // Set os.Args to simulate file mode invocation. + setArgs(t, []string{"cmd", filePath}) + + output := captureStdout(t, func() { + err := AsMain(noopProcessor) + assert.NoError(t, err, "file mode with valid input should succeed") + }) + + // Verify output is a valid ResourceList containing the ConfigMap. + assert.NotEmpty(t, output, "file mode should produce output on STDOUT") + assert.Contains(t, output, "kind: ResourceList") + assert.Contains(t, output, "my-config") + assert.Contains(t, output, "key1: value1") +} + +// TestFileMode_MultipleFiles verifies that multiple valid input files are +// combined into a single ResourceList output. +// Requirements: 4.1, 4.2 +func TestFileMode_MultipleFiles(t *testing.T) { + tmpDir := t.TempDir() + + cm1 := `apiVersion: v1 +kind: ConfigMap +metadata: + name: config-one + namespace: default +data: + foo: bar +` + cm2 := `apiVersion: v1 +kind: ConfigMap +metadata: + name: config-two + namespace: default +data: + baz: qux +` + file1 := filepath.Join(tmpDir, "cm1.yaml") + file2 := filepath.Join(tmpDir, "cm2.yaml") + require.NoError(t, os.WriteFile(file1, []byte(cm1), 0644)) + require.NoError(t, os.WriteFile(file2, []byte(cm2), 0644)) + + setArgs(t, []string{"cmd", file1, file2}) + + output := captureStdout(t, func() { + err := AsMain(noopProcessor) + assert.NoError(t, err, "file mode with multiple files should succeed") + }) + + // Both resources should appear in the output. + assert.Contains(t, output, "config-one") + assert.Contains(t, output, "config-two") + assert.Contains(t, output, "foo: bar") + assert.Contains(t, output, "baz: qux") +} + +// TestFileMode_NonExistentFile verifies that a non-existent file returns a +// descriptive error message including the file path. +// Requirements: 4.3 +func TestFileMode_NonExistentFile(t *testing.T) { + nonExistentPath := filepath.Join(t.TempDir(), "does-not-exist.yaml") + + setArgs(t, []string{"cmd", nonExistentPath}) + + err := AsMain(noopProcessor) + require.Error(t, err, "non-existent file should return an error") + assert.Contains(t, err.Error(), "file not found") + assert.Contains(t, err.Error(), nonExistentPath, "error should include the file path") +} + +// TestFileMode_InvalidYAML verifies that a file with invalid YAML returns a +// parse error. +// Requirements: 4.3 +func TestFileMode_InvalidYAML(t *testing.T) { + tmpDir := t.TempDir() + + invalidYAML := `{{{this is not valid YAML at all!!!` + filePath := filepath.Join(tmpDir, "invalid.yaml") + require.NoError(t, os.WriteFile(filePath, []byte(invalidYAML), 0644)) + + setArgs(t, []string{"cmd", filePath}) + + err := AsMain(noopProcessor) + require.Error(t, err, "invalid YAML file should return an error") + assert.Contains(t, err.Error(), filePath, "error should include the file path") + assert.Contains(t, err.Error(), "failed to parse KRM resources from") +} + +// TestFileMode_EmptyFile verifies that an empty file proceeds without error +// (valid for generators that don't require input items). +// Requirements: 4.1 +func TestFileMode_EmptyFile(t *testing.T) { + tmpDir := t.TempDir() + + // Write an empty file. + filePath := filepath.Join(tmpDir, "empty.yaml") + require.NoError(t, os.WriteFile(filePath, []byte(""), 0644)) + + setArgs(t, []string{"cmd", filePath}) + + output := captureStdout(t, func() { + err := AsMain(noopProcessor) + assert.NoError(t, err, "empty file should proceed without error") + }) + + // Output should be a valid ResourceList (possibly with no items). + assert.NotEmpty(t, output, "file mode should still produce output") + assert.Contains(t, output, "kind: ResourceList") +} + +// TestFileMode_WhitespaceOnlyFile verifies that a file containing only +// whitespace is treated as empty and proceeds without error. +// Requirements: 4.1 +func TestFileMode_WhitespaceOnlyFile(t *testing.T) { + tmpDir := t.TempDir() + + filePath := filepath.Join(tmpDir, "whitespace.yaml") + require.NoError(t, os.WriteFile(filePath, []byte(" \n\n \t \n"), 0644)) + + setArgs(t, []string{"cmd", filePath}) + + output := captureStdout(t, func() { + err := AsMain(noopProcessor) + assert.NoError(t, err, "whitespace-only file should proceed without error") + }) + + assert.Contains(t, output, "kind: ResourceList") +} + +// TestFileMode_OutputToStdout verifies that file mode output goes to STDOUT +// (not STDERR or elsewhere). +// Requirements: 4.2 +func TestFileMode_OutputToStdout(t *testing.T) { + tmpDir := t.TempDir() + + configMap := `apiVersion: v1 +kind: ConfigMap +metadata: + name: stdout-test + namespace: test +data: + hello: world +` + filePath := filepath.Join(tmpDir, "resource.yaml") + require.NoError(t, os.WriteFile(filePath, []byte(configMap), 0644)) + + setArgs(t, []string{"cmd", filePath}) + + // Capture both stdout and stderr to verify output goes to stdout only. + var stdoutOutput string + stderrOutput := captureStderr(t, func() { + stdoutOutput = captureStdout(t, func() { + err := AsMain(noopProcessor) + assert.NoError(t, err) + }) + }) + + // STDOUT should have the ResourceList output. + assert.Contains(t, stdoutOutput, "stdout-test") + assert.Contains(t, stdoutOutput, "kind: ResourceList") + + // STDERR should not contain the resource output. + assert.NotContains(t, stderrOutput, "stdout-test") +} + +// TestReadFilesAsResourceList_ValidFile tests the readFilesAsResourceList helper +// directly with a valid file. +func TestReadFilesAsResourceList_ValidFile(t *testing.T) { + tmpDir := t.TempDir() + + configMap := `apiVersion: v1 +kind: ConfigMap +metadata: + name: direct-test + namespace: default +data: + key: value +` + filePath := filepath.Join(tmpDir, "cm.yaml") + require.NoError(t, os.WriteFile(filePath, []byte(configMap), 0644)) + + rl, err := readFilesAsResourceList([]string{filePath}) + require.NoError(t, err) + require.NotNil(t, rl) + + // Should have one item. + assert.Len(t, rl.Items, 1) + assert.Equal(t, "direct-test", rl.Items[0].GetName()) + assert.Equal(t, "ConfigMap", rl.Items[0].GetKind()) + + // FunctionConfig should be set (empty KubeObject). + assert.NotNil(t, rl.FunctionConfig) +} + +// TestReadFilesAsResourceList_NonExistentFile tests the readFilesAsResourceList +// helper directly with a non-existent file. +func TestReadFilesAsResourceList_NonExistentFile(t *testing.T) { + nonExistentPath := "/tmp/definitely-does-not-exist-12345.yaml" + + rl, err := readFilesAsResourceList([]string{nonExistentPath}) + require.Error(t, err) + assert.Nil(t, rl) + assert.Contains(t, err.Error(), "file not found") + assert.Contains(t, err.Error(), nonExistentPath) +} + +// TestReadFilesAsResourceList_InvalidYAML tests the readFilesAsResourceList +// helper directly with invalid YAML content. +func TestReadFilesAsResourceList_InvalidYAML(t *testing.T) { + tmpDir := t.TempDir() + + filePath := filepath.Join(tmpDir, "bad.yaml") + require.NoError(t, os.WriteFile(filePath, []byte(`{{{not yaml`), 0644)) + + rl, err := readFilesAsResourceList([]string{filePath}) + require.Error(t, err) + assert.Nil(t, rl) + assert.Contains(t, err.Error(), "failed to parse KRM resources from") + assert.Contains(t, err.Error(), filePath) +} + +// TestReadFilesAsResourceList_EmptyFile tests the readFilesAsResourceList +// helper directly with an empty file. +func TestReadFilesAsResourceList_EmptyFile(t *testing.T) { + tmpDir := t.TempDir() + + filePath := filepath.Join(tmpDir, "empty.yaml") + require.NoError(t, os.WriteFile(filePath, []byte(""), 0644)) + + rl, err := readFilesAsResourceList([]string{filePath}) + require.NoError(t, err) + require.NotNil(t, rl) + + // Empty file should result in no items. + assert.Empty(t, rl.Items) + // FunctionConfig should still be set. + assert.NotNil(t, rl.FunctionConfig) +} + +// TestReadFilesAsResourceList_MultiDocument tests that a file with multiple +// YAML documents (separated by ---) produces multiple items. +func TestReadFilesAsResourceList_MultiDocument(t *testing.T) { + tmpDir := t.TempDir() + + multiDoc := `apiVersion: v1 +kind: ConfigMap +metadata: + name: first + namespace: default +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: second + namespace: default +` + filePath := filepath.Join(tmpDir, "multi.yaml") + require.NoError(t, os.WriteFile(filePath, []byte(multiDoc), 0644)) + + rl, err := readFilesAsResourceList([]string{filePath}) + require.NoError(t, err) + require.NotNil(t, rl) + + assert.Len(t, rl.Items, 2) + + // Verify both items are present (order may vary). + names := []string{rl.Items[0].GetName(), rl.Items[1].GetName()} + assert.Contains(t, names, "first") + assert.Contains(t, names, "second") +} + +// TestFileMode_ProcessorReceivesItems verifies that the processor actually +// receives the items from the file and can modify them. +// Requirements: 4.1, 4.2 +func TestFileMode_ProcessorReceivesItems(t *testing.T) { + tmpDir := t.TempDir() + + configMap := `apiVersion: v1 +kind: ConfigMap +metadata: + name: to-be-labeled + namespace: default +` + filePath := filepath.Join(tmpDir, "cm.yaml") + require.NoError(t, os.WriteFile(filePath, []byte(configMap), 0644)) + + // Use a processor that adds a label to all items. + labelProc := ResourceListProcessorFunc(func(rl *ResourceList) (bool, error) { + for _, item := range rl.Items { + if err := item.SetLabel("added-by", "test"); err != nil { + return false, err + } + } + return true, nil + }) + + setArgs(t, []string{"cmd", filePath}) + + output := captureStdout(t, func() { + err := AsMain(labelProc) + assert.NoError(t, err) + }) + + // Verify the label was added in the output. + assert.Contains(t, output, "added-by") + assert.Contains(t, output, "test") +} + +// TestFileMode_HelpTakesPrecedence verifies that --help takes precedence over +// file paths when both are present. +func TestFileMode_HelpTakesPrecedence(t *testing.T) { + tmpDir := t.TempDir() + + filePath := filepath.Join(tmpDir, "cm.yaml") + require.NoError(t, os.WriteFile(filePath, []byte("apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: test\n"), 0644)) + + setArgs(t, []string{"cmd", "--help", filePath}) + + output := captureStdout(t, func() { + err := AsMain(noopProcessor) + assert.NoError(t, err) + }) + + // Should show help, not process the file. + assert.Contains(t, output, "No documentation available") + assert.NotContains(t, output, "kind: ResourceList") +} + +// TestFileMode_MixedValidAndEmpty verifies that a mix of valid and empty files +// works correctly — only the valid file contributes items. +func TestFileMode_MixedValidAndEmpty(t *testing.T) { + tmpDir := t.TempDir() + + configMap := `apiVersion: v1 +kind: ConfigMap +metadata: + name: only-item + namespace: default +` + validFile := filepath.Join(tmpDir, "valid.yaml") + emptyFile := filepath.Join(tmpDir, "empty.yaml") + require.NoError(t, os.WriteFile(validFile, []byte(configMap), 0644)) + require.NoError(t, os.WriteFile(emptyFile, []byte(""), 0644)) + + setArgs(t, []string{"cmd", emptyFile, validFile}) + + output := captureStdout(t, func() { + err := AsMain(noopProcessor) + assert.NoError(t, err) + }) + + // Should contain the item from the valid file. + assert.Contains(t, output, "only-item") + // Should still be a valid ResourceList. + assert.Contains(t, output, "kind: ResourceList") + + // Verify we can parse the output. + rl, err := ParseResourceList([]byte(output)) + require.NoError(t, err) + assert.Len(t, rl.Items, 1) + assert.Equal(t, "only-item", rl.Items[0].GetName()) +} + +// TestFileMode_NonExistentAmongValid verifies that if one file in a list +// doesn't exist, the error is returned even if other files are valid. +// Requirements: 4.3 +func TestFileMode_NonExistentAmongValid(t *testing.T) { + tmpDir := t.TempDir() + + validFile := filepath.Join(tmpDir, "valid.yaml") + require.NoError(t, os.WriteFile(validFile, []byte("apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: test\n"), 0644)) + + nonExistent := filepath.Join(tmpDir, "missing.yaml") + + setArgs(t, []string{"cmd", validFile, nonExistent}) + + // Capture stderr to suppress the error log from AsMain. + captureStderr(t, func() { + err := AsMain(noopProcessor) + require.Error(t, err) + assert.Contains(t, err.Error(), "file not found") + assert.Contains(t, err.Error(), strings.TrimPrefix(nonExistent, "")) + }) +} diff --git a/go/fn/run_flags_test.go b/go/fn/run_flags_test.go new file mode 100644 index 00000000..bf3e1c63 --- /dev/null +++ b/go/fn/run_flags_test.go @@ -0,0 +1,312 @@ +// Copyright 2025 The kpt Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fn + +import ( + "bytes" + "encoding/json" + "io" + "os" + "strings" + "testing" + + "github.com/kptdev/krm-functions-sdk/go/fn/internal/docs" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// noopProcessor is a minimal ResourceListProcessor that does nothing. +// Used to satisfy AsMain's input requirement without triggering STDIN reads. +var noopProcessor = ResourceListProcessorFunc(func(rl *ResourceList) (bool, error) { + return true, nil +}) + +// captureStdout redirects os.Stdout to a pipe, runs fn, and returns what was written. +func captureStdout(t *testing.T, fn func()) string { + t.Helper() + + origStdout := os.Stdout + r, w, err := os.Pipe() + require.NoError(t, err) + os.Stdout = w + + fn() + + w.Close() + os.Stdout = origStdout + + var buf bytes.Buffer + _, err = io.Copy(&buf, r) + require.NoError(t, err) + r.Close() + + return buf.String() +} + +// captureStderr redirects os.Stderr to a pipe, runs fn, and returns what was written. +func captureStderr(t *testing.T, fn func()) string { + t.Helper() + + origStderr := os.Stderr + r, w, err := os.Pipe() + require.NoError(t, err) + os.Stderr = w + + fn() + + w.Close() + os.Stderr = origStderr + + var buf bytes.Buffer + _, err = io.Copy(&buf, r) + require.NoError(t, err) + r.Close() + + return buf.String() +} + +// setArgs temporarily sets os.Args for the duration of a test. +func setArgs(t *testing.T, args []string) { + t.Helper() + origArgs := os.Args + os.Args = args + t.Cleanup(func() { os.Args = origArgs }) +} + +// TestAsMain_HelpFlag_ExitsZero verifies that --help returns nil (exit 0) +// without reading STDIN. +// Requirements: 2.1 +func TestAsMain_HelpFlag_ExitsZero(t *testing.T) { + setArgs(t, []string{"cmd", "--help"}) + + // Close stdin to prove it's not read — if AsMain tries to read STDIN, + // it would get an error or EOF immediately. + origStdin := os.Stdin + r, w, err := os.Pipe() + require.NoError(t, err) + w.Close() // Close write end immediately — reading would get EOF + os.Stdin = r + t.Cleanup(func() { + os.Stdin = origStdin + r.Close() + }) + + output := captureStdout(t, func() { + err := AsMain(noopProcessor, WithDocs([]byte("some readme"), []byte("image: test"))) + assert.NoError(t, err, "--help should return nil (exit 0)") + }) + + // Should have produced some output + assert.NotEmpty(t, output, "--help should produce output") +} + +// TestAsMain_HelpFlag_NoDocs verifies that --help with no WithDocs prints +// a minimal message. +// Requirements: 2.3 +func TestAsMain_HelpFlag_NoDocs(t *testing.T) { + setArgs(t, []string{"cmd", "--help"}) + + output := captureStdout(t, func() { + err := AsMain(noopProcessor) + assert.NoError(t, err, "--help with no docs should return nil") + }) + + assert.Contains(t, output, "No documentation available") + assert.Contains(t, output, "fn.WithDocs") +} + +// TestAsMain_HelpFlag_WithDocs verifies that --help with WithDocs renders +// the README sections. +// Requirements: 2.2 +func TestAsMain_HelpFlag_WithDocs(t *testing.T) { + setArgs(t, []string{"cmd", "--help"}) + + readme := []byte(` +Set labels on resources + + + +The set-labels function adds labels to all resources. + + + + kpt fn eval --image set-labels:v0.1 + +`) + meta := []byte(`image: gcr.io/kpt-fn/set-labels:v0.1 +description: Set labels on all resources +`) + + output := captureStdout(t, func() { + err := AsMain(noopProcessor, WithDocs(readme, meta)) + assert.NoError(t, err) + }) + + assert.Contains(t, output, "Set labels on resources") + assert.Contains(t, output, "set-labels function adds labels") + assert.Contains(t, output, "kpt fn eval") +} + +// TestAsMain_DocFlag_OutputsValidJSON verifies that --doc outputs valid JSON +// and returns nil (exit 0). +// Requirements: 3.1 +func TestAsMain_DocFlag_OutputsValidJSON(t *testing.T) { + setArgs(t, []string{"cmd", "--doc"}) + + readme := []byte(` +Set labels + + + +Long description here. + +`) + meta := []byte(`image: gcr.io/kpt-fn/set-labels:v0.1 +description: Set labels on all resources +tags: + - mutator +license: Apache-2.0 +`) + + output := captureStdout(t, func() { + err := AsMain(noopProcessor, WithDocs(readme, meta)) + assert.NoError(t, err, "--doc should return nil (exit 0)") + }) + + // Verify it's valid JSON + var docOutput docs.DocOutput + err := json.Unmarshal([]byte(output), &docOutput) + require.NoError(t, err, "--doc output should be valid JSON") + + // Verify fields are populated + assert.Equal(t, "Set labels", docOutput.Short) + assert.Equal(t, "Long description here.", docOutput.Long) + assert.Equal(t, "gcr.io/kpt-fn/set-labels:v0.1", docOutput.Image) + assert.Equal(t, "Set labels on all resources", docOutput.Description) + assert.Equal(t, []string{"mutator"}, docOutput.Tags) + assert.Equal(t, "Apache-2.0", docOutput.License) +} + +// TestAsMain_DocFlag_NoDocs verifies that --doc with no WithDocs outputs `{}`. +// Requirements: 3.3 +func TestAsMain_DocFlag_NoDocs(t *testing.T) { + setArgs(t, []string{"cmd", "--doc"}) + + output := captureStdout(t, func() { + err := AsMain(noopProcessor) + assert.NoError(t, err, "--doc with no docs should return nil") + }) + + assert.Equal(t, "{}", strings.TrimSpace(output)) +} + +// TestAsMain_DocFlag_HiddenField verifies that hidden:true in metadata +// propagates to the --doc JSON output. +// Requirements: 3.6 +func TestAsMain_DocFlag_HiddenField(t *testing.T) { + setArgs(t, []string{"cmd", "--doc"}) + + readme := []byte(` +Hidden function + +`) + meta := []byte(`image: gcr.io/kpt-fn/hidden-fn:v0.1 +description: A hidden function +hidden: true +`) + + output := captureStdout(t, func() { + err := AsMain(noopProcessor, WithDocs(readme, meta)) + assert.NoError(t, err) + }) + + var docOutput docs.DocOutput + err := json.Unmarshal([]byte(output), &docOutput) + require.NoError(t, err, "--doc output should be valid JSON") + + assert.True(t, docOutput.Hidden, "hidden:true should propagate to JSON output") + assert.Equal(t, "gcr.io/kpt-fn/hidden-fn:v0.1", docOutput.Image) +} + +// TestAsMain_DocFlag_InvalidMetadataYAML verifies that invalid metadata YAML +// logs a warning and continues with zero-value metadata (only README fields in output). +// Requirements: 5.5, 3.6 +func TestAsMain_DocFlag_InvalidMetadataYAML(t *testing.T) { + setArgs(t, []string{"cmd", "--doc"}) + + readme := []byte(` +My function + +`) + invalidMeta := []byte(`{{{not valid yaml at all!!!`) + + var stdoutOutput string + stderrOutput := captureStderr(t, func() { + stdoutOutput = captureStdout(t, func() { + err := AsMain(noopProcessor, WithDocs(readme, invalidMeta)) + assert.NoError(t, err, "invalid metadata should not cause AsMain to fail") + }) + }) + + // Verify warning was logged to stderr + assert.Contains(t, stderrOutput, "warning") + assert.Contains(t, stderrOutput, "invalid metadata YAML") + + // Verify JSON output still contains README fields + var docOutput docs.DocOutput + err := json.Unmarshal([]byte(stdoutOutput), &docOutput) + require.NoError(t, err, "--doc output should still be valid JSON") + + assert.Equal(t, "My function", docOutput.Short) + // Metadata fields should be zero-value + assert.Empty(t, docOutput.Image) + assert.Empty(t, docOutput.Description) + assert.Empty(t, docOutput.Tags) + assert.False(t, docOutput.Hidden) +} + +// TestAsMain_HelpFlag_InvalidMetadataYAML verifies that --help with invalid +// metadata YAML logs a warning and continues rendering help from README only. +// Requirements: 5.5 +func TestAsMain_HelpFlag_InvalidMetadataYAML(t *testing.T) { + setArgs(t, []string{"cmd", "--help"}) + + readme := []byte(` +My function short desc + + + +Detailed description of the function. + +`) + // Use YAML that actually fails to parse (unclosed flow mapping) + invalidMeta := []byte(`{{{not valid yaml at all!!!`) + + var stdoutOutput string + stderrOutput := captureStderr(t, func() { + stdoutOutput = captureStdout(t, func() { + err := AsMain(noopProcessor, WithDocs(readme, invalidMeta)) + assert.NoError(t, err, "invalid metadata should not cause --help to fail") + }) + }) + + // Verify warning was logged + assert.Contains(t, stderrOutput, "warning") + assert.Contains(t, stderrOutput, "invalid metadata YAML") + + // Verify help output still contains README sections + assert.Contains(t, stdoutOutput, "My function short desc") + assert.Contains(t, stdoutOutput, "Detailed description of the function") +} From 0ed000e9c9482bed9992a67135e9e82e5b92cdf5 Mon Sep 17 00:00:00 2001 From: Fiachra Corcoran Date: Thu, 7 May 2026 09:44:27 +0100 Subject: [PATCH 2/8] chore: apply go fix modernization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mechanical changes from 'go fix ./...': - interface{} → any (Go 1.18+ type alias) - reflect.Ptr → reflect.Pointer (deprecated constant) - strings.Index+slice → strings.Cut (Go 1.18+ idiom) Signed-off-by: Fiachra Corcoran --- go/fn/internal/docs/markers.go | 6 +++--- go/fn/internal/docs/metadata_test.go | 2 +- go/fn/internal/docs/render_test.go | 4 ++-- go/fn/internal/test/variant_test.go | 2 +- go/fn/run_filemode_property_test.go | 22 ++++++++++++---------- 5 files changed, 19 insertions(+), 17 deletions(-) diff --git a/go/fn/internal/docs/markers.go b/go/fn/internal/docs/markers.go index 0c6eab24..e4586ab0 100644 --- a/go/fn/internal/docs/markers.go +++ b/go/fn/internal/docs/markers.go @@ -65,12 +65,12 @@ func extractSection(content, startMarker string) string { afterStart := startIdx + len(startMarker) remaining := content[afterStart:] - endIdx := strings.Index(remaining, markerEnd) - if endIdx < 0 { + before, _, ok := strings.Cut(remaining, markerEnd) + if !ok { return "" } - return strings.TrimSpace(remaining[:endIdx]) + return strings.TrimSpace(before) } // hasAnyMarker reports whether the content contains any mdtogo marker. diff --git a/go/fn/internal/docs/metadata_test.go b/go/fn/internal/docs/metadata_test.go index 07a8e121..6f4aaf70 100644 --- a/go/fn/internal/docs/metadata_test.go +++ b/go/fn/internal/docs/metadata_test.go @@ -18,8 +18,8 @@ import ( "reflect" "testing" - "pgregory.net/rapid" "go.yaml.in/yaml/v3" + "pgregory.net/rapid" ) // Feature: sdk-alignment, Property 2: Metadata YAML round-trip diff --git a/go/fn/internal/docs/render_test.go b/go/fn/internal/docs/render_test.go index 4631a90e..a7599086 100644 --- a/go/fn/internal/docs/render_test.go +++ b/go/fn/internal/docs/render_test.go @@ -400,7 +400,7 @@ func TestRenderDoc_EmptyInputs(t *testing.T) { } // Verify output is valid JSON. - var raw map[string]interface{} + var raw map[string]any if err := json.Unmarshal(buf.Bytes(), &raw); err != nil { t.Fatalf("output is not valid JSON: %v\nRaw: %s", err, buf.String()) } @@ -443,7 +443,7 @@ func TestRenderDoc_HiddenFieldSerialization(t *testing.T) { } // Verify the raw JSON contains "hidden": true. - var raw map[string]interface{} + var raw map[string]any if err := json.Unmarshal(buf.Bytes(), &raw); err != nil { t.Fatalf("output is not valid JSON: %v\nRaw: %s", err, buf.String()) } diff --git a/go/fn/internal/test/variant_test.go b/go/fn/internal/test/variant_test.go index 65ded0ad..0b0e1058 100644 --- a/go/fn/internal/test/variant_test.go +++ b/go/fn/internal/test/variant_test.go @@ -264,7 +264,7 @@ func TestBadNewFromTypedObject(t *testing.T) { type Foo struct { metav1.TypeMeta `json:",inline" yaml:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"` + metav1.ObjectMeta `json:"metadata" yaml:"metadata,omitempty"` DesiredReplicas int `json:"desiredReplicas,omitempty" yaml:"desiredReplicas,omitempty"` } diff --git a/go/fn/run_filemode_property_test.go b/go/fn/run_filemode_property_test.go index 5439c043..2893191c 100644 --- a/go/fn/run_filemode_property_test.go +++ b/go/fn/run_filemode_property_test.go @@ -18,6 +18,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "testing" "pgregory.net/rapid" @@ -41,11 +42,11 @@ func genKRMResource() *rapid.Generator[string] { // Generate 1-3 data entries with YAML-safe values (alphanumeric only, // no special characters that could be misinterpreted by the YAML parser). numEntries := rapid.IntRange(1, 3).Draw(t, "numEntries") - dataLines := "" - for i := 0; i < numEntries; i++ { + var dataLines strings.Builder + for i := range numEntries { key := rapid.StringMatching(`[a-z][a-z0-9]{1,8}`).Draw(t, fmt.Sprintf("key%d", i)) value := rapid.StringMatching(`[a-zA-Z0-9]{1,15}`).Draw(t, fmt.Sprintf("value%d", i)) - dataLines += fmt.Sprintf(" %s: %s\n", key, value) + dataLines.WriteString(fmt.Sprintf(" %s: %s\n", key, value)) } return fmt.Sprintf(`apiVersion: v1 kind: ConfigMap @@ -53,7 +54,7 @@ metadata: name: %s namespace: %s data: -%s`, name, namespace, dataLines) +%s`, name, namespace, dataLines.String()) }) } @@ -104,24 +105,25 @@ func TestProperty6_FileModeEquivalence(t *testing.T) { // --- STDIN mode path --- // Assemble the same resources into a ResourceList YAML (as STDIN would provide). - stdinInput := "apiVersion: config.kubernetes.io/v1\nkind: ResourceList\nitems:\n" + var stdinInput strings.Builder + stdinInput.WriteString("apiVersion: config.kubernetes.io/v1\nkind: ResourceList\nitems:\n") for _, res := range resources { // Indent each resource line under items as a YAML list element. - stdinInput += "- " + stdinInput.WriteString("- ") first := true for _, line := range splitLines(res) { if first { - stdinInput += line + "\n" + stdinInput.WriteString(line + "\n") first = false } else { - stdinInput += " " + line + "\n" + stdinInput.WriteString(" " + line + "\n") } } } - stdinOutput, err := Run(noopProc, []byte(stdinInput)) + stdinOutput, err := Run(noopProc, []byte(stdinInput.String())) if err != nil { - t.Fatalf("STDIN mode Run failed: %v\n Input:\n%s", err, stdinInput) + t.Fatalf("STDIN mode Run failed: %v\n Input:\n%s", err, stdinInput.String()) } // --- Compare outputs --- From bf78605aceb0562035b06e84e6af2813236ad9b1 Mon Sep 17 00:00:00 2001 From: Fiachra Corcoran Date: Thu, 7 May 2026 10:16:58 +0100 Subject: [PATCH 3/8] chore: gitignore rapid testdata failure files Signed-off-by: Fiachra Corcoran --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 1dc16f55..bd60a666 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ ts/create-kpt-functions/bin *.swo *.swp +# rapid property-based testing failure reproductions +**/testdata/rapid/ + From 035fc21d8cd8063ef9fe59b6fa76de8b3fd5d100 Mon Sep 17 00:00:00 2001 From: Fiachra Corcoran Date: Thu, 7 May 2026 13:58:08 +0100 Subject: [PATCH 4/8] =?UTF-8?q?fix:=20CI=20drift=20=E2=80=94=20use=20slice?= =?UTF-8?q?s.Contains,=20fix=20file=20permissions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace manual loops with slices.Contains for --help/--doc checks (matches what 'go fix' produces) - Change test file permissions from 0644 to 0600 (gosec G306) Signed-off-by: Fiachra Corcoran --- go/fn/run_filemode_property_test.go | 2 +- go/fn/run_filemode_test.go | 32 ++++++++++++++--------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/go/fn/run_filemode_property_test.go b/go/fn/run_filemode_property_test.go index 2893191c..1eefda76 100644 --- a/go/fn/run_filemode_property_test.go +++ b/go/fn/run_filemode_property_test.go @@ -78,7 +78,7 @@ func TestProperty6_FileModeEquivalence(t *testing.T) { var filePaths []string for i, res := range resources { path := filepath.Join(tmpDir, fmt.Sprintf("resource-%d.yaml", i)) - if err := os.WriteFile(path, []byte(res), 0644); err != nil { + if err := os.WriteFile(path, []byte(res), 0600); err != nil { t.Fatalf("failed to write temp file: %v", err) } filePaths = append(filePaths, path) diff --git a/go/fn/run_filemode_test.go b/go/fn/run_filemode_test.go index 7906eaaf..3994e851 100644 --- a/go/fn/run_filemode_test.go +++ b/go/fn/run_filemode_test.go @@ -40,7 +40,7 @@ data: key1: value1 ` filePath := filepath.Join(tmpDir, "configmap.yaml") - require.NoError(t, os.WriteFile(filePath, []byte(configMap), 0644)) + require.NoError(t, os.WriteFile(filePath, []byte(configMap), 0600)) // Set os.Args to simulate file mode invocation. setArgs(t, []string{"cmd", filePath}) @@ -81,8 +81,8 @@ data: ` file1 := filepath.Join(tmpDir, "cm1.yaml") file2 := filepath.Join(tmpDir, "cm2.yaml") - require.NoError(t, os.WriteFile(file1, []byte(cm1), 0644)) - require.NoError(t, os.WriteFile(file2, []byte(cm2), 0644)) + require.NoError(t, os.WriteFile(file1, []byte(cm1), 0600)) + require.NoError(t, os.WriteFile(file2, []byte(cm2), 0600)) setArgs(t, []string{"cmd", file1, file2}) @@ -120,7 +120,7 @@ func TestFileMode_InvalidYAML(t *testing.T) { invalidYAML := `{{{this is not valid YAML at all!!!` filePath := filepath.Join(tmpDir, "invalid.yaml") - require.NoError(t, os.WriteFile(filePath, []byte(invalidYAML), 0644)) + require.NoError(t, os.WriteFile(filePath, []byte(invalidYAML), 0600)) setArgs(t, []string{"cmd", filePath}) @@ -138,7 +138,7 @@ func TestFileMode_EmptyFile(t *testing.T) { // Write an empty file. filePath := filepath.Join(tmpDir, "empty.yaml") - require.NoError(t, os.WriteFile(filePath, []byte(""), 0644)) + require.NoError(t, os.WriteFile(filePath, []byte(""), 0600)) setArgs(t, []string{"cmd", filePath}) @@ -159,7 +159,7 @@ func TestFileMode_WhitespaceOnlyFile(t *testing.T) { tmpDir := t.TempDir() filePath := filepath.Join(tmpDir, "whitespace.yaml") - require.NoError(t, os.WriteFile(filePath, []byte(" \n\n \t \n"), 0644)) + require.NoError(t, os.WriteFile(filePath, []byte(" \n\n \t \n"), 0600)) setArgs(t, []string{"cmd", filePath}) @@ -186,7 +186,7 @@ data: hello: world ` filePath := filepath.Join(tmpDir, "resource.yaml") - require.NoError(t, os.WriteFile(filePath, []byte(configMap), 0644)) + require.NoError(t, os.WriteFile(filePath, []byte(configMap), 0600)) setArgs(t, []string{"cmd", filePath}) @@ -221,7 +221,7 @@ data: key: value ` filePath := filepath.Join(tmpDir, "cm.yaml") - require.NoError(t, os.WriteFile(filePath, []byte(configMap), 0644)) + require.NoError(t, os.WriteFile(filePath, []byte(configMap), 0600)) rl, err := readFilesAsResourceList([]string{filePath}) require.NoError(t, err) @@ -254,7 +254,7 @@ func TestReadFilesAsResourceList_InvalidYAML(t *testing.T) { tmpDir := t.TempDir() filePath := filepath.Join(tmpDir, "bad.yaml") - require.NoError(t, os.WriteFile(filePath, []byte(`{{{not yaml`), 0644)) + require.NoError(t, os.WriteFile(filePath, []byte(`{{{not yaml`), 0600)) rl, err := readFilesAsResourceList([]string{filePath}) require.Error(t, err) @@ -269,7 +269,7 @@ func TestReadFilesAsResourceList_EmptyFile(t *testing.T) { tmpDir := t.TempDir() filePath := filepath.Join(tmpDir, "empty.yaml") - require.NoError(t, os.WriteFile(filePath, []byte(""), 0644)) + require.NoError(t, os.WriteFile(filePath, []byte(""), 0600)) rl, err := readFilesAsResourceList([]string{filePath}) require.NoError(t, err) @@ -299,7 +299,7 @@ metadata: namespace: default ` filePath := filepath.Join(tmpDir, "multi.yaml") - require.NoError(t, os.WriteFile(filePath, []byte(multiDoc), 0644)) + require.NoError(t, os.WriteFile(filePath, []byte(multiDoc), 0600)) rl, err := readFilesAsResourceList([]string{filePath}) require.NoError(t, err) @@ -326,7 +326,7 @@ metadata: namespace: default ` filePath := filepath.Join(tmpDir, "cm.yaml") - require.NoError(t, os.WriteFile(filePath, []byte(configMap), 0644)) + require.NoError(t, os.WriteFile(filePath, []byte(configMap), 0600)) // Use a processor that adds a label to all items. labelProc := ResourceListProcessorFunc(func(rl *ResourceList) (bool, error) { @@ -356,7 +356,7 @@ func TestFileMode_HelpTakesPrecedence(t *testing.T) { tmpDir := t.TempDir() filePath := filepath.Join(tmpDir, "cm.yaml") - require.NoError(t, os.WriteFile(filePath, []byte("apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: test\n"), 0644)) + require.NoError(t, os.WriteFile(filePath, []byte("apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: test\n"), 0600)) setArgs(t, []string{"cmd", "--help", filePath}) @@ -383,8 +383,8 @@ metadata: ` validFile := filepath.Join(tmpDir, "valid.yaml") emptyFile := filepath.Join(tmpDir, "empty.yaml") - require.NoError(t, os.WriteFile(validFile, []byte(configMap), 0644)) - require.NoError(t, os.WriteFile(emptyFile, []byte(""), 0644)) + require.NoError(t, os.WriteFile(validFile, []byte(configMap), 0600)) + require.NoError(t, os.WriteFile(emptyFile, []byte(""), 0600)) setArgs(t, []string{"cmd", emptyFile, validFile}) @@ -412,7 +412,7 @@ func TestFileMode_NonExistentAmongValid(t *testing.T) { tmpDir := t.TempDir() validFile := filepath.Join(tmpDir, "valid.yaml") - require.NoError(t, os.WriteFile(validFile, []byte("apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: test\n"), 0644)) + require.NoError(t, os.WriteFile(validFile, []byte("apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: test\n"), 0600)) nonExistent := filepath.Join(tmpDir, "missing.yaml") From cae380b1e3dc6eb33fc7d41e27250f967ad24b72 Mon Sep 17 00:00:00 2001 From: Fiachra Corcoran Date: Thu, 7 May 2026 18:53:49 +0100 Subject: [PATCH 5/8] fix: use <- All existing tests continue tomdtogo--> as end marker (matches catalog READMEs) The catalog functions use <- All existing tests continue tomdtogo--> (bare) as the section end marker, not <- All existing tests continue tomdtogo:End-->. Updated the parser and all tests to match the real-world README format. Signed-off-by: Fiachra Corcoran --- go/fn/internal/docs/markers.go | 4 ++-- go/fn/internal/docs/markers_test.go | 6 +++--- go/fn/internal/docs/render_test.go | 12 ++++++------ go/fn/run_flags_test.go | 18 +++++++++--------- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/go/fn/internal/docs/markers.go b/go/fn/internal/docs/markers.go index e4586ab0..4bcb8bea 100644 --- a/go/fn/internal/docs/markers.go +++ b/go/fn/internal/docs/markers.go @@ -27,7 +27,7 @@ const ( markerShort = "" markerLong = "" markerExamples = "" - markerEnd = "" + markerEnd = "" ) // ParseMarkers extracts mdtogo marker sections from README content. @@ -56,7 +56,7 @@ func ParseMarkers(readme []byte) Sections { } // extractSection finds text between the given start marker and the next -// marker. Returns empty string if either marker is missing. +// end marker. Returns empty string if either marker is missing. func extractSection(content, startMarker string) string { startIdx := strings.Index(content, startMarker) if startIdx < 0 { diff --git a/go/fn/internal/docs/markers_test.go b/go/fn/internal/docs/markers_test.go index bc830748..a3896223 100644 --- a/go/fn/internal/docs/markers_test.go +++ b/go/fn/internal/docs/markers_test.go @@ -46,15 +46,15 @@ func genSectionContent() *rapid.Generator[string] { func formatMarkedREADME(short, long, examples string) string { return fmt.Sprintf(` %s - + %s - + %s - + `, short, long, examples) } diff --git a/go/fn/internal/docs/render_test.go b/go/fn/internal/docs/render_test.go index a7599086..7aa793b1 100644 --- a/go/fn/internal/docs/render_test.go +++ b/go/fn/internal/docs/render_test.go @@ -131,15 +131,15 @@ func TestProperty5_DocJSONContainsAllRequiredFields(t *testing.T) { // Format a README with valid mdtogo markers. readme := fmt.Sprintf(` %s - + %s - + %s - + `, short, long, examples) // Parse the README to get sections. @@ -226,15 +226,15 @@ func TestProperty3_HelpOutputContainsParsedSections(t *testing.T) { // Format a README with valid mdtogo markers. readme := fmt.Sprintf(` %s - + %s - + %s - + `, short, long, examples) // Parse the README to get sections (same as runtime would). diff --git a/go/fn/run_flags_test.go b/go/fn/run_flags_test.go index bf3e1c63..85e02a61 100644 --- a/go/fn/run_flags_test.go +++ b/go/fn/run_flags_test.go @@ -135,15 +135,15 @@ func TestAsMain_HelpFlag_WithDocs(t *testing.T) { readme := []byte(` Set labels on resources - + The set-labels function adds labels to all resources. - + kpt fn eval --image set-labels:v0.1 - + `) meta := []byte(`image: gcr.io/kpt-fn/set-labels:v0.1 description: Set labels on all resources @@ -167,11 +167,11 @@ func TestAsMain_DocFlag_OutputsValidJSON(t *testing.T) { readme := []byte(` Set labels - + Long description here. - + `) meta := []byte(`image: gcr.io/kpt-fn/set-labels:v0.1 description: Set labels on all resources @@ -220,7 +220,7 @@ func TestAsMain_DocFlag_HiddenField(t *testing.T) { readme := []byte(` Hidden function - + `) meta := []byte(`image: gcr.io/kpt-fn/hidden-fn:v0.1 description: A hidden function @@ -248,7 +248,7 @@ func TestAsMain_DocFlag_InvalidMetadataYAML(t *testing.T) { readme := []byte(` My function - + `) invalidMeta := []byte(`{{{not valid yaml at all!!!`) @@ -285,11 +285,11 @@ func TestAsMain_HelpFlag_InvalidMetadataYAML(t *testing.T) { readme := []byte(` My function short desc - + Detailed description of the function. - + `) // Use YAML that actually fails to parse (unclosed flow mapping) invalidMeta := []byte(`{{{not valid yaml at all!!!`) From c53c550a08ac8e1797007ce76c2311a2821cc753 Mon Sep 17 00:00:00 2001 From: Fiachra Corcoran Date: Mon, 25 May 2026 13:23:26 +0100 Subject: [PATCH 6/8] fix: skip single-dash flags in file mode arg parsing Go test runner passes -test.* flags via os.Args. The file mode arg parser must skip all flag-like arguments (starting with -), not just those starting with --. Signed-off-by: Fiachra Corcoran --- go/fn/run.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/go/fn/run.go b/go/fn/run.go index b2100199..0f3cfd7d 100644 --- a/go/fn/run.go +++ b/go/fn/run.go @@ -75,11 +75,13 @@ func AsMain(input any, opts ...Option) error { } // Collect non-flag positional arguments (file paths). + // Skip any argument that looks like a flag (starts with "-" or "--"). var filePaths []string for _, arg := range os.Args[1:] { - if !strings.HasPrefix(arg, "--") { - filePaths = append(filePaths, arg) + if strings.HasPrefix(arg, "-") { + continue } + filePaths = append(filePaths, arg) } err := func() error { From 293bc1fdaae6a8bab8dc93b61248a3d9bcd7eefb Mon Sep 17 00:00:00 2001 From: Fiachra Corcoran Date: Thu, 7 May 2026 23:08:47 +0100 Subject: [PATCH 7/8] docs: SDK documentation cleanup and examples fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Delete legacy docs/ directory (TypeScript API docs, Jekyll artifacts) - Add docs/tutorial.md — end-to-end function development workflow - Add docs/interfaces.md — Runner vs ResourceListProcessor guide - Add docs/testing.md — golden test patterns with testhelpers - Add docs/containerizing.md — Dockerfile patterns and local testing - Add CONTRIBUTING.md aligned with kpt/catalog conventions - Rewrite README.md with better flow and accurate descriptions - Fix go/get-started/ to use //go:embed + fn.WithDocs - Fix go/fn/examples/ go.mod - Rename testdata/test1/ to testdata/noop-passthrough/ - Update gcr.io/kpt-fn/ references to ghcr.io/kptdev/krm-functions-catalog/ - Add replace directive comments in go.mod files - Update .gitignore for compiled binary Signed-off-by: Fiachra Corcoran --- .gitignore | 3 + CONTRIBUTING.md | 126 ++ README.md | 96 +- docs/.nojekyll | 2 - docs/api/.nojekyll | 1 - docs/api/assets/highlight.css | 71 - docs/api/assets/icons.css | 1043 ------------- docs/api/assets/icons.png | Bin 9615 -> 0 bytes docs/api/assets/icons@2x.png | Bin 28144 -> 0 bytes docs/api/assets/main.js | 52 - docs/api/assets/search.js | 1 - docs/api/assets/style.css | 1384 ----------------- docs/api/assets/widgets.png | Bin 480 -> 0 bytes docs/api/assets/widgets@2x.png | Bin 855 -> 0 bytes docs/api/classes/Configs.html | 79 - docs/api/classes/FunctionConfigError.html | 3 - docs/api/classes/ResourceList.html | 8 - docs/api/classes/TestRunner.html | 38 - docs/api/index.html | 64 - docs/api/interfaces/FieldInfo.html | 3 - docs/api/interfaces/JsonArray.html | 3 - docs/api/interfaces/JsonMap.html | 3 - docs/api/interfaces/KptFunc.html | 11 - docs/api/interfaces/KubernetesObject.html | 3 - docs/api/interfaces/Result.html | 9 - docs/containerizing.md | 136 ++ docs/index.html | 5 - docs/interfaces.md | 178 +++ docs/testing.md | 217 +++ docs/tutorial.md | 229 +++ go/fn/examples/go.mod | 17 +- go/fn/examples/go.sum | 57 +- go/fn/internal/docs/metadata_test.go | 18 +- go/fn/internal/docs/render_test.go | 8 +- go/fn/run_flags_test.go | 10 +- go/get-started/go.mod | 4 +- go/get-started/go.sum | 2 + go/get-started/main.go | 11 +- go/get-started/metadata.yaml | 8 + .../_expected.yaml | 0 .../_fnconfig.yaml | 0 .../resources.yaml | 0 42 files changed, 1066 insertions(+), 2837 deletions(-) create mode 100644 CONTRIBUTING.md delete mode 100644 docs/.nojekyll delete mode 100644 docs/api/.nojekyll delete mode 100644 docs/api/assets/highlight.css delete mode 100644 docs/api/assets/icons.css delete mode 100644 docs/api/assets/icons.png delete mode 100644 docs/api/assets/icons@2x.png delete mode 100644 docs/api/assets/main.js delete mode 100644 docs/api/assets/search.js delete mode 100644 docs/api/assets/style.css delete mode 100644 docs/api/assets/widgets.png delete mode 100644 docs/api/assets/widgets@2x.png delete mode 100644 docs/api/classes/Configs.html delete mode 100644 docs/api/classes/FunctionConfigError.html delete mode 100644 docs/api/classes/ResourceList.html delete mode 100644 docs/api/classes/TestRunner.html delete mode 100644 docs/api/index.html delete mode 100644 docs/api/interfaces/FieldInfo.html delete mode 100644 docs/api/interfaces/JsonArray.html delete mode 100644 docs/api/interfaces/JsonMap.html delete mode 100644 docs/api/interfaces/KptFunc.html delete mode 100644 docs/api/interfaces/KubernetesObject.html delete mode 100644 docs/api/interfaces/Result.html create mode 100644 docs/containerizing.md delete mode 100644 docs/index.html create mode 100644 docs/interfaces.md create mode 100644 docs/testing.md create mode 100644 docs/tutorial.md create mode 100644 go/get-started/metadata.yaml rename go/get-started/testdata/{test1 => noop-passthrough}/_expected.yaml (100%) rename go/get-started/testdata/{test1 => noop-passthrough}/_fnconfig.yaml (100%) rename go/get-started/testdata/{test1 => noop-passthrough}/resources.yaml (100%) diff --git a/.gitignore b/.gitignore index bd60a666..b3971374 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ ts/create-kpt-functions/bin # rapid property-based testing failure reproductions **/testdata/rapid/ +# compiled binaries +go/get-started/get-started + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..6a6ad0cb --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,126 @@ +# Contributing to krm-functions-sdk + +We'd love to accept your contributions to this project. There are just a few +small guidelines you need to follow. + +## Developer Certificate of Origin (DCO) + +Contributors to this project should state that they agree with the terms published +at https://developercertificate.org/ for their contribution. To do this when +creating a commit with the Git CLI, a sign-off can be added with +[the -s option](https://git-scm.com/docs/git-commit#git-commit--s). The sign-off +is stored as part of the commit message itself. + +## Copyright notices + +All files should have the copyright notice. +``` +// Copyright 2026 The kpt Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +``` + +If the file has never been modified: use the creation year only + +* Example: `Copyright 2026 The kpt Authors` + +If the file has been modified: use a year range from creation to last modification + +* Example: `Copyright 2024-2026 The kpt Authors` + +## Building and Testing + +The SDK uses a Makefile-based workflow. From the repository root: + +```bash +# Run all checks (fix, vet, fmt, test, lint) +make go + +# Run only tests +cd go && make test + +# Run only linting +cd go && make lint + +# Tidy all go.mod files +make tidy +``` + +The CI script (`hack/ci-validate-go.sh`) runs `make go` and then checks that no +files were modified. If CI fails with "files are not to date", run `make go` +locally and commit the changes. + +## Code reviews + +All submissions, including submissions by project members, require review. We +use GitHub pull requests for this purpose. Consult [GitHub Help] for more +information on using pull requests. + +Process for code reviews. Before requesting human review, a PR must: + +* All tests passing +* All linting passing +* Meeting project code quality requirements, including passing all configured + static analysis and not reducing automated test coverage +* The comments from the first run of automatically generated comments (AI + generated comments, bot generated comments, etc.) of the PR are addressed + (addressing further re-runs of AI are optional) +* If it is not possible to resolve an automatic comment, please add a sub-comment + indicating why the automated comment cannot be resolved or ask for help in + resolving the comment +* The PR description states whether AI was used to help create the PR; if so, it + lists the AI tools used and the areas where they were used + +## Declare any use of AI + +> In addition to the above, the use of AI in the creation of PRs is allowed, but +> you must declare any use of AI and you must be able to explain the PR code +> independently of any AI tools. + +Update the PR description to state whether you used AI to help you create this +PR; if so, list the AI tools you have used and in what areas. + +For example: +```text +I have used AI in the creation of this PR. + +I have used the following AI tools: +- GitHub Copilot to analyse the code +- Kiro to generate the implementation and tests +``` + +### Attribute AI in the Git commit messages + +Following the [guidance of the Linux kernel](https://docs.kernel.org/process/coding-assistants.html#attribution) +we recommend the attribution of AI tools in the commit messages using the following format: + +```text +Assisted-by: AGENT_NAME:MODEL_VERSION [TOOL1] [TOOL2] +``` + +## Community Guidelines + +This project follows a [Code of Conduct]. + +## Community Discussion Groups + +1. Join our [Slack channel](https://kubernetes.slack.com/channels/kpt) +1. Join our [Discussions](https://github.com/kptdev/kpt/discussions) + +## Governance + +The governance of the kpt project is described in the +[governance repo](https://github.com/kptdev/governance). + +[GitHub Help]: https://help.github.com/articles/about-pull-requests/ +[Code of Conduct]: code-of-conduct.md diff --git a/README.md b/README.md index 15508b16..fcc91f61 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,111 @@ # KRM Functions SDK + [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fkptdev%2Fkrm-functions-sdk.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fkptdev%2Fkrm-functions-sdk?ref=badge_shield) [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/10658/badge)](https://www.bestpractices.dev/projects/10658) -An opinionated Go SDK for implementing KRM functions. +An opinionated Go SDK for implementing [KRM functions](https://kpt.dev/book/05-developing-functions/). -## Documentation +## Quick Start -[Documentation](https://kpt.dev/book/05-developing-functions/#developing-in-go) +A KRM function is a program that reads Kubernetes resources from STDIN, transforms or validates them, and writes the result to STDOUT. The SDK handles the I/O — you write the logic. -## Issues +```go +package main + +import ( + "context" + _ "embed" + "os" + + "github.com/kptdev/krm-functions-sdk/go/fn" +) + +//go:embed README.md +var readme []byte + +//go:embed metadata.yaml +var metadata []byte + +type SetLabels struct { + Labels map[string]string `json:"labels,omitempty"` +} + +func (r *SetLabels) Run(ctx *fn.Context, functionConfig *fn.KubeObject, items fn.KubeObjects, results *fn.Results) bool { + for _, obj := range items { + for k, v := range r.Labels { + obj.SetLabel(k, v) + } + } + return true +} + +func main() { + runner := fn.WithContext(context.Background(), &SetLabels{}) + if err := fn.AsMain(runner, fn.WithDocs(readme, metadata)); err != nil { + os.Exit(1) + } +} +``` + +A starter template is available at [`go/get-started/`](go/get-started/main.go). For the full walkthrough, see the [Tutorial](docs/tutorial.md). + +## How It Works -Please [Open Issues](https://github.com/kptdev/kpt/issues) for this repo at [kptdev/kpt](https://github.com/kptdev/kpt/). +`fn.AsMain` is the single entrypoint. It handles: -## Pull requests +- **STDIN/STDOUT** (default) — reads a ResourceList, processes it, writes the result +- **File mode** — pass file paths as positional args for local debugging +- **`--help`** — prints human-readable docs from embedded README markers +- **`--doc`** — outputs machine-readable JSON (consumed by `kpt fn doc` and catalog pipelines) -Open pull requests [here](https://github.com/kptdev/krm-functions-sdk/pulls). +Register embedded documentation with `fn.WithDocs`: -## Discussions +```go +fn.AsMain(runner, fn.WithDocs(readme, metadata)) +``` -Discussions are [here](https://github.com/kptdev/kpt/discussions). +The SDK provides two interfaces for implementing functions: + +| Interface | Use for | Can add/remove items? | Auto-parses config? | +|---|---|---|---| +| `fn.Runner` | Transformers, validators | No | Yes | +| `fn.ResourceListProcessor` | Generators, complex functions | Yes | No | + +See [Interfaces](docs/interfaces.md) for details and code examples. + +## Documentation + +- [API Reference](https://pkg.go.dev/github.com/kptdev/krm-functions-sdk/go/fn) — Go API docs +- [Tutorial](docs/tutorial.md) — end-to-end function development +- [Interfaces](docs/interfaces.md) — Runner vs ResourceListProcessor +- [Testing](docs/testing.md) — golden test patterns +- [Containerizing](docs/containerizing.md) — building and running function images + +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on DCO sign-off, copyright headers, and code review process. + +## Issues + +Please [open issues](https://github.com/kptdev/kpt/issues) at [kptdev/kpt](https://github.com/kptdev/kpt/). ## License Code is under the [Apache License 2.0](LICENSE), documentation is [CC BY 4.0](LICENSE-documentation). - [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fkptdev%2Fkrm-functions-sdk.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fkptdev%2Fkrm-functions-sdk?ref=badge_large) ## Governance -The governance of the kpt project and KRM Functiona Catalog are described in the +The governance of the kpt project is described in the [governance repo](https://github.com/kptdev/governance). ## Code of Conduct -The kpt project and the KRM Functions Catalog are following the +The kpt project follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md). -More information and links about the CNCF Code of Conduct are [here](code-of-conduct.md). +More information is [here](code-of-conduct.md). ## CNCF -The kpt project including the KRM Functions Catalog is a [CNCF Sandbox](https://www.cncf.io/sandbox-projects/) project. +The kpt project is a [CNCF Sandbox](https://www.cncf.io/sandbox-projects/) project. diff --git a/docs/.nojekyll b/docs/.nojekyll deleted file mode 100644 index 139597f9..00000000 --- a/docs/.nojekyll +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/docs/api/.nojekyll b/docs/api/.nojekyll deleted file mode 100644 index e2ac6616..00000000 --- a/docs/api/.nojekyll +++ /dev/null @@ -1 +0,0 @@ -TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. \ No newline at end of file diff --git a/docs/api/assets/highlight.css b/docs/api/assets/highlight.css deleted file mode 100644 index 23dd8667..00000000 --- a/docs/api/assets/highlight.css +++ /dev/null @@ -1,71 +0,0 @@ -:root { - --light-hl-0: #0000FF; - --dark-hl-0: #569CD6; - --light-hl-1: #000000; - --dark-hl-1: #D4D4D4; - --light-hl-2: #0070C1; - --dark-hl-2: #4FC1FF; - --light-hl-3: #001080; - --dark-hl-3: #9CDCFE; - --light-hl-4: #795E26; - --dark-hl-4: #DCDCAA; - --light-hl-5: #A31515; - --dark-hl-5: #CE9178; - --light-hl-6: #AF00DB; - --dark-hl-6: #C586C0; - --light-code-background: #FFFFFF; - --dark-code-background: #1E1E1E; -} - -@media (prefers-color-scheme: light) { :root { - --hl-0: var(--light-hl-0); - --hl-1: var(--light-hl-1); - --hl-2: var(--light-hl-2); - --hl-3: var(--light-hl-3); - --hl-4: var(--light-hl-4); - --hl-5: var(--light-hl-5); - --hl-6: var(--light-hl-6); - --code-background: var(--light-code-background); -} } - -@media (prefers-color-scheme: dark) { :root { - --hl-0: var(--dark-hl-0); - --hl-1: var(--dark-hl-1); - --hl-2: var(--dark-hl-2); - --hl-3: var(--dark-hl-3); - --hl-4: var(--dark-hl-4); - --hl-5: var(--dark-hl-5); - --hl-6: var(--dark-hl-6); - --code-background: var(--dark-code-background); -} } - -body.light { - --hl-0: var(--light-hl-0); - --hl-1: var(--light-hl-1); - --hl-2: var(--light-hl-2); - --hl-3: var(--light-hl-3); - --hl-4: var(--light-hl-4); - --hl-5: var(--light-hl-5); - --hl-6: var(--light-hl-6); - --code-background: var(--light-code-background); -} - -body.dark { - --hl-0: var(--dark-hl-0); - --hl-1: var(--dark-hl-1); - --hl-2: var(--dark-hl-2); - --hl-3: var(--dark-hl-3); - --hl-4: var(--dark-hl-4); - --hl-5: var(--dark-hl-5); - --hl-6: var(--dark-hl-6); - --code-background: var(--dark-code-background); -} - -.hl-0 { color: var(--hl-0); } -.hl-1 { color: var(--hl-1); } -.hl-2 { color: var(--hl-2); } -.hl-3 { color: var(--hl-3); } -.hl-4 { color: var(--hl-4); } -.hl-5 { color: var(--hl-5); } -.hl-6 { color: var(--hl-6); } -pre, code { background: var(--code-background); } diff --git a/docs/api/assets/icons.css b/docs/api/assets/icons.css deleted file mode 100644 index 776a3562..00000000 --- a/docs/api/assets/icons.css +++ /dev/null @@ -1,1043 +0,0 @@ -.tsd-kind-icon { - display: block; - position: relative; - padding-left: 20px; - text-indent: -20px; -} -.tsd-kind-icon:before { - content: ""; - display: inline-block; - vertical-align: middle; - width: 17px; - height: 17px; - margin: 0 3px 2px 0; - background-image: url(./icons.png); -} -@media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 144dpi) { - .tsd-kind-icon:before { - background-image: url(./icons@2x.png); - background-size: 238px 204px; - } -} - -.tsd-signature.tsd-kind-icon:before { - background-position: 0 -153px; -} - -.tsd-kind-object-literal > .tsd-kind-icon:before { - background-position: 0px -17px; -} -.tsd-kind-object-literal.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -17px; -} -.tsd-kind-object-literal.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -17px; -} - -.tsd-kind-class > .tsd-kind-icon:before { - background-position: 0px -34px; -} -.tsd-kind-class.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -34px; -} -.tsd-kind-class.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -34px; -} - -.tsd-kind-class.tsd-has-type-parameter > .tsd-kind-icon:before { - background-position: 0px -51px; -} -.tsd-kind-class.tsd-has-type-parameter.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -17px -51px; -} -.tsd-kind-class.tsd-has-type-parameter.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -51px; -} - -.tsd-kind-interface > .tsd-kind-icon:before { - background-position: 0px -68px; -} -.tsd-kind-interface.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -68px; -} -.tsd-kind-interface.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -68px; -} - -.tsd-kind-interface.tsd-has-type-parameter > .tsd-kind-icon:before { - background-position: 0px -85px; -} -.tsd-kind-interface.tsd-has-type-parameter.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -17px -85px; -} -.tsd-kind-interface.tsd-has-type-parameter.tsd-is-private - > .tsd-kind-icon:before { - background-position: -34px -85px; -} - -.tsd-kind-namespace > .tsd-kind-icon:before { - background-position: 0px -102px; -} -.tsd-kind-namespace.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -102px; -} -.tsd-kind-namespace.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -102px; -} - -.tsd-kind-module > .tsd-kind-icon:before { - background-position: 0px -102px; -} -.tsd-kind-module.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -102px; -} -.tsd-kind-module.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -102px; -} - -.tsd-kind-enum > .tsd-kind-icon:before { - background-position: 0px -119px; -} -.tsd-kind-enum.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -119px; -} -.tsd-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -119px; -} - -.tsd-kind-enum-member > .tsd-kind-icon:before { - background-position: 0px -136px; -} -.tsd-kind-enum-member.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -136px; -} -.tsd-kind-enum-member.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -136px; -} - -.tsd-kind-signature > .tsd-kind-icon:before { - background-position: 0px -153px; -} -.tsd-kind-signature.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -153px; -} -.tsd-kind-signature.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -153px; -} - -.tsd-kind-type-alias > .tsd-kind-icon:before { - background-position: 0px -170px; -} -.tsd-kind-type-alias.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -170px; -} -.tsd-kind-type-alias.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -170px; -} - -.tsd-kind-type-alias.tsd-has-type-parameter > .tsd-kind-icon:before { - background-position: 0px -187px; -} -.tsd-kind-type-alias.tsd-has-type-parameter.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -17px -187px; -} -.tsd-kind-type-alias.tsd-has-type-parameter.tsd-is-private - > .tsd-kind-icon:before { - background-position: -34px -187px; -} - -.tsd-kind-variable > .tsd-kind-icon:before { - background-position: -136px -0px; -} -.tsd-kind-variable.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -0px; -} -.tsd-kind-variable.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -0px; -} -.tsd-kind-variable.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -0px; -} -.tsd-kind-variable.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -0px; -} -.tsd-kind-variable.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -0px; -} -.tsd-kind-variable.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -0px; -} -.tsd-kind-variable.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -0px; -} -.tsd-kind-variable.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -0px; -} -.tsd-kind-variable.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -0px; -} -.tsd-kind-variable.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -0px; -} -.tsd-kind-variable.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -0px; -} -.tsd-kind-variable.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -0px; -} - -.tsd-kind-property > .tsd-kind-icon:before { - background-position: -136px -0px; -} -.tsd-kind-property.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -0px; -} -.tsd-kind-property.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -0px; -} -.tsd-kind-property.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -0px; -} -.tsd-kind-property.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -0px; -} -.tsd-kind-property.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -0px; -} -.tsd-kind-property.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -0px; -} -.tsd-kind-property.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -0px; -} -.tsd-kind-property.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -0px; -} -.tsd-kind-property.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -0px; -} -.tsd-kind-property.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -0px; -} -.tsd-kind-property.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -0px; -} -.tsd-kind-property.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -0px; -} - -.tsd-kind-get-signature > .tsd-kind-icon:before { - background-position: -136px -17px; -} -.tsd-kind-get-signature.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -17px; -} -.tsd-kind-get-signature.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -17px; -} - -.tsd-kind-set-signature > .tsd-kind-icon:before { - background-position: -136px -34px; -} -.tsd-kind-set-signature.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -34px; -} -.tsd-kind-set-signature.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -34px; -} - -.tsd-kind-accessor > .tsd-kind-icon:before { - background-position: -136px -51px; -} -.tsd-kind-accessor.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -51px; -} -.tsd-kind-accessor.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -51px; -} - -.tsd-kind-function > .tsd-kind-icon:before { - background-position: -136px -68px; -} -.tsd-kind-function.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -68px; -} -.tsd-kind-function.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-function.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -68px; -} -.tsd-kind-function.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -68px; -} -.tsd-kind-function.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -68px; -} -.tsd-kind-function.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -68px; -} -.tsd-kind-function.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-function.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -68px; -} -.tsd-kind-function.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -68px; -} -.tsd-kind-function.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-function.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -68px; -} -.tsd-kind-function.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -68px; -} - -.tsd-kind-method > .tsd-kind-icon:before { - background-position: -136px -68px; -} -.tsd-kind-method.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -68px; -} -.tsd-kind-method.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-method.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -68px; -} -.tsd-kind-method.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -68px; -} -.tsd-kind-method.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -68px; -} -.tsd-kind-method.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -68px; -} -.tsd-kind-method.tsd-parent-kind-class.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-method.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -68px; -} -.tsd-kind-method.tsd-parent-kind-enum.tsd-is-protected > .tsd-kind-icon:before { - background-position: -187px -68px; -} -.tsd-kind-method.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-method.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -68px; -} -.tsd-kind-method.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -68px; -} - -.tsd-kind-call-signature > .tsd-kind-icon:before { - background-position: -136px -68px; -} -.tsd-kind-call-signature.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -68px; -} -.tsd-kind-call-signature.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -68px; -} - -.tsd-kind-function.tsd-has-type-parameter > .tsd-kind-icon:before { - background-position: -136px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -153px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-class - > .tsd-kind-icon:before { - background-position: -51px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-enum - > .tsd-kind-icon:before { - background-position: -170px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-interface - > .tsd-kind-icon:before { - background-position: -204px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -85px; -} - -.tsd-kind-method.tsd-has-type-parameter > .tsd-kind-icon:before { - background-position: -136px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -153px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-class - > .tsd-kind-icon:before { - background-position: -51px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-enum - > .tsd-kind-icon:before { - background-position: -170px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-interface - > .tsd-kind-icon:before { - background-position: -204px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -85px; -} - -.tsd-kind-constructor > .tsd-kind-icon:before { - background-position: -136px -102px; -} -.tsd-kind-constructor.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -102px; -} -.tsd-kind-constructor.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -102px; -} - -.tsd-kind-constructor-signature > .tsd-kind-icon:before { - background-position: -136px -102px; -} -.tsd-kind-constructor-signature.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -102px; -} -.tsd-kind-constructor-signature.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-interface - > .tsd-kind-icon:before { - background-position: -204px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -102px; -} - -.tsd-kind-index-signature > .tsd-kind-icon:before { - background-position: -136px -119px; -} -.tsd-kind-index-signature.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -119px; -} -.tsd-kind-index-signature.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -119px; -} - -.tsd-kind-event > .tsd-kind-icon:before { - background-position: -136px -136px; -} -.tsd-kind-event.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -136px; -} -.tsd-kind-event.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -136px; -} -.tsd-kind-event.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -136px; -} -.tsd-kind-event.tsd-parent-kind-class.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -68px -136px; -} -.tsd-kind-event.tsd-parent-kind-class.tsd-is-protected > .tsd-kind-icon:before { - background-position: -85px -136px; -} -.tsd-kind-event.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -136px; -} -.tsd-kind-event.tsd-parent-kind-class.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -136px; -} -.tsd-kind-event.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -136px; -} -.tsd-kind-event.tsd-parent-kind-enum.tsd-is-protected > .tsd-kind-icon:before { - background-position: -187px -136px; -} -.tsd-kind-event.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -136px; -} -.tsd-kind-event.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -136px; -} -.tsd-kind-event.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -136px; -} - -.tsd-is-static > .tsd-kind-icon:before { - background-position: -136px -153px; -} -.tsd-is-static.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -153px; -} -.tsd-is-static.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -153px; -} -.tsd-is-static.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -153px; -} -.tsd-is-static.tsd-parent-kind-class.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -68px -153px; -} -.tsd-is-static.tsd-parent-kind-class.tsd-is-protected > .tsd-kind-icon:before { - background-position: -85px -153px; -} -.tsd-is-static.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -153px; -} -.tsd-is-static.tsd-parent-kind-class.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -153px; -} -.tsd-is-static.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -153px; -} -.tsd-is-static.tsd-parent-kind-enum.tsd-is-protected > .tsd-kind-icon:before { - background-position: -187px -153px; -} -.tsd-is-static.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -153px; -} -.tsd-is-static.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -153px; -} -.tsd-is-static.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -153px; -} - -.tsd-is-static.tsd-kind-function > .tsd-kind-icon:before { - background-position: -136px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-interface - > .tsd-kind-icon:before { - background-position: -204px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -170px; -} - -.tsd-is-static.tsd-kind-method > .tsd-kind-icon:before { - background-position: -136px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-interface - > .tsd-kind-icon:before { - background-position: -204px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -170px; -} - -.tsd-is-static.tsd-kind-call-signature > .tsd-kind-icon:before { - background-position: -136px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -153px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-class - > .tsd-kind-icon:before { - background-position: -51px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-enum - > .tsd-kind-icon:before { - background-position: -170px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-interface - > .tsd-kind-icon:before { - background-position: -204px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -170px; -} - -.tsd-is-static.tsd-kind-event > .tsd-kind-icon:before { - background-position: -136px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-interface - > .tsd-kind-icon:before { - background-position: -204px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -187px; -} diff --git a/docs/api/assets/icons.png b/docs/api/assets/icons.png deleted file mode 100644 index 3836d5fe46e48bbe186116855aae879c23935327..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9615 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 NZmR35l_G4O{0}dcmS_L~ diff --git a/docs/api/assets/icons@2x.png b/docs/api/assets/icons@2x.png deleted file mode 100644 index 5a209e2f6d7f915cc9cb6fe7a4264c8be4db87b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28144 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 Ce=Object.create;var J=Object.defineProperty;var Pe=Object.getOwnPropertyDescriptor;var Oe=Object.getOwnPropertyNames;var Re=Object.getPrototypeOf,_e=Object.prototype.hasOwnProperty;var Me=t=>J(t,"__esModule",{value:!0});var Fe=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var De=(t,e,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of Oe(e))!_e.call(t,n)&&n!=="default"&&J(t,n,{get:()=>e[n],enumerable:!(r=Pe(e,n))||r.enumerable});return t},Ae=t=>De(Me(J(t!=null?Ce(Re(t)):{},"default",t&&t.__esModule&&"default"in t?{get:()=>t.default,enumerable:!0}:{value:t,enumerable:!0})),t);var de=Fe((ue,he)=>{(function(){var t=function(e){var r=new t.Builder;return r.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),r.searchPipeline.add(t.stemmer),e.call(r,r),r.build()};t.version="2.3.9";t.utils={},t.utils.warn=function(e){return function(r){e.console&&console.warn&&console.warn(r)}}(this),t.utils.asString=function(e){return e==null?"":e.toString()},t.utils.clone=function(e){if(e==null)return e;for(var r=Object.create(null),n=Object.keys(e),i=0;i0){var h=t.utils.clone(r)||{};h.position=[a,l],h.index=s.length,s.push(new t.Token(n.slice(a,o),h))}a=o+1}}return s},t.tokenizer.separator=/[\s\-]+/;t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions=Object.create(null),t.Pipeline.registerFunction=function(e,r){r in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+r),e.label=r,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var r=e.label&&e.label in this.registeredFunctions;r||t.utils.warn(`Function is not registered with pipeline. This may cause problems when serialising the index. -`,e)},t.Pipeline.load=function(e){var r=new t.Pipeline;return e.forEach(function(n){var i=t.Pipeline.registeredFunctions[n];if(i)r.add(i);else throw new Error("Cannot load unregistered function: "+n)}),r},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(r){t.Pipeline.warnIfFunctionNotRegistered(r),this._stack.push(r)},this)},t.Pipeline.prototype.after=function(e,r){t.Pipeline.warnIfFunctionNotRegistered(r);var n=this._stack.indexOf(e);if(n==-1)throw new Error("Cannot find existingFn");n=n+1,this._stack.splice(n,0,r)},t.Pipeline.prototype.before=function(e,r){t.Pipeline.warnIfFunctionNotRegistered(r);var n=this._stack.indexOf(e);if(n==-1)throw new Error("Cannot find existingFn");this._stack.splice(n,0,r)},t.Pipeline.prototype.remove=function(e){var r=this._stack.indexOf(e);r!=-1&&this._stack.splice(r,1)},t.Pipeline.prototype.run=function(e){for(var r=this._stack.length,n=0;n1&&(oe&&(n=s),o!=e);)i=n-r,s=r+Math.floor(i/2),o=this.elements[s*2];if(o==e||o>e)return s*2;if(oc?h+=2:a==c&&(r+=n[l+1]*i[h+1],l+=2,h+=2);return r},t.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},t.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),r=1,n=0;r0){var o=s.str.charAt(0),a;o in s.node.edges?a=s.node.edges[o]:(a=new t.TokenSet,s.node.edges[o]=a),s.str.length==1&&(a.final=!0),i.push({node:a,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(s.editsRemaining!=0){if("*"in s.node.edges)var c=s.node.edges["*"];else{var c=new t.TokenSet;s.node.edges["*"]=c}if(s.str.length==0&&(c.final=!0),i.push({node:c,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&i.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),s.str.length==1&&(s.node.final=!0),s.str.length>=1){if("*"in s.node.edges)var l=s.node.edges["*"];else{var l=new t.TokenSet;s.node.edges["*"]=l}s.str.length==1&&(l.final=!0),i.push({node:l,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var h=s.str.charAt(0),f=s.str.charAt(1),v;f in s.node.edges?v=s.node.edges[f]:(v=new t.TokenSet,s.node.edges[f]=v),s.str.length==1&&(v.final=!0),i.push({node:v,editsRemaining:s.editsRemaining-1,str:h+s.str.slice(2)})}}}return n},t.TokenSet.fromString=function(e){for(var r=new t.TokenSet,n=r,i=0,s=e.length;i=e;r--){var n=this.uncheckedNodes[r],i=n.child.toString();i in this.minimizedNodes?n.parent.edges[n.char]=this.minimizedNodes[i]:(n.child._str=i,this.minimizedNodes[i]=n.child),this.uncheckedNodes.pop()}};t.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},t.Index.prototype.search=function(e){return this.query(function(r){var n=new t.QueryParser(e,r);n.parse()})},t.Index.prototype.query=function(e){for(var r=new t.Query(this.fields),n=Object.create(null),i=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),c=0;c1?this._b=1:this._b=e},t.Builder.prototype.k1=function(e){this._k1=e},t.Builder.prototype.add=function(e,r){var n=e[this._ref],i=Object.keys(this._fields);this._documents[n]=r||{},this.documentCount+=1;for(var s=0;s=this.length)return t.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},t.QueryLexer.prototype.width=function(){return this.pos-this.start},t.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},t.QueryLexer.prototype.backup=function(){this.pos-=1},t.QueryLexer.prototype.acceptDigitRun=function(){var e,r;do e=this.next(),r=e.charCodeAt(0);while(r>47&&r<58);e!=t.QueryLexer.EOS&&this.backup()},t.QueryLexer.prototype.more=function(){return this.pos1&&(e.backup(),e.emit(t.QueryLexer.TERM)),e.ignore(),e.more())return t.QueryLexer.lexText},t.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.EDIT_DISTANCE),t.QueryLexer.lexText},t.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.BOOST),t.QueryLexer.lexText},t.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(t.QueryLexer.TERM)},t.QueryLexer.termSeparator=t.tokenizer.separator,t.QueryLexer.lexText=function(e){for(;;){var r=e.next();if(r==t.QueryLexer.EOS)return t.QueryLexer.lexEOS;if(r.charCodeAt(0)==92){e.escapeCharacter();continue}if(r==":")return t.QueryLexer.lexField;if(r=="~")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexEditDistance;if(r=="^")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexBoost;if(r=="+"&&e.width()===1||r=="-"&&e.width()===1)return e.emit(t.QueryLexer.PRESENCE),t.QueryLexer.lexText;if(r.match(t.QueryLexer.termSeparator))return t.QueryLexer.lexTerm}},t.QueryParser=function(e,r){this.lexer=new t.QueryLexer(e),this.query=r,this.currentClause={},this.lexemeIdx=0},t.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=t.QueryParser.parseClause;e;)e=e(this);return this.query},t.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},t.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},t.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},t.QueryParser.parseClause=function(e){var r=e.peekLexeme();if(r!=null)switch(r.type){case t.QueryLexer.PRESENCE:return t.QueryParser.parsePresence;case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expected either a field or a term, found "+r.type;throw r.str.length>=1&&(n+=" with value '"+r.str+"'"),new t.QueryParseError(n,r.start,r.end)}},t.QueryParser.parsePresence=function(e){var r=e.consumeLexeme();if(r!=null){switch(r.str){case"-":e.currentClause.presence=t.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=t.Query.presence.REQUIRED;break;default:var n="unrecognised presence operator'"+r.str+"'";throw new t.QueryParseError(n,r.start,r.end)}var i=e.peekLexeme();if(i==null){var n="expecting term or field, found nothing";throw new t.QueryParseError(n,r.start,r.end)}switch(i.type){case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expecting term or field, found '"+i.type+"'";throw new t.QueryParseError(n,i.start,i.end)}}},t.QueryParser.parseField=function(e){var r=e.consumeLexeme();if(r!=null){if(e.query.allFields.indexOf(r.str)==-1){var n=e.query.allFields.map(function(o){return"'"+o+"'"}).join(", "),i="unrecognised field '"+r.str+"', possible fields: "+n;throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.fields=[r.str];var s=e.peekLexeme();if(s==null){var i="expecting term, found nothing";throw new t.QueryParseError(i,r.start,r.end)}switch(s.type){case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var i="expecting term, found '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseTerm=function(e){var r=e.consumeLexeme();if(r!=null){e.currentClause.term=r.str.toLowerCase(),r.str.indexOf("*")!=-1&&(e.currentClause.usePipeline=!1);var n=e.peekLexeme();if(n==null){e.nextClause();return}switch(n.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+n.type+"'";throw new t.QueryParseError(i,n.start,n.end)}}},t.QueryParser.parseEditDistance=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="edit distance must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.editDistance=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseBoost=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="boost must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.boost=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},function(e,r){typeof define=="function"&&define.amd?define(r):typeof ue=="object"?he.exports=r():e.lunr=r()}(this,function(){return t})})()});var le=[];function N(t,e){le.push({selector:e,constructor:t})}var X=class{constructor(){this.createComponents(document.body)}createComponents(e){le.forEach(r=>{e.querySelectorAll(r.selector).forEach(n=>{n.dataset.hasInstance||(new r.constructor({el:n}),n.dataset.hasInstance=String(!0))})})}};var Q=class{constructor(e){this.el=e.el}};var Z=class{constructor(){this.listeners={}}addEventListener(e,r){e in this.listeners||(this.listeners[e]=[]),this.listeners[e].push(r)}removeEventListener(e,r){if(!(e in this.listeners))return;let n=this.listeners[e];for(let i=0,s=n.length;i{let r=Date.now();return(...n)=>{r+e-Date.now()<0&&(t(...n),r=Date.now())}};var ee=class extends Z{constructor(){super();this.scrollTop=0;this.lastY=0;this.width=0;this.height=0;this.showToolbar=!0;this.toolbar=document.querySelector(".tsd-page-toolbar"),this.secondaryNav=document.querySelector(".tsd-navigation.secondary"),window.addEventListener("scroll",K(()=>this.onScroll(),10)),window.addEventListener("resize",K(()=>this.onResize(),10)),this.onResize(),this.onScroll()}triggerResize(){let e=new CustomEvent("resize",{detail:{width:this.width,height:this.height}});this.dispatchEvent(e)}onResize(){this.width=window.innerWidth||0,this.height=window.innerHeight||0;let e=new CustomEvent("resize",{detail:{width:this.width,height:this.height}});this.dispatchEvent(e)}onScroll(){this.scrollTop=window.scrollY||0;let e=new CustomEvent("scroll",{detail:{scrollTop:this.scrollTop}});this.dispatchEvent(e),this.hideShowToolbar()}hideShowToolbar(){let e=this.showToolbar;this.showToolbar=this.lastY>=this.scrollTop||this.scrollTop<=0,e!==this.showToolbar&&(this.toolbar.classList.toggle("tsd-page-toolbar--hide"),this.secondaryNav.classList.toggle("tsd-navigation--toolbar-hide")),this.lastY=this.scrollTop}},I=ee;I.instance=new ee;var te=class extends Q{constructor(e){super(e);this.anchors=[];this.index=-1;I.instance.addEventListener("resize",()=>this.onResize()),I.instance.addEventListener("scroll",r=>this.onScroll(r)),this.createAnchors()}createAnchors(){let e=window.location.href;e.indexOf("#")!=-1&&(e=e.substr(0,e.indexOf("#"))),this.el.querySelectorAll("a").forEach(r=>{let n=r.href;if(n.indexOf("#")==-1||n.substr(0,e.length)!=e)return;let i=n.substr(n.indexOf("#")+1),s=document.querySelector("a.tsd-anchor[name="+i+"]"),o=r.parentNode;!s||!o||this.anchors.push({link:o,anchor:s,position:0})}),this.onResize()}onResize(){let e;for(let n=0,i=this.anchors.length;nn.position-i.position);let r=new CustomEvent("scroll",{detail:{scrollTop:I.instance.scrollTop}});this.onScroll(r)}onScroll(e){let r=e.detail.scrollTop+5,n=this.anchors,i=n.length-1,s=this.index;for(;s>-1&&n[s].position>r;)s-=1;for(;s-1&&this.anchors[this.index].link.classList.remove("focus"),this.index=s,this.index>-1&&this.anchors[this.index].link.classList.add("focus"))}};var ce=(t,e=100)=>{let r;return(...n)=>{clearTimeout(r),r=setTimeout(()=>t(n),e)}};var pe=Ae(de());function fe(){let t=document.getElementById("tsd-search");if(!t)return;let e=document.getElementById("search-script");t.classList.add("loading"),e&&(e.addEventListener("error",()=>{t.classList.remove("loading"),t.classList.add("failure")}),e.addEventListener("load",()=>{t.classList.remove("loading"),t.classList.add("ready")}),window.searchData&&t.classList.remove("loading"));let r=document.querySelector("#tsd-search input"),n=document.querySelector("#tsd-search .results");if(!r||!n)throw new Error("The input field or the result list wrapper was not found");let i=!1;n.addEventListener("mousedown",()=>i=!0),n.addEventListener("mouseup",()=>{i=!1,t.classList.remove("has-focus")}),r.addEventListener("focus",()=>t.classList.add("has-focus")),r.addEventListener("blur",()=>{i||(i=!1,t.classList.remove("has-focus"))});let s={base:t.dataset.base+"/"};Ve(t,n,r,s)}function Ve(t,e,r,n){r.addEventListener("input",ce(()=>{ze(t,e,r,n)},200));let i=!1;r.addEventListener("keydown",s=>{i=!0,s.key=="Enter"?Ne(e,r):s.key=="Escape"?r.blur():s.key=="ArrowUp"?me(e,-1):s.key==="ArrowDown"?me(e,1):i=!1}),r.addEventListener("keypress",s=>{i&&s.preventDefault()}),document.body.addEventListener("keydown",s=>{s.altKey||s.ctrlKey||s.metaKey||!r.matches(":focus")&&s.key==="/"&&(r.focus(),s.preventDefault())})}function He(t,e){t.index||window.searchData&&(e.classList.remove("loading"),e.classList.add("ready"),t.data=window.searchData,t.index=pe.Index.load(window.searchData.index))}function ze(t,e,r,n){if(He(n,t),!n.index||!n.data)return;e.textContent="";let i=r.value.trim(),s=n.index.search(`*${i}*`);for(let o=0,a=Math.min(10,s.length);o${ve(c.parent,i)}.${l}`);let h=document.createElement("li");h.classList.value=c.classes;let f=document.createElement("a");f.href=n.base+c.url,f.classList.add("tsd-kind-icon"),f.innerHTML=l,h.append(f),e.appendChild(h)}}function me(t,e){let r=t.querySelector(".current");if(!r)r=t.querySelector(e==1?"li:first-child":"li:last-child"),r&&r.classList.add("current");else{let n=r;if(e===1)do n=n.nextElementSibling;while(n instanceof HTMLElement&&n.offsetParent==null);else do n=n.previousElementSibling;while(n instanceof HTMLElement&&n.offsetParent==null);n&&(r.classList.remove("current"),n.classList.add("current"))}}function Ne(t,e){let r=t.querySelector(".current");if(r||(r=t.querySelector("li:first-child")),r){let n=r.querySelector("a");n&&(window.location.href=n.href),e.blur()}}function ve(t,e){if(e==="")return t;let r=t.toLocaleLowerCase(),n=e.toLocaleLowerCase(),i=[],s=0,o=r.indexOf(n);for(;o!=-1;)i.push(re(t.substring(s,o)),`${re(t.substring(o,o+n.length))}`),s=o+n.length,o=r.indexOf(n,s);return i.push(re(t.substring(s))),i.join("")}var je={"&":"&","<":"<",">":">","'":"'",'"':"""};function re(t){return t.replace(/[&<>"'"]/g,e=>je[e])}var ge=class{constructor(e,r){this.signature=e,this.description=r}addClass(e){return this.signature.classList.add(e),this.description.classList.add(e),this}removeClass(e){return this.signature.classList.remove(e),this.description.classList.remove(e),this}},ne=class extends Q{constructor(e){super(e);this.groups=[];this.index=-1;this.createGroups(),this.container&&(this.el.classList.add("active"),Array.from(this.el.children).forEach(r=>{r.addEventListener("touchstart",n=>this.onClick(n)),r.addEventListener("click",n=>this.onClick(n))}),this.container.classList.add("active"),this.setIndex(0))}setIndex(e){if(e<0&&(e=0),e>this.groups.length-1&&(e=this.groups.length-1),this.index==e)return;let r=this.groups[e];if(this.index>-1){let n=this.groups[this.index];n.removeClass("current").addClass("fade-out"),r.addClass("current"),r.addClass("fade-in"),I.instance.triggerResize(),setTimeout(()=>{n.removeClass("fade-out"),r.removeClass("fade-in")},300)}else r.addClass("current"),I.instance.triggerResize();this.index=e}createGroups(){let e=this.el.children;if(e.length<2)return;this.container=this.el.nextElementSibling;let r=this.container.children;this.groups=[];for(let n=0;n{r.signature===e.currentTarget&&this.setIndex(n)})}};var C="mousedown",ye="mousemove",_="mouseup",G={x:0,y:0},xe=!1,ie=!1,Be=!1,A=!1,Le=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);document.documentElement.classList.add(Le?"is-mobile":"not-mobile");Le&&"ontouchstart"in document.documentElement&&(Be=!0,C="touchstart",ye="touchmove",_="touchend");document.addEventListener(C,t=>{ie=!0,A=!1;let e=C=="touchstart"?t.targetTouches[0]:t;G.y=e.pageY||0,G.x=e.pageX||0});document.addEventListener(ye,t=>{if(!!ie&&!A){let e=C=="touchstart"?t.targetTouches[0]:t,r=G.x-(e.pageX||0),n=G.y-(e.pageY||0);A=Math.sqrt(r*r+n*n)>10}});document.addEventListener(_,()=>{ie=!1});document.addEventListener("click",t=>{xe&&(t.preventDefault(),t.stopImmediatePropagation(),xe=!1)});var se=class extends Q{constructor(e){super(e);this.className=this.el.dataset.toggle||"",this.el.addEventListener(_,r=>this.onPointerUp(r)),this.el.addEventListener("click",r=>r.preventDefault()),document.addEventListener(C,r=>this.onDocumentPointerDown(r)),document.addEventListener(_,r=>this.onDocumentPointerUp(r))}setActive(e){if(this.active==e)return;this.active=e,document.documentElement.classList.toggle("has-"+this.className,e),this.el.classList.toggle("active",e);let r=(this.active?"to-has-":"from-has-")+this.className;document.documentElement.classList.add(r),setTimeout(()=>document.documentElement.classList.remove(r),500)}onPointerUp(e){A||(this.setActive(!0),e.preventDefault())}onDocumentPointerDown(e){if(this.active){if(e.target.closest(".col-menu, .tsd-filter-group"))return;this.setActive(!1)}}onDocumentPointerUp(e){if(!A&&this.active&&e.target.closest(".col-menu")){let r=e.target.closest("a");if(r){let n=window.location.href;n.indexOf("#")!=-1&&(n=n.substr(0,n.indexOf("#"))),r.href.substr(0,n.length)==n&&setTimeout(()=>this.setActive(!1),250)}}}};var oe=class{constructor(e,r){this.key=e,this.value=r,this.defaultValue=r,this.initialize(),window.localStorage[this.key]&&this.setValue(this.fromLocalStorage(window.localStorage[this.key]))}initialize(){}setValue(e){if(this.value==e)return;let r=this.value;this.value=e,window.localStorage[this.key]=this.toLocalStorage(e),this.handleValueChange(r,e)}},ae=class extends oe{initialize(){let e=document.querySelector("#tsd-filter-"+this.key);!e||(this.checkbox=e,this.checkbox.addEventListener("change",()=>{this.setValue(this.checkbox.checked)}))}handleValueChange(e,r){!this.checkbox||(this.checkbox.checked=this.value,document.documentElement.classList.toggle("toggle-"+this.key,this.value!=this.defaultValue))}fromLocalStorage(e){return e=="true"}toLocalStorage(e){return e?"true":"false"}},Ee=class extends oe{initialize(){document.documentElement.classList.add("toggle-"+this.key+this.value);let e=document.querySelector("#tsd-filter-"+this.key);if(!e)return;this.select=e;let r=()=>{this.select.classList.add("active")},n=()=>{this.select.classList.remove("active")};this.select.addEventListener(C,r),this.select.addEventListener("mouseover",r),this.select.addEventListener("mouseleave",n),this.select.querySelectorAll("li").forEach(i=>{i.addEventListener(_,s=>{e.classList.remove("active"),this.setValue(s.target.dataset.value||"")})}),document.addEventListener(C,i=>{this.select.contains(i.target)||this.select.classList.remove("active")})}handleValueChange(e,r){this.select.querySelectorAll("li.selected").forEach(s=>{s.classList.remove("selected")});let n=this.select.querySelector('li[data-value="'+r+'"]'),i=this.select.querySelector(".tsd-select-label");n&&i&&(n.classList.add("selected"),i.textContent=n.textContent),document.documentElement.classList.remove("toggle-"+e),document.documentElement.classList.add("toggle-"+r)}fromLocalStorage(e){return e}toLocalStorage(e){return e}},Y=class extends Q{constructor(e){super(e);this.optionVisibility=new Ee("visibility","private"),this.optionInherited=new ae("inherited",!0),this.optionExternals=new ae("externals",!0)}static isSupported(){try{return typeof window.localStorage!="undefined"}catch(e){return!1}}};function be(t){let e=localStorage.getItem("tsd-theme")||"os";t.value=e,we(e),t.addEventListener("change",()=>{localStorage.setItem("tsd-theme",t.value),we(t.value)})}function we(t){switch(t){case"os":document.body.classList.remove("light","dark");break;case"light":document.body.classList.remove("dark"),document.body.classList.add("light");break;case"dark":document.body.classList.remove("light"),document.body.classList.add("dark");break}}fe();N(te,".menu-highlight");N(ne,".tsd-signatures");N(se,"a[data-toggle]");Y.isSupported()?N(Y,"#tsd-filter"):document.documentElement.classList.add("no-filter");var Te=document.getElementById("theme");Te&&be(Te);var qe=new X;Object.defineProperty(window,"app",{value:qe});})(); -/*! - * lunr.Builder - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.Index - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.Pipeline - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.Set - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.TokenSet - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.Vector - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.stemmer - * Copyright (C) 2020 Oliver Nightingale - * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt - */ -/*! - * lunr.stopWordFilter - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.tokenizer - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.trimmer - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.utils - * Copyright (C) 2020 Oliver Nightingale - */ -/** - * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9 - * Copyright (C) 2020 Oliver Nightingale - * @license MIT - */ diff --git a/docs/api/assets/search.js b/docs/api/assets/search.js deleted file mode 100644 index 52a9f31b..00000000 --- a/docs/api/assets/search.js +++ /dev/null @@ -1 +0,0 @@ -window.searchData = {"kinds":{"32":"Variable","64":"Function","128":"Class","256":"Interface","512":"Constructor","1024":"Property","2048":"Method","65536":"Type literal","4194304":"Type alias"},"rows":[{"id":0,"kind":128,"name":"FunctionConfigError","url":"classes/FunctionConfigError.html","classes":"tsd-kind-class"},{"id":1,"kind":512,"name":"constructor","url":"classes/FunctionConfigError.html#constructor","classes":"tsd-kind-constructor tsd-parent-kind-class tsd-is-overwrite","parent":"FunctionConfigError"},{"id":2,"kind":64,"name":"addAnnotation","url":"index.html#addAnnotation","classes":"tsd-kind-function"},{"id":3,"kind":64,"name":"removeAnnotation","url":"index.html#removeAnnotation","classes":"tsd-kind-function"},{"id":4,"kind":64,"name":"getAnnotation","url":"index.html#getAnnotation","classes":"tsd-kind-function"},{"id":5,"kind":64,"name":"addLabel","url":"index.html#addLabel","classes":"tsd-kind-function"},{"id":6,"kind":64,"name":"removeLabel","url":"index.html#removeLabel","classes":"tsd-kind-function"},{"id":7,"kind":64,"name":"getLabel","url":"index.html#getLabel","classes":"tsd-kind-function"},{"id":8,"kind":32,"name":"ANNOTATION_PREFIX","url":"index.html#ANNOTATION_PREFIX","classes":"tsd-kind-variable"},{"id":9,"kind":32,"name":"SOURCE_PATH_ANNOTATION","url":"index.html#SOURCE_PATH_ANNOTATION","classes":"tsd-kind-variable"},{"id":10,"kind":32,"name":"SOURCE_INDEX_ANNOTATION","url":"index.html#SOURCE_INDEX_ANNOTATION","classes":"tsd-kind-variable"},{"id":11,"kind":32,"name":"ID_ANNOTATION","url":"index.html#ID_ANNOTATION","classes":"tsd-kind-variable"},{"id":12,"kind":32,"name":"LEGACY_ANNOTATION_PREFIX","url":"index.html#LEGACY_ANNOTATION_PREFIX","classes":"tsd-kind-variable"},{"id":13,"kind":32,"name":"LEGACY_SOURCE_PATH_ANNOTATION","url":"index.html#LEGACY_SOURCE_PATH_ANNOTATION","classes":"tsd-kind-variable"},{"id":14,"kind":32,"name":"LEGACY_SOURCE_INDEX_ANNOTATION","url":"index.html#LEGACY_SOURCE_INDEX_ANNOTATION","classes":"tsd-kind-variable"},{"id":15,"kind":32,"name":"LEGACY_ID_ANNOTATION","url":"index.html#LEGACY_ID_ANNOTATION","classes":"tsd-kind-variable"},{"id":16,"kind":64,"name":"generalResult","url":"index.html#generalResult","classes":"tsd-kind-function"},{"id":17,"kind":64,"name":"configFileResult","url":"index.html#configFileResult","classes":"tsd-kind-function"},{"id":18,"kind":64,"name":"kubernetesObjectResult","url":"index.html#kubernetesObjectResult","classes":"tsd-kind-function"},{"id":19,"kind":64,"name":"run","url":"index.html#run","classes":"tsd-kind-function"},{"id":20,"kind":64,"name":"runFnWithConfigs","url":"index.html#runFnWithConfigs","classes":"tsd-kind-function"},{"id":21,"kind":128,"name":"TestRunner","url":"classes/TestRunner.html","classes":"tsd-kind-class"},{"id":22,"kind":512,"name":"constructor","url":"classes/TestRunner.html#constructor","classes":"tsd-kind-constructor tsd-parent-kind-class","parent":"TestRunner"},{"id":23,"kind":2048,"name":"assert","url":"classes/TestRunner.html#assert","classes":"tsd-kind-method tsd-parent-kind-class","parent":"TestRunner"},{"id":24,"kind":2048,"name":"assertCallback","url":"classes/TestRunner.html#assertCallback","classes":"tsd-kind-method tsd-parent-kind-class","parent":"TestRunner"},{"id":25,"kind":64,"name":"isKubernetesObject","url":"index.html#isKubernetesObject","classes":"tsd-kind-function"},{"id":26,"kind":64,"name":"kubernetesKey","url":"index.html#kubernetesKey","classes":"tsd-kind-function"},{"id":27,"kind":256,"name":"KptFunc","url":"interfaces/KptFunc.html","classes":"tsd-kind-interface"},{"id":28,"kind":1024,"name":"usage","url":"interfaces/KptFunc.html#usage","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"KptFunc"},{"id":29,"kind":128,"name":"Configs","url":"classes/Configs.html","classes":"tsd-kind-class"},{"id":30,"kind":512,"name":"constructor","url":"classes/Configs.html#constructor","classes":"tsd-kind-constructor tsd-parent-kind-class","parent":"Configs"},{"id":31,"kind":2048,"name":"getAll","url":"classes/Configs.html#getAll","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Configs"},{"id":32,"kind":2048,"name":"get","url":"classes/Configs.html#get","classes":"tsd-kind-method tsd-parent-kind-class tsd-has-type-parameter","parent":"Configs"},{"id":33,"kind":2048,"name":"insert","url":"classes/Configs.html#insert","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Configs"},{"id":34,"kind":2048,"name":"delete","url":"classes/Configs.html#delete","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Configs"},{"id":35,"kind":2048,"name":"deleteAll","url":"classes/Configs.html#deleteAll","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Configs"},{"id":36,"kind":2048,"name":"groupBy","url":"classes/Configs.html#groupBy","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Configs"},{"id":37,"kind":2048,"name":"getFunctionConfig","url":"classes/Configs.html#getFunctionConfig","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Configs"},{"id":38,"kind":2048,"name":"getFunctionConfigMap","url":"classes/Configs.html#getFunctionConfigMap","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Configs"},{"id":39,"kind":2048,"name":"getFunctionConfigValue","url":"classes/Configs.html#getFunctionConfigValue","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Configs"},{"id":40,"kind":2048,"name":"hasUnexpectedFunctionParameter","url":"classes/Configs.html#hasUnexpectedFunctionParameter","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Configs"},{"id":41,"kind":2048,"name":"getFunctionConfigValueOrThrow","url":"classes/Configs.html#getFunctionConfigValueOrThrow","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Configs"},{"id":42,"kind":2048,"name":"addResults","url":"classes/Configs.html#addResults","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Configs"},{"id":43,"kind":2048,"name":"getResults","url":"classes/Configs.html#getResults","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Configs"},{"id":44,"kind":2048,"name":"toResourceList","url":"classes/Configs.html#toResourceList","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Configs"},{"id":45,"kind":2048,"name":"deepCopy","url":"classes/Configs.html#deepCopy","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Configs"},{"id":46,"kind":1024,"name":"logToStdErr","url":"classes/Configs.html#logToStdErr","classes":"tsd-kind-property tsd-parent-kind-class","parent":"Configs"},{"id":47,"kind":256,"name":"KubernetesObject","url":"interfaces/KubernetesObject.html","classes":"tsd-kind-interface"},{"id":48,"kind":1024,"name":"apiVersion","url":"interfaces/KubernetesObject.html#apiVersion","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"KubernetesObject"},{"id":49,"kind":1024,"name":"kind","url":"interfaces/KubernetesObject.html#kind","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"KubernetesObject"},{"id":50,"kind":1024,"name":"metadata","url":"interfaces/KubernetesObject.html#metadata","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"KubernetesObject"},{"id":51,"kind":128,"name":"ResourceList","url":"classes/ResourceList.html","classes":"tsd-kind-class"},{"id":52,"kind":512,"name":"constructor","url":"classes/ResourceList.html#constructor","classes":"tsd-kind-constructor tsd-parent-kind-class","parent":"ResourceList"},{"id":53,"kind":1024,"name":"apiVersion","url":"classes/ResourceList.html#apiVersion","classes":"tsd-kind-property tsd-parent-kind-class","parent":"ResourceList"},{"id":54,"kind":1024,"name":"kind","url":"classes/ResourceList.html#kind","classes":"tsd-kind-property tsd-parent-kind-class","parent":"ResourceList"},{"id":55,"kind":1024,"name":"metadata","url":"classes/ResourceList.html#metadata","classes":"tsd-kind-property tsd-parent-kind-class","parent":"ResourceList"},{"id":56,"kind":65536,"name":"__type","url":"classes/ResourceList.html#__type","classes":"tsd-kind-type-literal tsd-parent-kind-class","parent":"ResourceList"},{"id":57,"kind":1024,"name":"name","url":"classes/ResourceList.html#__type.name","classes":"tsd-kind-property tsd-parent-kind-type-literal","parent":"ResourceList.__type"},{"id":58,"kind":1024,"name":"items","url":"classes/ResourceList.html#items","classes":"tsd-kind-property tsd-parent-kind-class","parent":"ResourceList"},{"id":59,"kind":1024,"name":"results","url":"classes/ResourceList.html#results","classes":"tsd-kind-property tsd-parent-kind-class","parent":"ResourceList"},{"id":60,"kind":256,"name":"Result","url":"interfaces/Result.html","classes":"tsd-kind-interface"},{"id":61,"kind":1024,"name":"severity","url":"interfaces/Result.html#severity","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"Result"},{"id":62,"kind":1024,"name":"message","url":"interfaces/Result.html#message","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"Result"},{"id":63,"kind":1024,"name":"tags","url":"interfaces/Result.html#tags","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"Result"},{"id":64,"kind":65536,"name":"__type","url":"interfaces/Result.html#__type-2","classes":"tsd-kind-type-literal tsd-parent-kind-interface","parent":"Result"},{"id":65,"kind":1024,"name":"resourceRef","url":"interfaces/Result.html#resourceRef","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"Result"},{"id":66,"kind":65536,"name":"__type","url":"interfaces/Result.html#__type-1","classes":"tsd-kind-type-literal tsd-parent-kind-interface","parent":"Result"},{"id":67,"kind":1024,"name":"apiVersion","url":"interfaces/Result.html#__type-1.apiVersion","classes":"tsd-kind-property tsd-parent-kind-type-literal","parent":"Result.__type"},{"id":68,"kind":1024,"name":"kind","url":"interfaces/Result.html#__type-1.kind","classes":"tsd-kind-property tsd-parent-kind-type-literal","parent":"Result.__type"},{"id":69,"kind":1024,"name":"namespace","url":"interfaces/Result.html#__type-1.namespace","classes":"tsd-kind-property tsd-parent-kind-type-literal","parent":"Result.__type"},{"id":70,"kind":1024,"name":"name","url":"interfaces/Result.html#__type-1.name","classes":"tsd-kind-property tsd-parent-kind-type-literal","parent":"Result.__type"},{"id":71,"kind":1024,"name":"file","url":"interfaces/Result.html#file","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"Result"},{"id":72,"kind":65536,"name":"__type","url":"interfaces/Result.html#__type","classes":"tsd-kind-type-literal tsd-parent-kind-interface","parent":"Result"},{"id":73,"kind":1024,"name":"path","url":"interfaces/Result.html#__type.path","classes":"tsd-kind-property tsd-parent-kind-type-literal","parent":"Result.__type"},{"id":74,"kind":1024,"name":"index","url":"interfaces/Result.html#__type.index","classes":"tsd-kind-property tsd-parent-kind-type-literal","parent":"Result.__type"},{"id":75,"kind":1024,"name":"field","url":"interfaces/Result.html#field","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"Result"},{"id":76,"kind":4194304,"name":"Severity","url":"index.html#Severity","classes":"tsd-kind-type-alias"},{"id":77,"kind":256,"name":"JsonArray","url":"interfaces/JsonArray.html","classes":"tsd-kind-interface"},{"id":78,"kind":256,"name":"JsonMap","url":"interfaces/JsonMap.html","classes":"tsd-kind-interface"},{"id":79,"kind":4194304,"name":"Json","url":"index.html#Json","classes":"tsd-kind-type-alias"},{"id":80,"kind":256,"name":"FieldInfo","url":"interfaces/FieldInfo.html","classes":"tsd-kind-interface"},{"id":81,"kind":1024,"name":"path","url":"interfaces/FieldInfo.html#path","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"FieldInfo"},{"id":82,"kind":1024,"name":"currentValue","url":"interfaces/FieldInfo.html#currentValue","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"FieldInfo"},{"id":83,"kind":1024,"name":"suggestedValue","url":"interfaces/FieldInfo.html#suggestedValue","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"FieldInfo"}],"index":{"version":"2.3.9","fields":["name","parent"],"fieldVectors":[["name/0",[0,35.264]],["parent/0",[]],["name/1",[1,29.386]],["parent/1",[0,2.788]],["name/2",[2,40.372]],["parent/2",[]],["name/3",[3,40.372]],["parent/3",[]],["name/4",[4,40.372]],["parent/4",[]],["name/5",[5,40.372]],["parent/5",[]],["name/6",[6,40.372]],["parent/6",[]],["name/7",[7,40.372]],["parent/7",[]],["name/8",[8,40.372]],["parent/8",[]],["name/9",[9,40.372]],["parent/9",[]],["name/10",[10,40.372]],["parent/10",[]],["name/11",[11,40.372]],["parent/11",[]],["name/12",[12,40.372]],["parent/12",[]],["name/13",[13,40.372]],["parent/13",[]],["name/14",[14,40.372]],["parent/14",[]],["name/15",[15,40.372]],["parent/15",[]],["name/16",[16,40.372]],["parent/16",[]],["name/17",[17,40.372]],["parent/17",[]],["name/18",[18,40.372]],["parent/18",[]],["name/19",[19,40.372]],["parent/19",[]],["name/20",[20,40.372]],["parent/20",[]],["name/21",[21,29.386]],["parent/21",[]],["name/22",[1,29.386]],["parent/22",[21,2.324]],["name/23",[22,40.372]],["parent/23",[21,2.324]],["name/24",[23,40.372]],["parent/24",[21,2.324]],["name/25",[24,40.372]],["parent/25",[]],["name/26",[25,40.372]],["parent/26",[]],["name/27",[26,35.264]],["parent/27",[]],["name/28",[27,40.372]],["parent/28",[26,2.788]],["name/29",[28,15.249]],["parent/29",[]],["name/30",[1,29.386]],["parent/30",[28,1.206]],["name/31",[29,40.372]],["parent/31",[28,1.206]],["name/32",[30,40.372]],["parent/32",[28,1.206]],["name/33",[31,40.372]],["parent/33",[28,1.206]],["name/34",[32,40.372]],["parent/34",[28,1.206]],["name/35",[33,40.372]],["parent/35",[28,1.206]],["name/36",[34,40.372]],["parent/36",[28,1.206]],["name/37",[35,40.372]],["parent/37",[28,1.206]],["name/38",[36,40.372]],["parent/38",[28,1.206]],["name/39",[37,40.372]],["parent/39",[28,1.206]],["name/40",[38,40.372]],["parent/40",[28,1.206]],["name/41",[39,40.372]],["parent/41",[28,1.206]],["name/42",[40,40.372]],["parent/42",[28,1.206]],["name/43",[41,40.372]],["parent/43",[28,1.206]],["name/44",[42,40.372]],["parent/44",[28,1.206]],["name/45",[43,40.372]],["parent/45",[28,1.206]],["name/46",[44,40.372]],["parent/46",[28,1.206]],["name/47",[45,29.386]],["parent/47",[]],["name/48",[46,31.899]],["parent/48",[45,2.324]],["name/49",[47,31.899]],["parent/49",[45,2.324]],["name/50",[48,35.264]],["parent/50",[45,2.324]],["name/51",[49,23.026]],["parent/51",[]],["name/52",[1,29.386]],["parent/52",[49,1.821]],["name/53",[46,31.899]],["parent/53",[49,1.821]],["name/54",[47,31.899]],["parent/54",[49,1.821]],["name/55",[48,35.264]],["parent/55",[49,1.821]],["name/56",[50,29.386]],["parent/56",[49,1.821]],["name/57",[51,35.264]],["parent/57",[52,3.192]],["name/58",[53,40.372]],["parent/58",[49,1.821]],["name/59",[54,40.372]],["parent/59",[49,1.821]],["name/60",[55,20.913]],["parent/60",[]],["name/61",[56,35.264]],["parent/61",[55,1.654]],["name/62",[57,40.372]],["parent/62",[55,1.654]],["name/63",[58,40.372]],["parent/63",[55,1.654]],["name/64",[50,29.386]],["parent/64",[55,1.654]],["name/65",[59,40.372]],["parent/65",[55,1.654]],["name/66",[50,29.386]],["parent/66",[55,1.654]],["name/67",[46,31.899]],["parent/67",[60,2.033]],["name/68",[47,31.899]],["parent/68",[60,2.033]],["name/69",[61,40.372]],["parent/69",[60,2.033]],["name/70",[51,35.264]],["parent/70",[60,2.033]],["name/71",[62,40.372]],["parent/71",[55,1.654]],["name/72",[50,29.386]],["parent/72",[55,1.654]],["name/73",[63,35.264]],["parent/73",[60,2.033]],["name/74",[64,40.372]],["parent/74",[60,2.033]],["name/75",[65,40.372]],["parent/75",[55,1.654]],["name/76",[56,35.264]],["parent/76",[]],["name/77",[66,40.372]],["parent/77",[]],["name/78",[67,40.372]],["parent/78",[]],["name/79",[68,40.372]],["parent/79",[]],["name/80",[69,29.386]],["parent/80",[]],["name/81",[63,35.264]],["parent/81",[69,2.324]],["name/82",[70,40.372]],["parent/82",[69,2.324]],["name/83",[71,40.372]],["parent/83",[69,2.324]]],"invertedIndex":[["__type",{"_index":50,"name":{"56":{},"64":{},"66":{},"72":{}},"parent":{}}],["addannotation",{"_index":2,"name":{"2":{}},"parent":{}}],["addlabel",{"_index":5,"name":{"5":{}},"parent":{}}],["addresults",{"_index":40,"name":{"42":{}},"parent":{}}],["annotation_prefix",{"_index":8,"name":{"8":{}},"parent":{}}],["apiversion",{"_index":46,"name":{"48":{},"53":{},"67":{}},"parent":{}}],["assert",{"_index":22,"name":{"23":{}},"parent":{}}],["assertcallback",{"_index":23,"name":{"24":{}},"parent":{}}],["configfileresult",{"_index":17,"name":{"17":{}},"parent":{}}],["configs",{"_index":28,"name":{"29":{}},"parent":{"30":{},"31":{},"32":{},"33":{},"34":{},"35":{},"36":{},"37":{},"38":{},"39":{},"40":{},"41":{},"42":{},"43":{},"44":{},"45":{},"46":{}}}],["constructor",{"_index":1,"name":{"1":{},"22":{},"30":{},"52":{}},"parent":{}}],["currentvalue",{"_index":70,"name":{"82":{}},"parent":{}}],["deepcopy",{"_index":43,"name":{"45":{}},"parent":{}}],["delete",{"_index":32,"name":{"34":{}},"parent":{}}],["deleteall",{"_index":33,"name":{"35":{}},"parent":{}}],["field",{"_index":65,"name":{"75":{}},"parent":{}}],["fieldinfo",{"_index":69,"name":{"80":{}},"parent":{"81":{},"82":{},"83":{}}}],["file",{"_index":62,"name":{"71":{}},"parent":{}}],["functionconfigerror",{"_index":0,"name":{"0":{}},"parent":{"1":{}}}],["generalresult",{"_index":16,"name":{"16":{}},"parent":{}}],["get",{"_index":30,"name":{"32":{}},"parent":{}}],["getall",{"_index":29,"name":{"31":{}},"parent":{}}],["getannotation",{"_index":4,"name":{"4":{}},"parent":{}}],["getfunctionconfig",{"_index":35,"name":{"37":{}},"parent":{}}],["getfunctionconfigmap",{"_index":36,"name":{"38":{}},"parent":{}}],["getfunctionconfigvalue",{"_index":37,"name":{"39":{}},"parent":{}}],["getfunctionconfigvalueorthrow",{"_index":39,"name":{"41":{}},"parent":{}}],["getlabel",{"_index":7,"name":{"7":{}},"parent":{}}],["getresults",{"_index":41,"name":{"43":{}},"parent":{}}],["groupby",{"_index":34,"name":{"36":{}},"parent":{}}],["hasunexpectedfunctionparameter",{"_index":38,"name":{"40":{}},"parent":{}}],["id_annotation",{"_index":11,"name":{"11":{}},"parent":{}}],["index",{"_index":64,"name":{"74":{}},"parent":{}}],["insert",{"_index":31,"name":{"33":{}},"parent":{}}],["iskubernetesobject",{"_index":24,"name":{"25":{}},"parent":{}}],["items",{"_index":53,"name":{"58":{}},"parent":{}}],["json",{"_index":68,"name":{"79":{}},"parent":{}}],["jsonarray",{"_index":66,"name":{"77":{}},"parent":{}}],["jsonmap",{"_index":67,"name":{"78":{}},"parent":{}}],["kind",{"_index":47,"name":{"49":{},"54":{},"68":{}},"parent":{}}],["kptfunc",{"_index":26,"name":{"27":{}},"parent":{"28":{}}}],["kuberneteskey",{"_index":25,"name":{"26":{}},"parent":{}}],["kubernetesobject",{"_index":45,"name":{"47":{}},"parent":{"48":{},"49":{},"50":{}}}],["kubernetesobjectresult",{"_index":18,"name":{"18":{}},"parent":{}}],["legacy_annotation_prefix",{"_index":12,"name":{"12":{}},"parent":{}}],["legacy_id_annotation",{"_index":15,"name":{"15":{}},"parent":{}}],["legacy_source_index_annotation",{"_index":14,"name":{"14":{}},"parent":{}}],["legacy_source_path_annotation",{"_index":13,"name":{"13":{}},"parent":{}}],["logtostderr",{"_index":44,"name":{"46":{}},"parent":{}}],["message",{"_index":57,"name":{"62":{}},"parent":{}}],["metadata",{"_index":48,"name":{"50":{},"55":{}},"parent":{}}],["name",{"_index":51,"name":{"57":{},"70":{}},"parent":{}}],["namespace",{"_index":61,"name":{"69":{}},"parent":{}}],["path",{"_index":63,"name":{"73":{},"81":{}},"parent":{}}],["removeannotation",{"_index":3,"name":{"3":{}},"parent":{}}],["removelabel",{"_index":6,"name":{"6":{}},"parent":{}}],["resourcelist",{"_index":49,"name":{"51":{}},"parent":{"52":{},"53":{},"54":{},"55":{},"56":{},"58":{},"59":{}}}],["resourcelist.__type",{"_index":52,"name":{},"parent":{"57":{}}}],["resourceref",{"_index":59,"name":{"65":{}},"parent":{}}],["result",{"_index":55,"name":{"60":{}},"parent":{"61":{},"62":{},"63":{},"64":{},"65":{},"66":{},"71":{},"72":{},"75":{}}}],["result.__type",{"_index":60,"name":{},"parent":{"67":{},"68":{},"69":{},"70":{},"73":{},"74":{}}}],["results",{"_index":54,"name":{"59":{}},"parent":{}}],["run",{"_index":19,"name":{"19":{}},"parent":{}}],["runfnwithconfigs",{"_index":20,"name":{"20":{}},"parent":{}}],["severity",{"_index":56,"name":{"61":{},"76":{}},"parent":{}}],["source_index_annotation",{"_index":10,"name":{"10":{}},"parent":{}}],["source_path_annotation",{"_index":9,"name":{"9":{}},"parent":{}}],["suggestedvalue",{"_index":71,"name":{"83":{}},"parent":{}}],["tags",{"_index":58,"name":{"63":{}},"parent":{}}],["testrunner",{"_index":21,"name":{"21":{}},"parent":{"22":{},"23":{},"24":{}}}],["toresourcelist",{"_index":42,"name":{"44":{}},"parent":{}}],["usage",{"_index":27,"name":{"28":{}},"parent":{}}]],"pipeline":[]}} \ No newline at end of file diff --git a/docs/api/assets/style.css b/docs/api/assets/style.css deleted file mode 100644 index ff488199..00000000 --- a/docs/api/assets/style.css +++ /dev/null @@ -1,1384 +0,0 @@ -@import url("./icons.css"); - -:root { - /* Light */ - --light-color-background: #fcfcfc; - --light-color-secondary-background: #fff; - --light-color-text: #222; - --light-color-text-aside: #707070; - --light-color-link: #4da6ff; - --light-color-menu-divider: #eee; - --light-color-menu-divider-focus: #000; - --light-color-menu-label: #707070; - --light-color-panel: var(--light-color-secondary-background); - --light-color-panel-divider: #eee; - --light-color-comment-tag: #707070; - --light-color-comment-tag-text: #fff; - --light-color-ts: #9600ff; - --light-color-ts-interface: #647f1b; - --light-color-ts-enum: #937210; - --light-color-ts-class: #0672de; - --light-color-ts-private: #707070; - --light-color-toolbar: #fff; - --light-color-toolbar-text: #333; - --light-icon-filter: invert(0); - --light-external-icon: url("data:image/svg+xml;utf8,"); - - /* Dark */ - --dark-color-background: #36393f; - --dark-color-secondary-background: #2f3136; - --dark-color-text: #ffffff; - --dark-color-text-aside: #e6e4e4; - --dark-color-link: #00aff4; - --dark-color-menu-divider: #eee; - --dark-color-menu-divider-focus: #000; - --dark-color-menu-label: #707070; - --dark-color-panel: var(--dark-color-secondary-background); - --dark-color-panel-divider: #818181; - --dark-color-comment-tag: #dcddde; - --dark-color-comment-tag-text: #2f3136; - --dark-color-ts: #c97dff; - --dark-color-ts-interface: #9cbe3c; - --dark-color-ts-enum: #d6ab29; - --dark-color-ts-class: #3695f3; - --dark-color-ts-private: #e2e2e2; - --dark-color-toolbar: #34373c; - --dark-color-toolbar-text: #ffffff; - --dark-icon-filter: invert(1); - --dark-external-icon: url("data:image/svg+xml;utf8,"); -} - -@media (prefers-color-scheme: light) { - :root { - --color-background: var(--light-color-background); - --color-secondary-background: var(--light-color-secondary-background); - --color-text: var(--light-color-text); - --color-text-aside: var(--light-color-text-aside); - --color-link: var(--light-color-link); - --color-menu-divider: var(--light-color-menu-divider); - --color-menu-divider-focus: var(--light-color-menu-divider-focus); - --color-menu-label: var(--light-color-menu-label); - --color-panel: var(--light-color-panel); - --color-panel-divider: var(--light-color-panel-divider); - --color-comment-tag: var(--light-color-comment-tag); - --color-comment-tag-text: var(--light-color-comment-tag-text); - --color-ts: var(--light-color-ts); - --color-ts-interface: var(--light-color-ts-interface); - --color-ts-enum: var(--light-color-ts-enum); - --color-ts-class: var(--light-color-ts-class); - --color-ts-private: var(--light-color-ts-private); - --color-toolbar: var(--light-color-toolbar); - --color-toolbar-text: var(--light-color-toolbar-text); - --icon-filter: var(--light-icon-filter); - --external-icon: var(--light-external-icon); - } -} - -@media (prefers-color-scheme: dark) { - :root { - --color-background: var(--dark-color-background); - --color-secondary-background: var(--dark-color-secondary-background); - --color-text: var(--dark-color-text); - --color-text-aside: var(--dark-color-text-aside); - --color-link: var(--dark-color-link); - --color-menu-divider: var(--dark-color-menu-divider); - --color-menu-divider-focus: var(--dark-color-menu-divider-focus); - --color-menu-label: var(--dark-color-menu-label); - --color-panel: var(--dark-color-panel); - --color-panel-divider: var(--dark-color-panel-divider); - --color-comment-tag: var(--dark-color-comment-tag); - --color-comment-tag-text: var(--dark-color-comment-tag-text); - --color-ts: var(--dark-color-ts); - --color-ts-interface: var(--dark-color-ts-interface); - --color-ts-enum: var(--dark-color-ts-enum); - --color-ts-class: var(--dark-color-ts-class); - --color-ts-private: var(--dark-color-ts-private); - --color-toolbar: var(--dark-color-toolbar); - --color-toolbar-text: var(--dark-color-toolbar-text); - --icon-filter: var(--dark-icon-filter); - --external-icon: var(--dark-external-icon); - } -} - -body { - margin: 0; -} - -body.light { - --color-background: var(--light-color-background); - --color-secondary-background: var(--light-color-secondary-background); - --color-text: var(--light-color-text); - --color-text-aside: var(--light-color-text-aside); - --color-link: var(--light-color-link); - --color-menu-divider: var(--light-color-menu-divider); - --color-menu-divider-focus: var(--light-color-menu-divider-focus); - --color-menu-label: var(--light-color-menu-label); - --color-panel: var(--light-color-panel); - --color-panel-divider: var(--light-color-panel-divider); - --color-comment-tag: var(--light-color-comment-tag); - --color-comment-tag-text: var(--light-color-comment-tag-text); - --color-ts: var(--light-color-ts); - --color-ts-interface: var(--light-color-ts-interface); - --color-ts-enum: var(--light-color-ts-enum); - --color-ts-class: var(--light-color-ts-class); - --color-ts-private: var(--light-color-ts-private); - --color-toolbar: var(--light-color-toolbar); - --color-toolbar-text: var(--light-color-toolbar-text); - --icon-filter: var(--light-icon-filter); - --external-icon: var(--light-external-icon); -} - -body.dark { - --color-background: var(--dark-color-background); - --color-secondary-background: var(--dark-color-secondary-background); - --color-text: var(--dark-color-text); - --color-text-aside: var(--dark-color-text-aside); - --color-link: var(--dark-color-link); - --color-menu-divider: var(--dark-color-menu-divider); - --color-menu-divider-focus: var(--dark-color-menu-divider-focus); - --color-menu-label: var(--dark-color-menu-label); - --color-panel: var(--dark-color-panel); - --color-panel-divider: var(--dark-color-panel-divider); - --color-comment-tag: var(--dark-color-comment-tag); - --color-comment-tag-text: var(--dark-color-comment-tag-text); - --color-ts: var(--dark-color-ts); - --color-ts-interface: var(--dark-color-ts-interface); - --color-ts-enum: var(--dark-color-ts-enum); - --color-ts-class: var(--dark-color-ts-class); - --color-ts-private: var(--dark-color-ts-private); - --color-toolbar: var(--dark-color-toolbar); - --color-toolbar-text: var(--dark-color-toolbar-text); - --icon-filter: var(--dark-icon-filter); - --external-icon: var(--dark-external-icon); -} - -h1 { - font-size: 2em; - margin: 0.67em 0; -} - -h2 { - font-size: 1.5em; - margin: 0.83em 0; -} - -h3 { - font-size: 1.17em; - margin: 1em 0; -} - -h4, -.tsd-index-panel h3 { - font-size: 1em; - margin: 1.33em 0; -} - -h5 { - font-size: 0.83em; - margin: 1.67em 0; -} - -h6 { - font-size: 0.67em; - margin: 2.33em 0; -} - -pre { - white-space: pre; - white-space: pre-wrap; - word-wrap: break-word; -} - -dl, -menu, -ol, -ul { - margin: 1em 0; -} - -dd { - margin: 0 0 0 40px; -} - -.container { - max-width: 1200px; - margin: 0 auto; - padding: 0 40px; -} -@media (max-width: 640px) { - .container { - padding: 0 20px; - } -} - -.container-main { - padding-bottom: 200px; -} - -.row { - display: flex; - position: relative; - margin: 0 -10px; -} -.row:after { - visibility: hidden; - display: block; - content: ""; - clear: both; - height: 0; -} - -.col-4, -.col-8 { - box-sizing: border-box; - float: left; - padding: 0 10px; -} - -.col-4 { - width: 33.3333333333%; -} -.col-8 { - width: 66.6666666667%; -} - -ul.tsd-descriptions > li > :first-child, -.tsd-panel > :first-child, -.col-8 > :first-child, -.col-4 > :first-child, -ul.tsd-descriptions > li > :first-child > :first-child, -.tsd-panel > :first-child > :first-child, -.col-8 > :first-child > :first-child, -.col-4 > :first-child > :first-child, -ul.tsd-descriptions > li > :first-child > :first-child > :first-child, -.tsd-panel > :first-child > :first-child > :first-child, -.col-8 > :first-child > :first-child > :first-child, -.col-4 > :first-child > :first-child > :first-child { - margin-top: 0; -} -ul.tsd-descriptions > li > :last-child, -.tsd-panel > :last-child, -.col-8 > :last-child, -.col-4 > :last-child, -ul.tsd-descriptions > li > :last-child > :last-child, -.tsd-panel > :last-child > :last-child, -.col-8 > :last-child > :last-child, -.col-4 > :last-child > :last-child, -ul.tsd-descriptions > li > :last-child > :last-child > :last-child, -.tsd-panel > :last-child > :last-child > :last-child, -.col-8 > :last-child > :last-child > :last-child, -.col-4 > :last-child > :last-child > :last-child { - margin-bottom: 0; -} - -@keyframes fade-in { - from { - opacity: 0; - } - to { - opacity: 1; - } -} -@keyframes fade-out { - from { - opacity: 1; - visibility: visible; - } - to { - opacity: 0; - } -} -@keyframes fade-in-delayed { - 0% { - opacity: 0; - } - 33% { - opacity: 0; - } - 100% { - opacity: 1; - } -} -@keyframes fade-out-delayed { - 0% { - opacity: 1; - visibility: visible; - } - 66% { - opacity: 0; - } - 100% { - opacity: 0; - } -} -@keyframes shift-to-left { - from { - transform: translate(0, 0); - } - to { - transform: translate(-25%, 0); - } -} -@keyframes unshift-to-left { - from { - transform: translate(-25%, 0); - } - to { - transform: translate(0, 0); - } -} -@keyframes pop-in-from-right { - from { - transform: translate(100%, 0); - } - to { - transform: translate(0, 0); - } -} -@keyframes pop-out-to-right { - from { - transform: translate(0, 0); - visibility: visible; - } - to { - transform: translate(100%, 0); - } -} -body { - background: var(--color-background); - font-family: "Segoe UI", sans-serif; - font-size: 16px; - color: var(--color-text); -} - -a { - color: var(--color-link); - text-decoration: none; -} -a:hover { - text-decoration: underline; -} -a.external[target="_blank"] { - background-image: var(--external-icon); - background-position: top 3px right; - background-repeat: no-repeat; - padding-right: 13px; -} - -code, -pre { - font-family: Menlo, Monaco, Consolas, "Courier New", monospace; - padding: 0.2em; - margin: 0; - font-size: 14px; -} - -pre { - padding: 10px; -} -pre code { - padding: 0; - font-size: 100%; -} - -blockquote { - margin: 1em 0; - padding-left: 1em; - border-left: 4px solid gray; -} - -.tsd-typography { - line-height: 1.333em; -} -.tsd-typography ul { - list-style: square; - padding: 0 0 0 20px; - margin: 0; -} -.tsd-typography h4, -.tsd-typography .tsd-index-panel h3, -.tsd-index-panel .tsd-typography h3, -.tsd-typography h5, -.tsd-typography h6 { - font-size: 1em; - margin: 0; -} -.tsd-typography h5, -.tsd-typography h6 { - font-weight: normal; -} -.tsd-typography p, -.tsd-typography ul, -.tsd-typography ol { - margin: 1em 0; -} - -@media (min-width: 901px) and (max-width: 1024px) { - html .col-content { - width: 72%; - } - html .col-menu { - width: 28%; - } - html .tsd-navigation { - padding-left: 10px; - } -} -@media (max-width: 900px) { - html .col-content { - float: none; - width: 100%; - } - html .col-menu { - position: fixed !important; - overflow: auto; - -webkit-overflow-scrolling: touch; - z-index: 1024; - top: 0 !important; - bottom: 0 !important; - left: auto !important; - right: 0 !important; - width: 100%; - padding: 20px 20px 0 0; - max-width: 450px; - visibility: hidden; - background-color: var(--color-panel); - transform: translate(100%, 0); - } - html .col-menu > *:last-child { - padding-bottom: 20px; - } - html .overlay { - content: ""; - display: block; - position: fixed; - z-index: 1023; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(0, 0, 0, 0.75); - visibility: hidden; - } - - .to-has-menu .overlay { - animation: fade-in 0.4s; - } - - .to-has-menu :is(header, footer, .col-content) { - animation: shift-to-left 0.4s; - } - - .to-has-menu .col-menu { - animation: pop-in-from-right 0.4s; - } - - .from-has-menu .overlay { - animation: fade-out 0.4s; - } - - .from-has-menu :is(header, footer, .col-content) { - animation: unshift-to-left 0.4s; - } - - .from-has-menu .col-menu { - animation: pop-out-to-right 0.4s; - } - - .has-menu body { - overflow: hidden; - } - .has-menu .overlay { - visibility: visible; - } - .has-menu :is(header, footer, .col-content) { - transform: translate(-25%, 0); - } - .has-menu .col-menu { - visibility: visible; - transform: translate(0, 0); - display: grid; - grid-template-rows: auto 1fr; - max-height: 100vh; - } - .has-menu .tsd-navigation { - max-height: 100%; - } -} - -.tsd-page-title { - padding: 70px 0 20px 0; - margin: 0 0 40px 0; - background: var(--color-panel); - box-shadow: 0 0 5px rgba(0, 0, 0, 0.35); -} -.tsd-page-title h1 { - margin: 0; -} - -.tsd-breadcrumb { - margin: 0; - padding: 0; - color: var(--color-text-aside); -} -.tsd-breadcrumb a { - color: var(--color-text-aside); - text-decoration: none; -} -.tsd-breadcrumb a:hover { - text-decoration: underline; -} -.tsd-breadcrumb li { - display: inline; -} -.tsd-breadcrumb li:after { - content: " / "; -} - -dl.tsd-comment-tags { - overflow: hidden; -} -dl.tsd-comment-tags dt { - float: left; - padding: 1px 5px; - margin: 0 10px 0 0; - border-radius: 4px; - border: 1px solid var(--color-comment-tag); - color: var(--color-comment-tag); - font-size: 0.8em; - font-weight: normal; -} -dl.tsd-comment-tags dd { - margin: 0 0 10px 0; -} -dl.tsd-comment-tags dd:before, -dl.tsd-comment-tags dd:after { - display: table; - content: " "; -} -dl.tsd-comment-tags dd pre, -dl.tsd-comment-tags dd:after { - clear: both; -} -dl.tsd-comment-tags p { - margin: 0; -} - -.tsd-panel.tsd-comment .lead { - font-size: 1.1em; - line-height: 1.333em; - margin-bottom: 2em; -} -.tsd-panel.tsd-comment .lead:last-child { - margin-bottom: 0; -} - -.toggle-protected .tsd-is-private { - display: none; -} - -.toggle-public .tsd-is-private, -.toggle-public .tsd-is-protected, -.toggle-public .tsd-is-private-protected { - display: none; -} - -.toggle-inherited .tsd-is-inherited { - display: none; -} - -.toggle-externals .tsd-is-external { - display: none; -} - -#tsd-filter { - position: relative; - display: inline-block; - height: 40px; - vertical-align: bottom; -} -.no-filter #tsd-filter { - display: none; -} -#tsd-filter .tsd-filter-group { - display: inline-block; - height: 40px; - vertical-align: bottom; - white-space: nowrap; -} -#tsd-filter input { - display: none; -} -@media (max-width: 900px) { - #tsd-filter .tsd-filter-group { - display: block; - position: absolute; - top: 40px; - right: 20px; - height: auto; - background-color: var(--color-panel); - visibility: hidden; - transform: translate(50%, 0); - box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); - } - .has-options #tsd-filter .tsd-filter-group { - visibility: visible; - } - .to-has-options #tsd-filter .tsd-filter-group { - animation: fade-in 0.2s; - } - .from-has-options #tsd-filter .tsd-filter-group { - animation: fade-out 0.2s; - } - #tsd-filter label, - #tsd-filter .tsd-select { - display: block; - padding-right: 20px; - } -} - -footer { - border-top: 1px solid var(--color-panel-divider); - background-color: var(--color-panel); -} -footer.with-border-bottom { - border-bottom: 1px solid var(--color-panel-divider); -} -footer .tsd-legend-group { - font-size: 0; -} -footer .tsd-legend { - display: inline-block; - width: 25%; - padding: 0; - font-size: 16px; - list-style: none; - line-height: 1.333em; - vertical-align: top; -} -@media (max-width: 900px) { - footer .tsd-legend { - width: 50%; - } -} - -.tsd-hierarchy { - list-style: square; - padding: 0 0 0 20px; - margin: 0; -} -.tsd-hierarchy .target { - font-weight: bold; -} - -.tsd-index-panel .tsd-index-content { - margin-bottom: -30px !important; -} -.tsd-index-panel .tsd-index-section { - margin-bottom: 30px !important; -} -.tsd-index-panel h3 { - margin: 0 -20px 10px -20px; - padding: 0 20px 10px 20px; - border-bottom: 1px solid var(--color-panel-divider); -} -.tsd-index-panel ul.tsd-index-list { - -webkit-column-count: 3; - -moz-column-count: 3; - -ms-column-count: 3; - -o-column-count: 3; - column-count: 3; - -webkit-column-gap: 20px; - -moz-column-gap: 20px; - -ms-column-gap: 20px; - -o-column-gap: 20px; - column-gap: 20px; - padding: 0; - list-style: none; - line-height: 1.333em; -} -@media (max-width: 900px) { - .tsd-index-panel ul.tsd-index-list { - -webkit-column-count: 1; - -moz-column-count: 1; - -ms-column-count: 1; - -o-column-count: 1; - column-count: 1; - } -} -@media (min-width: 901px) and (max-width: 1024px) { - .tsd-index-panel ul.tsd-index-list { - -webkit-column-count: 2; - -moz-column-count: 2; - -ms-column-count: 2; - -o-column-count: 2; - column-count: 2; - } -} -.tsd-index-panel ul.tsd-index-list li { - -webkit-page-break-inside: avoid; - -moz-page-break-inside: avoid; - -ms-page-break-inside: avoid; - -o-page-break-inside: avoid; - page-break-inside: avoid; -} -.tsd-index-panel a, -.tsd-index-panel .tsd-parent-kind-module a { - color: var(--color-ts); -} -.tsd-index-panel .tsd-parent-kind-interface a { - color: var(--color-ts-interface); -} -.tsd-index-panel .tsd-parent-kind-enum a { - color: var(--color-ts-enum); -} -.tsd-index-panel .tsd-parent-kind-class a { - color: var(--color-ts-class); -} -.tsd-index-panel .tsd-kind-module a { - color: var(--color-ts); -} -.tsd-index-panel .tsd-kind-interface a { - color: var(--color-ts-interface); -} -.tsd-index-panel .tsd-kind-enum a { - color: var(--color-ts-enum); -} -.tsd-index-panel .tsd-kind-class a { - color: var(--color-ts-class); -} -.tsd-index-panel .tsd-is-private a { - color: var(--color-ts-private); -} - -.tsd-flag { - display: inline-block; - padding: 1px 5px; - border-radius: 4px; - color: var(--color-comment-tag-text); - background-color: var(--color-comment-tag); - text-indent: 0; - font-size: 14px; - font-weight: normal; -} - -.tsd-anchor { - position: absolute; - top: -100px; -} - -.tsd-member { - position: relative; -} -.tsd-member .tsd-anchor + h3 { - margin-top: 0; - margin-bottom: 0; - border-bottom: none; -} -.tsd-member [data-tsd-kind] { - color: var(--color-ts); -} -.tsd-member [data-tsd-kind="Interface"] { - color: var(--color-ts-interface); -} -.tsd-member [data-tsd-kind="Enum"] { - color: var(--color-ts-enum); -} -.tsd-member [data-tsd-kind="Class"] { - color: var(--color-ts-class); -} -.tsd-member [data-tsd-kind="Private"] { - color: var(--color-ts-private); -} - -.tsd-navigation { - margin: 0 0 0 40px; -} -.tsd-navigation a { - display: block; - padding-top: 2px; - padding-bottom: 2px; - border-left: 2px solid transparent; - color: var(--color-text); - text-decoration: none; - transition: border-left-color 0.1s; -} -.tsd-navigation a:hover { - text-decoration: underline; -} -.tsd-navigation ul { - margin: 0; - padding: 0; - list-style: none; -} -.tsd-navigation li { - padding: 0; -} - -.tsd-navigation.primary { - padding-bottom: 40px; -} -.tsd-navigation.primary a { - display: block; - padding-top: 6px; - padding-bottom: 6px; -} -.tsd-navigation.primary ul li a { - padding-left: 5px; -} -.tsd-navigation.primary ul li li a { - padding-left: 25px; -} -.tsd-navigation.primary ul li li li a { - padding-left: 45px; -} -.tsd-navigation.primary ul li li li li a { - padding-left: 65px; -} -.tsd-navigation.primary ul li li li li li a { - padding-left: 85px; -} -.tsd-navigation.primary ul li li li li li li a { - padding-left: 105px; -} -.tsd-navigation.primary > ul { - border-bottom: 1px solid var(--color-panel-divider); -} -.tsd-navigation.primary li { - border-top: 1px solid var(--color-panel-divider); -} -.tsd-navigation.primary li.current > a { - font-weight: bold; -} -.tsd-navigation.primary li.label span { - display: block; - padding: 20px 0 6px 5px; - color: var(--color-menu-label); -} -.tsd-navigation.primary li.globals + li > span, -.tsd-navigation.primary li.globals + li > a { - padding-top: 20px; -} - -.tsd-navigation.secondary { - max-height: calc(100vh - 1rem - 40px); - overflow: auto; - position: sticky; - top: calc(0.5rem + 40px); - transition: 0.3s; -} -.tsd-navigation.secondary.tsd-navigation--toolbar-hide { - max-height: calc(100vh - 1rem); - top: 0.5rem; -} -.tsd-navigation.secondary ul { - transition: opacity 0.2s; -} -.tsd-navigation.secondary ul li a { - padding-left: 25px; -} -.tsd-navigation.secondary ul li li a { - padding-left: 45px; -} -.tsd-navigation.secondary ul li li li a { - padding-left: 65px; -} -.tsd-navigation.secondary ul li li li li a { - padding-left: 85px; -} -.tsd-navigation.secondary ul li li li li li a { - padding-left: 105px; -} -.tsd-navigation.secondary ul li li li li li li a { - padding-left: 125px; -} -.tsd-navigation.secondary ul.current a { - border-left-color: var(--color-panel-divider); -} -.tsd-navigation.secondary li.focus > a, -.tsd-navigation.secondary ul.current li.focus > a { - border-left-color: var(--color-menu-divider-focus); -} -.tsd-navigation.secondary li.current { - margin-top: 20px; - margin-bottom: 20px; - border-left-color: var(--color-panel-divider); -} -.tsd-navigation.secondary li.current > a { - font-weight: bold; -} - -@media (min-width: 901px) { - .menu-sticky-wrap { - position: static; - } -} - -.tsd-panel { - margin: 20px 0; - padding: 20px; - background-color: var(--color-panel); - box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); -} -.tsd-panel:empty { - display: none; -} -.tsd-panel > h1, -.tsd-panel > h2, -.tsd-panel > h3 { - margin: 1.5em -20px 10px -20px; - padding: 0 20px 10px 20px; - border-bottom: 1px solid var(--color-panel-divider); -} -.tsd-panel > h1.tsd-before-signature, -.tsd-panel > h2.tsd-before-signature, -.tsd-panel > h3.tsd-before-signature { - margin-bottom: 0; - border-bottom: 0; -} -.tsd-panel table { - display: block; - width: 100%; - overflow: auto; - margin-top: 10px; - word-break: normal; - word-break: keep-all; - border-collapse: collapse; -} -.tsd-panel table th { - font-weight: bold; -} -.tsd-panel table th, -.tsd-panel table td { - padding: 6px 13px; - border: 1px solid var(--color-panel-divider); -} -.tsd-panel table tr { - background: var(--color-background); -} -.tsd-panel table tr:nth-child(even) { - background: var(--color-secondary-background); -} - -.tsd-panel-group { - margin: 60px 0; -} -.tsd-panel-group > h1, -.tsd-panel-group > h2, -.tsd-panel-group > h3 { - padding-left: 20px; - padding-right: 20px; -} - -#tsd-search { - transition: background-color 0.2s; -} -#tsd-search .title { - position: relative; - z-index: 2; -} -#tsd-search .field { - position: absolute; - left: 0; - top: 0; - right: 40px; - height: 40px; -} -#tsd-search .field input { - box-sizing: border-box; - position: relative; - top: -50px; - z-index: 1; - width: 100%; - padding: 0 10px; - opacity: 0; - outline: 0; - border: 0; - background: transparent; - color: var(--color-text); -} -#tsd-search .field label { - position: absolute; - overflow: hidden; - right: -40px; -} -#tsd-search .field input, -#tsd-search .title { - transition: opacity 0.2s; -} -#tsd-search .results { - position: absolute; - visibility: hidden; - top: 40px; - width: 100%; - margin: 0; - padding: 0; - list-style: none; - box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); -} -#tsd-search .results li { - padding: 0 10px; - background-color: var(--color-background); -} -#tsd-search .results li:nth-child(even) { - background-color: var(--color-panel); -} -#tsd-search .results li.state { - display: none; -} -#tsd-search .results li.current, -#tsd-search .results li:hover { - background-color: var(--color-panel-divider); -} -#tsd-search .results a { - display: block; -} -#tsd-search .results a:before { - top: 10px; -} -#tsd-search .results span.parent { - color: var(--color-text-aside); - font-weight: normal; -} -#tsd-search.has-focus { - background-color: var(--color-panel-divider); -} -#tsd-search.has-focus .field input { - top: 0; - opacity: 1; -} -#tsd-search.has-focus .title { - z-index: 0; - opacity: 0; -} -#tsd-search.has-focus .results { - visibility: visible; -} -#tsd-search.loading .results li.state.loading { - display: block; -} -#tsd-search.failure .results li.state.failure { - display: block; -} - -.tsd-signature { - margin: 0 0 1em 0; - padding: 10px; - border: 1px solid var(--color-panel-divider); - font-family: Menlo, Monaco, Consolas, "Courier New", monospace; - font-size: 14px; - overflow-x: auto; -} -.tsd-signature.tsd-kind-icon { - padding-left: 30px; -} -.tsd-signature.tsd-kind-icon:before { - top: 10px; - left: 10px; -} -.tsd-panel > .tsd-signature { - margin-left: -20px; - margin-right: -20px; - border-width: 1px 0; -} -.tsd-panel > .tsd-signature.tsd-kind-icon { - padding-left: 40px; -} -.tsd-panel > .tsd-signature.tsd-kind-icon:before { - left: 20px; -} - -.tsd-signature-symbol { - color: var(--color-text-aside); - font-weight: normal; -} - -.tsd-signature-type { - font-style: italic; - font-weight: normal; -} - -.tsd-signatures { - padding: 0; - margin: 0 0 1em 0; - border: 1px solid var(--color-panel-divider); -} -.tsd-signatures .tsd-signature { - margin: 0; - border-width: 1px 0 0 0; - transition: background-color 0.1s; -} -.tsd-signatures .tsd-signature:first-child { - border-top-width: 0; -} -.tsd-signatures .tsd-signature.current { - background-color: var(--color-panel-divider); -} -.tsd-signatures.active > .tsd-signature { - cursor: pointer; -} -.tsd-panel > .tsd-signatures { - margin-left: -20px; - margin-right: -20px; - border-width: 1px 0; -} -.tsd-panel > .tsd-signatures .tsd-signature.tsd-kind-icon { - padding-left: 40px; -} -.tsd-panel > .tsd-signatures .tsd-signature.tsd-kind-icon:before { - left: 20px; -} -.tsd-panel > a.anchor + .tsd-signatures { - border-top-width: 0; - margin-top: -20px; -} - -ul.tsd-descriptions { - position: relative; - overflow: hidden; - padding: 0; - list-style: none; -} -ul.tsd-descriptions.active > .tsd-description { - display: none; -} -ul.tsd-descriptions.active > .tsd-description.current { - display: block; -} -ul.tsd-descriptions.active > .tsd-description.fade-in { - animation: fade-in-delayed 0.3s; -} -ul.tsd-descriptions.active > .tsd-description.fade-out { - animation: fade-out-delayed 0.3s; - position: absolute; - display: block; - top: 0; - left: 0; - right: 0; - opacity: 0; - visibility: hidden; -} -ul.tsd-descriptions h4, -ul.tsd-descriptions .tsd-index-panel h3, -.tsd-index-panel ul.tsd-descriptions h3 { - font-size: 16px; - margin: 1em 0 0.5em 0; -} - -ul.tsd-parameters, -ul.tsd-type-parameters { - list-style: square; - margin: 0; - padding-left: 20px; -} -ul.tsd-parameters > li.tsd-parameter-signature, -ul.tsd-type-parameters > li.tsd-parameter-signature { - list-style: none; - margin-left: -20px; -} -ul.tsd-parameters h5, -ul.tsd-type-parameters h5 { - font-size: 16px; - margin: 1em 0 0.5em 0; -} -ul.tsd-parameters .tsd-comment, -ul.tsd-type-parameters .tsd-comment { - margin-top: -0.5em; -} - -.tsd-sources { - font-size: 14px; - color: var(--color-text-aside); - margin: 0 0 1em 0; -} -.tsd-sources a { - color: var(--color-text-aside); - text-decoration: underline; -} -.tsd-sources ul, -.tsd-sources p { - margin: 0 !important; -} -.tsd-sources ul { - list-style: none; - padding: 0; -} - -.tsd-page-toolbar { - position: fixed; - z-index: 1; - top: 0; - left: 0; - width: 100%; - height: 40px; - color: var(--color-toolbar-text); - background: var(--color-toolbar); - border-bottom: 1px solid var(--color-panel-divider); - transition: transform 0.3s linear; -} -.tsd-page-toolbar a { - color: var(--color-toolbar-text); - text-decoration: none; -} -.tsd-page-toolbar a.title { - font-weight: bold; -} -.tsd-page-toolbar a.title:hover { - text-decoration: underline; -} -.tsd-page-toolbar .table-wrap { - display: table; - width: 100%; - height: 40px; -} -.tsd-page-toolbar .table-cell { - display: table-cell; - position: relative; - white-space: nowrap; - line-height: 40px; -} -.tsd-page-toolbar .table-cell:first-child { - width: 100%; -} - -.tsd-page-toolbar--hide { - transform: translateY(-100%); -} - -.tsd-select .tsd-select-list li:before, -.tsd-select .tsd-select-label:before, -.tsd-widget:before { - content: ""; - display: inline-block; - width: 40px; - height: 40px; - margin: 0 -8px 0 0; - background-image: url(./widgets.png); - background-repeat: no-repeat; - text-indent: -1024px; - vertical-align: bottom; - filter: var(--icon-filter); -} -@media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 144dpi) { - .tsd-select .tsd-select-list li:before, - .tsd-select .tsd-select-label:before, - .tsd-widget:before { - background-image: url(./widgets@2x.png); - background-size: 320px 40px; - } -} - -.tsd-widget { - display: inline-block; - overflow: hidden; - opacity: 0.8; - height: 40px; - transition: opacity 0.1s, background-color 0.2s; - vertical-align: bottom; - cursor: pointer; -} -.tsd-widget:hover { - opacity: 0.9; -} -.tsd-widget.active { - opacity: 1; - background-color: var(--color-panel-divider); -} -.tsd-widget.no-caption { - width: 40px; -} -.tsd-widget.no-caption:before { - margin: 0; -} -.tsd-widget.search:before { - background-position: 0 0; -} -.tsd-widget.menu:before { - background-position: -40px 0; -} -.tsd-widget.options:before { - background-position: -80px 0; -} -.tsd-widget.options, -.tsd-widget.menu { - display: none; -} -@media (max-width: 900px) { - .tsd-widget.options, - .tsd-widget.menu { - display: inline-block; - } -} -input[type="checkbox"] + .tsd-widget:before { - background-position: -120px 0; -} -input[type="checkbox"]:checked + .tsd-widget:before { - background-position: -160px 0; -} - -.tsd-select { - position: relative; - display: inline-block; - height: 40px; - transition: opacity 0.1s, background-color 0.2s; - vertical-align: bottom; - cursor: pointer; -} -.tsd-select .tsd-select-label { - opacity: 0.6; - transition: opacity 0.2s; -} -.tsd-select .tsd-select-label:before { - background-position: -240px 0; -} -.tsd-select.active .tsd-select-label { - opacity: 0.8; -} -.tsd-select.active .tsd-select-list { - visibility: visible; - opacity: 1; - transition-delay: 0s; -} -.tsd-select .tsd-select-list { - position: absolute; - visibility: hidden; - top: 40px; - left: 0; - margin: 0; - padding: 0; - opacity: 0; - list-style: none; - box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); - transition: visibility 0s 0.2s, opacity 0.2s; -} -.tsd-select .tsd-select-list li { - padding: 0 20px 0 0; - background-color: var(--color-background); -} -.tsd-select .tsd-select-list li:before { - background-position: 40px 0; -} -.tsd-select .tsd-select-list li:nth-child(even) { - background-color: var(--color-panel); -} -.tsd-select .tsd-select-list li:hover { - background-color: var(--color-panel-divider); -} -.tsd-select .tsd-select-list li.selected:before { - background-position: -200px 0; -} -@media (max-width: 900px) { - .tsd-select .tsd-select-list { - top: 0; - left: auto; - right: 100%; - margin-right: -5px; - } - .tsd-select .tsd-select-label:before { - background-position: -280px 0; - } -} - -img { - max-width: 100%; -} diff --git a/docs/api/assets/widgets.png b/docs/api/assets/widgets.png deleted file mode 100644 index c7380532ac1b45400620011c37c4dcb7aec27a4c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 480 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoH8@y+q^jrZML>b&o-U3d6^w6h1+IPUz|;DW zIZ;96kdsD>Qv^q=09&hp0GpEni<1IR%gvP3v%OR9*{MuRTKWHZyIbuBt)Ci`cU_&% z1T+i^Y)o{%281-<3TpPAUTzw5v;RY=>1rvxmPl96#kYc9hX!6V^nB|ad#(S+)}?8C zr_H+lT3B#So$T=?$(w3-{rbQ4R<@nsf$}$hwSO)A$8&`(j+wQf=Jwhb0`CvhR5DCf z^OgI)KQemrUFPH+UynC$Y~QHG%DbTVh-Skz{enNU)cV_hPu~{TD7TPZl>0&K>iuE| z7AYn$7)Jrb9GE&SfQW4q&G*@N|4cHI`VakFa5-C!ov&XD)J(qp$rJJ*9e z-sHv}#g*T7Cv048d1v~BEAzM5FztAse#q78WWC^BUCzQ U&wLp6h6BX&boFyt=akR{0G%$)mH+?% diff --git a/docs/api/assets/widgets@2x.png b/docs/api/assets/widgets@2x.png deleted file mode 100644 index 4bbbd57272f3b28f47527d4951ad10f950b8ad43..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 855 zcmeAS@N?(olHy`uVBq!ia0y~yU}^xe12~w0Jcmn z@(X6T|9^jgLcx21{)7exgY)a>N6m2F0<`Rqr;B4q1>>88jUdw-7W`c)zLE*mq8W2H z-<&Jl_Hco5BuC5n@AbF5GD82~-e8-v=#zCyUX0F-o}8pPfAv`!GN$ff+TL<~@kgt} z62eO?_|&+>xBmM$@p|z`tIKEdpPf8%qI>4r7@jn<=eta*{3~?g(zz{Ke9zc-G^gr? z-7foa?LcS!hmbwzru}ICvbWLlW8;+l-}!^=c32!^nV`+`C*;0-*Y%l94pC;Cb3GXz zzSf%a!{gVr{Y_lVuUj+a)*Ca+!-Hu%xmP&&X-2CuANY8^i{D7Kg6qzP zXz_ps9+lN8ESH{K4`yu&b~I>N9xGlE&;2u*b?+Go!AhN?m-bxlLvtC#MzDF2kFzfHJ1W7ybqdefSqVhbOykd*Yi%EDuhs z4wF{ft^bv2+DDnKb8gj1FuvcV`M}luS>lO<^)8x>y1#R;a=-ZKwWTQQb)ioBbi;zh zD!f5V)8581to1LL7c9!l^PSC$NBPYif!_vAZhmL4)v4U)4UsrLYiH_9rmQDd?)(e5 z^pcH>qvBg*i0dus2r*mp4;zKvu=P#s-ti;2obl`NjjwoYd>e(oo#j_uyRb<7Pv^If zzZ|mGHmV)8^tbO%^>eqMw(@7(&3g{jEp-Najo7V75xI_ZHK*FA`elF{r5}E*d7+j_R diff --git a/docs/api/classes/Configs.html b/docs/api/classes/Configs.html deleted file mode 100644 index 7aa0a1dc..00000000 --- a/docs/api/classes/Configs.html +++ /dev/null @@ -1,79 +0,0 @@ -Configs | kpt-functions
Options
All
  • Public
  • Public/Protected
  • All
Menu
-

Configs is an in-memory document store for Kubernetes objects populated from/to configuration files.

-

It enables performing rich query and mutation operations.

-

Hierarchy

  • Configs

Index

Constructors

constructor

  • -

    Creates a Config.

    -

    Parameters

    • input: KubernetesObject[] = []
      -

      Input Kubernetes objects. -If supplied multiple objects with the same kubernetesKey discards all but the last one. -Does not preserve insertion order of the given objects.

      -
    • Optional functionConfig: KubernetesObject
      -

      Kubernetes object used to parameterize the function's behavior.

      -
    • Optional results: Result[]
      -

      For testing only: List of Results returned by the function.

      -

    Returns Configs

Properties

logToStdErr

logToStdErr: undefined | boolean
-

Determines whether addResults should also log to stderr.

-

Methods

addResults

  • addResults(...results: Result[]): void
  • -

    Adds given result(s) representing structured findings by the function.

    -

    Parameters

    Returns void

deepCopy

delete

  • -

    Deletes all objects with the same kubernetesKey as any of the given objects.

    -

    Does not throw if given duplicates or keys which are not present in the Configs.

    -

    Parameters

    Returns void

deleteAll

  • deleteAll(): void

get

  • -

    Returns an array of objects matching the given Kind type predicate.

    -

    Casts to an array of Kind. May throw if isKind is incorrect.

    -

    The ordering of objects is deterministic.

    -

    Returned objects are pass-by-reference; mutating them results in changes being persisted.

    -

    Type parameters

    Parameters

    Returns Kind[]

getAll

  • -

    Returns an array of all the objects in this Configs.

    -

    The ordering of objects is deterministic.

    -

    Returned objects are pass-by-reference; mutating them results in changes being persisted.

    -

    Returns KubernetesObject[]

getFunctionConfig

getFunctionConfigMap

  • getFunctionConfigMap(): undefined | Map<string, string>
  • -

    Returns the map of data values if functionConfig is of kind ConfigMap.

    -

    Throws a FunctionConfigError if functionConfig is undefined OR -if the kind is not a v1/ConfigMap.

    -

    Returns undefined | Map<string, string>

getFunctionConfigValue

  • getFunctionConfigValue(key: string): undefined | string
  • -

    Returns the value for the given key if functionConfig is of kind ConfigMap.

    -

    Throws a FunctionConfigError if functionConfig kind is not a ConfigMap.

    -

    Returns undefined if functionConfig is undefined OR -if the ConfigMap has no such key in the 'data' section.

    -
    key

    key The key in the 'data' field in the ConfigMap object given as the functionConfig.

    -

    Parameters

    • key: string

    Returns undefined | string

getFunctionConfigValueOrThrow

  • getFunctionConfigValueOrThrow(key: string): string

getResults

  • -

    Get result(s) representing structured findings by the function.

    -

    Returns Result[]

groupBy

  • -

    Partitions the objects using the provided key function

    -

    The ordering of objects with the same key is deterministic.

    -

    Example: Partition configs by Namespace:

    -
    const configsByNamespace = configs.groupBy((o) => o.metadata.namespace)
    -
    -

    Parameters

    Returns [string, KubernetesObject[]][]

hasUnexpectedFunctionParameter

  • hasUnexpectedFunctionParameter(expectedDataKeys: string[]): undefined | string[]
  • -

    Detects if an unknown value has been provided to the config map

    -

    Parameters

    • expectedDataKeys: string[]
      -

      The set of keys expected in the Configs data

      -

    Returns undefined | string[]

    Returns undefined if the config map is undefined. Otherwise -returns a string[] containing the invalid keys. The string[] will be empty -if all of the config maps keys are members of the expecteKeys.

    -

insert

  • -

    Inserts objects into the Configs.

    -

    If another object already in Configs has the same kubernetesKey, replaces that one with the -given object.

    -

    If inserting multiple objects with the same kubernetesKey, discards all but the last one.

    -

    Does not preserve insertion order of the given objects.

    -

    Parameters

    Returns void

toResourceList

Legend

  • Constructor
  • Property
  • Method
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/api/classes/FunctionConfigError.html b/docs/api/classes/FunctionConfigError.html deleted file mode 100644 index 64757918..00000000 --- a/docs/api/classes/FunctionConfigError.html +++ /dev/null @@ -1,3 +0,0 @@ -FunctionConfigError | kpt-functions
Options
All
  • Public
  • Public/Protected
  • All
Menu

Class FunctionConfigError

-

Represents an error with the functionConfig used to parametrize the function.

-

Hierarchy

  • Error
    • FunctionConfigError

Index

Constructors

Constructors

constructor

Legend

  • Constructor
  • Property
  • Method
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/api/classes/ResourceList.html b/docs/api/classes/ResourceList.html deleted file mode 100644 index b110156d..00000000 --- a/docs/api/classes/ResourceList.html +++ /dev/null @@ -1,8 +0,0 @@ -ResourceList | kpt-functions
Options
All
  • Public
  • Public/Protected
  • All
Menu

Class ResourceList

-

ResourceList is the wire format for the output of the kpt function as defined by the spec: -https://github.com/kubernetes-sigs/kustomize/blob/master/cmd/config/docs/api-conventions/functions-spec.md

-

Hierarchy

  • ResourceList

Implements

Index

Constructors

constructor

Properties

Readonly apiVersion

apiVersion: "config.kubernetes.io/v1" = 'config.kubernetes.io/v1'

Readonly items

Readonly kind

kind: "ResourceList" = 'ResourceList'

Readonly metadata

metadata: { name: string } = ...

Type declaration

  • name: string

Optional Readonly results

results?: Result[]

Legend

  • Constructor
  • Property
  • Method
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/api/classes/TestRunner.html b/docs/api/classes/TestRunner.html deleted file mode 100644 index 0833a6a6..00000000 --- a/docs/api/classes/TestRunner.html +++ /dev/null @@ -1,38 +0,0 @@ -TestRunner | kpt-functions
Options
All
  • Public
  • Public/Protected
  • All
Menu

Class TestRunner

-

TestRunner makes it easy to write unit tests for kpt functions.

-

Hierarchy

  • TestRunner

Index

Constructors

constructor

Methods

assert

  • assert(input?: Configs, expectedOutput?: Configs | "unchanged", expectedErrorType?: new (...args: any[]) => Error, expectedErrorMessage?: string | RegExp): Promise<void>
  • -

    Runs the KptFunc and asserts the expected output or error.

    -

    Example usage:

    -
    const RUNNER = new TestRunner(myFunc);

    it('function is a NO OP', async () => {
    await RUNNER.assert());
    }; -
    -

    Parameters

    • input: Configs = ...
      -

      input Configs passed to the function. It is deep-copied before running the function. - If undefined, assumes an empty Configs.

      -
    • Optional expectedOutput: Configs | "unchanged"
      -

      expected resultant Configs after running the function regardless of success or failure. - Use 'unchanged' if the function is not expected to change input Configs.

      -
    • Optional expectedErrorType: new (...args: any[]) => Error
      -

      expected error type to be thrown.

      -
        • new (...args: any[]): Error
        • Parameters

          • Rest ...args: any[]

          Returns Error

    • Optional expectedErrorMessage: string | RegExp
      -

      expected message of expection to be thrown.

      -

    Returns Promise<void>

assertCallback

  • assertCallback(input?: Configs, expectedOutput?: Configs | "unchanged", expectedErrorType?: new (...args: any[]) => Error, expectedErrorMessage?: string | RegExp): () => Promise<void>
  • -

    Similar to assert method, but instead returns an assertion function that can be passed directly to 'it'.

    -

    Example usage:

    -
    const RUNNER = new TestRunner(myFunc);

    it('function is a NO OP', RUNNER.assertCallback()); -
    -

    Parameters

    • input: Configs = ...
      -

      input Configs passed to the function. It is deep-copied before running the function. - If undefined, assumes an empty Configs.

      -
    • Optional expectedOutput: Configs | "unchanged"
      -

      expected resultant Configs after running the function regardless of success or failure. - Use 'unchanged' if the function is not expected to change input Configs.

      -
    • Optional expectedErrorType: new (...args: any[]) => Error
      -

      expected error type to be thrown.

      -
        • new (...args: any[]): Error
        • Parameters

          • Rest ...args: any[]

          Returns Error

    • Optional expectedErrorMessage: string | RegExp
      -

      expected message of expection to be thrown.

      -

    Returns () => Promise<void>

      • (): Promise<void>
      • -

        Similar to assert method, but instead returns an assertion function that can be passed directly to 'it'.

        -

        Example usage:

        -
        const RUNNER = new TestRunner(myFunc);

        it('function is a NO OP', RUNNER.assertCallback()); -
        -

        Returns Promise<void>

Legend

  • Constructor
  • Property
  • Method
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/api/index.html b/docs/api/index.html deleted file mode 100644 index aababc67..00000000 --- a/docs/api/index.html +++ /dev/null @@ -1,64 +0,0 @@ -kpt-functions
Options
All
  • Public
  • Public/Protected
  • All
Menu

kpt-functions

Index

Type aliases

Json

Json: null | boolean | number | string | JsonArray | JsonMap
-

Any plain old JSON value according to ECMA-404.

-

Severity

Severity: "error" | "warn" | "info"
-

Severity of a configuration result.

-

Variables

ANNOTATION_PREFIX

ANNOTATION_PREFIX: "internal.config.kubernetes.io" = 'internal.config.kubernetes.io'

ID_ANNOTATION

ID_ANNOTATION: string = ...

LEGACY_ANNOTATION_PREFIX

LEGACY_ANNOTATION_PREFIX: "config.kubernetes.io" = 'config.kubernetes.io'

LEGACY_ID_ANNOTATION

LEGACY_ID_ANNOTATION: "config.k8s.io/id" = ...

LEGACY_SOURCE_INDEX_ANNOTATION

LEGACY_SOURCE_INDEX_ANNOTATION: string = ...

LEGACY_SOURCE_PATH_ANNOTATION

LEGACY_SOURCE_PATH_ANNOTATION: string = ...

SOURCE_INDEX_ANNOTATION

SOURCE_INDEX_ANNOTATION: string = ...

SOURCE_PATH_ANNOTATION

SOURCE_PATH_ANNOTATION: string = ...

Functions

addAnnotation

  • -

    Add an annotation to a KubernetesObject's metadata. Overwrites the previously existing annotation if it exists. -Return the resulting object.

    -

    Parameters

    • o: KubernetesObject
      -

      The object to add the annotation to.

      -
    • annotation: string
      -

      The annotation to set.

      -
    • value: string
      -

      The value to set the annotation to.

      -

    Returns KubernetesObject

addLabel

  • -

    Add a label to a KubernetesObject's metadata. Overwrites the previously existing label if it exists. -Return the resulting object.

    -

    Parameters

    • o: KubernetesObject
      -

      The object to add the label to.

      -
    • label: string
      -

      The label to set.

      -
    • value: string
      -

      The value to set the label to.

      -

    Returns KubernetesObject

configFileResult

  • configFileResult(message: string, path: string, severity?: Severity, tags?: {}): Result
  • -

    A result relating to a configuration file.

    -

    Parameters

    • message: string
    • path: string
    • severity: Severity = 'error'
    • Optional tags: {}
      • [key: string]: string

    Returns Result

generalResult

  • generalResult(message: string, severity?: Severity, tags?: {}): Result

getAnnotation

  • -

    Get the value of the object's annotation, or undefined if it is not set.

    -

    Parameters

    • o: KubernetesObject
      -

      The object to get the annotation from.

      -
    • annotation: string
      -

      The annotation to get.

      -

    Returns string | undefined

getLabel

  • -

    Get the value of the object's label, or undefined if it is not set.

    -

    Parameters

    • o: KubernetesObject
      -

      The object to get the label from.

      -
    • label: string
      -

      The label to get.

      -

    Returns string | undefined

isKubernetesObject

kubernetesKey

  • -

    A unique key for a Kubernetes object defined as tuple of (apiVersion, kind, namespace, name).

    -

    Parameters

    Returns string

kubernetesObjectResult

removeAnnotation

  • -

    Remove an annotation from a KubernetesObject's metadata. If the resulting metadata.annotations is empty, removes -it. Return the resulting object.

    -

    Parameters

    • o: KubernetesObject
      -

      The object to remove the annotation from.

      -
    • annotation: string
      -

      The annotation to remove.

      -

    Returns KubernetesObject

removeLabel

  • -

    Remove a label from a KubernetesObject's metadata. If the resulting metadata.labels is empty, removes -it. Return the resulting object.

    -

    Parameters

    • o: KubernetesObject
      -

      The object to remove the label from.

      -
    • label: string
      -

      The label to remove.

      -

    Returns KubernetesObject

run

  • -

    This is the main entrypoint for running a kpt function.

    -

    This method does not throw any errors and can be invoked at the top-level without getting -an unhandled promise rejection error.

    -

    Parameters

    Returns Promise<void>

runFnWithConfigs

Legend

  • Constructor
  • Property
  • Method
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/api/interfaces/FieldInfo.html b/docs/api/interfaces/FieldInfo.html deleted file mode 100644 index 25e04d36..00000000 --- a/docs/api/interfaces/FieldInfo.html +++ /dev/null @@ -1,3 +0,0 @@ -FieldInfo | kpt-functions
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface FieldInfo

-

Metadata about a specific field in a Kubernetes object.

-

Hierarchy

  • FieldInfo

Index

Properties

Optional currentValue

currentValue?: Json

path

path: string

Optional suggestedValue

suggestedValue?: Json

Legend

  • Constructor
  • Property
  • Method
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/api/interfaces/JsonArray.html b/docs/api/interfaces/JsonArray.html deleted file mode 100644 index 1713f622..00000000 --- a/docs/api/interfaces/JsonArray.html +++ /dev/null @@ -1,3 +0,0 @@ -JsonArray | kpt-functions
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface JsonArray

-

A plain old JSON array according to ECMA-404.

-

Hierarchy

  • Array<Json>
    • JsonArray

Legend

  • Constructor
  • Property
  • Method
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/api/interfaces/JsonMap.html b/docs/api/interfaces/JsonMap.html deleted file mode 100644 index b099f736..00000000 --- a/docs/api/interfaces/JsonMap.html +++ /dev/null @@ -1,3 +0,0 @@ -JsonMap | kpt-functions
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface JsonMap

-

A plain old JSON object/map according to ECMA-404.

-

Hierarchy

  • JsonMap

Indexable

[field: string]: Json

Legend

  • Constructor
  • Property
  • Method
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/api/interfaces/KptFunc.html b/docs/api/interfaces/KptFunc.html deleted file mode 100644 index aed6c5ef..00000000 --- a/docs/api/interfaces/KptFunc.html +++ /dev/null @@ -1,11 +0,0 @@ -KptFunc | kpt-functions
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface KptFunc

Hierarchy

  • KptFunc

Callable

  • KptFunc(configs: Configs): Promise<void>
  • -

    A function consumes and optionally mutates Kubernetes configurations using the given Configs object.

    -

    The function should:

    -
      -
    • Throw errors when encountering operational issues such as IO exceptions.
    • -
    • Avoid writing to stdout (e.g. using process.stdout) as it is used for chaining functions. -Use stderr instead.
    • -
    -

    Parameters

    Returns Promise<void>

Index

Properties

Properties

usage

usage: string
-

Usage message describing what the function does, how to use it, and how to configure it.

-

Legend

  • Constructor
  • Property
  • Method
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/api/interfaces/KubernetesObject.html b/docs/api/interfaces/KubernetesObject.html deleted file mode 100644 index a7a3c7c9..00000000 --- a/docs/api/interfaces/KubernetesObject.html +++ /dev/null @@ -1,3 +0,0 @@ -KubernetesObject | kpt-functions
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface KubernetesObject

-

Interface implemented by Kubernetes objects.

-

Hierarchy

  • KubernetesObject

Implemented by

Index

Properties

apiVersion

apiVersion: string

kind

kind: string

metadata

metadata: ObjectMeta

Legend

  • Constructor
  • Property
  • Method
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/api/interfaces/Result.html b/docs/api/interfaces/Result.html deleted file mode 100644 index 07148d9b..00000000 --- a/docs/api/interfaces/Result.html +++ /dev/null @@ -1,9 +0,0 @@ -Result | kpt-functions
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface Result

-

Result represents a configuration-related issue returned by a function.

-

It can be at the following granularities:

-
    -
  • A file containing multiple objects
  • -
  • A specific kubernetes object
  • -
  • A specific field of a kubernetes object
  • -
-

Hierarchy

  • Result

Index

Properties

Optional field

field?: FieldInfo

Optional file

file?: { index?: number; path?: string }

Type declaration

  • Optional index?: number
  • Optional path?: string

message

message: string

Optional resourceRef

resourceRef?: { apiVersion: string; kind: string; name: string; namespace: string }

Type declaration

  • apiVersion: string
  • kind: string
  • name: string
  • namespace: string

severity

severity: Severity

Optional tags

tags?: {}

Type declaration

  • [key: string]: string

Legend

  • Constructor
  • Property
  • Method
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/containerizing.md b/docs/containerizing.md new file mode 100644 index 00000000..b1844781 --- /dev/null +++ b/docs/containerizing.md @@ -0,0 +1,136 @@ +# Containerizing KRM Functions + +KRM functions are distributed as container images. This guide covers building +and running containerized functions. + +## Dockerfile + +The [krm-functions-catalog](https://github.com/kptdev/krm-functions-catalog) +provides a shared Dockerfile at `build/docker/go/Dockerfile` that all catalog +functions use. It accepts `BUILDER_IMAGE` and `BASE_IMAGE` as build args. + +For standalone functions or local development, use a multi-stage build with a +minimal base image. The function binary should be statically linked (no CGO) so +it can run on `scratch` or `distroless`: + +```dockerfile +FROM golang:1.26-alpine AS builder +ENV CGO_ENABLED=0 +WORKDIR /go/src/ +COPY go.mod go.sum ./ +RUN go mod download +COPY . . +RUN go build -o /usr/local/bin/function ./ + +FROM scratch +COPY --from=builder /usr/local/bin/function /usr/local/bin/function +ENTRYPOINT ["function"] +``` + +Key points: +- `CGO_ENABLED=0` produces a static binary that runs on `scratch`. +- The `scratch` base image has zero overhead — no shell, no OS packages. +- If you need TLS certificates (e.g., for network calls), use `gcr.io/distroless/static` instead of `scratch`. +- Copy only the binary to the final image to minimize size. + +### Alternative with distroless + +```dockerfile +FROM golang:1.26-alpine AS builder +ENV CGO_ENABLED=0 +WORKDIR /go/src/ +COPY go.mod go.sum ./ +RUN go mod download +COPY . . +RUN go build -o /usr/local/bin/function ./ + +FROM gcr.io/distroless/static:nonroot +COPY --from=builder /usr/local/bin/function /usr/local/bin/function +ENTRYPOINT ["function"] +``` + +## Building + +```bash +docker build -t ghcr.io/kptdev/krm-functions-catalog/my-function:v0.1 . +``` + +### Image Naming Convention + +Follow this pattern for function images: + +``` +ghcr.io/kptdev/krm-functions-catalog/{function-name}:{version} +``` + +Examples: +- `ghcr.io/kptdev/krm-functions-catalog/set-labels:v0.1` +- `ghcr.io/kptdev/krm-functions-catalog/enforce-namespace:v1.0` +- `ghcr.io/kptdev/krm-functions-catalog/generate-configmap:v0.3` + +Use semantic versioning for tags. Avoid `latest` in production pipelines. + +## Running + +KRM functions read from STDIN and write to STDOUT: + +```bash +docker run --rm -i ghcr.io/kptdev/krm-functions-catalog/my-function:v0.1 < input.yaml > output.yaml +``` + +### With file mode + +```bash +docker run --rm -v $(pwd):/data ghcr.io/kptdev/krm-functions-catalog/my-function:v0.1 /data/deployment.yaml +``` + +### Help and doc flags + +```bash +docker run --rm ghcr.io/kptdev/krm-functions-catalog/my-function:v0.1 --help +docker run --rm ghcr.io/kptdev/krm-functions-catalog/my-function:v0.1 --doc +``` + +## Using with kpt + +In a `Kptfile` pipeline, `kpt fn render` will pull the image from the registry +and run it against your package resources: + +```yaml +apiVersion: kpt.dev/v1 +kind: Kptfile +metadata: + name: my-package +pipeline: + mutators: + - image: ghcr.io/kptdev/krm-functions-catalog/set-labels:v0.1 + configMap: + app: my-app + validators: + - image: ghcr.io/kptdev/krm-functions-catalog/enforce-namespace:v1.0 + configMap: + namespace: production +``` + +Note: the image must be published and accessible from the machine running +`kpt fn render`. For local development, build the image locally first and it +will be used from the local Docker cache without pulling. + +## Tips + +- Keep images small — a typical Go KRM function image is 5–15 MB with `scratch`. +- Pin dependency versions in `go.mod` for reproducible builds. +- Use `.dockerignore` to exclude test data, docs, and other non-build files. +- Test the container locally before publishing: + ```bash + echo '{"apiVersion":"config.kubernetes.io/v1","kind":"ResourceList","items":[]}' | \ + docker run --rm -i ghcr.io/kptdev/krm-functions-catalog/my-function:v0.1 + ``` + +## Publishing + +Publishing function images to a registry is handled by the +[krm-functions-catalog](https://github.com/kptdev/krm-functions-catalog) +CI pipeline. See the catalog's +[CONTRIBUTING.md](https://github.com/kptdev/krm-functions-catalog/blob/main/CONTRIBUTING.md) +for the release workflow. \ No newline at end of file diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 6b76b1e2..00000000 --- a/docs/index.html +++ /dev/null @@ -1,5 +0,0 @@ - - -Redirecting to https://googlecontainertools.github.io/kpt - - \ No newline at end of file diff --git a/docs/interfaces.md b/docs/interfaces.md new file mode 100644 index 00000000..53a89d5b --- /dev/null +++ b/docs/interfaces.md @@ -0,0 +1,178 @@ +# Interfaces + +The SDK provides two interfaces for implementing KRM functions. Choose based on +what your function needs to do. + +## fn.Runner + +Use `fn.Runner` for **transformers** (mutators) and **validators**. This is the +recommended interface for most functions. + +```go +type Runner interface { + Run(context *Context, functionConfig *KubeObject, items KubeObjects, results *Results) bool +} +``` + +Characteristics: +- The SDK automatically parses `functionConfig` into your struct's exported fields (via JSON tags). +- You can **modify** existing items but cannot add or remove items from the slice. +- Return `true` for success, `false` for failure. +- Use `results` to report structured info/warning/error messages. + +### Example: Validator + +```go +var _ fn.Runner = &EnforceNamespace{} + +type EnforceNamespace struct { + Namespace string `json:"namespace"` +} + +func (r *EnforceNamespace) Run(ctx *fn.Context, functionConfig *fn.KubeObject, items fn.KubeObjects, results *fn.Results) bool { + for _, obj := range items { + if obj.GetNamespace() != r.Namespace { + results.Errorf("resource %s/%s has namespace %q, expected %q", + obj.GetKind(), obj.GetName(), obj.GetNamespace(), r.Namespace) + } + } + return results.ExitCode() == 0 +} + +func main() { + runner := fn.WithContext(context.Background(), &EnforceNamespace{}) + if err := fn.AsMain(runner); err != nil { + os.Exit(1) + } +} +``` + +### Example: Transformer (Mutator) + +```go +var _ fn.Runner = &SetAnnotations{} + +type SetAnnotations struct { + Annotations map[string]string `json:"annotations,omitempty"` +} + +func (r *SetAnnotations) Run(ctx *fn.Context, functionConfig *fn.KubeObject, items fn.KubeObjects, results *fn.Results) bool { + for _, obj := range items { + for k, v := range r.Annotations { + if err := obj.SetAnnotation(k, v); err != nil { + results.ErrorE(err) + } + } + } + return results.ExitCode() == 0 +} +``` + +## fn.ResourceListProcessor + +Use `fn.ResourceListProcessor` for **generators** and **complex functions** that +need full control over the ResourceList. + +```go +type ResourceListProcessor interface { + Process(rl *ResourceList) (bool, error) +} +``` + +Characteristics: +- Full access to `ResourceList.Items` — you can add, remove, or modify items. +- You must parse `functionConfig` manually from `rl.FunctionConfig`. +- You can modify `rl.Results` directly. +- Return `(true, nil)` for success, `(false, err)` for failure. + +### Example: Generator + +```go +type ConfigMapGenerator struct{} + +func (g *ConfigMapGenerator) Process(rl *fn.ResourceList) (bool, error) { + // Parse functionConfig manually + name, _, _ := rl.FunctionConfig.NestedString("metadata", "name") + + // Generate a new ConfigMap + cm := fn.NewEmptyKubeObject() + if err := cm.SetAPIVersion("v1"); err != nil { + return false, err + } + if err := cm.SetKind("ConfigMap"); err != nil { + return false, err + } + if err := cm.SetName(name + "-generated"); err != nil { + return false, err + } + if err := cm.SetNamespace("default"); err != nil { + return false, err + } + + // Add to items + rl.Items = append(rl.Items, cm) + return true, nil +} + +func main() { + if err := fn.AsMain(&ConfigMapGenerator{}); err != nil { + os.Exit(1) + } +} +``` + +### ResourceListProcessorFunc + +For simple cases, use the function adapter instead of defining a struct: + +```go +type ResourceListProcessorFunc func(rl *ResourceList) (bool, error) +``` + +Example: + +```go +func main() { + processor := fn.ResourceListProcessorFunc(func(rl *fn.ResourceList) (bool, error) { + for _, obj := range rl.Items { + if err := obj.SetLabel("managed-by", "my-function"); err != nil { + return false, err + } + } + return true, nil + }) + if err := fn.AsMain(processor); err != nil { + os.Exit(1) + } +} +``` + +## Choosing Between Interfaces + +| Capability | fn.Runner | fn.ResourceListProcessor | +|---|---|---| +| Auto-parse functionConfig | ✅ | ❌ (manual) | +| Modify existing items | ✅ | ✅ | +| Add new items | ❌ | ✅ | +| Remove items | ❌ | ✅ | +| Access full ResourceList | ❌ | ✅ | +| Best for | Transformers, Validators | Generators, Complex functions | + +## Wrapping a Runner + +`fn.Runner` is wrapped into a `ResourceListProcessor` internally using +`fn.WithContext`: + +```go +runner := fn.WithContext(context.Background(), &MyFunction{}) +// runner implements ResourceListProcessor and can be passed to fn.AsMain +``` + +This wrapper handles: +1. Parsing `functionConfig` into your struct fields +2. Calling your `Run` method with the parsed context +3. Collecting results and determining success/failure + +--- + +Next: [Testing](testing.md) — golden test patterns for verifying your function. diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 00000000..a917528b --- /dev/null +++ b/docs/testing.md @@ -0,0 +1,217 @@ +# Testing KRM Functions + +The SDK provides a golden test framework in `fn/testhelpers` for snapshot-based +testing of KRM functions. + +## Golden Test Pattern + +Golden tests compare function output against expected baseline files. This +approach catches regressions and makes it easy to review output changes. + +### Directory Structure + +``` +testdata/ +├── test-case-1/ +│ ├── _expected.yaml # Expected output (full ResourceList YAML) +│ ├── _fnconfig.yaml # FunctionConfig for this test case +│ └── resources.yaml # Input KRM resources +└── test-case-2/ + ├── _expected.yaml + ├── _fnconfig.yaml + └── resources.yaml +``` + +Conventions: +- Files prefixed with `_` are special — they are not included in the input items. +- `_fnconfig.yaml` contains the functionConfig passed to your function. +- `_expected.yaml` contains the expected ResourceList output. +- All other `.yaml` files in the directory are parsed as input resources. +- You can have multiple input files (e.g., `deployments.yaml`, `services.yaml`). + +### Writing a Golden Test + +```go +package main + +import ( + "context" + "testing" + + "github.com/kptdev/krm-functions-sdk/go/fn" + "github.com/kptdev/krm-functions-sdk/go/fn/testhelpers" +) + +func TestFunction(t *testing.T) { + runner := fn.WithContext(context.TODO(), &YourFunction{}) + testhelpers.RunGoldenTests(t, "testdata", runner) +} +``` + +`RunGoldenTests` will: +1. Discover all subdirectories under `testdata/`. +2. For each subdirectory, parse all non-`_` prefixed YAML files as input items. +3. Parse `_fnconfig.yaml` as the functionConfig. +4. Run your processor against the assembled ResourceList. +5. Compare the output against `_expected.yaml`. + +### Example Test Data + +The following example is illustrative — it shows what test data looks like for a +function that sets labels. The [`go/get-started/`](../go/get-started/) example +provides a minimal working skeleton you can build from. + +`testdata/add-labels/_fnconfig.yaml`: +```yaml +apiVersion: fn.kpt.dev/v1alpha1 +kind: SetLabels +metadata: + name: my-config +labels: + app: my-app +``` + +`testdata/add-labels/resources.yaml`: +```yaml +apiVersion: v1 +kind: Service +metadata: + name: my-service +spec: + selector: + app: my-app +``` + +`testdata/add-labels/_expected.yaml`: +```yaml +apiVersion: config.kubernetes.io/v1 +kind: ResourceList +items: +- apiVersion: v1 + kind: Service + metadata: + name: my-service + labels: + app: my-app + spec: + selector: + app: my-app +functionConfig: + apiVersion: fn.kpt.dev/v1alpha1 + kind: SetLabels + metadata: + name: my-config + labels: + app: my-app +results: +- message: updated labels + severity: info +``` + +## Running Tests + +From your function's module root (where `go.mod` lives): + +```bash +go test ./... +``` + +If the function output doesn't match `_expected.yaml`, the test fails with a +diff showing what changed. See [`go/get-started/`](../go/get-started/) for a +complete working example. + +## Updating Expected Output + +When your function's output changes intentionally, regenerate the expected files: + +```bash +WRITE_GOLDEN_OUTPUT=1 go test ./... +``` + +This overwrites all `_expected.yaml` files with the actual output. Review the +diffs in version control before committing. + +**Caution:** `WRITE_GOLDEN_OUTPUT` accepts whatever the function currently +produces as "correct." If the function has a bug, you've just blessed buggy +output. Golden tests verify *stability* (did the output change?), not +*correctness* (is the output right?). Always review the diffs carefully. +For correctness guarantees, complement golden tests with property-based tests +that assert invariants (e.g., "all resources have the expected label"). + +Note: other kpt ecosystem projects use different env var names for the same +purpose (`KPT_E2E_UPDATE_EXPECTED` in kpt, `UPDATE_GOLDEN_FILES` in porch). +`WRITE_GOLDEN_OUTPUT` is the standard for the SDK and catalog functions. + +## Testing a ResourceListProcessor + +`RunGoldenTests` accepts any `fn.ResourceListProcessor`, so it works with both +`fn.Runner` (wrapped via `fn.WithContext`) and direct `ResourceListProcessor` +implementations: + +```go +func TestGenerator(t *testing.T) { + testhelpers.RunGoldenTests(t, "testdata", &MyGenerator{}) +} +``` + +## Unit Testing Without Golden Files + +For simpler unit tests, you can construct a ResourceList directly: + +```go +func TestSetLabels(t *testing.T) { + input := []byte(` +apiVersion: config.kubernetes.io/v1 +kind: ResourceList +items: +- apiVersion: v1 + kind: ConfigMap + metadata: + name: test +functionConfig: + apiVersion: fn.kpt.dev/v1alpha1 + kind: SetLabels + labels: + env: prod +`) + runner := fn.WithContext(context.TODO(), &SetLabels{}) + output, err := fn.Run(runner, input) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + rl, err := fn.ParseResourceList(output) + if err != nil { + t.Fatalf("failed to parse output: %v", err) + } + + label, _, _ := rl.Items[0].NestedString("metadata", "labels", "env") + if label != "prod" { + t.Errorf("expected label env=prod, got %q", label) + } +} +``` + +## Tips + +- Keep test cases focused — one behavior per test directory. +- Use descriptive directory names (e.g., `empty-input`, `missing-namespace`, `multiple-resources`). +- The `_fnconfig.yaml` can be empty if your function doesn't require configuration. +- Golden tests catch unintentional formatting changes too, which helps maintain stable output. + +## End-to-End Testing + +The SDK's `testhelpers.RunGoldenTests` tests function logic in isolation — no +container, no kpt CLI. For full integration testing (container execution, +`kpt fn eval`/`kpt fn render` pipelines), the kpt repo provides a separate e2e +test runner at +[`pkg/test/runner`](https://github.com/kptdev/kpt/tree/main/pkg/test/runner). + +The e2e runner uses a different test structure (`.expected/` directories with +`config.yaml`, `diff.patch`, `results.yaml`) and is used by the +[krm-functions-catalog](https://github.com/kptdev/krm-functions-catalog) `tests/` +directory to validate functions running inside containers against `kpt fn render`. + +--- + +Next: [Containerizing](containerizing.md) — packaging your function as a container image. diff --git a/docs/tutorial.md b/docs/tutorial.md new file mode 100644 index 00000000..158bbc81 --- /dev/null +++ b/docs/tutorial.md @@ -0,0 +1,229 @@ +# Tutorial: Developing a KRM Function + +This tutorial walks through the end-to-end workflow for building a KRM function +using the Go SDK. By the end, you'll have a working function with embedded +documentation, golden tests, and support for `--help`, `--doc`, and standalone +file mode. + +For a complete working example, see [`go/get-started/`](../go/get-started/). + +## 1. Create Your Function + +A KRM function implements the `fn.Runner` interface: + +```go +type Runner interface { + Run(context *Context, functionConfig *KubeObject, items KubeObjects, results *Results) bool +} +``` + +Here's a minimal function that sets labels on all resources: + +```go +package main + +import ( + "context" + _ "embed" + "os" + + "github.com/kptdev/krm-functions-sdk/go/fn" +) + +//go:embed README.md +var readme []byte + +//go:embed metadata.yaml +var metadata []byte + +var _ fn.Runner = &SetLabels{} + +type SetLabels struct { + Labels map[string]string `json:"labels,omitempty"` +} + +func (r *SetLabels) Run(ctx *fn.Context, functionConfig *fn.KubeObject, items fn.KubeObjects, results *fn.Results) bool { + for _, obj := range items { + for k, v := range r.Labels { + if err := obj.SetLabel(k, v); err != nil { + results.ErrorE(err) + } + } + } + return results.ExitCode() == 0 +} + +func main() { + runner := fn.WithContext(context.Background(), &SetLabels{}) + if err := fn.AsMain(runner, fn.WithDocs(readme, metadata)); err != nil { + os.Exit(1) + } +} +``` + +Key points: +- Your struct fields are automatically populated from `functionConfig` (JSON unmarshaling). +- Return `true` for success, `false` for failure. +- Use `results` to report structured messages (info, warning, error). + +## 2. Embed Documentation with `//go:embed` + +The SDK uses Go's embed directive to bundle documentation into the binary. +Two files are needed: + +### README.md + +Use `` markers to define sections that `--help` and `--doc` extract: + + # set-labels + + + Set labels on all resources in the package. + + + + ## Usage + + The `set-labels` function adds or updates labels on all KRM resources. + It accepts a `SetLabels` functionConfig with a `labels` map. + + ### FunctionConfig + + ```yaml + apiVersion: fn.kpt.dev/v1alpha1 + kind: SetLabels + metadata: + name: my-config + labels: + app: my-app + env: production + ``` + + + + + + Set a single label on all resources: + + ```yaml + apiVersion: fn.kpt.dev/v1alpha1 + kind: SetLabels + labels: + team: platform + ``` + + + +### metadata.yaml + +```yaml +image: ghcr.io/kptdev/krm-functions-catalog/set-labels:v0.1 +description: Set labels on all resources +tags: + - mutator + - labels +sourceURL: https://github.com/kptdev/krm-functions/tree/main/functions/go/set-labels +examplePackageURLs: + - https://github.com/kptdev/krm-functions/tree/main/examples/set-labels-simple +license: Apache-2.0 +hidden: false +``` + +### Wire it up + +In your `main.go`: + +```go +//go:embed README.md +var readme []byte + +//go:embed metadata.yaml +var metadata []byte + +func main() { + runner := fn.WithContext(context.Background(), &SetLabels{}) + if err := fn.AsMain(runner, fn.WithDocs(readme, metadata)); err != nil { + os.Exit(1) + } +} +``` + +## 3. Running Your Function + +### Standard mode (STDIN/STDOUT) + +Pipe a ResourceList through your function: + +```bash +cat input.yaml | go run . > output.yaml +``` + +### Help mode + +View human-readable documentation: + +```bash +go run . --help +``` + +This prints the Short, Long, and Examples sections extracted from your README markers. + +### Doc mode + +Get machine-readable JSON documentation (consumed by `kpt fn doc` and catalog pipelines): + +```bash +go run . --doc +``` + +### File mode + +Process KRM files directly without constructing a ResourceList: + +```bash +go run . deployment.yaml service.yaml +``` + +This reads the YAML files, assembles them into a ResourceList with an empty +functionConfig, processes them, and writes the result to STDOUT. + +## 4. Testing with Golden Tests + +The SDK provides `testhelpers.RunGoldenTests` for snapshot-based testing. + +Create a test directory structure: + +``` +testdata/ +├── test-case-1/ +│ ├── _expected.yaml # Expected output (ResourceList YAML) +│ ├── _fnconfig.yaml # FunctionConfig for this test case +│ └── resources.yaml # Input resources +└── test-case-2/ + ├── _expected.yaml + ├── _fnconfig.yaml + └── resources.yaml +``` + +Write your test: + +```go +func TestFunction(t *testing.T) { + runner := fn.WithContext(context.TODO(), &SetLabels{}) + testhelpers.RunGoldenTests(t, "testdata", runner) +} +``` + +Update expected output after changes: + +```bash +WRITE_GOLDEN_OUTPUT=1 go test ./... +``` + +See [testing](testing.md) for more details. + +## 5. Next Steps + +- [Interfaces](interfaces.md) — when to use `fn.Runner` vs `fn.ResourceListProcessor` +- [Testing](testing.md) — golden test patterns in depth +- [Containerizing](containerizing.md) — packaging your function as a container image diff --git a/go/fn/examples/go.mod b/go/fn/examples/go.mod index 76e52b5a..a169c10f 100644 --- a/go/fn/examples/go.mod +++ b/go/fn/examples/go.mod @@ -2,18 +2,20 @@ module github.com/kptdev/krm-functions-sdk/go/fn/examples go 1.26.3 +// NOTE: replace directive is for in-repo development only. +// External consumers should use: require github.com/kptdev/krm-functions-sdk/go/fn v1.x.x replace github.com/kptdev/krm-functions-sdk/go/fn => ../ require ( - github.com/kptdev/krm-functions-sdk/go/fn v1.0.2 - k8s.io/api v0.36.1 + github.com/kptdev/krm-functions-sdk/go/fn v1.0.0 + k8s.io/api v0.34.1 k8s.io/apimachinery v0.36.1 sigs.k8s.io/kustomize/kyaml v0.21.1 ) require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/fxamacker/cbor/v2 v2.9.2 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-errors/errors v1.5.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-openapi/jsonpointer v0.23.1 // indirect @@ -30,6 +32,7 @@ require ( github.com/go-openapi/swag/stringutils v0.26.0 // indirect github.com/go-openapi/swag/typeutils v0.26.0 // indirect github.com/go-openapi/swag/yamlutils v0.26.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect github.com/google/gnostic-models v0.7.1 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kr/text v0.2.0 // indirect @@ -41,15 +44,15 @@ require ( github.com/xlab/treeprint v1.2.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/net v0.54.0 // indirect + golang.org/x/net v0.49.0 // indirect golang.org/x/sys v0.44.0 // indirect - golang.org/x/text v0.37.0 // indirect + golang.org/x/text v0.33.0 // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/inf.v0 v0.9.1 // indirect k8s.io/klog/v2 v2.140.0 // indirect k8s.io/kube-openapi v0.0.0-20260520065146-aa012df4f4af // indirect - k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 // indirect + k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v6 v6.4.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect ) diff --git a/go/fn/examples/go.sum b/go/fn/examples/go.sum index 4e1a9f1e..e9891fbd 100644 --- a/go/fn/examples/go.sum +++ b/go/fn/examples/go.sum @@ -3,8 +3,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fxamacker/cbor/v2 v2.9.2 h1:X4Ksno9+x3cz0TZv69ec1hxP/+tymuR8PXQJyDwfh78= -github.com/fxamacker/cbor/v2 v2.9.2/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= @@ -43,6 +43,8 @@ github.com/go-openapi/testify/enable/yaml/v2 v2.4.2 h1:5zRca5jw7lzVREKCZVNBpysDN github.com/go-openapi/testify/enable/yaml/v2 v2.4.2/go.mod h1:XVevPw5hUXuV+5AkI1u1PeAm27EQVrhXTTCPAF85LmE= github.com/go-openapi/testify/v2 v2.4.2 h1:tiByHpvE9uHrrKjOszax7ZvKB7QOgizBWGBLuq0ePx4= github.com/go-openapi/testify/v2 v2.4.2/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -50,6 +52,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -82,16 +86,43 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= -golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -102,23 +133,25 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.36.1 h1:XbL/EMj8K2aJpJtePmqUyQMsM0D4QI2pvl7YKJ20FTY= -k8s.io/api v0.36.1/go.mod h1:KOWo4ey3TINlXjeHVuwB3i+tXXnu+UcwFBHlI/9dvEo= +k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= +k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= k8s.io/apimachinery v0.36.1 h1:G63Gjx2W+q0YD+72Vo8oY0nDnePVwnuzTmmy5ENrVSA= k8s.io/apimachinery v0.36.1/go.mod h1:ibYOR00vW/I1kzvi5SF0dRuJ52BvKtfvRdOn35GPQ+8= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= k8s.io/kube-openapi v0.0.0-20260520065146-aa012df4f4af h1:zLXA2Irn14q2/06WMkxViyr7YCPUO2lJ0QYE9Juy5vA= k8s.io/kube-openapi v0.0.0-20260520065146-aa012df4f4af/go.mod h1:V/QaCUYDa+0QpcHhVVc5l99Uz56wEMEXBSj9oCDkNDY= -k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 h1:wU4tMEhLGgIbLvXQb1cfN+EcM0wf7zC6CPF+C79jroc= -k8s.io/utils v0.0.0-20260507154919-ff6756f316d2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +pgregory.net/rapid v1.3.0 h1:vBvO0VSqti75J1jjYqpgPNBLKMd1+gxa9fYo7vk/Exc= +pgregory.net/rapid v1.3.0/go.mod h1:dPlE4OBBxgXPqkP79flB6sJL1dx5azpI7HQ9MY9Z7uk= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7fI= sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v6 v6.4.0 h1:qmp2e3ZfFi1/jJbDGpD4mt3wyp6PE1NfKHCYLqgNQJo= -sigs.k8s.io/structured-merge-diff/v6 v6.4.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/go/fn/internal/docs/metadata_test.go b/go/fn/internal/docs/metadata_test.go index 6f4aaf70..c91851af 100644 --- a/go/fn/internal/docs/metadata_test.go +++ b/go/fn/internal/docs/metadata_test.go @@ -58,7 +58,7 @@ func genMetadata() *rapid.Generator[Metadata] { // --- Unit Tests for ParseMetadata --- func TestParseMetadata_CompleteValid(t *testing.T) { - input := []byte(`image: gcr.io/kpt-fn/set-labels:v0.1 + input := []byte(`image: ghcr.io/kptdev/krm-functions-catalog/set-labels:v0.1 description: Set labels on all resources tags: - mutator @@ -75,8 +75,8 @@ hidden: false t.Fatalf("ParseMetadata returned unexpected error: %v", err) } - if m.Image != "gcr.io/kpt-fn/set-labels:v0.1" { - t.Errorf("Image = %q, want %q", m.Image, "gcr.io/kpt-fn/set-labels:v0.1") + if m.Image != "ghcr.io/kptdev/krm-functions-catalog/set-labels:v0.1" { + t.Errorf("Image = %q, want %q", m.Image, "ghcr.io/kptdev/krm-functions-catalog/set-labels:v0.1") } if m.Description != "Set labels on all resources" { t.Errorf("Description = %q, want %q", m.Description, "Set labels on all resources") @@ -139,10 +139,10 @@ func TestParseMetadata_PartialFields(t *testing.T) { }{ { name: "only image field", - input: []byte("image: gcr.io/kpt-fn/my-func:v1\n"), + input: []byte("image: ghcr.io/kptdev/krm-functions-catalog/my-func:v1\n"), validate: func(t *testing.T, m Metadata) { - if m.Image != "gcr.io/kpt-fn/my-func:v1" { - t.Errorf("Image = %q, want %q", m.Image, "gcr.io/kpt-fn/my-func:v1") + if m.Image != "ghcr.io/kptdev/krm-functions-catalog/my-func:v1" { + t.Errorf("Image = %q, want %q", m.Image, "ghcr.io/kptdev/krm-functions-catalog/my-func:v1") } if m.Description != "" { t.Errorf("Description = %q, want empty", m.Description) @@ -215,17 +215,17 @@ func TestParseMetadata_HiddenFieldPropagation(t *testing.T) { }{ { name: "hidden true", - input: []byte("image: gcr.io/kpt-fn/my-func:v1\nhidden: true\n"), + input: []byte("image: ghcr.io/kptdev/krm-functions-catalog/my-func:v1\nhidden: true\n"), hidden: true, }, { name: "hidden false explicit", - input: []byte("image: gcr.io/kpt-fn/my-func:v1\nhidden: false\n"), + input: []byte("image: ghcr.io/kptdev/krm-functions-catalog/my-func:v1\nhidden: false\n"), hidden: false, }, { name: "hidden absent defaults to false", - input: []byte("image: gcr.io/kpt-fn/my-func:v1\n"), + input: []byte("image: ghcr.io/kptdev/krm-functions-catalog/my-func:v1\n"), hidden: false, }, } diff --git a/go/fn/internal/docs/render_test.go b/go/fn/internal/docs/render_test.go index 7aa793b1..6cab63b5 100644 --- a/go/fn/internal/docs/render_test.go +++ b/go/fn/internal/docs/render_test.go @@ -265,10 +265,10 @@ func TestRenderHelp_FullSectionsAndMetadata(t *testing.T) { sections := Sections{ Short: "Set labels on all resources", Long: "The set-labels function adds or updates labels on all resources in the package.", - Examples: " kpt fn eval --image gcr.io/kpt-fn/set-labels:v0.1 -- label_name=label_value", + Examples: " kpt fn eval --image ghcr.io/kptdev/krm-functions-catalog/set-labels:v0.1 -- label_name=label_value", } meta := Metadata{ - Image: "gcr.io/kpt-fn/set-labels:v0.1", + Image: "ghcr.io/kptdev/krm-functions-catalog/set-labels:v0.1", Description: "Set labels on all resources", Tags: []string{"mutator", "labels"}, } @@ -320,10 +320,10 @@ func TestRenderDoc_ValidJSON_AllFields(t *testing.T) { sections := Sections{ Short: "Set labels on all resources", Long: "The set-labels function adds or updates labels.", - Examples: " kpt fn eval --image gcr.io/kpt-fn/set-labels:v0.1", + Examples: " kpt fn eval --image ghcr.io/kptdev/krm-functions-catalog/set-labels:v0.1", } meta := Metadata{ - Image: "gcr.io/kpt-fn/set-labels:v0.1", + Image: "ghcr.io/kptdev/krm-functions-catalog/set-labels:v0.1", Description: "Set labels on all resources", Tags: []string{"mutator", "labels"}, SourceURL: "https://github.com/kptdev/krm-functions/tree/main/functions/go/set-labels", diff --git a/go/fn/run_flags_test.go b/go/fn/run_flags_test.go index 85e02a61..6fa732f9 100644 --- a/go/fn/run_flags_test.go +++ b/go/fn/run_flags_test.go @@ -145,7 +145,7 @@ The set-labels function adds labels to all resources. kpt fn eval --image set-labels:v0.1 `) - meta := []byte(`image: gcr.io/kpt-fn/set-labels:v0.1 + meta := []byte(`image: ghcr.io/kptdev/krm-functions-catalog/set-labels:v0.1 description: Set labels on all resources `) @@ -173,7 +173,7 @@ Set labels Long description here. `) - meta := []byte(`image: gcr.io/kpt-fn/set-labels:v0.1 + meta := []byte(`image: ghcr.io/kptdev/krm-functions-catalog/set-labels:v0.1 description: Set labels on all resources tags: - mutator @@ -193,7 +193,7 @@ license: Apache-2.0 // Verify fields are populated assert.Equal(t, "Set labels", docOutput.Short) assert.Equal(t, "Long description here.", docOutput.Long) - assert.Equal(t, "gcr.io/kpt-fn/set-labels:v0.1", docOutput.Image) + assert.Equal(t, "ghcr.io/kptdev/krm-functions-catalog/set-labels:v0.1", docOutput.Image) assert.Equal(t, "Set labels on all resources", docOutput.Description) assert.Equal(t, []string{"mutator"}, docOutput.Tags) assert.Equal(t, "Apache-2.0", docOutput.License) @@ -222,7 +222,7 @@ func TestAsMain_DocFlag_HiddenField(t *testing.T) { Hidden function `) - meta := []byte(`image: gcr.io/kpt-fn/hidden-fn:v0.1 + meta := []byte(`image: ghcr.io/kptdev/krm-functions-catalog/hidden-fn:v0.1 description: A hidden function hidden: true `) @@ -237,7 +237,7 @@ hidden: true require.NoError(t, err, "--doc output should be valid JSON") assert.True(t, docOutput.Hidden, "hidden:true should propagate to JSON output") - assert.Equal(t, "gcr.io/kpt-fn/hidden-fn:v0.1", docOutput.Image) + assert.Equal(t, "ghcr.io/kptdev/krm-functions-catalog/hidden-fn:v0.1", docOutput.Image) } // TestAsMain_DocFlag_InvalidMetadataYAML verifies that invalid metadata YAML diff --git a/go/get-started/go.mod b/go/get-started/go.mod index 8110e34a..2ce88914 100644 --- a/go/get-started/go.mod +++ b/go/get-started/go.mod @@ -2,9 +2,11 @@ module github.com/kptdev/krm-functions-sdk/go/get-started go 1.26.3 +// NOTE: replace directive is for in-repo development only. +// External consumers should use: require github.com/kptdev/krm-functions-sdk/go/fn v1.x.x replace github.com/kptdev/krm-functions-sdk/go/fn => ../fn -require github.com/kptdev/krm-functions-sdk/go/fn v1.0.2 +require github.com/kptdev/krm-functions-sdk/go/fn v1.0.0 require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect diff --git a/go/get-started/go.sum b/go/get-started/go.sum index 8ed5c62b..9389024f 100644 --- a/go/get-started/go.sum +++ b/go/get-started/go.sum @@ -77,6 +77,8 @@ k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= k8s.io/kube-openapi v0.0.0-20260520065146-aa012df4f4af h1:zLXA2Irn14q2/06WMkxViyr7YCPUO2lJ0QYE9Juy5vA= k8s.io/kube-openapi v0.0.0-20260520065146-aa012df4f4af/go.mod h1:V/QaCUYDa+0QpcHhVVc5l99Uz56wEMEXBSj9oCDkNDY= +pgregory.net/rapid v1.3.0 h1:vBvO0VSqti75J1jjYqpgPNBLKMd1+gxa9fYo7vk/Exc= +pgregory.net/rapid v1.3.0/go.mod h1:dPlE4OBBxgXPqkP79flB6sJL1dx5azpI7HQ9MY9Z7uk= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7fI= diff --git a/go/get-started/main.go b/go/get-started/main.go index b772ec87..9efdc9f5 100644 --- a/go/get-started/main.go +++ b/go/get-started/main.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt Authors +// Copyright 2022, 2026 The kpt Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,11 +16,18 @@ package main import ( "context" + _ "embed" "os" "github.com/kptdev/krm-functions-sdk/go/fn" ) +//go:embed README.md +var readme []byte + +//go:embed metadata.yaml +var metadata []byte + var _ fn.Runner = &YourFunction{} // TODO: Change to your functionConfig "Kind" name. @@ -41,7 +48,7 @@ func (r *YourFunction) Run(ctx *fn.Context, functionConfig *fn.KubeObject, items func main() { runner := fn.WithContext(context.Background(), &YourFunction{}) - if err := fn.AsMain(runner); err != nil { + if err := fn.AsMain(runner, fn.WithDocs(readme, metadata)); err != nil { os.Exit(1) } } diff --git a/go/get-started/metadata.yaml b/go/get-started/metadata.yaml new file mode 100644 index 00000000..ce0f2264 --- /dev/null +++ b/go/get-started/metadata.yaml @@ -0,0 +1,8 @@ +image: ghcr.io/kptdev/krm-functions-catalog/some-function-name:v0.1 +description: Explain what this function does in one or two sentences. +tags: + - mutator +sourceURL: https://github.com/kptdev/krm-functions-sdk/tree/main/go/get-started +examplePackageURLs: [] +license: Apache-2.0 +hidden: false diff --git a/go/get-started/testdata/test1/_expected.yaml b/go/get-started/testdata/noop-passthrough/_expected.yaml similarity index 100% rename from go/get-started/testdata/test1/_expected.yaml rename to go/get-started/testdata/noop-passthrough/_expected.yaml diff --git a/go/get-started/testdata/test1/_fnconfig.yaml b/go/get-started/testdata/noop-passthrough/_fnconfig.yaml similarity index 100% rename from go/get-started/testdata/test1/_fnconfig.yaml rename to go/get-started/testdata/noop-passthrough/_fnconfig.yaml diff --git a/go/get-started/testdata/test1/resources.yaml b/go/get-started/testdata/noop-passthrough/resources.yaml similarity index 100% rename from go/get-started/testdata/test1/resources.yaml rename to go/get-started/testdata/noop-passthrough/resources.yaml From 98e175f707ab4014fe723078fd8bddf229df8834 Mon Sep 17 00:00:00 2001 From: Fiachra Corcoran Date: Fri, 29 May 2026 08:52:34 +0100 Subject: [PATCH 8/8] docs: address PR review comments for text improvements Apply text suggestions from mpgreaves review: - Expand contractions (we'd, you'll, shouldn't, it's, you've, doesn't) - Add missing periods to sentence fragments - Improve sentence structure and clarity - Use more formal phrasing in documentation Assisted-by: Kiro:Auto [code-editing] Signed-off-by: Fiachra Corcoran --- CONTRIBUTING.md | 8 ++++---- docs/containerizing.md | 6 +++--- docs/interfaces.md | 8 ++++---- docs/testing.md | 10 +++++----- docs/tutorial.md | 4 ++-- go/fn/internal/docs/render.go | 2 +- go/fn/run_flags_test.go | 4 ++-- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6a6ad0cb..3de147b7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing to krm-functions-sdk -We'd love to accept your contributions to this project. There are just a few +We would love to accept your contributions to this project. There are just a few small guidelines you need to follow. ## Developer Certificate of Origin (DCO) @@ -30,11 +30,11 @@ All files should have the copyright notice. // limitations under the License. ``` -If the file has never been modified: use the creation year only +If the file has never been modified: use the creation year only. * Example: `Copyright 2026 The kpt Authors` -If the file has been modified: use a year range from creation to last modification +If the file has been modified: use a year range from creation to last modification. * Example: `Copyright 2024-2026 The kpt Authors` @@ -57,7 +57,7 @@ make tidy ``` The CI script (`hack/ci-validate-go.sh`) runs `make go` and then checks that no -files were modified. If CI fails with "files are not to date", run `make go` +files have been modified. If the CI script fails with "files are not to date", run `make go` locally and commit the changes. ## Code reviews diff --git a/docs/containerizing.md b/docs/containerizing.md index b1844781..de73504f 100644 --- a/docs/containerizing.md +++ b/docs/containerizing.md @@ -6,11 +6,11 @@ and running containerized functions. ## Dockerfile The [krm-functions-catalog](https://github.com/kptdev/krm-functions-catalog) -provides a shared Dockerfile at `build/docker/go/Dockerfile` that all catalog +provides a shared Dockerfile at `build/docker/go/Dockerfile` that all the catalog functions use. It accepts `BUILDER_IMAGE` and `BASE_IMAGE` as build args. For standalone functions or local development, use a multi-stage build with a -minimal base image. The function binary should be statically linked (no CGO) so +minimal base image. The function binary should be statically linked (no CGO), so it can run on `scratch` or `distroless`: ```dockerfile @@ -113,7 +113,7 @@ pipeline: ``` Note: the image must be published and accessible from the machine running -`kpt fn render`. For local development, build the image locally first and it +`kpt fn render`. For local development, build the image locally first. It will be used from the local Docker cache without pulling. ## Tips diff --git a/docs/interfaces.md b/docs/interfaces.md index 53a89d5b..d133fd5f 100644 --- a/docs/interfaces.md +++ b/docs/interfaces.md @@ -1,7 +1,7 @@ # Interfaces -The SDK provides two interfaces for implementing KRM functions. Choose based on -what your function needs to do. +The SDK provides two interfaces for implementing KRM functions. Choose according +to your function requirements. ## fn.Runner @@ -16,7 +16,7 @@ type Runner interface { Characteristics: - The SDK automatically parses `functionConfig` into your struct's exported fields (via JSON tags). -- You can **modify** existing items but cannot add or remove items from the slice. +- You can **modify** existing items, but you cannot add or remove items from the slice. - Return `true` for success, `false` for failure. - Use `results` to report structured info/warning/error messages. @@ -168,7 +168,7 @@ runner := fn.WithContext(context.Background(), &MyFunction{}) // runner implements ResourceListProcessor and can be passed to fn.AsMain ``` -This wrapper handles: +This wrapper handles the following: 1. Parsing `functionConfig` into your struct fields 2. Calling your `Run` method with the parsed context 3. Collecting results and determining success/failure diff --git a/docs/testing.md b/docs/testing.md index a917528b..fe27f140 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -5,7 +5,7 @@ testing of KRM functions. ## Golden Test Pattern -Golden tests compare function output against expected baseline files. This +Golden tests compare the function output against the expected baseline files. This approach catches regressions and makes it easy to review output changes. ### Directory Structure @@ -116,7 +116,7 @@ From your function's module root (where `go.mod` lives): go test ./... ``` -If the function output doesn't match `_expected.yaml`, the test fails with a +If the function output does not match `_expected.yaml`, the test fails with a diff showing what changed. See [`go/get-started/`](../go/get-started/) for a complete working example. @@ -132,7 +132,7 @@ This overwrites all `_expected.yaml` files with the actual output. Review the diffs in version control before committing. **Caution:** `WRITE_GOLDEN_OUTPUT` accepts whatever the function currently -produces as "correct." If the function has a bug, you've just blessed buggy +produces as "correct." If the function has a bug, you have just blessed buggy output. Golden tests verify *stability* (did the output change?), not *correctness* (is the output right?). Always review the diffs carefully. For correctness guarantees, complement golden tests with property-based tests @@ -197,7 +197,7 @@ functionConfig: - Keep test cases focused — one behavior per test directory. - Use descriptive directory names (e.g., `empty-input`, `missing-namespace`, `multiple-resources`). - The `_fnconfig.yaml` can be empty if your function doesn't require configuration. -- Golden tests catch unintentional formatting changes too, which helps maintain stable output. +- Golden tests also catch unintentional formatting changes. This helps to maintain a stable output. ## End-to-End Testing @@ -210,7 +210,7 @@ test runner at The e2e runner uses a different test structure (`.expected/` directories with `config.yaml`, `diff.patch`, `results.yaml`) and is used by the [krm-functions-catalog](https://github.com/kptdev/krm-functions-catalog) `tests/` -directory to validate functions running inside containers against `kpt fn render`. +directory to validate the functions running inside the containers against `kpt fn render`. --- diff --git a/docs/tutorial.md b/docs/tutorial.md index 158bbc81..ca2d151d 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -1,7 +1,7 @@ # Tutorial: Developing a KRM Function This tutorial walks through the end-to-end workflow for building a KRM function -using the Go SDK. By the end, you'll have a working function with embedded +using the Go SDK. By the end, you will have a working function with embedded documentation, golden tests, and support for `--help`, `--doc`, and standalone file mode. @@ -17,7 +17,7 @@ type Runner interface { } ``` -Here's a minimal function that sets labels on all resources: +Here is a minimal function that sets labels on all the resources: ```go package main diff --git a/go/fn/internal/docs/render.go b/go/fn/internal/docs/render.go index f7185fac..e6c17839 100644 --- a/go/fn/internal/docs/render.go +++ b/go/fn/internal/docs/render.go @@ -77,7 +77,7 @@ func isMetadataEmpty(meta Metadata) bool { } // RenderDoc writes JSON-encoded DocOutput to w. -// Returns error only if JSON encoding fails (shouldn't happen with these types). +// Returns error only if JSON encoding fails (should not happen with these types). func RenderDoc(w io.Writer, sections Sections, meta Metadata) error { out := DocOutput{ Short: sections.Short, diff --git a/go/fn/run_flags_test.go b/go/fn/run_flags_test.go index 6fa732f9..dfc5f5e1 100644 --- a/go/fn/run_flags_test.go +++ b/go/fn/run_flags_test.go @@ -91,7 +91,7 @@ func setArgs(t *testing.T, args []string) { func TestAsMain_HelpFlag_ExitsZero(t *testing.T) { setArgs(t, []string{"cmd", "--help"}) - // Close stdin to prove it's not read — if AsMain tries to read STDIN, + // Close stdin to prove it is not read — if AsMain tries to read STDIN, // it would get an error or EOF immediately. origStdin := os.Stdin r, w, err := os.Pipe() @@ -185,7 +185,7 @@ license: Apache-2.0 assert.NoError(t, err, "--doc should return nil (exit 0)") }) - // Verify it's valid JSON + // Verify that it is a valid JSON var docOutput docs.DocOutput err := json.Unmarshal([]byte(output), &docOutput) require.NoError(t, err, "--doc output should be valid JSON")