goscript 0.0.23 → 0.0.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +2 -2
  2. package/cmd/goscript/cmd_compile.go +18 -2
  3. package/compiler/analysis.go +74 -132
  4. package/compiler/analysis_test.go +220 -0
  5. package/compiler/assignment.go +37 -43
  6. package/compiler/builtin_test.go +90 -0
  7. package/compiler/compiler.go +307 -22
  8. package/compiler/composite-lit.go +108 -43
  9. package/compiler/config.go +7 -3
  10. package/compiler/config_test.go +6 -33
  11. package/compiler/decl.go +7 -1
  12. package/compiler/expr-call.go +212 -2
  13. package/compiler/expr-selector.go +66 -41
  14. package/compiler/expr-star.go +57 -65
  15. package/compiler/expr-type.go +1 -1
  16. package/compiler/expr-value.go +1 -1
  17. package/compiler/expr.go +125 -20
  18. package/compiler/field.go +4 -4
  19. package/compiler/primitive.go +11 -10
  20. package/compiler/spec-struct.go +3 -3
  21. package/compiler/spec-value.go +75 -29
  22. package/compiler/spec.go +9 -3
  23. package/compiler/stmt-assign.go +36 -2
  24. package/compiler/stmt-for.go +11 -0
  25. package/compiler/stmt-range.go +314 -1
  26. package/compiler/stmt.go +52 -0
  27. package/compiler/type.go +83 -15
  28. package/dist/gs/builtin/builtin.d.ts +9 -0
  29. package/dist/gs/builtin/builtin.js +46 -0
  30. package/dist/gs/builtin/builtin.js.map +1 -0
  31. package/dist/gs/builtin/channel.d.ts +193 -0
  32. package/dist/gs/builtin/channel.js +471 -0
  33. package/dist/gs/builtin/channel.js.map +1 -0
  34. package/dist/gs/builtin/defer.d.ts +38 -0
  35. package/dist/gs/builtin/defer.js +54 -0
  36. package/dist/gs/builtin/defer.js.map +1 -0
  37. package/dist/gs/builtin/index.d.ts +1 -0
  38. package/dist/gs/builtin/index.js +2 -0
  39. package/dist/gs/builtin/index.js.map +1 -0
  40. package/dist/gs/builtin/io.d.ts +16 -0
  41. package/dist/gs/builtin/io.js +15 -0
  42. package/dist/gs/builtin/io.js.map +1 -0
  43. package/dist/gs/builtin/map.d.ts +33 -0
  44. package/dist/gs/builtin/map.js +44 -0
  45. package/dist/gs/builtin/map.js.map +1 -0
  46. package/dist/gs/builtin/slice.d.ts +173 -0
  47. package/dist/gs/builtin/slice.js +799 -0
  48. package/dist/gs/builtin/slice.js.map +1 -0
  49. package/dist/gs/builtin/type.d.ts +203 -0
  50. package/dist/gs/builtin/type.js +744 -0
  51. package/dist/gs/builtin/type.js.map +1 -0
  52. package/dist/gs/builtin/varRef.d.ts +14 -0
  53. package/dist/gs/builtin/varRef.js +14 -0
  54. package/dist/gs/builtin/varRef.js.map +1 -0
  55. package/dist/gs/cmp/index.d.ts +4 -0
  56. package/dist/gs/cmp/index.js +27 -0
  57. package/dist/gs/cmp/index.js.map +1 -0
  58. package/dist/gs/context/context.d.ts +26 -0
  59. package/dist/gs/context/context.js +305 -0
  60. package/dist/gs/context/context.js.map +1 -0
  61. package/dist/gs/context/index.d.ts +1 -0
  62. package/dist/gs/context/index.js +2 -0
  63. package/dist/gs/context/index.js.map +1 -0
  64. package/dist/gs/internal/goarch/index.d.ts +6 -0
  65. package/dist/gs/internal/goarch/index.js +14 -0
  66. package/dist/gs/internal/goarch/index.js.map +1 -0
  67. package/dist/gs/iter/index.d.ts +1 -0
  68. package/dist/gs/iter/index.js +2 -0
  69. package/dist/gs/iter/index.js.map +1 -0
  70. package/dist/gs/iter/iter.d.ts +4 -0
  71. package/dist/gs/iter/iter.js +91 -0
  72. package/dist/gs/iter/iter.js.map +1 -0
  73. package/dist/gs/math/bits/index.d.ts +47 -0
  74. package/dist/gs/math/bits/index.js +298 -0
  75. package/dist/gs/math/bits/index.js.map +1 -0
  76. package/dist/gs/runtime/index.d.ts +1 -0
  77. package/dist/gs/runtime/index.js +2 -0
  78. package/dist/gs/runtime/index.js.map +1 -0
  79. package/dist/gs/runtime/runtime.d.ts +41 -0
  80. package/dist/gs/runtime/runtime.js +158 -0
  81. package/dist/gs/runtime/runtime.js.map +1 -0
  82. package/dist/gs/slices/index.d.ts +1 -0
  83. package/dist/gs/slices/index.js +2 -0
  84. package/dist/gs/slices/index.js.map +1 -0
  85. package/dist/gs/slices/slices.d.ts +8 -0
  86. package/dist/gs/slices/slices.js +20 -0
  87. package/dist/gs/slices/slices.js.map +1 -0
  88. package/dist/gs/time/index.d.ts +1 -0
  89. package/dist/gs/time/index.js +2 -0
  90. package/dist/gs/time/index.js.map +1 -0
  91. package/dist/gs/time/time.d.ts +57 -0
  92. package/dist/gs/time/time.js +208 -0
  93. package/dist/gs/time/time.js.map +1 -0
  94. package/package.json +3 -2
