@ihelpee/prumo-plugin-go-gin 0.0.0-stage → 0.9.0

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.
@@ -0,0 +1,596 @@
1
+ // prumo-go-extractor — fatos de AST Go em JSON canônico v1.
2
+ //
3
+ // Este programa é PARSER, não implementador da lei (spec
4
+ // docs/especificacao-prumo-go-gin.md §4.2): ele serializa fatos sintáticos
5
+ // e nunca decide "isto é violação?". A decisão vive 100% no core Node.
6
+ //
7
+ // R1.1: apenas stdlib Go. Qualquer import externo aqui é falha de build.
8
+ // R1.2: nunca executa o código analisado; apenas parser.ParseFile — parse
9
+ // puramente sintático, sem go build, sem go list, sem rede.
10
+ package main
11
+
12
+ import (
13
+ "encoding/json"
14
+ "flag"
15
+ "fmt"
16
+ "go/ast"
17
+ "go/format"
18
+ "go/parser"
19
+ "go/token"
20
+ "os"
21
+ "path/filepath"
22
+ "reflect"
23
+ "strconv"
24
+ "strings"
25
+ )
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // Contrato JSON v1 (spec §1.4) — congelado.
29
+ // ---------------------------------------------------------------------------
30
+
31
+ type ImportInfo struct {
32
+ Path string `json:"path"`
33
+ Alias string `json:"alias"`
34
+ Line int `json:"line"`
35
+ }
36
+
37
+ type EngineRoot struct {
38
+ Name string `json:"name"`
39
+ Line int `json:"line"`
40
+ }
41
+
42
+ type RouteInfo struct {
43
+ Method string `json:"method"`
44
+ Path string `json:"path"`
45
+ Handler string `json:"handler"`
46
+ Receiver string `json:"receiver"`
47
+ InlineGuards []string `json:"inlineGuards"`
48
+ Line int `json:"line"`
49
+ }
50
+
51
+ type GroupDef struct {
52
+ Var string `json:"var"`
53
+ Parent string `json:"parent"`
54
+ Path string `json:"path"`
55
+ Guards []string `json:"guards"`
56
+ Line int `json:"line"`
57
+ }
58
+
59
+ type UseCall struct {
60
+ Receiver string `json:"receiver"`
61
+ Guards []string `json:"guards"`
62
+ Line int `json:"line"`
63
+ }
64
+
65
+ type BindingInfo struct {
66
+ Func string `json:"func"`
67
+ ArgExpr string `json:"argExpr"`
68
+ VarName string `json:"varName"`
69
+ Line int `json:"line"`
70
+ }
71
+
72
+ type FieldInfo struct {
73
+ Name string `json:"name"`
74
+ Type string `json:"type"`
75
+ BindingTag string `json:"bindingTag"`
76
+ HasBinding bool `json:"hasBinding"`
77
+ }
78
+
79
+ type StructInfo struct {
80
+ Name string `json:"name"`
81
+ Line int `json:"line"`
82
+ Fields []FieldInfo `json:"fields"`
83
+ }
84
+
85
+ type VarType struct {
86
+ Var string `json:"var"`
87
+ Type string `json:"type"`
88
+ Line int `json:"line"`
89
+ }
90
+
91
+ type ErrSite struct {
92
+ Expr string `json:"expr"`
93
+ Line int `json:"line"`
94
+ }
95
+
96
+ type ErrorHandling struct {
97
+ ExplicitDiscards []ErrSite `json:"explicitDiscards"`
98
+ UncheckedErrCalls []ErrSite `json:"uncheckedErrCalls"`
99
+ }
100
+
101
+ type FileModel struct {
102
+ Path string `json:"path"`
103
+ Package string `json:"package"`
104
+ IsTest bool `json:"isTest"`
105
+ IsInternal bool `json:"isInternal"`
106
+ Imports []ImportInfo `json:"imports"`
107
+ DynamicImports []string `json:"dynamicImports"`
108
+ EngineRoots []EngineRoot `json:"engineRoots"`
109
+ Routes []RouteInfo `json:"routes"`
110
+ GroupDefs []GroupDef `json:"groupDefs"`
111
+ UseCalls []UseCall `json:"useCalls"`
112
+ Bindings []BindingInfo `json:"bindings"`
113
+ Structs []StructInfo `json:"structs"`
114
+ VarTypes []VarType `json:"varTypes"`
115
+ UsesErr bool `json:"usesErr"`
116
+ // Fatos p/ heurística R3.8 (a decisão é do core): nomes das funções
117
+ // chamadas cujo resultado foi atribuído a uma variável de erro
118
+ // (`err := f()`, `x, err := f()`) — "esta chamada já aparece com err
119
+ // neste arquivo".
120
+ CheckedCallees []string `json:"checkedCallees"`
121
+ ErrorHandling ErrorHandling `json:"errorHandling"`
122
+ // R1.3: arquivo com erro de parse não derruba o run.
123
+ Error string `json:"error,omitempty"`
124
+ }
125
+
126
+ type Model struct {
127
+ Version int `json:"version"`
128
+ Module *string `json:"module"`
129
+ Files []FileModel `json:"files"`
130
+ }
131
+
132
+ // ---------------------------------------------------------------------------
133
+ // Léxicos de reconhecimento (fatos sintáticos; a LEI dos símbolos é do core)
134
+ // ---------------------------------------------------------------------------
135
+
136
+ var routeMethods = map[string]string{
137
+ "GET": "GET",
138
+ "POST": "POST",
139
+ "PUT": "PUT",
140
+ "PATCH": "PATCH",
141
+ "DELETE": "DELETE",
142
+ "HEAD": "HEAD",
143
+ "OPTIONS": "OPTIONS",
144
+ "Any": "ANY",
145
+ }
146
+
147
+ var bindFuncs = map[string]bool{
148
+ "ShouldBindJSON": true, "BindJSON": true,
149
+ "ShouldBind": true, "Bind": true,
150
+ "ShouldBindWith": true, "BindWith": true,
151
+ "ShouldBindQuery": true, "BindQuery": true,
152
+ "ShouldBindQueryWith": true, "BindQueryWith": true,
153
+ "ShouldBindUri": true, "BindUri": true,
154
+ "ShouldBindUriWith": true, "BindUriWith": true,
155
+ "ShouldBindHeader": true, "BindHeader": true,
156
+ "ShouldBindForm": true, "BindForm": true,
157
+ "ShouldBindYAML": true, "BindYAML": true,
158
+ "ShouldBindXML": true, "BindXML": true,
159
+ "ShouldBindMsgPack": true, "BindMsgPack": true,
160
+ }
161
+
162
+ // ---------------------------------------------------------------------------
163
+ // Helpers
164
+ // ---------------------------------------------------------------------------
165
+
166
+ func exprText(fset *token.FileSet, n ast.Node) string {
167
+ var sb strings.Builder
168
+ if err := format.Node(&sb, fset, n); err != nil {
169
+ return ""
170
+ }
171
+ return sb.String()
172
+ }
173
+
174
+ // callName extrai o nome canônico de um guard/handler: Auth -> "Auth",
175
+ // gin.Recovery -> "gin.Recovery", Auth("realm") -> "Auth".
176
+ func callName(e ast.Expr) string {
177
+ switch v := e.(type) {
178
+ case *ast.Ident:
179
+ return v.Name
180
+ case *ast.SelectorExpr:
181
+ return callName(v.X) + "." + v.Sel.Name
182
+ case *ast.CallExpr:
183
+ return callName(v.Fun)
184
+ case *ast.IndexExpr:
185
+ return callName(v.X)
186
+ case *ast.IndexListExpr:
187
+ return callName(v.X)
188
+ case *ast.ParenExpr:
189
+ return callName(v.X)
190
+ }
191
+ return ""
192
+ }
193
+
194
+ func stringLit(e ast.Expr) (string, bool) {
195
+ lit, ok := e.(*ast.BasicLit)
196
+ if !ok || lit.Kind != token.STRING {
197
+ return "", false
198
+ }
199
+ s, err := strconv.Unquote(lit.Value)
200
+ if err != nil {
201
+ return "", false
202
+ }
203
+ return s, true
204
+ }
205
+
206
+ func isIdent(e ast.Expr, name string) bool {
207
+ id, ok := e.(*ast.Ident)
208
+ return ok && id.Name == name
209
+ }
210
+
211
+ // selectorCall reconhece `X.Sel(args...)` e retorna (X, args).
212
+ func selectorCall(call *ast.CallExpr, sel string) (ast.Expr, []ast.Expr, bool) {
213
+ se, ok := call.Fun.(*ast.SelectorExpr)
214
+ if !ok || se.Sel.Name != sel {
215
+ return nil, nil, false
216
+ }
217
+ return se.X, call.Args, true
218
+ }
219
+
220
+ // ---------------------------------------------------------------------------
221
+ // go.mod do alvo: apenas o module path (ler go.mod NÃO é resolver deps).
222
+ // ---------------------------------------------------------------------------
223
+
224
+ func modulePath(root string) *string {
225
+ data, err := os.ReadFile(filepath.Join(root, "go.mod"))
226
+ if err != nil {
227
+ return nil
228
+ }
229
+ for _, line := range strings.Split(string(data), "\n") {
230
+ trimmed := strings.TrimSpace(line)
231
+ if rest, ok := strings.CutPrefix(trimmed, "module "); ok {
232
+ mod := strings.TrimSpace(strings.SplitN(rest, "//", 2)[0])
233
+ mod = strings.Trim(mod, "\"")
234
+ if mod != "" {
235
+ return &mod
236
+ }
237
+ }
238
+ }
239
+ return nil
240
+ }
241
+
242
+ // ---------------------------------------------------------------------------
243
+ // Extração por arquivo
244
+ // ---------------------------------------------------------------------------
245
+
246
+ func extractFile(root, rel string) FileModel {
247
+ fm := FileModel{
248
+ Path: rel,
249
+ IsTest: strings.HasSuffix(rel, "_test.go"),
250
+ IsInternal: strings.Contains("/"+filepath.ToSlash(rel), "/internal/"),
251
+ Imports: []ImportInfo{},
252
+ DynamicImports: []string{},
253
+ EngineRoots: []EngineRoot{},
254
+ Routes: []RouteInfo{},
255
+ GroupDefs: []GroupDef{},
256
+ UseCalls: []UseCall{},
257
+ Bindings: []BindingInfo{},
258
+ Structs: []StructInfo{},
259
+ VarTypes: []VarType{},
260
+ CheckedCallees: []string{},
261
+ ErrorHandling: ErrorHandling{ExplicitDiscards: []ErrSite{}, UncheckedErrCalls: []ErrSite{}},
262
+ }
263
+
264
+ fset := token.NewFileSet()
265
+ af, err := parser.ParseFile(fset, filepath.Join(root, rel), nil, parser.SkipObjectResolution)
266
+ if err != nil {
267
+ fm.Error = "parse"
268
+ return fm
269
+ }
270
+ fm.Package = af.Name.Name
271
+
272
+ ast.Inspect(af, func(n ast.Node) bool {
273
+ switch node := n.(type) {
274
+
275
+ // imports (R2.1–R2.4: alias, dot e blank são todos arestas)
276
+ case *ast.GenDecl:
277
+ if node.Tok == token.IMPORT {
278
+ for _, spec := range node.Specs {
279
+ is, ok := spec.(*ast.ImportSpec)
280
+ if !ok {
281
+ continue
282
+ }
283
+ path, ok := stringLit(is.Path)
284
+ if !ok {
285
+ continue
286
+ }
287
+ alias := ""
288
+ if is.Name != nil {
289
+ alias = is.Name.Name // inclui "." e "_"
290
+ }
291
+ fm.Imports = append(fm.Imports, ImportInfo{
292
+ Path: path, Alias: alias, Line: fset.Position(is.Pos()).Line,
293
+ })
294
+ }
295
+ }
296
+ if node.Tok == token.TYPE {
297
+ for _, spec := range node.Specs {
298
+ ts, ok := spec.(*ast.TypeSpec)
299
+ if !ok {
300
+ continue
301
+ }
302
+ st, ok := ts.Type.(*ast.StructType)
303
+ if !ok {
304
+ continue
305
+ }
306
+ fm.Structs = append(fm.Structs, extractStruct(fset, ts, st))
307
+ }
308
+ }
309
+ if node.Tok == token.VAR {
310
+ for _, spec := range node.Specs {
311
+ vs, ok := spec.(*ast.ValueSpec)
312
+ if !ok || vs.Type == nil {
313
+ continue
314
+ }
315
+ ty := exprText(fset, vs.Type)
316
+ for _, name := range vs.Names {
317
+ if name.Name == "_" {
318
+ continue
319
+ }
320
+ fm.VarTypes = append(fm.VarTypes, VarType{
321
+ Var: name.Name, Type: ty, Line: fset.Position(name.Pos()).Line,
322
+ })
323
+ }
324
+ }
325
+ }
326
+
327
+ // engineRoots: r := gin.Default() / r := gin.New()
328
+ case *ast.AssignStmt:
329
+ handleAssign(fset, node, &fm)
330
+
331
+ case *ast.CallExpr:
332
+ handleCall(fset, node, &fm)
333
+
334
+ case *ast.FuncDecl:
335
+ if node.Type.Params != nil {
336
+ for _, p := range node.Type.Params.List {
337
+ ty := exprText(fset, p.Type)
338
+ for _, name := range p.Names {
339
+ if name.Name == "_" {
340
+ continue
341
+ }
342
+ fm.VarTypes = append(fm.VarTypes, VarType{
343
+ Var: name.Name, Type: ty, Line: fset.Position(name.Pos()).Line,
344
+ })
345
+ }
346
+ }
347
+ }
348
+
349
+ case *ast.Ident:
350
+ if node.Name == "err" {
351
+ fm.UsesErr = true
352
+ }
353
+ }
354
+ return true
355
+ })
356
+
357
+ return fm
358
+ }
359
+
360
+ func extractStruct(fset *token.FileSet, ts *ast.TypeSpec, st *ast.StructType) StructInfo {
361
+ si := StructInfo{Name: ts.Name.Name, Line: fset.Position(ts.Pos()).Line, Fields: []FieldInfo{}}
362
+ if st.Fields == nil {
363
+ return si
364
+ }
365
+ for _, f := range st.Fields.List {
366
+ ty := exprText(fset, f.Type)
367
+ tag := ""
368
+ hasBinding := false
369
+ if f.Tag != nil {
370
+ if raw, err := strconv.Unquote(f.Tag.Value); err == nil {
371
+ tag = strings.TrimSpace(reflect.StructTag(raw).Get("binding"))
372
+ hasBinding = tag != ""
373
+ }
374
+ }
375
+ if len(f.Names) == 0 { // campo embutido
376
+ name := ty
377
+ if idx := strings.LastIndexByte(name, '.'); idx >= 0 {
378
+ name = name[idx+1:]
379
+ }
380
+ name = strings.TrimPrefix(name, "*")
381
+ si.Fields = append(si.Fields, FieldInfo{Name: name, Type: ty, BindingTag: tag, HasBinding: hasBinding})
382
+ continue
383
+ }
384
+ for _, n := range f.Names {
385
+ si.Fields = append(si.Fields, FieldInfo{Name: n.Name, Type: ty, BindingTag: tag, HasBinding: hasBinding})
386
+ }
387
+ }
388
+ return si
389
+ }
390
+
391
+ func handleAssign(fset *token.FileSet, as *ast.AssignStmt, fm *FileModel) {
392
+ line := fset.Position(as.Pos()).Line
393
+
394
+ // engineRoots / groupDefs / varTypes por :=
395
+ if as.Tok == token.DEFINE && len(as.Lhs) == 1 && len(as.Rhs) == 1 {
396
+ if id, ok := as.Lhs[0].(*ast.Ident); ok && id.Name != "_" {
397
+ if call, ok := as.Rhs[0].(*ast.CallExpr); ok {
398
+ // r := gin.Default() | gin.New()
399
+ if x, _, ok := selectorCall(call, "Default"); ok && isIdent(x, "gin") {
400
+ fm.EngineRoots = append(fm.EngineRoots, EngineRoot{Name: id.Name, Line: line})
401
+ }
402
+ if x, _, ok := selectorCall(call, "New"); ok && isIdent(x, "gin") {
403
+ fm.EngineRoots = append(fm.EngineRoots, EngineRoot{Name: id.Name, Line: line})
404
+ }
405
+ // api := r.Group("/api", Middleware())
406
+ if parent, args, ok := selectorCall(call, "Group"); ok && len(args) >= 1 {
407
+ path, _ := stringLit(args[0])
408
+ guards := []string{}
409
+ for _, a := range args[1:] {
410
+ if g := callName(a); g != "" {
411
+ guards = append(guards, g)
412
+ }
413
+ }
414
+ fm.GroupDefs = append(fm.GroupDefs, GroupDef{
415
+ Var: id.Name, Parent: exprText(fset, parent), Path: path, Guards: guards, Line: line,
416
+ })
417
+ }
418
+ }
419
+ // req := T{} / req := &T{}
420
+ switch rhs := as.Rhs[0].(type) {
421
+ case *ast.CompositeLit:
422
+ if rhs.Type != nil {
423
+ fm.VarTypes = append(fm.VarTypes, VarType{Var: id.Name, Type: exprText(fset, rhs.Type), Line: line})
424
+ }
425
+ case *ast.UnaryExpr:
426
+ if rhs.Op == token.AND {
427
+ if cl, ok := rhs.X.(*ast.CompositeLit); ok && cl.Type != nil {
428
+ fm.VarTypes = append(fm.VarTypes, VarType{Var: id.Name, Type: exprText(fset, cl.Type), Line: line})
429
+ }
430
+ }
431
+ }
432
+ }
433
+ }
434
+
435
+ // Fato R3.8: `err := f()` / `x, err := f()` — a chamada f aparece com
436
+ // err neste arquivo. O core usa isso para reduzir falso-positivo do
437
+ // sub-caso heurístico; a decisão de violar é do core.
438
+ for _, l := range as.Lhs {
439
+ if id, ok := l.(*ast.Ident); ok && (id.Name == "err" || strings.HasPrefix(id.Name, "err")) {
440
+ if call, ok := as.Rhs[0].(*ast.CallExpr); ok {
441
+ if n := callName(call.Fun); n != "" {
442
+ fm.CheckedCallees = append(fm.CheckedCallees, n)
443
+ }
444
+ }
445
+ break
446
+ }
447
+ }
448
+
449
+ // R3.7 (fatos): descarte explícito — `_ = err`, `_ = f()`, `x, _ := f()`.
450
+ hasBlank := false
451
+ for _, l := range as.Lhs {
452
+ if isIdent(l, "_") {
453
+ hasBlank = true
454
+ break
455
+ }
456
+ }
457
+ if hasBlank {
458
+ discard := false
459
+ if len(as.Lhs) > 1 {
460
+ _, isCall := as.Rhs[0].(*ast.CallExpr)
461
+ discard = isCall
462
+ } else if len(as.Lhs) == 1 {
463
+ switch r := as.Rhs[0].(type) {
464
+ case *ast.CallExpr:
465
+ discard = true
466
+ case *ast.Ident:
467
+ discard = r.Name == "err" || strings.HasPrefix(r.Name, "err")
468
+ }
469
+ }
470
+ if discard {
471
+ fm.ErrorHandling.ExplicitDiscards = append(fm.ErrorHandling.ExplicitDiscards, ErrSite{
472
+ Expr: exprText(fset, as), Line: line,
473
+ })
474
+ }
475
+ }
476
+ }
477
+
478
+ func handleCall(fset *token.FileSet, call *ast.CallExpr, fm *FileModel) {
479
+ line := fset.Position(call.Pos()).Line
480
+
481
+ // rotas Gin: r.POST("/x", Guard(), Handler)
482
+ if se, ok := call.Fun.(*ast.SelectorExpr); ok {
483
+ method, isRoute := routeMethods[se.Sel.Name]
484
+ if isRoute && len(call.Args) >= 2 {
485
+ path, okPath := stringLit(call.Args[0])
486
+ if okPath {
487
+ handler := ""
488
+ if len(call.Args) >= 1 {
489
+ handler = callName(call.Args[len(call.Args)-1])
490
+ }
491
+ guards := []string{}
492
+ for _, a := range call.Args[1 : len(call.Args)-1] {
493
+ if g := callName(a); g != "" {
494
+ guards = append(guards, g)
495
+ }
496
+ }
497
+ fm.Routes = append(fm.Routes, RouteInfo{
498
+ Method: method, Path: path, Handler: handler,
499
+ Receiver: exprText(fset, se.X), InlineGuards: guards, Line: line,
500
+ })
501
+ }
502
+ }
503
+
504
+ // r.Use(A(), B())
505
+ if recv, args, ok := selectorCall(call, "Use"); ok {
506
+ guards := []string{}
507
+ for _, a := range args {
508
+ if g := callName(a); g != "" {
509
+ guards = append(guards, g)
510
+ }
511
+ }
512
+ fm.UseCalls = append(fm.UseCalls, UseCall{Receiver: exprText(fset, recv), Guards: guards, Line: line})
513
+ }
514
+
515
+ // c.ShouldBindJSON(&req)
516
+ if bindFuncs[se.Sel.Name] && len(call.Args) >= 1 {
517
+ argExpr := exprText(fset, call.Args[0])
518
+ varName := strings.TrimPrefix(argExpr, "&")
519
+ fm.Bindings = append(fm.Bindings, BindingInfo{
520
+ Func: se.Sel.Name, ArgExpr: argExpr, VarName: varName, Line: line,
521
+ })
522
+ }
523
+ }
524
+
525
+ }
526
+
527
+ func main() {
528
+ root := flag.String("root", ".", "raiz do projeto-alvo")
529
+ filesFlag := flag.String("files", "", "caminhos relativos .go separados por vírgula")
530
+ flag.Parse()
531
+
532
+ // R1.1: stdlib-only. go.mod sem require é a prova mecânica.
533
+ model := Model{Version: 1, Module: modulePath(*root), Files: []FileModel{}}
534
+
535
+ var rels []string
536
+ for _, f := range strings.Split(*filesFlag, ",") {
537
+ f = strings.TrimSpace(f)
538
+ if f != "" {
539
+ rels = append(rels, filepath.FromSlash(f))
540
+ }
541
+ }
542
+
543
+ for _, rel := range rels {
544
+ model.Files = append(model.Files, extractFile(*root, rel))
545
+ }
546
+
547
+ // ExprStmt puro: precisa do contexto de statement, então uma passada
548
+ // dedicada por arquivo após o Inspect principal seria redundante — o
549
+ // Inspect acima não vê o wrapper ExprStmt. Refazemos com DeclFiles.
550
+ for i, rel := range rels {
551
+ if model.Files[i].Error != "" {
552
+ continue
553
+ }
554
+ fset := token.NewFileSet()
555
+ af, err := parser.ParseFile(fset, filepath.Join(*root, rel), nil, parser.SkipObjectResolution)
556
+ if err != nil {
557
+ continue
558
+ }
559
+ for _, decl := range af.Decls {
560
+ fd, ok := decl.(*ast.FuncDecl)
561
+ if !ok || fd.Body == nil {
562
+ continue
563
+ }
564
+ for _, stmt := range fd.Body.List {
565
+ es, ok := stmt.(*ast.ExprStmt)
566
+ if !ok {
567
+ continue
568
+ }
569
+ call, isCall := es.X.(*ast.CallExpr)
570
+ if !isCall {
571
+ continue
572
+ }
573
+ // Fato sintático: registro de rota/middleware Gin nunca é
574
+ // candidato a "retorno de error ignorado" (retorna *Router).
575
+ if se, ok := call.Fun.(*ast.SelectorExpr); ok {
576
+ if _, isRoute := routeMethods[se.Sel.Name]; isRoute {
577
+ continue
578
+ }
579
+ if se.Sel.Name == "Use" || se.Sel.Name == "Group" || se.Sel.Name == "Static" || se.Sel.Name == "StaticFile" || se.Sel.Name == "StaticFS" || se.Sel.Name == "NoRoute" || se.Sel.Name == "NoMethod" {
580
+ continue
581
+ }
582
+ }
583
+ model.Files[i].ErrorHandling.UncheckedErrCalls = append(
584
+ model.Files[i].ErrorHandling.UncheckedErrCalls,
585
+ ErrSite{Expr: exprText(fset, es.X), Line: fset.Position(es.Pos()).Line},
586
+ )
587
+ }
588
+ }
589
+ }
590
+
591
+ enc := json.NewEncoder(os.Stdout)
592
+ if err := enc.Encode(model); err != nil {
593
+ fmt.Fprintln(os.Stderr, "encode:", err)
594
+ os.Exit(1)
595
+ }
596
+ }
@@ -0,0 +1,3 @@
1
+ module ihelpee.dev/prumo-go-extractor
2
+
3
+ go 1.21
package/package.json CHANGED
@@ -1,6 +1,38 @@
1
1
  {
2
2
  "name": "@ihelpee/prumo-plugin-go-gin",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.9.0",
4
+ "type": "module",
5
+ "description": "Go/Gin plugin for Prumo: mutation route guards (inline/Group.Use/Engine.Use), error-handling enforcement, payload binding validation, and Go-native oracle isolation.",
6
+ "author": "Alex Gotardi <alexgotardi@gmail.com>",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "go-extractor",
19
+ "package.json",
20
+ "README.md"
21
+ ],
22
+ "sideEffects": false,
23
+ "license": "Apache-2.0",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/ihelpee/prumo.git"
27
+ },
28
+ "engines": {
29
+ "node": ">=20.11",
30
+ "go": ">=1.21"
31
+ },
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "dependencies": {
36
+ "@ihelpee/prumo": "0.9.0"
37
+ }
6
38
  }