@lovrozagar/honey 0.1.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.
Files changed (131) hide show
  1. package/README.md +51 -0
  2. package/docs/api-ref/go.md +399 -0
  3. package/docs/api-ref/python.md +399 -0
  4. package/docs/api-ref/rust.md +399 -0
  5. package/docs/api-ref/typescript.md +399 -0
  6. package/docs/invalidate-spec.md +303 -0
  7. package/docs/sdk.md +722 -0
  8. package/package.json +120 -0
  9. package/src/accepts.ts +78 -0
  10. package/src/body-limit.ts +100 -0
  11. package/src/build/index.ts +174 -0
  12. package/src/cli-go/auth.go +15 -0
  13. package/src/cli-go/config.go +165 -0
  14. package/src/cli-go/errors.go +57 -0
  15. package/src/cli-go/multipart.go +55 -0
  16. package/src/cli-go/output.go +109 -0
  17. package/src/cli-go/root.go +30 -0
  18. package/src/cli-go/stream.go +88 -0
  19. package/src/cli-go/version.go +5 -0
  20. package/src/cli.ts +204 -0
  21. package/src/client/error.ts +27 -0
  22. package/src/client/http.ts +502 -0
  23. package/src/client/index.ts +127 -0
  24. package/src/client/sdk.ts +265 -0
  25. package/src/client/sse.ts +102 -0
  26. package/src/client/types.ts +211 -0
  27. package/src/client/ws.ts +115 -0
  28. package/src/client-go/errors.go +207 -0
  29. package/src/client-go/invalidation.go +373 -0
  30. package/src/client-go/realtime.go +317 -0
  31. package/src/client-go/result.go +20 -0
  32. package/src/client-go/runtime.go +413 -0
  33. package/src/client-go/sse.go +149 -0
  34. package/src/client-go/transport.go +452 -0
  35. package/src/client-go/ws.go +104 -0
  36. package/src/client-mcp/README.md +10 -0
  37. package/src/client-mcp/server.ts +94 -0
  38. package/src/client-mcp/types.ts +19 -0
  39. package/src/client-python/_errors.py +115 -0
  40. package/src/client-python/_invalidation.py +578 -0
  41. package/src/client-python/_realtime.py +156 -0
  42. package/src/client-python/_runtime.py +469 -0
  43. package/src/client-python/_sse.py +99 -0
  44. package/src/client-python/_transport.py +335 -0
  45. package/src/client-python/_ws.py +131 -0
  46. package/src/client-python/py.typed +0 -0
  47. package/src/client-rust/errors.rs +200 -0
  48. package/src/client-rust/invalidation.rs +441 -0
  49. package/src/client-rust/invalidation_sync.rs +306 -0
  50. package/src/client-rust/realtime.rs +695 -0
  51. package/src/client-rust/result.rs +22 -0
  52. package/src/client-rust/runtime.rs +547 -0
  53. package/src/client-rust/runtime_sync.rs +452 -0
  54. package/src/client-rust/sse.rs +167 -0
  55. package/src/client-rust/ws.rs +143 -0
  56. package/src/cloudflare/realtime.ts +236 -0
  57. package/src/codegen-go-cli.ts +1180 -0
  58. package/src/codegen-go.ts +1010 -0
  59. package/src/codegen-hash.ts +8 -0
  60. package/src/codegen-ir.ts +517 -0
  61. package/src/codegen-loaders.ts +53 -0
  62. package/src/codegen-mcp.ts +364 -0
  63. package/src/codegen-python.ts +1028 -0
  64. package/src/codegen-rust.ts +1652 -0
  65. package/src/codegen-sanitize.ts +67 -0
  66. package/src/codegen-schema-naming.ts +146 -0
  67. package/src/codegen.ts +5015 -0
  68. package/src/context.ts +210 -0
  69. package/src/cookie-sign.ts +58 -0
  70. package/src/cookie.ts +89 -0
  71. package/src/cors.ts +96 -0
  72. package/src/crypto.ts +19 -0
  73. package/src/csrf.ts +54 -0
  74. package/src/curl-logger.ts +235 -0
  75. package/src/detect-runtime.ts +19 -0
  76. package/src/error.ts +52 -0
  77. package/src/errors.ts +87 -0
  78. package/src/etag.ts +74 -0
  79. package/src/feature-load.ts +23 -0
  80. package/src/go-type-emitter.ts +404 -0
  81. package/src/honey-response.ts +354 -0
  82. package/src/i18n-slot.ts +20 -0
  83. package/src/i18n.ts +234 -0
  84. package/src/index.ts +3843 -0
  85. package/src/init.ts +149 -0
  86. package/src/input.ts +8 -0
  87. package/src/ip-restrict.ts +192 -0
  88. package/src/logger.ts +149 -0
  89. package/src/middleware.ts +199 -0
  90. package/src/node-request.ts +240 -0
  91. package/src/node.ts +212 -0
  92. package/src/openapi/register.ts +19 -0
  93. package/src/openapi/scalar.ts +33 -0
  94. package/src/openapi/spec-factory.ts +46 -0
  95. package/src/openapi/spec.ts +31 -0
  96. package/src/openapi/swagger.ts +34 -0
  97. package/src/plugin.ts +771 -0
  98. package/src/powered-by.ts +17 -0
  99. package/src/pretty-json.ts +39 -0
  100. package/src/proxy.ts +155 -0
  101. package/src/python-type-emitter.ts +144 -0
  102. package/src/realtime/buffer.ts +101 -0
  103. package/src/realtime/bus.ts +93 -0
  104. package/src/realtime/longpoll.ts +148 -0
  105. package/src/realtime/protocol.ts +128 -0
  106. package/src/realtime/route.ts +104 -0
  107. package/src/realtime/runtime.ts +430 -0
  108. package/src/request-id.ts +23 -0
  109. package/src/request-to-curl.ts +34 -0
  110. package/src/response.ts +414 -0
  111. package/src/rust-type-emitter.ts +412 -0
  112. package/src/secure-headers.ts +69 -0
  113. package/src/serve-register.ts +8 -0
  114. package/src/serve-slot.ts +20 -0
  115. package/src/serve.ts +141 -0
  116. package/src/server-timing.ts +66 -0
  117. package/src/static.ts +60 -0
  118. package/src/telemetry/otel.ts +139 -0
  119. package/src/testing.ts +114 -0
  120. package/src/timeout.ts +36 -0
  121. package/src/tree.ts +464 -0
  122. package/src/ts-type-emitter.ts +106 -0
  123. package/src/type-emitter.ts +524 -0
  124. package/src/type-extractor.ts +749 -0
  125. package/src/types.ts +638 -0
  126. package/src/validation.ts +300 -0
  127. package/src/ws/bun.ts +89 -0
  128. package/src/ws/cloudflare.ts +133 -0
  129. package/src/ws/deno.ts +109 -0
  130. package/src/ws/node.ts +161 -0
  131. package/src/yaml.ts +80 -0
