-
Notifications
You must be signed in to change notification settings - Fork 6
[kernel-869] exclude files from extensions #98
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,11 @@ import ( | |
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| const ( | ||
| // MaxExtensionSize is the maximum allowed size for extension bundles from API (50MB) | ||
| MaxExtensionSize = 50 * 1024 * 1024 | ||
| ) | ||
|
|
||
| // ExtensionsService defines the subset of the Kernel SDK extension client that we use. | ||
| type ExtensionsService interface { | ||
| List(ctx context.Context, opts ...option.RequestOption) (res *[]kernel.ExtensionListResponse, err error) | ||
|
|
@@ -294,31 +299,73 @@ func (e ExtensionsCmd) Upload(ctx context.Context, in ExtensionsUploadInput) err | |
| return fmt.Errorf("directory %s does not exist", absDir) | ||
| } | ||
|
|
||
| // Pre-flight size check | ||
| if in.Output != "json" { | ||
| pterm.Info.Println("Analyzing extension directory...") | ||
| } | ||
|
|
||
| tmpFile := filepath.Join(os.TempDir(), fmt.Sprintf("kernel_ext_%d.zip", time.Now().UnixNano())) | ||
|
|
||
| if in.Output != "json" { | ||
| pterm.Info.Println("Zipping extension directory...") | ||
| } | ||
| if err := util.ZipDirectory(absDir, tmpFile); err != nil { | ||
|
|
||
| // Use new extension-specific zip function | ||
| stats, err := util.ZipExtensionDirectory(absDir, tmpFile, &util.ExtensionZipOptions{ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since this is in a generic util package, consider enhancing the existing |
||
| ExcludeDefaults: false, // Apply defaults | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| }) | ||
| if err != nil { | ||
| pterm.Error.Println("Failed to zip directory") | ||
| return err | ||
| } | ||
| defer os.Remove(tmpFile) | ||
|
|
||
| // Get zip file size | ||
| fileInfo, err := os.Stat(tmpFile) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to stat zip: %w", err) | ||
| } | ||
|
|
||
| if in.Output != "json" { | ||
| pterm.Success.Printf("Created bundle: %s (%d files)\n", | ||
| util.FormatBytes(fileInfo.Size()), stats.FilesIncluded) | ||
| } | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: obvious comment, can remove |
||
| // Final size validation | ||
|
|
||
| if fileInfo.Size() > MaxExtensionSize { | ||
| pterm.Error.Printf("Extension bundle is too large: %s (max: 50MB)\n", | ||
| util.FormatBytes(fileInfo.Size())) | ||
| pterm.Info.Println("\nSuggestions to reduce size:") | ||
| pterm.Info.Println(" 1. Ensure you're building the extension for production") | ||
| pterm.Info.Println(" 2. Remove unnecessary assets (large images, videos)") | ||
| pterm.Info.Println(" 3. Check manifest.json references only needed files") | ||
| return fmt.Errorf("bundle exceeds maximum size") | ||
| } | ||
|
|
||
| // Open file for upload | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: obvious comment, can remove |
||
| f, err := os.Open(tmpFile) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to open temp zip: %w", err) | ||
| } | ||
| defer f.Close() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: obvious comment, can remove |
||
|
|
||
| // Upload | ||
| if in.Output != "json" { | ||
| pterm.Info.Println("Uploading extension...") | ||
| } | ||
|
|
||
| params := kernel.ExtensionUploadParams{File: f} | ||
| if in.Name != "" { | ||
| params.Name = kernel.Opt(in.Name) | ||
| } | ||
|
|
||
| item, err := e.extensions.Upload(ctx, params) | ||
| if err != nil { | ||
| return util.CleanedUpSdkError{Err: err} | ||
| } | ||
|
|
||
| // Display results | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: obvious comment, can remove |
||
| if in.Output == "json" { | ||
| return util.PrintPrettyJSON(item) | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,223 @@ | ||
| package util | ||
|
|
||
| import ( | ||
| "archive/zip" | ||
| "fmt" | ||
| "io" | ||
| "os" | ||
| "path/filepath" | ||
| "strings" | ||
| "sync" | ||
|
|
||
| "github.com/boyter/gocodewalker" | ||
| ) | ||
|
|
||
| // DefaultExtensionExclusions contains patterns for files that are not needed | ||
| // Focus on large directories and files that provide meaningful size reduction. | ||
| // These will be passed to gocodewalker's ExcludeDirectory and ExcludeFilename fields. | ||
| var DefaultExtensionExclusions = struct { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. to make this more generic: extract |
||
| // ExcludeDirectory: exact directory names (case-sensitive) | ||
| ExcludeDirectory []string | ||
| // ExcludeFilenamePatterns: patterns for filename matching (handled manually) | ||
| ExcludeFilenamePatterns []string | ||
| }{ | ||
| ExcludeDirectory: []string{ | ||
| // Dependencies | ||
| "node_modules", | ||
|
|
||
| // Version control | ||
| ".git", | ||
|
|
||
| // Test/Coverage | ||
| "__tests__", | ||
| "coverage", | ||
| }, | ||
|
|
||
| ExcludeFilenamePatterns: []string{ | ||
| // Test files | ||
| "*.test.js", | ||
| "*.test.ts", | ||
| "*.spec.js", | ||
| "*.spec.ts", | ||
|
|
||
| // Log files | ||
| "*.log", | ||
|
|
||
| // Temp files | ||
| "*.swp", | ||
| }, | ||
| } | ||
|
|
||
| // ExtensionZipOptions configures extension-specific zipping behavior | ||
| type ExtensionZipOptions struct { | ||
| ExcludeDefaults bool // If true, don't apply default exclusions | ||
| } | ||
|
|
||
| // ZipStats tracks statistics about the zipping operation | ||
| type ZipStats struct { | ||
| mu sync.Mutex | ||
| FilesIncluded int | ||
| FilesExcluded int | ||
| BytesIncluded int64 | ||
| BytesExcluded int64 | ||
| } | ||
|
|
||
| func (s *ZipStats) AddIncluded(bytes int64) { | ||
| s.mu.Lock() | ||
| defer s.mu.Unlock() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. verify mutex usage is correct here - should be using |
||
| s.FilesIncluded++ | ||
| s.BytesIncluded += bytes | ||
| } | ||
|
|
||
| func (s *ZipStats) AddExcluded(bytes int64) { | ||
| s.mu.Lock() | ||
| defer s.mu.Unlock() | ||
| s.FilesExcluded++ | ||
| s.BytesExcluded += bytes | ||
| } | ||
|
|
||
| // ZipExtensionDirectory zips a Chrome extension directory with smart defaults | ||
| // that automatically exclude development files (node_modules, .git, etc.) | ||
| func ZipExtensionDirectory(srcDir, destZip string, opts *ExtensionZipOptions) (*ZipStats, error) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. there's already a |
||
| if opts == nil { | ||
| opts = &ExtensionZipOptions{} | ||
| } | ||
|
|
||
| stats := &ZipStats{} | ||
|
|
||
| zipFile, err := os.Create(destZip) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| defer zipFile.Close() | ||
|
|
||
| zipWriter := zip.NewWriter(zipFile) | ||
| defer zipWriter.Close() | ||
|
|
||
| // Configure file walker with exclusions | ||
| fileQueue := make(chan *gocodewalker.File, 256) | ||
| walker := gocodewalker.NewFileWalker(srcDir, fileQueue) | ||
| walker.IncludeHidden = true | ||
|
|
||
| // Apply default exclusions unless disabled | ||
| if !opts.ExcludeDefaults { | ||
| walker.ExcludeDirectory = append(walker.ExcludeDirectory, DefaultExtensionExclusions.ExcludeDirectory...) | ||
| walker.ExcludeFilename = append(walker.ExcludeFilename, DefaultExtensionExclusions.ExcludeFilenamePatterns...) | ||
| } | ||
|
|
||
| // Ensure walker is terminated on any return path to prevent goroutine leak | ||
| defer walker.Terminate() | ||
|
|
||
| // Track walker errors | ||
| errChan := make(chan error, 1) | ||
| go func() { | ||
| errChan <- walker.Start() | ||
| }() | ||
archandatta marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // Track directories we've added to avoid duplicates | ||
| dirsAdded := make(map[string]struct{}) | ||
|
|
||
| // Process files from walker | ||
| for f := range fileQueue { | ||
| relPath, err := filepath.Rel(srcDir, f.Location) | ||
| if err != nil { | ||
| return stats, err | ||
| } | ||
| relPath = filepath.ToSlash(relPath) | ||
|
|
||
| // Check against pattern-based exclusions (if defaults are enabled) | ||
| shouldExclude := false | ||
| if !opts.ExcludeDefaults { | ||
| // Check filename against patterns only if defaults are enabled | ||
| filename := filepath.Base(f.Location) | ||
| for _, pattern := range DefaultExtensionExclusions.ExcludeFilenamePatterns { | ||
| matched, err := filepath.Match(pattern, filename) | ||
| if err == nil && matched { | ||
| shouldExclude = true | ||
| break | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if shouldExclude { | ||
| continue | ||
| } | ||
|
|
||
| // Ensure parent directories exist in archive | ||
| if dir := filepath.Dir(relPath); dir != "." && dir != "" { | ||
| segments := strings.Split(dir, "/") | ||
| var current string | ||
| for _, segment := range segments { | ||
| if current == "" { | ||
| current = segment | ||
| } else { | ||
| current = current + "/" + segment | ||
| } | ||
| if _, exists := dirsAdded[current+"/"]; !exists { | ||
| if _, err := zipWriter.Create(current + "/"); err != nil { | ||
| return stats, err | ||
| } | ||
| dirsAdded[current+"/"] = struct{}{} | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Get file info | ||
| fileInfo, err := os.Lstat(f.Location) | ||
| if err != nil { | ||
| return stats, err | ||
| } | ||
|
|
||
| // Handle symlinks | ||
| if fileInfo.Mode()&os.ModeSymlink != 0 { | ||
| linkTarget, err := os.Readlink(f.Location) | ||
| if err != nil { | ||
| return stats, err | ||
| } | ||
|
|
||
| hdr := &zip.FileHeader{ | ||
| Name: relPath, | ||
| Method: zip.Store, | ||
| } | ||
| hdr.SetMode(os.ModeSymlink | 0777) | ||
|
|
||
| zipFileWriter, err := zipWriter.CreateHeader(hdr) | ||
| if err != nil { | ||
| return stats, err | ||
| } | ||
| if _, err := zipFileWriter.Write([]byte(linkTarget)); err != nil { | ||
| return stats, err | ||
| } | ||
| stats.AddIncluded(int64(len(linkTarget))) | ||
| } else { | ||
| // Regular file | ||
| zipFileWriter, err := zipWriter.Create(relPath) | ||
| if err != nil { | ||
| return stats, err | ||
| } | ||
|
|
||
| file, err := os.Open(f.Location) | ||
| if err != nil { | ||
| return stats, err | ||
| } | ||
|
|
||
| written, err := io.Copy(zipFileWriter, file) | ||
| closeErr := file.Close() | ||
| if closeErr != nil { | ||
| return stats, closeErr | ||
| } | ||
| if err != nil { | ||
| return stats, err | ||
| } | ||
|
|
||
| stats.AddIncluded(written) | ||
| } | ||
| } | ||
|
|
||
| // Check if walker had an error | ||
| if err := <-errChan; err != nil { | ||
| return stats, fmt.Errorf("directory walk failed: %w", err) | ||
| } | ||
|
|
||
| return stats, nil | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: consider renaming to
MaxExtensionSizeBytesto be more explicit about units