@@ -11,6 +11,8 @@ import (
11
11
  "slices"
12
12
  "strings"
13
13
 
14
+ "go/constant"
15
+
14
16
  gs "github.com/aperturerobotics/goscript"
15
17
  "github.com/sirupsen/logrus"
16
18
  "golang.org/x/tools/go/packages"
@@ -72,6 +74,16 @@ func NewCompiler(conf *Config, le *logrus.Entry, opts *packages.Config) (*Compil
72
74
  return &Compiler{config: *conf, le: le, opts: *opts}, nil
73
75
  }
74
76
 
77
+ // CompilationResult contains information about what was compiled
78
+ type CompilationResult struct {
79
+ // CompiledPackages contains the package paths of all packages that were actually compiled to TypeScript
80
+ CompiledPackages []string
81
+ // CopiedPackages contains the package paths of all packages that were copied from handwritten sources
82
+ CopiedPackages []string
83
+ // OriginalPackages contains the package paths that were explicitly requested for compilation
84
+ OriginalPackages []string
85
+ }
86
+
75
87
  // CompilePackages loads Go packages based on the provided patterns and
76
88
  // then compiles each loaded package into TypeScript. It uses the context for
77
89
  // cancellation and applies the compiler's configured options during package loading.
@@ -79,7 +91,8 @@ func NewCompiler(conf *Config, le *logrus.Entry, opts *packages.Config) (*Compil
79
91
  // invokes its `Compile` method.
80
92
  // If c.config.AllDependencies is true, it will also compile all dependencies
81
93
  // of the requested packages, including standard library dependencies.
82
- func (c *Compiler) CompilePackages(ctx context.Context, patterns ...string) error {
94
+ // Returns a CompilationResult with information about what was compiled.
95
+ func (c *Compiler) CompilePackages(ctx context.Context, patterns ...string) (*CompilationResult, error) {
83
96
  opts := c.opts
84
97
  opts.Context = ctx
85
98
 
@@ -87,7 +100,7 @@ func (c *Compiler) CompilePackages(ctx context.Context, patterns ...string) erro
87
100
  opts.Mode |= packages.NeedImports
88
101
  pkgs, err := packages.Load(&opts, patterns...)
89
102
  if err != nil {
90
- return fmt.Errorf("failed to load packages: %w", err)
103
+ return nil, fmt.Errorf("failed to load packages: %w", err)
91
104
  }
92
105
 
93
106
  // build a list of packages that patterns matched
@@ -96,12 +109,23 @@ func (c *Compiler) CompilePackages(ctx context.Context, patterns ...string) erro
96
109
  patternPkgPaths = append(patternPkgPaths, pkg.PkgPath)
97
110
  }
98
111
 
112
+ result := &CompilationResult{
113
+ OriginalPackages: patternPkgPaths,
114
+ }
115
+
99
116
  // If AllDependencies is true, we need to collect all dependencies
100
117
  if c.config.AllDependencies {
101
118
  // Create a set to track processed packages by their ID
102
119
  processed := make(map[string]bool)
103
120
  var allPkgs []*packages.Package
104
121
 
122
+ // Helper function to check if a package has a handwritten equivalent
123
+ hasHandwrittenEquivalent := func(pkgPath string) bool {
124
+ gsSourcePath := "gs/" + pkgPath
125
+ _, gsErr := gs.GsOverrides.ReadDir(gsSourcePath)
126
+ return gsErr == nil
127
+ }
128
+
105
129
  // Visit all packages and their dependencies
106
130
  var visit func(pkg *packages.Package)
107
131
  visit = func(pkg *packages.Package) {
@@ -109,8 +133,17 @@ func (c *Compiler) CompilePackages(ctx context.Context, patterns ...string) erro
109
133
  return
110
134
  }
111
135
  processed[pkg.ID] = true
136
+
137
+ // Add this package to the list of all packages
112
138
  allPkgs = append(allPkgs, pkg)
113
139
 
140
+ // Check if this package has a handwritten equivalent
141
+ if hasHandwrittenEquivalent(pkg.PkgPath) {
142
+ // Add this package but don't visit its dependencies
143
+ c.le.Debugf("Skipping dependencies of handwritten package: %s", pkg.PkgPath)
144
+ return
145
+ }
146
+
114
147
  // Visit all imports, including standard library packages
115
148
  for _, imp := range pkg.Imports {
116
149
  visit(imp)
@@ -146,17 +179,57 @@ func (c *Compiler) CompilePackages(ctx context.Context, patterns ...string) erro
146
179
  */
147
180
  }
148
181
 
182
+ // If DisableEmitBuiltin is false, we need to copy the builtin package to the output directory
183
+ if !c.config.DisableEmitBuiltin {
184
+ c.le.Infof("Copying builtin package to output directory")
185
+ builtinPath := "gs/builtin"
186
+ outputPath := ComputeModulePath(c.config.OutputPath, "builtin")
187
+ if err := c.copyEmbeddedPackage(builtinPath, outputPath); err != nil {
188
+ return nil, fmt.Errorf("failed to copy builtin package to output directory: %w", err)
189
+ }
190
+ result.CopiedPackages = append(result.CopiedPackages, "builtin")
191
+ }
192
+
149
193
  // Compile all packages
150
194
  for _, pkg := range pkgs {
151
195
  // Check if the package has a handwritten equivalent
196
+ // If the package was explicitly requested, skip this logic
152
197
  if !slices.Contains(patternPkgPaths, pkg.PkgPath) {
153
- _, gsErr := gs.GsOverrides.ReadDir("gs/" + pkg.PkgPath)
198
+ gsSourcePath := "gs/" + pkg.PkgPath
199
+ _, gsErr := gs.GsOverrides.ReadDir(gsSourcePath)
154
200
  if gsErr != nil && !os.IsNotExist(gsErr) {
155
- return gsErr
201
+ return nil, gsErr
156
202
  }
157
203
  if gsErr == nil {
158
- c.le.Infof("Skipping compilation for overridden package %s", pkg.PkgPath)
159
- continue
204
+ if c.config.DisableEmitBuiltin {
205
+ c.le.Infof("Skipping compilation for overridden package %s", pkg.PkgPath)
206
+ result.CopiedPackages = append(result.CopiedPackages, pkg.PkgPath)
207
+ continue
208
+ } else {
209
+ // If DisableEmitBuiltin is false, we need to copy the handwritten package to the output directory
210
+ c.le.Infof("Copying handwritten package %s to output directory", pkg.PkgPath)
211
+
212
+ // Compute output path for this package
213
+ outputPath := ComputeModulePath(c.config.OutputPath, pkg.PkgPath)
214
+
215
+ // Remove existing directory if it exists
216
+ if err := os.RemoveAll(outputPath); err != nil {
217
+ return nil, fmt.Errorf("failed to remove existing output directory for %s: %w", pkg.PkgPath, err)
218
+ }
219
+
220
+ // Create the output directory
221
+ if err := os.MkdirAll(outputPath, 0o755); err != nil {
222
+ return nil, fmt.Errorf("failed to create output directory for %s: %w", pkg.PkgPath, err)
223
+ }
224
+
225
+ // Copy files from embedded FS to output directory
226
+ if err := c.copyEmbeddedPackage(gsSourcePath, outputPath); err != nil {
227
+ return nil, fmt.Errorf("failed to copy embedded package %s: %w", pkg.PkgPath, err)
228
+ }
229
+
230
+ result.CopiedPackages = append(result.CopiedPackages, pkg.PkgPath)
231
+ continue
232
+ }
160
233
  }
161
234
  }
162
235
 
@@ -166,17 +239,42 @@ func (c *Compiler) CompilePackages(ctx context.Context, patterns ...string) erro
166
239
  continue
167
240
  }
168
241
 
242
+ // Check if this is the unsafe package, which is not supported in GoScript
243
+ if pkg.PkgPath == "unsafe" {
244
+ // Find which package depends on unsafe by looking at the import graph
245
+ var dependentPackages []string
246
+ for _, otherPkg := range pkgs {
247
+ if otherPkg.PkgPath != "unsafe" {
248
+ for importPath := range otherPkg.Imports {
249
+ if importPath == "unsafe" {
250
+ dependentPackages = append(dependentPackages, otherPkg.PkgPath)
251
+ break
252
+ }
253
+ }
254
+ }
255
+ }
256
+
257
+ dependentList := "unknown package"
258
+ if len(dependentPackages) > 0 {
259
+ dependentList = strings.Join(dependentPackages, ", ")
260
+ }
261
+
262
+ return nil, fmt.Errorf("cannot compile package 'unsafe': GoScript does not support the unsafe package due to its low-level memory operations that are incompatible with TypeScript/JavaScript. This package is required by: %s. Consider using alternative approaches that don't require unsafe operations", dependentList)
263
+ }
264
+
169
265
  pkgCompiler, err := NewPackageCompiler(c.le, &c.config, pkg)
170
266
  if err != nil {
171
- return fmt.Errorf("failed to create package compiler for %s: %w", pkg.PkgPath, err)
267
+ return nil, fmt.Errorf("failed to create package compiler for %s: %w", pkg.PkgPath, err)
172
268
  }
173
269
 
174
270
  if err := pkgCompiler.Compile(ctx); err != nil {
175
- return fmt.Errorf("failed to compile package %s: %w", pkg.PkgPath, err)
271
+ return nil, fmt.Errorf("failed to compile package %s: %w", pkg.PkgPath, err)
176
272
  }
273
+
274
+ result.CompiledPackages = append(result.CompiledPackages, pkg.PkgPath)
177
275
  }
178
276
 
179
- return nil
277
+ return result, nil
180
278
  }
181
279
 
182
280
  // PackageCompiler is responsible for compiling an entire Go package into
@@ -202,7 +300,7 @@ func NewPackageCompiler(
202
300
  le: le,
203
301
  pkg: pkg,
204
302
  compilerConf: compilerConf,
205
- outputPath: ComputeModulePath(compilerConf.OutputPathRoot, pkg.PkgPath),
303
+ outputPath: ComputeModulePath(compilerConf.OutputPath, pkg.PkgPath),
206
304
  }
207
305
 
208
306
  return res, nil
@@ -287,7 +385,7 @@ func (c *PackageCompiler) generateIndexFile(compiledFiles []string) error {
287
385
  // CompileFile handles the compilation of a single Go source file to TypeScript.
288
386
  // It first performs a pre-compilation analysis of the file using `AnalyzeFile`
289
387
  // to gather information necessary for accurate TypeScript generation (e.g.,
290
- // about boxing, async functions, defer statements).
388
+ // about varRefing, async functions, defer statements).
291
389
  // Then, it creates a `FileCompiler` instance for the file and invokes its
292
390
  // `Compile` method to generate the TypeScript code.
293
391
  func (p *PackageCompiler) CompileFile(ctx context.Context, name string, syntax *ast.File) error {
@@ -351,7 +449,7 @@ func (c *FileCompiler) Compile(ctx context.Context) error {
351
449
  pkgPath := c.pkg.PkgPath
352
450
 
353
451
  outputFilePath := TranslateGoFilePathToTypescriptFilePath(pkgPath, filepath.Base(c.fullPath))
354
- outputFilePathAbs := filepath.Join(c.compilerConfig.OutputPathRoot, outputFilePath)
452
+ outputFilePathAbs := filepath.Join(c.compilerConfig.OutputPath, outputFilePath)
355
453
 
356
454
  if err := os.MkdirAll(filepath.Dir(outputFilePathAbs), 0o755); err != nil {
357
455
  return err
@@ -382,7 +480,7 @@ func (c *FileCompiler) Compile(ctx context.Context) error {
382
480
  // GoToTSCompiler is the core component responsible for translating Go AST nodes
383
481
  // and type information into TypeScript code. It uses a `TSCodeWriter` to output
384
482
  // the generated TypeScript and relies on `Analysis` data to make informed
385
- // decisions about code generation (e.g., boxing, async behavior).
483
+ // decisions about code generation (e.g., varRefing, async behavior).
386
484
  type GoToTSCompiler struct {
387
485
  tsw *TSCodeWriter
388
486
 
@@ -407,15 +505,14 @@ func NewGoToTSCompiler(tsw *TSCodeWriter, pkg *packages.Package, analysis *Analy
407
505
  // WriteIdent translates a Go identifier (`ast.Ident`) used as a value (e.g.,
408
506
  // variable, function name) into its TypeScript equivalent.
409
507
  // - If the identifier is `nil`, it writes `null`.
508
+ // - If the identifier refers to a constant, it writes the constant's evaluated value.
410
509
  // - Otherwise, it writes the identifier's name.
411
- // - If `accessBoxedValue` is true and the analysis (`c.analysis.NeedsBoxedAccess`)
412
- // indicates that this identifier refers to a variable whose value is stored
413
- // in a box (due to its address being taken or other boxing requirements),
414
- // it appends `.value` to access the actual value from the box.
510
+ // - If `accessVarRefedValue` is true and the analysis (`c.analysis.NeedsVarRefAccess`)
511
+ // indicates the variable is variable referenced, `.value` is appended to access the contained value.
415
512
  //
416
513
  // This function relies on `go/types` (`TypesInfo.Uses` or `Defs`) to resolve
417
- // the identifier and the `Analysis` data to determine boxing needs.
418
- func (c *GoToTSCompiler) WriteIdent(exp *ast.Ident, accessBoxedValue bool) {
514
+ // the identifier and the `Analysis` data to determine varRefing needs.
515
+ func (c *GoToTSCompiler) WriteIdent(exp *ast.Ident, accessVarRefedValue bool) {
419
516
  if exp.Name == "nil" {
420
517
  c.tsw.WriteLiterally("null")
421
518
  return
@@ -428,11 +525,25 @@ func (c *GoToTSCompiler) WriteIdent(exp *ast.Ident, accessBoxedValue bool) {
428
525
  obj = c.pkg.TypesInfo.Defs[exp]
429
526
  }
430
527
 
431
- // Write the identifier name first
432
- c.tsw.WriteLiterally(exp.Name)
528
+ // Check if this identifier refers to a constant
529
+ if obj != nil {
530
+ if constObj, isConst := obj.(*types.Const); isConst {
531
+ // Only evaluate constants from the current package being compiled
532
+ // Don't evaluate constants from imported packages (they should use their exported names)
533
+ // Special case: predeclared constants like iota have a nil package, so we should evaluate them
534
+ if constObj.Pkg() == c.pkg.Types || constObj.Pkg() == nil {
535
+ // Write the constant's evaluated value instead of the identifier name
536
+ c.writeConstantValue(constObj)
537
+ return
538
+ }
539
+ }
540
+ }
541
+
542
+ // Write the identifier name first, sanitizing if it's a reserved word
543
+ c.tsw.WriteLiterally(c.sanitizeIdentifier(exp.Name))
433
544
 
434
545
  // Determine if we need to access .value based on analysis data
435
- if obj != nil && accessBoxedValue && c.analysis.NeedsBoxedAccess(obj) {
546
+ if obj != nil && accessVarRefedValue && c.analysis.NeedsVarRefAccess(obj) {
436
547
  c.tsw.WriteLiterally("!.value")
437
548
  }
438
549
  }
@@ -627,3 +738,177 @@ func (c *GoToTSCompiler) WriteDoc(doc *ast.CommentGroup) {
627
738
  }
628
739
  }
629
740
  }
741
+
742
+ // sanitizeIdentifier checks if an identifier is a JavaScript/TypeScript reserved word
743
+ // and prefixes it with an underscore if it is. This prevents compilation errors
744
+ // when Go identifiers conflict with JS/TS keywords.
745
+ func (c *GoToTSCompiler) sanitizeIdentifier(name string) string {
746
+ // Don't sanitize boolean literals - they are valid in both Go and JS/TS
747
+ if name == "true" || name == "false" {
748
+ return name
749
+ }
750
+
751
+ // List of JavaScript/TypeScript reserved words that could conflict
752
+ reservedWords := map[string]bool{
753
+ "abstract": true,
754
+ "any": true,
755
+ "as": true,
756
+ "asserts": true,
757
+ "async": true,
758
+ "await": true,
759
+ "boolean": true,
760
+ "break": true,
761
+ "case": true,
762
+ "catch": true,
763
+ "class": true,
764
+ "const": true,
765
+ "constructor": true,
766
+ "continue": true,
767
+ "debugger": true,
768
+ "declare": true,
769
+ "default": true,
770
+ "delete": true,
771
+ "do": true,
772
+ "else": true,
773
+ "enum": true,
774
+ "export": true,
775
+ "extends": true,
776
+ "finally": true,
777
+ "for": true,
778
+ "from": true,
779
+ "function": true,
780
+ "get": true,
781
+ "if": true,
782
+ "implements": true,
783
+ "import": true,
784
+ "in": true,
785
+ "instanceof": true,
786
+ "interface": true,
787
+ "is": true,
788
+ "keyof": true,
789
+ "let": true,
790
+ "module": true,
791
+ "namespace": true,
792
+ "never": true,
793
+ "new": true,
794
+ "null": true,
795
+ "number": true,
796
+ "object": true,
797
+ "of": true,
798
+ "package": true,
799
+ "private": true,
800
+ "protected": true,
801
+ "public": true,
802
+ "readonly": true,
803
+ "require": true,
804
+ "return": true,
805
+ "set": true,
806
+ "static": true,
807
+ "string": true,
808
+ "super": true,
809
+ "switch": true,
810
+ "symbol": true,
811
+ "this": true,
812
+ "throw": true,
813
+ "try": true,
814
+ "type": true,
815
+ "typeof": true,
816
+ "undefined": true,
817
+ "unique": true,
818
+ "unknown": true,
819
+ "var": true,
820
+ "void": true,
821
+ "while": true,
822
+ "with": true,
823
+ "yield": true,
824
+ }
825
+
826
+ if reservedWords[name] {
827
+ return "_" + name
828
+ }
829
+ return name
830
+ }
831
+
832
+ // writeConstantValue writes the evaluated value of a Go constant to TypeScript.
833
+ // It handles different constant types (integer, float, string, boolean, complex)
834
+ // and writes the appropriate TypeScript literal.
835
+ func (c *GoToTSCompiler) writeConstantValue(constObj *types.Const) {
836
+ val := constObj.Val()
837
+
838
+ switch val.Kind() {
839
+ case constant.Int:
840
+ // For integer constants, write the string representation
841
+ c.tsw.WriteLiterally(val.String())
842
+ case constant.Float:
843
+ // For float constants, write the string representation
844
+ c.tsw.WriteLiterally(val.String())
845
+ case constant.String:
846
+ // For string constants, write as a quoted string literal
847
+ c.tsw.WriteLiterally(val.String()) // val.String() already includes quotes
848
+ case constant.Bool:
849
+ // For boolean constants, write true/false
850
+ if constant.BoolVal(val) {
851
+ c.tsw.WriteLiterally("true")
852
+ } else {
853
+ c.tsw.WriteLiterally("false")
854
+ }
855
+ case constant.Complex:
856
+ // For complex constants, we need to handle them specially
857
+ // For now, write as a comment indicating unsupported
858
+ c.tsw.WriteLiterally("/* complex constant: " + val.String() + " */")
859
+ default:
860
+ // For unknown constant types, write as a comment
861
+ c.tsw.WriteLiterally("/* unknown constant: " + val.String() + " */")
862
+ }
863
+ }
864
+
865
+ // copyEmbeddedPackage recursively copies files from an embedded FS path to a filesystem directory.
866
+ // It handles both regular files and directories.
867
+ func (c *Compiler) copyEmbeddedPackage(embeddedPath string, outputPath string) error {
868
+ // Remove the output path if it exists
869
+ if err := os.RemoveAll(outputPath); err != nil {
870
+ return fmt.Errorf("failed to remove output directory %s: %w", outputPath, err)
871
+ }
872
+
873
+ // Create the output path
874
+ if err := os.MkdirAll(outputPath, 0o755); err != nil {
875
+ return fmt.Errorf("failed to create output directory %s: %w", outputPath, err)
876
+ }
877
+
878
+ // List the entries in the embedded path
879
+ entries, err := gs.GsOverrides.ReadDir(embeddedPath)
880
+ if err != nil {
881
+ return fmt.Errorf("failed to read embedded directory %s: %w", embeddedPath, err)
882
+ }
883
+
884
+ // Process each entry
885
+ for _, entry := range entries {
886
+ entryPath := filepath.Join(embeddedPath, entry.Name())
887
+ outputEntryPath := filepath.Join(outputPath, entry.Name())
888
+
889
+ if entry.IsDir() {
890
+ // Create the output directory
891
+ if err := os.MkdirAll(outputEntryPath, 0o755); err != nil {
892
+ return fmt.Errorf("failed to create output directory %s: %w", outputEntryPath, err)
893
+ }
894
+
895
+ // Recursively copy the directory contents
896
+ if err := c.copyEmbeddedPackage(entryPath, outputEntryPath); err != nil {
897
+ return err
898
+ }
899
+ } else {
900
+ // Read the file content from the embedded FS
901
+ content, err := gs.GsOverrides.ReadFile(entryPath)
902
+ if err != nil {
903
+ return fmt.Errorf("failed to read embedded file %s: %w", entryPath, err)
904
+ }
905
+
906
+ // Write the content to the output file
907
+ if err := os.WriteFile(outputEntryPath, content, 0o644); err != nil {
908
+ return fmt.Errorf("failed to write file %s: %w", outputEntryPath, err)
909
+ }
910
+ }
911
+ }
912
+
913
+ return nil
914
+ }