Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
24ac2b7
feat: import packages from extension verifier
shyim May 13, 2025
5bbb3ed
refactor: extract HTML comment start constant and add linter directives
shyim May 13, 2025
0b258b9
feat: import twigparser package
shyim May 13, 2025
294a5f1
refactor: improve twig parser readability with constants and switch s…
shyim May 13, 2025
ece8ed1
deps: add google/generative-ai-go and google/api dependencies
shyim May 13, 2025
4114507
feat: add windsurf rules
shyim May 13, 2025
ae88f56
feat: import all go packages
shyim May 13, 2025
38ea680
refactor: standardize import ordering and use logging package instead…
shyim May 13, 2025
c2e6fbd
feat: introduce constants for attribute keys, values, and file extens…
shyim May 13, 2025
9c28f64
feat: import tooling
shyim May 13, 2025
cae43a5
refactor: improve tools setup with better error handling and npm depe…
shyim May 13, 2025
f02eb16
feat: add multiple report formats and file copying utility for extens…
shyim May 13, 2025
c167cd7
refactor: move linter config files into dedicated configs directory
shyim May 13, 2025
4480395
refactor: improve error handling and use constants for check severity…
shyim May 13, 2025
89c0dce
ci: remove Windows support from build and test workflows
shyim May 13, 2025
15df731
feat: add extension format/fix commands and reorganize config files
shyim May 13, 2025
225e5fd
chore: add linter ignore comment for fmt.Print usage in rector output
shyim May 13, 2025
cff8f39
fix: update phpstan paths to use environment variable for vendor dire…
shyim May 13, 2025
957d44d
feat: add project validation commands for fixing, formatting and vali…
shyim May 13, 2025
9c0ebe7
fix: skip tool setup when not running full extension validation
shyim May 13, 2025
989ba47
feat: add AI-powered Twig template upgrade command with Docker base i…
shyim May 15, 2025
b522cf1
chore: update VSCode settings and fix typo in Twig upgrade prompt
shyim May 15, 2025
aa3d0ad
feat: Add only-custom-static-extensions (#528)
amenk May 14, 2025
137d854
chore: update dependencies
shyim May 14, 2025
d32b610
feat: improve git tag handling and changelog generation with version-…
shyim May 14, 2025
fc17772
fix: improve version handling and changelog generation using semantic…
shyim May 14, 2025
edfbf46
style: reorder imports in git.go to follow standard format
shyim May 14, 2025
f1ad1c8
fix: snippet validator to use correct filepath
shyim May 14, 2025
01368bf
feat: enable storefront snippet validation by uncommenting validation…
shyim May 14, 2025
8a005c9
refactor: add context to SetupTools and improve Docker container conf…
shyim May 15, 2025
e20a68e
refactor: use ToolDirectory config instead of getting current working…
shyim May 15, 2025
69f6289
chore: exclude invalid.php from PHP problem reporting in VS Code sett…
shyim May 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/base-docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ on:
push:
tags-ignore:
- "*"
branches:
- main
paths:
- 'Dockerfile.base'

Expand Down
1 change: 0 additions & 1 deletion .github/workflows/go_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ jobs:
os:
- ubuntu-latest
- macos-14
- windows-latest

runs-on: ${{ matrix.os }}
steps:
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ dist/
/project
dump.sql*

/testdata

# Devenv
.devenv*
devenv.local.nix
Expand Down
2 changes: 0 additions & 2 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ linters:
- unused
- whitespace
- asciicheck
- godot
- gocyclo
- gocritic
- errcheck
Expand All @@ -40,7 +39,6 @@ linters:
- ginkgolinter
- gocheckcompilerdirectives
- goconst
- godot
- godox
- nilnil
exclusions:
Expand Down
8 changes: 0 additions & 8 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,10 @@ builds:
- -trimpath
goos:
- linux
- windows
- darwin
ignore:
- goos: linux
goarch: '386'
- goos: windows
goarch: '386'
- goos: windows
goarch: 'arm64'

archives:
- name_template: >-
Expand All @@ -32,9 +27,6 @@ archives:
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
format_overrides:
- goos: windows
formats: ["zip"]
files:
- LICENSE
- completions/*
Expand Down
14 changes: 14 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Attach Debugger",
"type": "go",
"request": "attach",
"mode": "remote",
"remotePath": "${workspaceFolder}",
"port": 40001,
"host": "localhost"
}
]
}
24 changes: 18 additions & 6 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
{
"go.formatTool": "gofmt",
"go.useLanguageServer": true,
"gopls": {
"formatting.gofumpt": true
"go.lintTool": "golangci-lint",
"go.lintFlags": [
"--path-mode=abs",
"--fast-only"
],
"go.formatTool": "custom",
"go.alternateTools": {
"customFormatter": "golangci-lint"
},
"go.formatFlags": [
"fmt",
"--stdin"
],
"go.useLanguageServer": true,
"go.diagnostic.vulncheck": "Imports",
"go.inlayHints.functionTypeParameters": true,
"go.inlayHints.assignVariableTypes": true,
Expand All @@ -12,11 +21,14 @@
"files.exclude": {
".devenv": true,
".direnv": true,
".idea": true
".idea": true,
},
"php.problems.exclude": {
"internal/phplint/testdata/invalid.php": true
},
"go.toolsManagement.autoUpdate": false,
"go.toolsManagement.checkForUpdates": "off",
"yaml.schemas": {
"https://raw.githubusercontent.com/shopware/shopware-cli/main/extension/shopware-extension-schema.json": "file:///workspaces/shopware-cli/.shopware-extension.yml"
}
},
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
trigger: always_on
description:
globs:
alwaysApply: true
---
# General rules

Expand Down
4 changes: 2 additions & 2 deletions .cursor/rules/test.mdc → .windsurf/rules/test.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
description:
trigger: glob
globs: *_test.go
alwaysApply: false
---

- Use testify assert
- Prefer assert.ElementsMatch on lists to ignore ordering issues
- Use t.Setenv for environment variables
Expand Down
12 changes: 12 additions & 0 deletions Dockerfile.base
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,18 @@ RUN <<EOF
echo 'memory_limit=512M' > /etc/php/conf.d/docker.ini
EOF

COPY internal/verifier/js /opt/verifier/js
COPY internal/verifier/php /opt/verifier/php

RUN <<EOF
set -euxo pipefail

npm install --prefix /opt/verifier/js
composer install --no-dev --working-dir /opt/verifier/php
EOF

ENV SHOPWARE_CLI_TOOLS_DIR=/opt/verifier

COPY scripts/entrypoint.sh /usr/local/bin/entrypoint.sh

CMD ["sh"]
12 changes: 12 additions & 0 deletions cmd/extension/extension_ai.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package extension

import "github.com/spf13/cobra"

var extensionAiCmd = &cobra.Command{
Use: "ai",
Short: "AI commands (experimental)",
}

func init() {
extensionRootCmd.AddCommand(extensionAiCmd)
}
210 changes: 210 additions & 0 deletions cmd/extension/extension_ai_twig_upgrade.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
package extension

import (
"context"
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"time"

"github.com/spf13/cobra"

"github.com/shopware/shopware-cli/extension"
"github.com/shopware/shopware-cli/internal/llm"
"github.com/shopware/shopware-cli/internal/twigparser"
"github.com/shopware/shopware-cli/internal/verifier"
"github.com/shopware/shopware-cli/logging"
)

const systemPrompt = `
You are a helper agent to help to upgrade Twig templates. I will give you the old and new template happened in the Software and as third the extended template. Apply the changes happen between old and new template to the extended template.
- Do only the necessary changes to the extended template.
- Do only modify the content inside the block and dont add new blocks
- Please also only output the modified extended template nothing more.
- Adjust also HTML elements to be more accessibility friendly.
- If in a {% block %} is {{ parent() }}, ignore it and dont modify the content of the block
`

var extensionAiTwigUpgradeCmd = &cobra.Command{
Use: "twig-upgrade [path] [old-shopware-version] [new-shopware-version]",
Short: "Upgrade Twig templates using AI",
Args: cobra.ExactArgs(3),
RunE: func(cmd *cobra.Command, args []string) error {
ext, err := extension.GetExtensionByFolder(args[0])
if err != nil {
return err
}

logging.FromContext(cmd.Context()).Debugf("System Prompt:\n%s\n", systemPrompt)

toolCfg, err := verifier.ConvertExtensionToToolConfig(ext)
if err != nil {
return err
}

client, err := llm.NewLLMClient(cmd.Flag("provider").Value.String())
if err != nil {
return err
}

options := &llm.LLMOptions{
Model: cmd.Flag("model").Value.String(),
SystemPrompt: systemPrompt,
}

for _, sourceDirectory := range toolCfg.SourceDirectories {
twigFolder := path.Join(sourceDirectory, "Resources", "views", "storefront")

if _, err := os.Stat(twigFolder); os.IsNotExist(err) {
return nil
}

oldVersion, err := cloneShopwareStorefront(cmd.Context(), args[1])
if err != nil {
return err
}

defer func() {
if err := os.RemoveAll(oldVersion); err != nil {
fmt.Fprintf(os.Stderr, "Failed to remove old version directory: %v\n", err)
}
}()

newVersion, err := cloneShopwareStorefront(cmd.Context(), args[2])
if err != nil {
return err
}

defer func() {
if err := os.RemoveAll(newVersion); err != nil {
fmt.Fprintf(os.Stderr, "Failed to remove new version directory: %v\n", err)
}
}()

err = filepath.Walk(twigFolder, func(file string, info os.FileInfo, _ error) error {
if info.IsDir() {
return nil
}

if filepath.Ext(file) != ".twig" {
return nil
}

content, err := os.ReadFile(file)
if err != nil {
return err
}

ast, err := twigparser.ParseTemplate(string(content))
if err != nil {
return err
}

extends := ast.Extends()

if extends == nil {
return nil
}

tpl := extends.Template

if tpl[0] == '@' {
tplParts := strings.Split(tpl, "/")
tplParts = tplParts[1:]
tpl = strings.Join(tplParts, "/")
}

oldTemplateText, err := os.ReadFile(path.Join(oldVersion, "Resources", "views", tpl))
if err != nil {
fmt.Printf("Template %s not found in old version\n", tpl)
return nil
}

newTemplateText, err := os.ReadFile(path.Join(newVersion, "Resources", "views", tpl))
if err != nil {
fmt.Printf("Template %s not found in new version\n", tpl)
return nil
}

var str strings.Builder
str.WriteString("This was the old template:\n")
str.WriteString("```twig\n")
str.WriteString(string(oldTemplateText))
str.WriteString("\n```\n")
str.WriteString("and this is the new one:\n")
str.WriteString("```twig\n")
str.WriteString(string(newTemplateText))
str.WriteString("\n```\n")
str.WriteString("and this is my template:\n")
str.WriteString("```twig\n")
str.WriteString(string(content))
str.WriteString("\n```")

startTime := time.Now()
logging.FromContext(cmd.Context()).Infof("Processing file %s", file)

logging.FromContext(cmd.Context()).Debugf("Input to LLM for file %s:\n%s\n", file, str.String())

text, err := client.Generate(cmd.Context(), str.String(), options)
if err != nil {
return err
}

logging.FromContext(cmd.Context()).Infof("Processed file %s in %s", file, time.Since(startTime))

if strings.Contains(text, "</think>") {
thinkEndIndex := strings.Index(text, "</think>")
if thinkEndIndex != -1 {
text = text[thinkEndIndex+len("</think>"):]
}
}

start := strings.Index(text, "```twig")
end := strings.LastIndex(text, "```")

if start == -1 || end == -1 {
return nil
}

text = strings.TrimPrefix(text[start+7:end], "\n")

contentStr := string(content)
if strings.TrimSpace(text) == strings.TrimSpace(contentStr) {
return nil
}

return os.WriteFile(file, []byte(text), os.ModePerm)
})
if err != nil {
return err
}
}

return nil
},
}

func init() {
extensionAiTwigUpgradeCmd.Flags().String("model", "gemma3:4b", "The model to use for the upgrade")
extensionAiTwigUpgradeCmd.Flags().String("provider", "ollama", "The provider to use for the upgrade")
extensionAiCmd.AddCommand(extensionAiTwigUpgradeCmd)
}

func cloneShopwareStorefront(ctx context.Context, version string) (string, error) {
tempDir, err := os.MkdirTemp(os.TempDir(), "shopware")
if err != nil {
return "", err
}

git := exec.CommandContext(ctx, "git", "-c", "advice.detachedHead=false", "clone", "-q", "--branch", "v"+version, "https://github.com/shopware/storefront", tempDir, "--depth", "1")
output, err := git.CombinedOutput()
if err != nil {
logging.FromContext(ctx).Error(string(output))
return "", err
}

return tempDir, nil
}
Loading