@@ -0,0 +1,57 @@
1
+ // Code generated by honey. DO NOT EDIT.
2
+ package cli
3
+
4
+ import (
5
+ "context"
6
+ "errors"
7
+ "net"
8
+ )
9
+
10
+ // ExitFor maps a CLI-level error to a process exit code.
11
+ //
12
+ // Codes:
13
+ //
14
+ // 0 success
15
+ // 1 4xx client error (status < 500)
16
+ // 2 5xx server error (status >= 500)
17
+ // 3 network / transport failure or status == 0
18
+ // 4 bad flags / bad config / missing api key
19
+ // 5 3xx unexpected redirect
20
+ func ExitFor(err error) int {
21
+ if err == nil {
22
+ return 0
23
+ }
24
+ if errors.Is(err, ErrMissingAPIKey) {
25
+ return 4
26
+ }
27
+ if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
28
+ return 3
29
+ }
30
+
31
+ var netErr net.Error
32
+ if errors.As(err, &netErr) {
33
+ return 3
34
+ }
35
+
36
+ var apiErr interface{ StatusCode() int }
37
+ if errors.As(err, &apiErr) {
38
+ return exitForStatus(apiErr.StatusCode())
39
+ }
40
+
41
+ return 3
42
+ }
43
+
44
+ func exitForStatus(status int) int {
45
+ switch {
46
+ case status == 0:
47
+ return 3
48
+ case status >= 300 && status < 400:
49
+ return 5
50
+ case status >= 400 && status < 500:
51
+ return 1
52
+ case status >= 500 && status < 600:
53
+ return 2
54
+ default:
55
+ return 1
56
+ }
57
+ }
@@ -0,0 +1,55 @@
1
+ // Code generated by honey. DO NOT EDIT.
2
+ package cli
3
+
4
+ import (
5
+ "bytes"
6
+ "fmt"
7
+ "io"
8
+ "mime/multipart"
9
+ "os"
10
+ "path/filepath"
11
+ )
12
+
13
+ // BuildMultipart assembles a multipart/form-data body.
14
+ //
15
+ // fields are written as plain form fields.
16
+ // files are keyed by form-field-name → absolute-or-relative file path.
17
+ //
18
+ // Returns the body reader, the Content-Type header value (including boundary),
19
+ // and any error.
20
+ func BuildMultipart(fields map[string]string, files map[string]string) (io.Reader, string, error) {
21
+ var buf bytes.Buffer
22
+ writer := multipart.NewWriter(&buf)
23
+
24
+ for name, val := range fields {
25
+ if err := writer.WriteField(name, val); err != nil {
26
+ return nil, "", fmt.Errorf("write field %q: %w", name, err)
27
+ }
28
+ }
29
+
30
+ for name, path := range files {
31
+ if path == "" {
32
+ return nil, "", fmt.Errorf("missing file path for field %q", name)
33
+ }
34
+ file, err := os.Open(path)
35
+ if err != nil {
36
+ return nil, "", fmt.Errorf("open file %s: %w", path, err)
37
+ }
38
+ part, err := writer.CreateFormFile(name, filepath.Base(path))
39
+ if err != nil {
40
+ file.Close()
41
+ return nil, "", fmt.Errorf("create form file %q: %w", name, err)
42
+ }
43
+ if _, err := io.Copy(part, file); err != nil {
44
+ file.Close()
45
+ return nil, "", fmt.Errorf("copy file %s: %w", path, err)
46
+ }
47
+ file.Close()
48
+ }
49
+
50
+ if err := writer.Close(); err != nil {
51
+ return nil, "", fmt.Errorf("close multipart writer: %w", err)
52
+ }
53
+
54
+ return &buf, writer.FormDataContentType(), nil
55
+ }
@@ -0,0 +1,109 @@
1
+ // Code generated by honey. DO NOT EDIT.
2
+ package cli
3
+
4
+ import (
5
+ "encoding/json"
6
+ "fmt"
7
+ "io"
8
+ "sort"
9
+ "strings"
10
+ "text/tabwriter"
11
+
12
+ "sigs.k8s.io/yaml"
13
+ )
14
+
15
+ // Emit writes data to w in the requested mode.
16
+ //
17
+ // Supported modes: json (pretty, 2-space indent), ndjson, yaml, table.
18
+ // Unknown modes return an error that ExitFor maps to exit code 4.
19
+ func Emit(data any, mode string, w io.Writer) error {
20
+ if data == nil {
21
+ return nil
22
+ }
23
+ switch mode {
24
+ case "", "json":
25
+ enc, err := json.MarshalIndent(data, "", " ")
26
+ if err != nil {
27
+ return err
28
+ }
29
+ _, err = fmt.Fprintln(w, string(enc))
30
+ return err
31
+ case "ndjson":
32
+ enc, err := json.Marshal(data)
33
+ if err != nil {
34
+ return err
35
+ }
36
+ _, err = fmt.Fprintln(w, string(enc))
37
+ return err
38
+ case "yaml":
39
+ enc, err := yaml.Marshal(data)
40
+ if err != nil {
41
+ return err
42
+ }
43
+ _, err = w.Write(enc)
44
+ return err
45
+ case "table":
46
+ return printTable(data, w)
47
+ default:
48
+ return fmt.Errorf("unknown output mode %q: valid modes are json, ndjson, yaml, table", mode)
49
+ }
50
+ }
51
+
52
+ // printTable renders data as an aligned table when possible, or falls back to raw print.
53
+ func printTable(data any, w io.Writer) error {
54
+ switch v := data.(type) {
55
+ case []any:
56
+ if len(v) == 0 {
57
+ return nil
58
+ }
59
+ rows := make([]map[string]any, 0, len(v))
60
+ for _, row := range v {
61
+ if m, ok := row.(map[string]any); ok {
62
+ rows = append(rows, m)
63
+ } else {
64
+ fmt.Fprintln(w, row)
65
+ return nil
66
+ }
67
+ }
68
+ keys := collectKeys(rows)
69
+ tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0)
70
+ fmt.Fprintln(tw, strings.Join(keys, "\t"))
71
+ for _, row := range rows {
72
+ cols := make([]string, len(keys))
73
+ for i, k := range keys {
74
+ cols[i] = fmt.Sprintf("%v", row[k])
75
+ }
76
+ fmt.Fprintln(tw, strings.Join(cols, "\t"))
77
+ }
78
+ return tw.Flush()
79
+ case map[string]any:
80
+ keys := make([]string, 0, len(v))
81
+ for k := range v {
82
+ keys = append(keys, k)
83
+ }
84
+ sort.Strings(keys)
85
+ tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0)
86
+ for _, k := range keys {
87
+ fmt.Fprintf(tw, "%s\t%v\n", k, v[k])
88
+ }
89
+ return tw.Flush()
90
+ default:
91
+ _, err := fmt.Fprintln(w, data)
92
+ return err
93
+ }
94
+ }
95
+
96
+ func collectKeys(rows []map[string]any) []string {
97
+ seen := map[string]bool{}
98
+ keys := []string{}
99
+ for _, row := range rows {
100
+ for k := range row {
101
+ if !seen[k] {
102
+ seen[k] = true
103
+ keys = append(keys, k)
104
+ }
105
+ }
106
+ }
107
+ sort.Strings(keys)
108
+ return keys
109
+ }
@@ -0,0 +1,30 @@
1
+ // Code generated by honey. DO NOT EDIT.
2
+ package cli
3
+
4
+ import (
5
+ "github.com/spf13/cobra"
6
+ )
7
+
8
+ // NewRoot constructs a cobra root command with the standard persistent flags.
9
+ //
10
+ // Flags (all persistent, inherited by subcommands):
11
+ //
12
+ // --api-key API key (overrides env + config file)
13
+ // --base-url Override API base URL
14
+ // --output Output mode: json|ndjson|yaml|table (default json)
15
+ // --verbose Print request metadata to stderr
16
+ // --config Path to config file
17
+ // --timeout Go duration string, e.g. "30s"
18
+ func NewRoot(binaryName, version string) *cobra.Command {
19
+ root := &cobra.Command{
20
+ Use: binaryName,
21
+ Version: version,
22
+ }
23
+ root.PersistentFlags().String("api-key", "", "API key (overrides env + config file)")
24
+ root.PersistentFlags().String("base-url", "", "Override API base URL")
25
+ root.PersistentFlags().String("output", "json", "Output mode: json|ndjson|yaml|table")
26
+ root.PersistentFlags().Bool("verbose", false, "Print request metadata to stderr")
27
+ root.PersistentFlags().String("config", "", "Path to config file")
28
+ root.PersistentFlags().String("timeout", "30s", "Request timeout (Go duration)")
29
+ return root
30
+ }
@@ -0,0 +1,88 @@
1
+ // Code generated by honey. DO NOT EDIT.
2
+ package cli
3
+
4
+ import (
5
+ "encoding/json"
6
+ "fmt"
7
+ "io"
8
+ "iter"
9
+ "os"
10
+
11
+ "sigs.k8s.io/yaml"
12
+ )
13
+
14
+ // SSEEvent is the concrete SSE event type consumed by StreamSSE. The CLI's
15
+ // generated cmd/*.go files wrap the SDK's iterator into a Seq2[SSEEvent, error]
16
+ // before passing it in, so this runtime stays SDK-agnostic. Go generics do not
17
+ // support struct-field access through a type parameter, so this is a plain
18
+ // concrete struct rather than a constrained generic.
19
+ type SSEEvent struct {
20
+ Data string
21
+ Event string
22
+ ID string
23
+ Retry int
24
+ }
25
+
26
+ // streamEvent is the on-wire shape that gets emitted for each SSE event.
27
+ type streamEvent struct {
28
+ Event string `json:"event,omitempty"`
29
+ ID string `json:"id,omitempty"`
30
+ Data json.RawMessage `json:"data"`
31
+ Retry int `json:"retry,omitempty"`
32
+ }
33
+
34
+ // StreamSSE consumes an iter.Seq2 of SSE events and writes them to w.
35
+ //
36
+ // --output modes:
37
+ //
38
+ // ndjson (default for streams) → one JSON object per line.
39
+ // yaml → YAML documents separated by "---\n".
40
+ // table → falls back to ndjson; prints a warning to os.Stderr.
41
+ // json → treated as ndjson (no way to represent a live stream as one object).
42
+ func StreamSSE(it iter.Seq2[SSEEvent, error], w io.Writer, mode string) error {
43
+ if mode == "table" {
44
+ fmt.Fprintln(os.Stderr, "warning: --output=table is not supported for streaming endpoints; falling back to ndjson")
45
+ mode = "ndjson"
46
+ }
47
+ for ev, err := range it {
48
+ if err != nil {
49
+ return err
50
+ }
51
+ out := streamEvent{
52
+ Event: ev.Event,
53
+ ID: ev.ID,
54
+ Retry: ev.Retry,
55
+ }
56
+ if ev.Data == "" {
57
+ out.Data = json.RawMessage("null")
58
+ } else if json.Valid([]byte(ev.Data)) {
59
+ out.Data = json.RawMessage(ev.Data)
60
+ } else {
61
+ enc, _ := json.Marshal(ev.Data)
62
+ out.Data = enc
63
+ }
64
+
65
+ switch mode {
66
+ case "yaml":
67
+ enc, err := yaml.Marshal(out)
68
+ if err != nil {
69
+ return err
70
+ }
71
+ if _, err := w.Write(enc); err != nil {
72
+ return err
73
+ }
74
+ if _, err := fmt.Fprint(w, "---\n"); err != nil {
75
+ return err
76
+ }
77
+ default:
78
+ enc, err := json.Marshal(out)
79
+ if err != nil {
80
+ return err
81
+ }
82
+ if _, err := fmt.Fprintln(w, string(enc)); err != nil {
83
+ return err
84
+ }
85
+ }
86
+ }
87
+ return nil
88
+ }
@@ -0,0 +1,5 @@
1
+ // Code generated by honey. DO NOT EDIT.
2
+ package cli
3
+
4
+ // Version is overridable at build time via -ldflags.
5
+ const Version = "dev"
package/src/cli.ts ADDED
@@ -0,0 +1,204 @@
1
+ #!/usr/bin/env bun
2
+ import { existsSync, statSync, watch } from "node:fs"
3
+ import { dirname, resolve } from "node:path"
4
+ import { parseInitFlags, runInit } from "./init.ts"
5
+ import type { HoneyGoCliConfig, HoneyVitePluginConfig } from "./plugin.ts"
6
+ import { generateAndWrite, type getLastHoneyConfig, resolveHoneyConfig } from "./plugin.ts"
7
+
8
+ const USAGE =
9
+ "Usage: honey generate [--watch] [--config <path>] [--app <path>] [flags]\n" +
10
+ " honey init [--cf] [--force]"
11
+
12
+ type CliFlags = {
13
+ app?: string
14
+ cli?: boolean
15
+ cliBinaryName?: string
16
+ cliConfigName?: string
17
+ cliDefaultBaseUrl?: string
18
+ cliEnvPrefix?: string
19
+ cliModulePath?: string
20
+ cliOut?: string
21
+ cliSdkModulePath?: string
22
+ config?: string
23
+ manifest?: boolean
24
+ mergeTree?: string
25
+ sdk?: boolean
26
+ tree?: boolean
27
+ types?: boolean
28
+ watch?: boolean
29
+ }
30
+
31
+ const BOOLEAN_FLAGS = new Set(["cli", "manifest", "sdk", "tree", "types", "watch"])
32
+
33
+ function parseArgs(args: string[]): CliFlags {
34
+ const parsed: Record<string, string> = {}
35
+ for (let i = 0; i < args.length; i++) {
36
+ const arg = args[i]
37
+ if (!arg.startsWith("--")) continue
38
+ const key = arg.slice(2)
39
+ if (BOOLEAN_FLAGS.has(key)) {
40
+ parsed[key] = "true"
41
+ } else if (i + 1 < args.length) {
42
+ parsed[key] = args[i + 1]
43
+ i++
44
+ }
45
+ }
46
+
47
+ return {
48
+ app: parsed.app,
49
+ cli: parsed.cli !== undefined,
50
+ cliBinaryName: parsed["cli-binary-name"],
51
+ cliConfigName: parsed["cli-config-name"],
52
+ cliDefaultBaseUrl: parsed["cli-default-base-url"],
53
+ cliEnvPrefix: parsed["cli-env-prefix"],
54
+ cliModulePath: parsed["cli-module-path"],
55
+ cliOut: parsed["cli-out"],
56
+ cliSdkModulePath: parsed["cli-sdk-module-path"],
57
+ config: parsed.config,
58
+ manifest: parsed.manifest !== undefined,
59
+ mergeTree: parsed["merge-tree"],
60
+ sdk: parsed.sdk !== undefined,
61
+ tree: parsed.tree !== undefined,
62
+ types: parsed.types !== undefined,
63
+ watch: parsed.watch !== undefined,
64
+ }
65
+ }
66
+
67
+ async function loadConfigFromVite(configPath: string): Promise<HoneyVitePluginConfig | undefined> {
68
+ if (!existsSync(configPath)) return undefined
69
+
70
+ const { createJiti } = await import("jiti")
71
+ const jiti = createJiti(configPath, { fsCache: false, interopDefault: true, moduleCache: false })
72
+
73
+ /* importing the config executes honey() which stashes config */
74
+ await jiti.import(configPath)
75
+
76
+ /* read stashed config from the same module graph */
77
+ const pluginMod = (await jiti.import("honey/plugin")) as Record<string, unknown>
78
+ const getter = pluginMod.getLastHoneyConfig as typeof getLastHoneyConfig | undefined
79
+ return getter?.()
80
+ }
81
+
82
+ function applyCodegenFlags(target: HoneyVitePluginConfig, flags: CliFlags): void {
83
+ if (!target.codegen) target.codegen = {}
84
+ const cg = target.codegen
85
+ if (flags.mergeTree) cg.mergeTree = flags.mergeTree
86
+ if (flags.manifest) cg.manifest = true
87
+ if (flags.sdk) cg.sdk = true
88
+ if (flags.tree) cg.tree = true
89
+ if (flags.types) cg.types = true
90
+ const cliOverride = buildCliConfigFromFlags(flags)
91
+ if (cliOverride) cg.cli = cliOverride
92
+ }
93
+
94
+ function mergeCliOverrides(base: HoneyVitePluginConfig, flags: CliFlags): HoneyVitePluginConfig {
95
+ const merged = { ...base }
96
+ if (flags.app) merged.app = flags.app
97
+ applyCodegenFlags(merged, flags)
98
+ return merged
99
+ }
100
+
101
+ function buildCliConfigFromFlags(flags: CliFlags): HoneyGoCliConfig | undefined {
102
+ if (!flags.cli && !flags.cliOut && !flags.cliBinaryName) return undefined
103
+ if (!flags.cliOut || !flags.cliBinaryName) {
104
+ throw new Error("--cli requires --cli-out and --cli-binary-name")
105
+ }
106
+ return {
107
+ binaryName: flags.cliBinaryName,
108
+ configName: flags.cliConfigName,
109
+ defaultBaseURL: flags.cliDefaultBaseUrl,
110
+ envPrefix: flags.cliEnvPrefix,
111
+ modulePath: flags.cliModulePath,
112
+ out: flags.cliOut,
113
+ sdkModulePath: flags.cliSdkModulePath,
114
+ }
115
+ }
116
+
117
+ function configFromFlags(flags: CliFlags): HoneyVitePluginConfig | undefined {
118
+ if (!flags.app) return undefined
119
+ const config: HoneyVitePluginConfig = { app: flags.app }
120
+ applyCodegenFlags(config, flags)
121
+ return config
122
+ }
123
+
124
+ const GEN_IGNORE_RE = /(_gen[/\\]|\.gen\.(tsx?|json|d\.ts)$)/
125
+
126
+ async function main() {
127
+ const args = process.argv.slice(2)
128
+ const command = args[0]
129
+
130
+ if (command === "init") {
131
+ runInit(process.cwd(), parseInitFlags(args.slice(1)))
132
+ return
133
+ }
134
+
135
+ if (command !== "generate") {
136
+ console.error(USAGE)
137
+ process.exit(1)
138
+ }
139
+
140
+ const flags = parseArgs(args.slice(1))
141
+ const cwd = process.cwd()
142
+
143
+ /* resolve config: vite.config.ts → CLI flags fallback */
144
+ const configPath = resolve(cwd, flags.config ?? "vite.config.ts")
145
+ let rawConfig: HoneyVitePluginConfig | undefined
146
+
147
+ const viteConfig = await loadConfigFromVite(configPath)
148
+ if (viteConfig) {
149
+ rawConfig = mergeCliOverrides(viteConfig, flags)
150
+ } else {
151
+ rawConfig = configFromFlags(flags)
152
+ }
153
+
154
+ if (!rawConfig) {
155
+ console.error("No config found. Provide a vite.config.ts with honey() or use --app flag.")
156
+ process.exit(1)
157
+ }
158
+
159
+ const resolved = resolveHoneyConfig(rawConfig)
160
+
161
+ async function generate(): Promise<void> {
162
+ try {
163
+ await generateAndWrite(resolved, cwd)
164
+ console.log("honey: generated")
165
+ } catch (err) {
166
+ console.error("honey: generation failed", err)
167
+ if (!flags.watch) process.exit(1)
168
+ }
169
+ }
170
+
171
+ await generate()
172
+
173
+ if (flags.watch) {
174
+ if (!resolved.app) throw new Error("watch mode requires --app or a vite honey() config")
175
+ const appAbs = resolve(cwd, resolved.app)
176
+ const srcDir = dirname(appAbs)
177
+ let debounceTimer: ReturnType<typeof setTimeout> | undefined
178
+ let lastMtime = existsSync(appAbs) ? statSync(appAbs).mtimeMs : 0
179
+
180
+ const schedule = (): void => {
181
+ clearTimeout(debounceTimer)
182
+ debounceTimer = setTimeout(() => generate(), 100)
183
+ }
184
+
185
+ console.log(`honey: watching ${srcDir}`)
186
+ watch(srcDir, { recursive: true }, (_event, filename) => {
187
+ if (!filename || GEN_IGNORE_RE.test(String(filename))) return
188
+ schedule()
189
+ })
190
+ watch(appAbs, schedule)
191
+ setInterval(() => {
192
+ if (!existsSync(appAbs)) return
193
+ const mtime = statSync(appAbs).mtimeMs
194
+ if (mtime === lastMtime) return
195
+ lastMtime = mtime
196
+ schedule()
197
+ }, 250)
198
+ }
199
+ }
200
+
201
+ main().catch((err) => {
202
+ console.error(err)
203
+ process.exit(1)
204
+ })
@@ -0,0 +1,27 @@
1
+ export type ClientErrorInit<TBody = unknown> = {
2
+ body: TBody
3
+ message: string
4
+ response: Response
5
+ status: number
6
+ }
7
+
8
+ export class ClientError<TBody = unknown> extends Error {
9
+ readonly body: TBody
10
+ readonly response: Response
11
+ readonly status: number
12
+
13
+ constructor(init: ClientErrorInit<TBody>) {
14
+ super(init.message)
15
+ if (typeof Error.captureStackTrace === "function") {
16
+ Error.captureStackTrace(this, ClientError)
17
+ }
18
+ this.name = "ClientError"
19
+ this.body = init.body
20
+ this.response = init.response
21
+ this.status = init.status
22
+ }
23
+ }
24
+
25
+ export function isClientError<T = unknown>(e: unknown): e is ClientError<T> {
26
+ return e instanceof ClientError
27
+ }