opencode-snippets 1.1.2 → 1.2.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.
package/src/logger.ts CHANGED
@@ -1,121 +1,121 @@
1
- import { writeFileSync, mkdirSync, existsSync } from "fs"
2
- import { join } from "path"
3
- import { OPENCODE_CONFIG_DIR } from "./constants.js"
1
+ import { existsSync, mkdirSync, writeFileSync } from "fs";
2
+ import { join } from "path";
3
+ import { OPENCODE_CONFIG_DIR } from "./constants.js";
4
4
 
5
5
  /**
6
6
  * Check if debug logging is enabled via environment variable
7
7
  */
8
8
  function isDebugEnabled(): boolean {
9
- const value = process.env.DEBUG_SNIPPETS
10
- return value === "1" || value === "true"
9
+ const value = process.env.DEBUG_SNIPPETS;
10
+ return value === "1" || value === "true";
11
11
  }
12
12
 
13
13
  export class Logger {
14
- private logDir: string
14
+ private logDir: string;
15
15
 
16
- constructor() {
17
- this.logDir = join(OPENCODE_CONFIG_DIR, "logs", "snippets")
18
- }
16
+ constructor(logDirOverride?: string) {
17
+ this.logDir = logDirOverride ?? join(OPENCODE_CONFIG_DIR, "logs", "snippets");
18
+ }
19
19
 
20
- private get enabled(): boolean {
21
- return isDebugEnabled()
22
- }
20
+ get enabled(): boolean {
21
+ return isDebugEnabled();
22
+ }
23
23
 
24
- private ensureLogDir() {
25
- if (!existsSync(this.logDir)) {
26
- mkdirSync(this.logDir, { recursive: true })
27
- }
24
+ private ensureLogDir() {
25
+ if (!existsSync(this.logDir)) {
26
+ mkdirSync(this.logDir, { recursive: true });
28
27
  }
29
-
30
- private formatData(data?: any): string {
31
- if (!data) return ""
32
-
33
- const parts: string[] = []
34
- for (const [key, value] of Object.entries(data)) {
35
- if (value === undefined || value === null) continue
36
-
37
- // Format arrays compactly
38
- if (Array.isArray(value)) {
39
- if (value.length === 0) continue
40
- parts.push(`${key}=[${value.slice(0, 3).join(",")}${value.length > 3 ? `...+${value.length - 3}` : ""}]`)
41
- }
42
- else if (typeof value === 'object') {
43
- const str = JSON.stringify(value)
44
- if (str.length < 50) {
45
- parts.push(`${key}=${str}`)
46
- }
47
- }
48
- else {
49
- parts.push(`${key}=${value}`)
50
- }
28
+ }
29
+
30
+ private formatData(data?: Record<string, unknown>): string {
31
+ if (!data) return "";
32
+
33
+ const parts: string[] = [];
34
+ for (const [key, value] of Object.entries(data)) {
35
+ if (value === undefined || value === null) continue;
36
+
37
+ // Format arrays compactly
38
+ if (Array.isArray(value)) {
39
+ if (value.length === 0) continue;
40
+ parts.push(
41
+ `${key}=[${value.slice(0, 3).join(",")}${value.length > 3 ? `...+${value.length - 3}` : ""}]`,
42
+ );
43
+ } else if (typeof value === "object") {
44
+ const str = JSON.stringify(value);
45
+ if (str.length < 50) {
46
+ parts.push(`${key}=${str}`);
51
47
  }
52
- return parts.join(" ")
48
+ } else {
49
+ parts.push(`${key}=${value}`);
50
+ }
53
51
  }
54
-
55
- private getCallerFile(): string {
56
- const originalPrepareStackTrace = Error.prepareStackTrace
57
- try {
58
- const err = new Error()
59
- Error.prepareStackTrace = (_, stack) => stack
60
- const stack = err.stack as unknown as NodeJS.CallSite[]
61
- Error.prepareStackTrace = originalPrepareStackTrace
62
-
63
- for (let i = 3; i < stack.length; i++) {
64
- const filename = stack[i]?.getFileName()
65
- if (filename && !filename.includes('logger.')) {
66
- const match = filename.match(/([^/\\]+)\.[tj]s$/)
67
- return match ? match[1] : 'unknown'
68
- }
69
- }
70
- return 'unknown'
71
- } catch {
72
- return 'unknown'
52
+ return parts.join(" ");
53
+ }
54
+
55
+ private getCallerFile(): string {
56
+ const originalPrepareStackTrace = Error.prepareStackTrace;
57
+ try {
58
+ const err = new Error();
59
+ Error.prepareStackTrace = (_, stack) => stack;
60
+ const stack = err.stack as unknown as NodeJS.CallSite[];
61
+ Error.prepareStackTrace = originalPrepareStackTrace;
62
+
63
+ for (let i = 3; i < stack.length; i++) {
64
+ const filename = stack[i]?.getFileName();
65
+ if (filename && !filename.includes("logger.")) {
66
+ const match = filename.match(/([^/\\]+)\.[tj]s$/);
67
+ return match ? match[1] : "unknown";
73
68
  }
69
+ }
70
+ return "unknown";
71
+ } catch {
72
+ return "unknown";
74
73
  }
74
+ }
75
75
 
76
- private write(level: string, component: string, message: string, data?: any) {
77
- if (!this.enabled) return
78
-
79
- try {
80
- this.ensureLogDir()
81
-
82
- const timestamp = new Date().toISOString()
83
- const dataStr = this.formatData(data)
76
+ private write(level: string, component: string, message: string, data?: Record<string, unknown>) {
77
+ if (!this.enabled) return;
84
78
 
85
- const dailyLogDir = join(this.logDir, "daily")
86
- if (!existsSync(dailyLogDir)) {
87
- mkdirSync(dailyLogDir, { recursive: true })
88
- }
79
+ try {
80
+ this.ensureLogDir();
89
81
 
90
- const logLine = `${timestamp} ${level.padEnd(5)} ${component}: ${message}${dataStr ? " | " + dataStr : ""}\n`
82
+ const timestamp = new Date().toISOString();
83
+ const dataStr = this.formatData(data);
91
84
 
92
- const logFile = join(dailyLogDir, `${new Date().toISOString().split('T')[0]}.log`)
93
- writeFileSync(logFile, logLine, { flag: "a" })
94
- } catch (error) {
95
- // Silent fail
96
- }
97
- }
98
-
99
- info(message: string, data?: any) {
100
- const component = this.getCallerFile()
101
- this.write("INFO", component, message, data)
102
- }
85
+ const dailyLogDir = join(this.logDir, "daily");
86
+ if (!existsSync(dailyLogDir)) {
87
+ mkdirSync(dailyLogDir, { recursive: true });
88
+ }
103
89
 
104
- debug(message: string, data?: any) {
105
- const component = this.getCallerFile()
106
- this.write("DEBUG", component, message, data)
107
- }
108
-
109
- warn(message: string, data?: any) {
110
- const component = this.getCallerFile()
111
- this.write("WARN", component, message, data)
112
- }
90
+ const logLine = `${timestamp} ${level.padEnd(5)} ${component}: ${message}${dataStr ? ` | ${dataStr}` : ""}\n`;
113
91
 
114
- error(message: string, data?: any) {
115
- const component = this.getCallerFile()
116
- this.write("ERROR", component, message, data)
92
+ const logFile = join(dailyLogDir, `${new Date().toISOString().split("T")[0]}.log`);
93
+ writeFileSync(logFile, logLine, { flag: "a" });
94
+ } catch {
95
+ // Silent fail
117
96
  }
97
+ }
98
+
99
+ info(message: string, data?: Record<string, unknown>) {
100
+ const component = this.getCallerFile();
101
+ this.write("INFO", component, message, data);
102
+ }
103
+
104
+ debug(message: string, data?: Record<string, unknown>) {
105
+ const component = this.getCallerFile();
106
+ this.write("DEBUG", component, message, data);
107
+ }
108
+
109
+ warn(message: string, data?: Record<string, unknown>) {
110
+ const component = this.getCallerFile();
111
+ this.write("WARN", component, message, data);
112
+ }
113
+
114
+ error(message: string, data?: Record<string, unknown>) {
115
+ const component = this.getCallerFile();
116
+ this.write("ERROR", component, message, data);
117
+ }
118
118
  }
119
119
 
120
120
  // Export singleton logger instance
121
- export const logger = new Logger()
121
+ export const logger = new Logger();
package/src/shell.ts CHANGED
@@ -1,44 +1,50 @@
1
- import { PATTERNS } from "./constants.js"
2
- import { logger } from "./logger.js"
1
+ import { PATTERNS } from "./constants.js";
2
+ import { logger } from "./logger.js";
3
3
 
4
4
  /**
5
5
  * Executes shell commands in text using !`command` syntax
6
- *
6
+ *
7
7
  * @param text - The text containing shell commands to execute
8
8
  * @param ctx - The plugin context (with Bun shell)
9
9
  * @returns The text with shell commands replaced by their output
10
10
  */
11
- export async function executeShellCommands(
12
- text: string,
13
- ctx: any
14
- ): Promise<string> {
15
- let result = text
16
-
11
+ export type ShellContext = {
12
+ $: (
13
+ template: TemplateStringsArray,
14
+ ...args: unknown[]
15
+ ) => {
16
+ quiet: () => { nothrow: () => { text: () => Promise<string> } };
17
+ };
18
+ };
19
+
20
+ export async function executeShellCommands(text: string, ctx: ShellContext): Promise<string> {
21
+ let result = text;
22
+
17
23
  // Reset regex state (global flag requires this)
18
- PATTERNS.SHELL_COMMAND.lastIndex = 0
19
-
24
+ PATTERNS.SHELL_COMMAND.lastIndex = 0;
25
+
20
26
  // Find all shell command matches
21
- const matches = [...text.matchAll(PATTERNS.SHELL_COMMAND)]
22
-
27
+ const matches = [...text.matchAll(PATTERNS.SHELL_COMMAND)];
28
+
23
29
  // Execute each command and replace in text
24
30
  for (const match of matches) {
25
- const cmd = match[1]
26
- const placeholder = match[0]
27
-
31
+ const cmd = match[1];
32
+ const _placeholder = match[0];
33
+
28
34
  try {
29
- const output = await ctx.$`${{ raw: cmd }}`.quiet().nothrow().text()
30
- // Format like slash commands: print command first, then output
31
- const replacement = `$ ${cmd}\n--> ${output.trim()}`
32
- result = result.replace(placeholder, replacement)
35
+ const output = await ctx.$`${{ raw: cmd }}`.quiet().nothrow().text();
36
+ // Deviate from slash commands' substitution mechanism: print command first, then output
37
+ const replacement = `$ ${cmd}\n--> ${output.trim()}`;
38
+ result = result.replace(_placeholder, replacement);
33
39
  } catch (error) {
34
40
  // If shell command fails, leave it as-is
35
41
  // This preserves the original syntax for debugging
36
42
  logger.warn("Shell command execution failed", {
37
43
  command: cmd,
38
- error: error instanceof Error ? error.message : String(error)
39
- })
44
+ error: error instanceof Error ? error.message : String(error),
45
+ });
40
46
  }
41
47
  }
42
-
43
- return result
48
+
49
+ return result;
44
50
  }
package/src/types.ts CHANGED
@@ -3,24 +3,24 @@
3
3
  */
4
4
  export interface Snippet {
5
5
  /** The primary name/key of the snippet */
6
- name: string
6
+ name: string;
7
7
  /** The content of the snippet (without frontmatter) */
8
- content: string
8
+ content: string;
9
9
  /** Alternative names that also trigger this snippet */
10
- aliases: string[]
10
+ aliases: string[];
11
11
  }
12
12
 
13
13
  /**
14
14
  * Snippet registry that maps keys to content
15
15
  */
16
- export type SnippetRegistry = Map<string, string>
16
+ export type SnippetRegistry = Map<string, string>;
17
17
 
18
18
  /**
19
19
  * Frontmatter data from snippet files
20
20
  */
21
21
  export interface SnippetFrontmatter {
22
22
  /** Alternative hashtags for this snippet */
23
- aliases?: string[]
23
+ aliases?: string[];
24
24
  /** Optional description of what this snippet does */
25
- description?: string
25
+ description?: string;
26
26
  }