From 226475772e80a729ebea8d12d453bb1c335bbc37 Mon Sep 17 00:00:00 2001 From: David de Regt Date: Fri, 27 Sep 2024 21:51:59 -0400 Subject: [PATCH] feat: Add support for native modules Tested with stencil-sentry --- .github/workflows/build-release.yml | 21 ++++ .gitignore | 4 + .mise.toml | 2 + .vscode/settings.json | 6 + README.md | 1 + manifest.yaml | 8 ++ stencil.lock | 6 +- .../.github/workflows/build-release.yml.tpl | 97 +++++++++++++++ templates/.gitignore.tpl | 4 + templates/.golangci.yml.tpl | 114 ++++++++++++++++++ templates/.goreleaser.yaml.tpl | 48 ++++++++ templates/.mise.toml.tpl | 39 ++++++ templates/.vscode/extensions.json.tpl | 3 + templates/.vscode/settings.json.tpl | 6 + templates/cmd/plugin/instance.go.tpl | 67 ++++++++++ templates/cmd/plugin/instance_test.go.tpl | 33 +++++ templates/cmd/plugin/plugin.go.tpl | 29 +++++ templates/go.mod.tpl | 18 +++ templates/manifest.yaml.tpl | 5 + templates/package.json.tpl | 3 + 20 files changed, 511 insertions(+), 3 deletions(-) create mode 100644 templates/.golangci.yml.tpl create mode 100644 templates/.goreleaser.yaml.tpl create mode 100644 templates/cmd/plugin/instance.go.tpl create mode 100644 templates/cmd/plugin/instance_test.go.tpl create mode 100644 templates/cmd/plugin/plugin.go.tpl create mode 100644 templates/go.mod.tpl diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 614a4d3..8d73fac 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -11,6 +11,11 @@ on: - 'main' paths: - '.github/workflows/build-release.yml' + - 'cmd/**' + - 'pkg/**' + - 'internal/**' + - 'go.mod' + - 'go.sum' - 'templates/**' - 'tests/**' - 'manifest.yaml' @@ -56,6 +61,9 @@ jobs: with: persist-credentials: false token: ${{ steps.generate_token.outputs.token }} + # These two required for builds to successfully amend commits + ref: ${{ github.head_ref }} + fetch-depth: 2 - name: Install Tool Versions uses: jdx/mise-action@052520c41a328779551db19a76697ffa34f3eabc with: @@ -74,6 +82,9 @@ jobs: run: mise run buildtest env: GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} + ## <> + + ## <> - name: Run Tests run: mise run runtest env: @@ -95,7 +106,13 @@ jobs: - name: Checkout uses: actions/checkout@v4 with: + fetch-depth: 0 + fetch-tags: true token: ${{ secrets.GITHUB_TOKEN }} + - name: Set git User + run: | + git config user.name github-actions + git config user.email github-actions@github.com - name: Install Tool Versions uses: jdx/mise-action@052520c41a328779551db19a76697ffa34f3eabc with: @@ -108,3 +125,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: npx semantic-release + + ## <> + + ## <> diff --git a/.gitignore b/.gitignore index e73eb47..c179996 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ *.dll *.so *.dylib +__debug_bin* # Editor files *.swp @@ -36,6 +37,9 @@ vendor # macOS .DS_Store +# Currently a build artifact for native modules +CHANGELOG.md + # Ignore literally everything under the tests directory (except the README) -- specifically include the dir and stencil.yaml file # for each test case in the ignores block below tests/* diff --git a/.mise.toml b/.mise.toml index 6041558..f0cc1dd 100644 --- a/.mise.toml +++ b/.mise.toml @@ -2,6 +2,8 @@ nodejs = "22" yarn = "1.22.22" + + [tasks.cleantest] description = "Helper to clean the test directory" dir = "tests" diff --git a/.vscode/settings.json b/.vscode/settings.json index cc06081..d059932 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,10 @@ { + "go.lintTool": "golangci-lint", + "go.lintFlags": [ + "--fast" + ], + "go.formatTool": "goimports", + "go.useLanguageServer": true, "files.trimTrailingWhitespace": true, "files.exclude": { "**/bin": true, diff --git a/README.md b/README.md index 6a619e8..0197f11 100644 --- a/README.md +++ b/README.md @@ -10,4 +10,5 @@ In the `arguments` section of the `manifest.yaml` file, you can specify the foll | -------------------- | --------------- | -------------------------------------------------------------- | | `description` | Required | Friendly-but-short description string for the frontend app | | `owner` | Required | Pod or Portfolio ID for the owner of the frontend app | +| `nativeModule` | `false` | Does this template module include native module golang code | | `buildAndTestRunner` | `ubuntu-latest` | The github actions runner to use for the build and test CI job | diff --git a/manifest.yaml b/manifest.yaml index d359ad8..b656a10 100644 --- a/manifest.yaml +++ b/manifest.yaml @@ -20,11 +20,19 @@ arguments: schema: type: string ## <> + nativeModule: + required: false + description: Does this template module include native module golang code + schema: + type: boolean buildAndTestRunner: required: false description: The github actions runner to use for the build and test CI job schema: type: string + packageJsonDeps: + required: false + description: The package.json dependencies to add to the generated package.json ## <> modules: - name: github.com/udemy/eng-team-management diff --git a/stencil.lock b/stencil.lock index 6e11896..89c2007 100644 --- a/stencil.lock +++ b/stencil.lock @@ -1,10 +1,10 @@ -version: 0.10.0 +version: 0.10.2 modules: - name: github.com/udemy/eng-team-management url: https://github.com/udemy/eng-team-management version: - commit: 4d5cf0c23526a24ccc553310dba01b1fbddc0911 - tag: v0.1.8 + commit: 8eacc46158f50de1a13f754b4e5634749de3252c + tag: v0.2.5 - name: github.com/udemy/stencil-templatemodule url: ./ version: diff --git a/templates/.github/workflows/build-release.yml.tpl b/templates/.github/workflows/build-release.yml.tpl index 42213f0..8473987 100644 --- a/templates/.github/workflows/build-release.yml.tpl +++ b/templates/.github/workflows/build-release.yml.tpl @@ -11,10 +11,17 @@ on: - 'main' paths: - '.github/workflows/build-release.yml' + - 'cmd/**' + - 'pkg/**' + - 'internal/**' + - 'go.mod' + - 'go.sum' - 'templates/**' - 'tests/**' - 'manifest.yaml' - 'stencil.yaml' + - '.goreleaser.yaml' + - '.mise.toml' env: GH_ROLE_ARN: arn:aws:iam::602046956384:role/GithubActions-github-actions-services-repos-Role @@ -56,6 +63,9 @@ jobs: with: persist-credentials: false token: {{ "${{ steps.generate_token.outputs.token }}" }} + # These two required for builds to successfully amend commits + ref: {{ "${{ github.head_ref }}" }} + fetch-depth: 2 - name: Install Tool Versions uses: jdx/mise-action@052520c41a328779551db19a76697ffa34f3eabc with: @@ -67,6 +77,36 @@ jobs: with: github-token: {{ "${{ github.token }}" }} version: 'latest' +{{- if stencil.Arg "nativeModule" }} + - name: Get Go directories + id: go + run: | + echo "cache_dir=$(go env GOCACHE)" >> "$GITHUB_OUTPUT" + echo "mod_cache_dir=$(go env GOMODCACHE)" >> "$GITHUB_OUTPUT" + - uses: actions/cache@v4 + with: + path: {{ "${{ steps.go.outputs.cache_dir }}" }} + key: {{ "${{ github.workflow }}-${{ runner.os }}-go-build-cache-${{ hashFiles('**/go.sum') }}" }} + - uses: actions/cache@v4 + with: + path: {{ "${{ steps.go.outputs.mod_cache_dir }}" }} + key: {{ "${{ github.workflow }}-${{ runner.os }}-go-mod-cache-${{ hashFiles('go.sum') }}" }} + - name: Lint + uses: golangci/golangci-lint-action@v4 + with: + version: latest + # We already use setup-go's pkg cache and actions/cache's build cache, so don't double-up + skip-pkg-cache: true + skip-build-cache: true + args: --timeout=6m + - name: Build Go binary + run: mise run build + - name: Run Go Tests + run: go run gotest.tools/gotestsum@v1.11.0 + ## <> +{{ file.Block "gotestvars" }} + ## <> +{{- end }} ## <> {{ file.Block "buildtestauth" }} ## <> @@ -74,6 +114,9 @@ jobs: run: mise run buildtest env: GITHUB_TOKEN: {{ "${{ steps.generate_token.outputs.token }}" }} + ## <> +{{ file.Block "buildTestEnvVars" }} + ## <> - name: Run Tests run: mise run runtest env: @@ -95,16 +138,70 @@ jobs: - name: Checkout uses: actions/checkout@v4 with: + fetch-depth: 0 + fetch-tags: true token: {{ "${{ secrets.GITHUB_TOKEN }}" }} + - name: Set git User + run: | + git config user.name github-actions + git config user.email github-actions@github.com - name: Install Tool Versions uses: jdx/mise-action@052520c41a328779551db19a76697ffa34f3eabc with: experimental: true env: GH_TOKEN: {{ "${{ secrets.GITHUB_TOKEN }}" }} +{{- if stencil.Arg "nativeModule" }} + - name: Get Go directories + id: go + run: | + echo "cache_dir=$(go env GOCACHE)" >> "$GITHUB_OUTPUT" + echo "mod_cache_dir=$(go env GOMODCACHE)" >> "$GITHUB_OUTPUT" + - uses: actions/cache@v4 + with: + path: {{ "${{ steps.go.outputs.cache_dir }}" }} + key: {{ "${{ github.workflow }}-${{ runner.os }}-go-build-cache-${{ hashFiles('**/go.sum') }}" }} + - uses: actions/cache@v4 + with: + path: {{ "${{ steps.go.outputs.mod_cache_dir }}" }} + key: {{ "${{ github.workflow }}-${{ runner.os }}-go-mod-cache-${{ hashFiles('go.sum') }}" }} + - name: Retrieve goreleaser version + run: |- + echo "version=$(mise current goreleaser)" >> "$GITHUB_OUTPUT" + id: goreleaser + - name: Get next version + id: next_version + run: |- + get-next-version --target github-action + - name: Create Tag + if: {{ "${{ steps.next_version.outputs.hasNextVersion == 'true' }}" }} + run: |- + git tag -a {{ "\"v${{ steps.next_version.outputs.version }}\" -m \"Release v${{ steps.next_version.outputs.version }}\"" }} + - name: Generate CHANGELOG + if: {{ "${{ steps.next_version.outputs.hasNextVersion == 'true' }}" }} + run: |- + mise run changelog + - name: Create release artifacts and Github Release + if: {{ "${{ steps.next_version.outputs.hasNextVersion == 'true' }}" }} + uses: goreleaser/goreleaser-action@v6 + with: + distribution: goreleaser + version: {{ "v${{ steps.goreleaser.outputs.version }}" }} + args: release --release-notes CHANGELOG.md --clean + env: + GITHUB_TOKEN: {{ "${{ secrets.GITHUB_TOKEN }}" }} + ## <> +{{ file.Block "goreleaserEnvVars" }} + ## <> +{{- else }} - name: Install Semantic-Release run: yarn install - name: Release env: GITHUB_TOKEN: {{ "${{ secrets.GITHUB_TOKEN }}" }} run: npx semantic-release +{{- end }} + + ## <> +{{ file.Block "extraActions" }} + ## <> diff --git a/templates/.gitignore.tpl b/templates/.gitignore.tpl index 5b8429e..8432740 100644 --- a/templates/.gitignore.tpl +++ b/templates/.gitignore.tpl @@ -4,6 +4,7 @@ *.dll *.so *.dylib +__debug_bin* # Editor files *.swp @@ -36,6 +37,9 @@ vendor # macOS .DS_Store +# Currently a build artifact for native modules +CHANGELOG.md + # Ignore literally everything under the tests directory (except the README) -- specifically include the dir and stencil.yaml file # for each test case in the ignores block below tests/* diff --git a/templates/.golangci.yml.tpl b/templates/.golangci.yml.tpl new file mode 100644 index 0000000..51def48 --- /dev/null +++ b/templates/.golangci.yml.tpl @@ -0,0 +1,114 @@ +{{- if not (stencil.Arg "nativeModule") -}} +{{- file.Delete -}} +{{- end -}} +# yaml-language-server: $schema=https://json.schemastore.org/golangci-lint +# Linter settings +linters-settings: + depguard: + rules: + main: + deny: + - pkg: github.com/go-logr/logr + desc: Use the pkg/log logging package + - pkg: github.com/sirupsen/logrus + desc: Use the pkg/log logging package + - pkg: log + desc: Use the pkg/log logging package + errcheck: + check-blank: true + govet: + enable-all: true + disable: + - fieldalignment + settings: + shadow: + strict: true + gocyclo: + min-complexity: 25 + dupl: + threshold: 100 + goconst: + min-len: 3 + min-occurrences: 3 + lll: + line-length: 140 + gocritic: + enabled-tags: + - diagnostic + - experimental + - opinionated + - performance + - style + disabled-checks: + - whyNoLint # Doesn't seem to work properly + funlen: + lines: 500 + statements: 50 + +linters: + # Inverted configuration with enable-all and disable is not scalable during updates of golangci-lint. + disable-all: true + enable: + - bodyclose + - copyloopvar + - depguard + - dogsled + - errcheck + - errorlint + - exhaustive # Checks exhaustiveness of enum switch statements. + - funlen + - gochecknoinits + - goconst + - gocritic + - gocyclo + - gofmt + - goimports + - revive + - gosec + - gosimple + - govet + - ineffassign + - lll + # - misspell # The reason we're disabling this right now is because it uses 1/2 of the memory of the run. + - nakedret + - staticcheck + - typecheck + - unconvert + - unparam + - unused + - whitespace + +issues: + exclude: + # We allow error shadowing + - 'declaration of "err" shadows declaration at' + - "var-naming: don't use an underscore in package name" + + # Excluding configuration per-path, per-linter, per-text and per-source + exclude-rules: + # Exclude some linters from running on tests files. + - path: _test\.go + linters: + - gocyclo + - errcheck + - gosec + - funlen + - gochecknoglobals # Globals in test files are tolerated. + - goconst # Repeated consts in test files are tolerated. + # This rule is buggy and breaks on our `///Block` lines. Disable for now. + - linters: + - gocritic + text: "commentFormatting: put a space" + # This rule incorrectly flags nil references after assert.Assert(t, x != nil) + - path: _test\.go + text: "SA5011" + linters: + - staticcheck + - linters: + - lll + source: "^//go:generate " + +output: + formats: + - format: colored-line-number + sort-results: true diff --git a/templates/.goreleaser.yaml.tpl b/templates/.goreleaser.yaml.tpl new file mode 100644 index 0000000..48393d7 --- /dev/null +++ b/templates/.goreleaser.yaml.tpl @@ -0,0 +1,48 @@ +{{- if not (stencil.Arg "nativeModule") -}} +{{- file.Delete -}} +{{- end -}} +# yaml-language-server: $schema=https://goreleaser.com/static/schema.json +version: 2 +project_name: {{ .Config.Name }} +report_sizes: true +metadata: + mod_timestamp: "{{ "{{ .CommitTimestamp }}" }}" +builds: + - main: ./cmd/plugin + flags: + - -trimpath + ldflags: + - -s + - -w + ## <> +{{ file.Block "pluginLdflags" }} + ## <> + env: + - CGO_ENABLED=0 + goarch: + - amd64 + - arm64 + ## <> +{{ file.Block "pluginExtraArch" }} + ## <> + goos: + - linux + - darwin + - windows + ## <> +{{ file.Block "pluginExtraOS" }} + ## <> + ignore: + - goos: windows + goarch: arm + mod_timestamp: "{{ "{{ .CommitTimestamp }}" }}" +checksum: + name_template: "checksums.txt" +snapshot: + name_template: "{{ "{{ incpatch .Version }}-next" }}" +changelog: + use: git +release: + prerelease: "auto" + footer: |- + {{ "**Full Changelog**: https://github.com/udemy/eng-team-management/compare/{{ .PreviousTag }}...{{ .Tag }}" }} diff --git a/templates/.mise.toml.tpl b/templates/.mise.toml.tpl index b8e9827..64fe59b 100644 --- a/templates/.mise.toml.tpl +++ b/templates/.mise.toml.tpl @@ -1,6 +1,45 @@ [tools] nodejs = "22" yarn = "1.22.22" +{{- if stencil.Arg "nativeModule" }} +git-cliff = "latest" +golang = "1.23" +golangci-lint = "1.60" +goreleaser = "latest" +"go:gotest.tools/gotestsum" = "v1.12.0" +"go:golang.org/x/tools/cmd/goimports" = "latest" +"go:github.com/thenativeweb/get-next-version" = "latest" +{{- end }} + +{{ if stencil.Arg "nativeModule" -}} +[tasks.build] +description = "Build a binary for the current platform/architecture" +run = "go build -trimpath -o ./bin/ -v ./cmd/..." + +[tasks.changelog] +description = "Generate a changelog for the current version" +outputs = ["CHANGELOG.md"] +run = ["git-cliff --latest --output CHANGELOG.md"] + +[tasks.fmt] +alias = "format" +description = "Format code" +run = ["go mod tidy", "gofmt -s -w .", "goimports -w ."] + +[tasks.lint] +description = "Run linters" +run = "golangci-lint run" + +[tasks.next-version] +description = """Get the version number that would be released if a release was ran right now. +Pass --rc to get the next release candidate version. +""" +run = ["./.github/scripts/get-next-version.sh"] + +[tasks.test] +description = "Run tests" +run = "gotestsum" +{{- end }} [tasks.cleantest] description = "Helper to clean the test directory" diff --git a/templates/.vscode/extensions.json.tpl b/templates/.vscode/extensions.json.tpl index 08f27d7..f9733a1 100644 --- a/templates/.vscode/extensions.json.tpl +++ b/templates/.vscode/extensions.json.tpl @@ -6,6 +6,9 @@ "redhat.vscode-yaml", "foxundermoon.shell-format", "vsls-contrib.codetour", +{{- if stencil.Arg "nativeModule" }} + "golang.go", +{{- end }} "tamasfe.even-better-toml" ] } diff --git a/templates/.vscode/settings.json.tpl b/templates/.vscode/settings.json.tpl index cc06081..d059932 100644 --- a/templates/.vscode/settings.json.tpl +++ b/templates/.vscode/settings.json.tpl @@ -1,4 +1,10 @@ { + "go.lintTool": "golangci-lint", + "go.lintFlags": [ + "--fast" + ], + "go.formatTool": "goimports", + "go.useLanguageServer": true, "files.trimTrailingWhitespace": true, "files.exclude": { "**/bin": true, diff --git a/templates/cmd/plugin/instance.go.tpl b/templates/cmd/plugin/instance.go.tpl new file mode 100644 index 0000000..af22a77 --- /dev/null +++ b/templates/cmd/plugin/instance.go.tpl @@ -0,0 +1,67 @@ +{{- if not (stencil.Arg "nativeModule") -}} +{{- file.Delete -}} +{{- else -}} +{{- file.Static -}} +{{- end -}} +package main + +import ( + "context" + "encoding/json" + "fmt" + "io" + "os" + "os/exec" + "strings" + + "github.com/pkg/errors" + "go.rgst.io/stencil/pkg/extensions/apiv1" +) + +// _ ensures that Instance fits the apiv1.Implementation interface. +var _ apiv1.Implementation = &Instance{} + +// Instance contains a [apiv1.Implementation] satisfying plugin. +type Instance struct { + ctx context.Context +} + +// NewInstance creates a new [Instance]. +func NewInstance(ctx context.Context) (*Instance, error) { + return &Instance{ + ctx: ctx, + }, nil +} + +// GetConfig returns a [apiv1.Config] for the [Instance]. +func (*Instance) GetConfig() (*apiv1.Config, error) { + return &apiv1.Config{}, nil +} + +func (*Instance) GetTemplateFunctions() ([]*apiv1.TemplateFunction, error) { + return []*apiv1.TemplateFunction{ + { + Name: "DoSomething", + NumberOfArguments: 1, + }, + }, nil +} + +func (i *Instance) ExecuteTemplateFunction(tfe *apiv1.TemplateFunctionExec) (any, error) { + switch tfe.Name { //nolint:gocritic + case "DoSomething": + return i.DoSomething(tfe) + } + + return nil, fmt.Errorf("unknown template function: %s", tfe.Name) +} + +// DoSomething does something, we promise +func (i *Instance) DoSomething(t *apiv1.TemplateFunctionExec) (any, error) { + someArg, ok := t.Arguments[0].(string) + if !ok { + return nil, fmt.Errorf("expected string argument, got %T", t.Arguments[0]) + } + + return nil, nil +} diff --git a/templates/cmd/plugin/instance_test.go.tpl b/templates/cmd/plugin/instance_test.go.tpl new file mode 100644 index 0000000..1e06fb4 --- /dev/null +++ b/templates/cmd/plugin/instance_test.go.tpl @@ -0,0 +1,33 @@ +{{- if not (stencil.Arg "nativeModule") -}} +{{- file.Delete -}} +{{- else -}} +{{- file.Static -}} +{{- end -}} +package main + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.rgst.io/stencil/pkg/extensions/apiv1" +) + +func TestGetTeam(t *testing.T) { + i, err := NewInstance(context.Background()) + assert.NoError(t, err) + + tiRaw, err := i.GetTeamByID(&apiv1.TemplateFunctionExec{Arguments: []any{"platform"}}) + assert.NoError(t, err) + ti := tiRaw.(models.OwningTeam) + + assert.Equal(t, "platform", *ti.ID) +} + +func TestGetInvalidTeam(t *testing.T) { + i, err := NewInstance(context.Background()) + assert.NoError(t, err) + + _, err = i.GetTeamByID(&apiv1.TemplateFunctionExec{Arguments: []any{"sdfsdfsdfwesd"}}) + assert.Error(t, err) +} diff --git a/templates/cmd/plugin/plugin.go.tpl b/templates/cmd/plugin/plugin.go.tpl new file mode 100644 index 0000000..35bfa81 --- /dev/null +++ b/templates/cmd/plugin/plugin.go.tpl @@ -0,0 +1,29 @@ +{{- if not (stencil.Arg "nativeModule") -}} +{{- file.Delete -}} +{{- end -}} +package main + +import ( + "context" + "os" + + "go.rgst.io/stencil/pkg/extensions/apiv1" + "go.rgst.io/stencil/pkg/slogext" +) + +// main starts the native extension +func main() { + ctx := context.Background() + log := slogext.New() + + pi, err := NewInstance(ctx) + if err != nil { + log.WithError(err).Error("failed to create plugin instance") + os.Exit(1) + } + + if err := apiv1.NewExtensionImplementation(pi, log); err != nil { + log.WithError(err).Error("failed to create extension") + os.Exit(1) + } +} diff --git a/templates/go.mod.tpl b/templates/go.mod.tpl new file mode 100644 index 0000000..0fd481e --- /dev/null +++ b/templates/go.mod.tpl @@ -0,0 +1,18 @@ +{{- if not (stencil.Arg "nativeModule") -}} +{{- file.Delete -}} +{{- end -}} +module github.com/udemy/{{ .Config.Name }} + +go 1.23.1 + +// <> +{{- if empty (trim (file.Block "requires")) }} +require ( + github.com/pkg/errors v0.9.1 + github.com/stretchr/testify v1.9.0 + go.rgst.io/stencil v0.10.2 +) +{{- else }} +{{ file.Block "requires" }} +{{- end }} +// <> diff --git a/templates/manifest.yaml.tpl b/templates/manifest.yaml.tpl index d4b58d3..ece6005 100644 --- a/templates/manifest.yaml.tpl +++ b/templates/manifest.yaml.tpl @@ -1,4 +1,9 @@ name: github.com/udemy/{{ .Config.Name }} +{{- if stencil.Arg "nativeModule" }} +type: templates,extension +{{- else }} +type: templates +{{- end }} dirReplacements: ## <> {{ file.Block "dirReplacements" }} diff --git a/templates/package.json.tpl b/templates/package.json.tpl index 65992e4..59f4b83 100644 --- a/templates/package.json.tpl +++ b/templates/package.json.tpl @@ -1,6 +1,9 @@ { "name": "{{ .Config.Name }}", "devDependencies": { +{{- range $key, $value := (stencil.Arg "packageJsonDeps") }} + "{{ $key }}": "{{ $value }}", +{{- end }} "semantic-release": "23.0.8" }, "dependencies": {