chore(build): introduce go-task for project automation

Adds a comprehensive Taskfile.yml to centralize all project scripts for building, testing, linting, and Docker image management.

The GitHub Actions CI workflow is refactored to utilize these `task` commands, resulting in a cleaner, more readable, and maintainable configuration. This approach ensures consistency between local development and CI environments.
This commit is contained in:
2025-11-06 03:52:21 +01:00
parent f8fecc3967
commit 59f2de9d22
14 changed files with 652 additions and 130 deletions

View File

@ -615,8 +615,7 @@ func BenchmarkDocxExporter_Export(b *testing.B) {
// Create temporary directory
tempDir := b.TempDir()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
outputPath := filepath.Join(tempDir, "benchmark-course.docx")
_ = exporter.Export(course, outputPath)
// Clean up for next iteration
@ -641,7 +640,7 @@ func BenchmarkDocxExporter_ComplexCourse(b *testing.B) {
}
// Fill with test data
for i := 0; i < 10; i++ {
for i := range 10 {
lesson := models.Lesson{
ID: "lesson-" + string(rune(i)),
Title: "Lesson " + string(rune(i)),
@ -649,13 +648,13 @@ func BenchmarkDocxExporter_ComplexCourse(b *testing.B) {
Items: make([]models.Item, 5), // 5 items per lesson
}
for j := 0; j < 5; j++ {
for j := range 5 {
item := models.Item{
Type: "text",
Items: make([]models.SubItem, 3), // 3 sub-items per item
}
for k := 0; k < 3; k++ {
for k := range 3 {
item.Items[k] = models.SubItem{
Heading: "<h3>Heading " + string(rune(k)) + "</h3>",
Paragraph: "<p>Paragraph content with <strong>formatting</strong> for performance testing.</p>",
@ -670,8 +669,7 @@ func BenchmarkDocxExporter_ComplexCourse(b *testing.B) {
tempDir := b.TempDir()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
outputPath := filepath.Join(tempDir, "benchmark-complex.docx")
_ = exporter.Export(course, outputPath)
os.Remove(outputPath)

View File

@ -449,8 +449,7 @@ func BenchmarkFactory_CreateExporter(b *testing.B) {
htmlCleaner := services.NewHTMLCleaner()
factory := NewFactory(htmlCleaner)
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
_, _ = factory.CreateExporter("markdown")
}
}
@ -460,8 +459,7 @@ func BenchmarkFactory_CreateExporter_Docx(b *testing.B) {
htmlCleaner := services.NewHTMLCleaner()
factory := NewFactory(htmlCleaner)
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
_, _ = factory.CreateExporter("docx")
}
}
@ -471,8 +469,7 @@ func BenchmarkFactory_GetSupportedFormats(b *testing.B) {
htmlCleaner := services.NewHTMLCleaner()
factory := NewFactory(htmlCleaner)
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = factory.GetSupportedFormats()
}
}

View File

@ -841,8 +841,7 @@ func BenchmarkHTMLExporter_Export(b *testing.B) {
// Create temporary directory
tempDir := b.TempDir()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
outputPath := filepath.Join(tempDir, "benchmark-course.html")
_ = exporter.Export(course, outputPath)
// Clean up for next iteration
@ -865,8 +864,7 @@ func BenchmarkHTMLExporter_ProcessTextItem(b *testing.B) {
},
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
var buf bytes.Buffer
exporter.processTextItem(&buf, item)
}
@ -889,7 +887,7 @@ func BenchmarkHTMLExporter_ComplexCourse(b *testing.B) {
}
// Fill with test data
for i := 0; i < 10; i++ {
for i := range 10 {
lesson := models.Lesson{
ID: "lesson-" + string(rune(i)),
Title: "Lesson " + string(rune(i)),
@ -897,13 +895,13 @@ func BenchmarkHTMLExporter_ComplexCourse(b *testing.B) {
Items: make([]models.Item, 5), // 5 items per lesson
}
for j := 0; j < 5; j++ {
for j := range 5 {
item := models.Item{
Type: "text",
Items: make([]models.SubItem, 3), // 3 sub-items per item
}
for k := 0; k < 3; k++ {
for k := range 3 {
item.Items[k] = models.SubItem{
Heading: "<h3>Heading " + string(rune(k)) + "</h3>",
Paragraph: "<p>Paragraph content with <strong>formatting</strong> for performance testing.</p>",
@ -918,8 +916,7 @@ func BenchmarkHTMLExporter_ComplexCourse(b *testing.B) {
tempDir := b.TempDir()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
outputPath := filepath.Join(tempDir, "benchmark-complex.html")
_ = exporter.Export(course, outputPath)
os.Remove(outputPath)

View File

@ -661,8 +661,7 @@ func BenchmarkMarkdownExporter_Export(b *testing.B) {
// Create temporary directory
tempDir := b.TempDir()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
outputPath := filepath.Join(tempDir, "benchmark-course.md")
_ = exporter.Export(course, outputPath)
// Clean up for next iteration
@ -685,8 +684,7 @@ func BenchmarkMarkdownExporter_ProcessTextItem(b *testing.B) {
},
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
var buf bytes.Buffer
exporter.processTextItem(&buf, item, "###")
}

View File

@ -18,7 +18,7 @@ type Lesson struct {
// Items is an ordered array of content items within the lesson
Items []Item `json:"items"`
// Position stores the ordering information for the lesson
Position interface{} `json:"position"`
Position any `json:"position"`
// Ready indicates whether the lesson is marked as complete
Ready bool `json:"ready"`
// CreatedAt is the timestamp when the lesson was created
@ -41,9 +41,9 @@ type Item struct {
// Items contains the actual content elements (sub-items) of this item
Items []SubItem `json:"items"`
// Settings contains configuration options specific to this item type
Settings interface{} `json:"settings"`
Settings any `json:"settings"`
// Data contains additional structured data for the item
Data interface{} `json:"data"`
Data any `json:"data"`
// Media contains any associated media for the item
Media *Media `json:"media,omitempty"`
}

View File

@ -133,7 +133,7 @@ func TestLesson_JSONMarshalUnmarshal(t *testing.T) {
Ready: true,
CreatedAt: "2023-06-01T12:00:00Z",
UpdatedAt: "2023-06-01T13:00:00Z",
Position: map[string]interface{}{"x": 1, "y": 2},
Position: map[string]any{"x": 1, "y": 2},
Items: []Item{
{
ID: "item-test",
@ -154,8 +154,8 @@ func TestLesson_JSONMarshalUnmarshal(t *testing.T) {
},
},
},
Settings: map[string]interface{}{"autoplay": false},
Data: map[string]interface{}{"metadata": "test"},
Settings: map[string]any{"autoplay": false},
Data: map[string]any{"metadata": "test"},
},
},
}
@ -197,11 +197,11 @@ func TestItem_JSONMarshalUnmarshal(t *testing.T) {
Feedback: "Well done!",
},
},
Settings: map[string]interface{}{
Settings: map[string]any{
"allowRetry": true,
"showAnswer": true,
},
Data: map[string]interface{}{
Data: map[string]any{
"points": 10,
"weight": 1.5,
},
@ -475,7 +475,7 @@ func TestLabelSet_JSONMarshalUnmarshal(t *testing.T) {
func TestEmptyStructures(t *testing.T) {
testCases := []struct {
name string
data interface{}
data any
}{
{"Empty Course", Course{}},
{"Empty CourseInfo", CourseInfo{}},
@ -626,8 +626,7 @@ func BenchmarkCourse_JSONMarshal(b *testing.B) {
},
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
_, _ = json.Marshal(course)
}
}
@ -660,17 +659,16 @@ func BenchmarkCourse_JSONUnmarshal(b *testing.B) {
jsonData, _ := json.Marshal(course)
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
var result Course
_ = json.Unmarshal(jsonData, &result)
}
}
// compareMaps compares two interface{} values that should be maps
func compareMaps(original, unmarshaled interface{}) bool {
origMap, origOk := original.(map[string]interface{})
unMap, unOk := unmarshaled.(map[string]interface{})
func compareMaps(original, unmarshaled any) bool {
origMap, origOk := original.(map[string]any)
unMap, unOk := unmarshaled.(map[string]any)
if !origOk || !unOk {
// If not maps, use deep equal

View File

@ -168,7 +168,7 @@ func TestHTMLCleaner_CleanHTML_LargeContent(t *testing.T) {
// Create a large HTML string
var builder strings.Builder
builder.WriteString("<html><body>")
for i := 0; i < 1000; i++ {
for i := range 1000 {
builder.WriteString("<p>Paragraph ")
builder.WriteString(string(rune('0' + i%10)))
builder.WriteString(" with some content &amp; entities.</p>")
@ -299,8 +299,7 @@ func BenchmarkHTMLCleaner_CleanHTML(b *testing.B) {
cleaner := NewHTMLCleaner()
input := "<div class=\"content\"><h1>Course Title</h1><p>This is a <em>great</em> course about &amp; HTML entities like &nbsp; and &quot;quotes&quot;.</p><ul><li>Item 1</li><li>Item 2</li></ul></div>"
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
cleaner.CleanHTML(input)
}
}
@ -311,15 +310,14 @@ func BenchmarkHTMLCleaner_CleanHTML_Large(b *testing.B) {
// Create a large HTML string
var builder strings.Builder
for i := 0; i < 100; i++ {
for i := range 100 {
builder.WriteString("<p>Paragraph ")
builder.WriteString(string(rune('0' + i%10)))
builder.WriteString(" with some content &amp; entities &lt;test&gt;.</p>")
}
input := builder.String()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
cleaner.CleanHTML(input)
}
}

View File

@ -420,8 +420,7 @@ func BenchmarkExtractShareID(b *testing.B) {
parser := &ArticulateParser{}
uri := "https://rise.articulate.com/share/N_APNg40Vr2CSH2xNz-ZLATM5kNviDIO#/"
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
_, _ = parser.extractShareID(uri)
}
}
@ -433,8 +432,7 @@ func BenchmarkBuildAPIURL(b *testing.B) {
}
shareID := "N_APNg40Vr2CSH2xNz-ZLATM5kNviDIO"
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = parser.buildAPIURL(shareID)
}
}