@neurodevs/meta-node 0.13.0 → 0.14.1

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 (25) hide show
  1. package/build/__tests__/AbstractPackageTest.d.ts +21 -0
  2. package/build/__tests__/AbstractPackageTest.js +62 -1
  3. package/build/__tests__/AbstractPackageTest.js.map +1 -1
  4. package/build/__tests__/impl/TypescriptClassSnippetSuite.test.d.ts +21 -2
  5. package/build/__tests__/impl/TypescriptClassSnippetSuite.test.js +200 -35
  6. package/build/__tests__/impl/TypescriptClassSnippetSuite.test.js.map +1 -1
  7. package/build/__tests__/impl/VscodeSnippetKeybinder.test.d.ts +0 -9
  8. package/build/__tests__/impl/VscodeSnippetKeybinder.test.js +2 -25
  9. package/build/__tests__/impl/VscodeSnippetKeybinder.test.js.map +1 -1
  10. package/build/impl/TypescriptClassSnippetSuite.d.ts +39 -1
  11. package/build/impl/TypescriptClassSnippetSuite.js +230 -1
  12. package/build/impl/TypescriptClassSnippetSuite.js.map +1 -1
  13. package/build/scripts/installSnippetSuite.d.ts +1 -0
  14. package/build/scripts/installSnippetSuite.js +17 -0
  15. package/build/scripts/installSnippetSuite.js.map +1 -0
  16. package/build/testDoubles/SnippetSuite/FakeSnippetSuite.d.ts +2 -0
  17. package/build/testDoubles/SnippetSuite/FakeSnippetSuite.js +5 -0
  18. package/build/testDoubles/SnippetSuite/FakeSnippetSuite.js.map +1 -1
  19. package/package.json +1 -1
  20. package/src/__tests__/AbstractPackageTest.ts +51 -1
  21. package/src/__tests__/impl/TypescriptClassSnippetSuite.test.ts +248 -2
  22. package/src/__tests__/impl/VscodeSnippetKeybinder.test.ts +2 -42
  23. package/src/impl/TypescriptClassSnippetSuite.ts +292 -1
  24. package/src/scripts/installSnippetSuite.ts +15 -0
  25. package/src/testDoubles/SnippetSuite/FakeSnippetSuite.ts +6 -0
@@ -1,13 +1,304 @@
1
+ import { readFile, writeFile } from 'fs/promises'
2
+ import expandHomeDir from '../scripts/expandHomeDir'
3
+
1
4
  export default class TypescriptClassSnippetSuite implements SnippetSuite {
2
5
  public static Class?: SnippetSuiteConstructor
6
+ public static readFile = readFile
7
+ public static writeFile = writeFile
8
+
9
+ private originalSnippetsFile!: string
10
+ private originalKeybindingsFile!: string
3
11
 
4
12
  protected constructor() {}
5
13
 
6
14
  public static Create() {
7
15
  return new (this.Class ?? this)()
8
16
  }
17
+
18
+ public async install() {
19
+ await this.installGlobalSnippets()
20
+ await this.installGlobalKeybindings()
21
+ }
22
+
23
+ private async installGlobalSnippets() {
24
+ this.originalSnippetsFile = await this.loadSnippetsFile()
25
+
26
+ if (!this.hasSnippetMarkers) {
27
+ await this.installFreshSnippets()
28
+ } else {
29
+ await this.updateExistingSnippets()
30
+ }
31
+ }
32
+
33
+ private async loadSnippetsFile() {
34
+ return await this.readFile(this.snippetsPath, 'utf-8')
35
+ }
36
+
37
+ private get snippetsPath() {
38
+ return `${this.vscodeDir}/snippets/custom.code-snippets`
39
+ }
40
+
41
+ private readonly vscodeDir = expandHomeDir(
42
+ '~/Library/Application Support/Code/User'
43
+ )
44
+
45
+ private get hasSnippetMarkers() {
46
+ return this.snippetStartIdx !== -1 && this.snippetEndIdx !== -1
47
+ }
48
+
49
+ private get snippetStartIdx() {
50
+ return this.originalSnippetsFile.indexOf(this.snippetStartMarker)
51
+ }
52
+
53
+ private snippetStartMarker = '// === TYPESCRIPT CLASS SNIPPETS BEGIN ==='
54
+
55
+ private get snippetEndIdx() {
56
+ return (
57
+ this.originalSnippetsFile.indexOf(this.snippetEndMarker) +
58
+ this.snippetEndMarker.length
59
+ )
60
+ }
61
+
62
+ private snippetEndMarker = '// === TYPESCRIPT CLASS SNIPPETS END ==='
63
+
64
+ private async installFreshSnippets() {
65
+ await this.writeFile(this.snippetsPath, this.freshSnippetsFile)
66
+ }
67
+
68
+ private get freshSnippetsFile() {
69
+ const lastBraceIdx = this.originalSnippetsFile.lastIndexOf('}')
70
+ const before = this.originalSnippetsFile.slice(0, lastBraceIdx)
71
+
72
+ return `${before}${this.snippetsBlock}\n}`
73
+ }
74
+
75
+ private get snippetsBlock() {
76
+ return ` ${this.snippetStartMarker}\n${this.indentedSnippets}\n ${this.snippetEndMarker}`
77
+ }
78
+
79
+ private async updateExistingSnippets() {
80
+ const before = this.originalSnippetsFile.slice(0, this.snippetStartIdx)
81
+
82
+ const existing = this.originalSnippetsFile.slice(
83
+ this.snippetStartIdx,
84
+ this.snippetEndIdx
85
+ )
86
+
87
+ const after = this.originalSnippetsFile.slice(this.snippetEndIdx)
88
+
89
+ if (existing.trim() !== this.snippetsBlock.trim()) {
90
+ await this.writeFile(
91
+ this.snippetsPath,
92
+ `${before}${this.snippetsBlock}${after}`
93
+ )
94
+ }
95
+ }
96
+
97
+ private async installGlobalKeybindings() {
98
+ this.originalKeybindingsFile = await this.loadKeybindingsFile()
99
+
100
+ if (!this.hasKeybindMarkers) {
101
+ await this.installFreshKeybindings()
102
+ } else {
103
+ await this.updateExistingKeybindings()
104
+ }
105
+ }
106
+
107
+ private async loadKeybindingsFile() {
108
+ return await this.readFile(this.keybindingsPath, 'utf-8')
109
+ }
110
+
111
+ private get keybindingsPath() {
112
+ return `${this.vscodeDir}/keybindings.json`
113
+ }
114
+
115
+ private get hasKeybindMarkers() {
116
+ return this.keybindStartIdx !== -1 && this.keybindEndIdx !== -1
117
+ }
118
+
119
+ private get keybindStartIdx() {
120
+ return this.originalKeybindingsFile.indexOf(this.keybindStartMarker)
121
+ }
122
+
123
+ private get keybindEndIdx() {
124
+ return (
125
+ this.originalKeybindingsFile.indexOf(this.keybindEndMarker) +
126
+ this.keybindEndMarker.length
127
+ )
128
+ }
129
+
130
+ private async installFreshKeybindings() {
131
+ await this.writeFile(this.keybindingsPath, this.freshKeybindingsFile)
132
+ }
133
+
134
+ private get freshKeybindingsFile() {
135
+ const lastBraceIdx = this.originalKeybindingsFile.lastIndexOf(']')
136
+ const before = this.originalKeybindingsFile.slice(0, lastBraceIdx)
137
+
138
+ return `${before}${this.keybindingBlock}\n]`
139
+ }
140
+
141
+ private get keybindingBlock() {
142
+ return ` ${this.keybindStartMarker}\n${this.indentedKeybindings}\n ${this.keybindEndMarker}`
143
+ }
144
+
145
+ private keybindStartMarker = '// === TYPESCRIPT CLASS KEYBINDINGS BEGIN ==='
146
+ private keybindEndMarker = '// === TYPESCRIPT CLASS KEYBINDINGS END ==='
147
+
148
+ private async updateExistingKeybindings() {
149
+ const before = this.originalKeybindingsFile.slice(
150
+ 0,
151
+ this.keybindStartIdx
152
+ )
153
+
154
+ const existing = this.originalKeybindingsFile.slice(
155
+ this.keybindStartIdx,
156
+ this.keybindEndIdx
157
+ )
158
+
159
+ const after = this.originalKeybindingsFile.slice(this.keybindEndIdx)
160
+
161
+ if (existing.trim() !== this.keybindingBlock.trim()) {
162
+ await this.writeFile(
163
+ this.keybindingsPath,
164
+ `${before}${this.keybindingBlock}${after}`
165
+ )
166
+ }
167
+ }
168
+
169
+ private get readFile() {
170
+ return TypescriptClassSnippetSuite.readFile
171
+ }
172
+
173
+ private get writeFile() {
174
+ return TypescriptClassSnippetSuite.writeFile
175
+ }
176
+
177
+ private readonly snippets = `
178
+ // === PUBLIC ===
179
+ "Public constructor": { "scope": "typescript", "prefix": "public.constructor", "body": ["public constructor(\${1:args}) {\${2:} }"] },
180
+ "Public field": { "scope": "typescript", "prefix": "public.field", "body": ["public \${1:newField} = \${2:undefined}"] },
181
+ "Public readonly field": { "scope": "typescript", "prefix": "public.readonly.field", "body": ["public readonly \${1:newField} = \${2:undefined}"] },
182
+ "Public getter": { "scope": "typescript", "prefix": "public.getter", "body": ["public get \${1:newProperty}() { return \${2:undefined} }"] },
183
+ "Public setter": { "scope": "typescript", "prefix": "public.setter", "body": ["public set \${1:newProperty}(\${2:_value}: \${3:unknown}) {\${4:} }"] },
184
+ "Public method": { "scope": "typescript", "prefix": "public.method", "body": ["public \${1:newMethod}(\${2:}): \${3:void} {\${4:} }"] },
185
+ "Public async method": { "scope": "typescript", "prefix": "public.async.method", "body": ["public async \${1:newMethod}(\${2:}): \${3:Promise<unknown>} {\${4:} }"] },
186
+ "Public abstract method": { "scope": "typescript", "prefix": "public.abstract.method", "body": ["public abstract \${1:newMethod}(\${2:}): \${3:unknown}"] },
187
+
188
+ // === PROTECTED ===
189
+ "Protected constructor": { "scope": "typescript", "prefix": "protected.constructor", "body": ["protected constructor(\${1:args}) {\${2:} }"] },
190
+ "Protected field": { "scope": "typescript", "prefix": "protected.field", "body": ["protected \${1:newField} = \${2:undefined}"] },
191
+ "Protected readonly field": { "scope": "typescript", "prefix": "protected.readonly.field", "body": ["protected readonly \${1:newField} = \${2:undefined}"] },
192
+ "Protected getter": { "scope": "typescript", "prefix": "protected.getter", "body": ["protected get \${1:newProperty}() { return \${2:undefined} }"] },
193
+ "Protected setter": { "scope": "typescript", "prefix": "protected.setter", "body": ["protected set \${1:newProperty}(\${2:_value}: \${3:unknown}) {\${4:} }"] },
194
+ "Protected method": { "scope": "typescript", "prefix": "protected.method", "body": ["protected \${1:newMethod}(\${2:}): \${3:void} {\${4:} }"] },
195
+ "Protected async method": { "scope": "typescript", "prefix": "protected.async.method", "body": ["protected async \${1:newMethod}(\${2:}): \${3:Promise<unknown>} {\${4:} }"] },
196
+ "Protected abstract method": { "scope": "typescript", "prefix": "protected.abstract.method", "body": ["protected abstract \${1:newMethod}(\${2:}): \${3:unknown}"] },
197
+
198
+ // === PRIVATE ===
199
+ "Private constructor": { "scope": "typescript", "prefix": "private.constructor", "body": ["private constructor(\${1:args}) {\${2:} }"] },
200
+ "Private field": { "scope": "typescript", "prefix": "private.field", "body": ["private \${1:newField} = \${2:undefined}"] },
201
+ "Private readonly field": { "scope": "typescript", "prefix": "private.readonly.field", "body": ["private readonly \${1:newField} = \${2:undefined}"] },
202
+ "Private getter": { "scope": "typescript", "prefix": "private.getter", "body": ["private get \${1:newProperty}() { return \${2:undefined} }"] },
203
+ "Private setter": { "scope": "typescript", "prefix": "private.setter", "body": ["private set \${1:newProperty}(\${2:_value}: \${3:unknown}) {\${4:} }"] },
204
+ "Private method": { "scope": "typescript", "prefix": "private.method", "body": ["private \${1:newMethod}(\${2:}): \${3:void} {\${4:} }"] },
205
+ "Private async method": { "scope": "typescript", "prefix": "private.async.method", "body": ["private async \${1:newMethod}(\${2:}): \${3:Promise<unknown>} {\${4:} }"] },
206
+ "Private abstract method": { "scope": "typescript", "prefix": "private.abstract.method", "body": ["private abstract \${1:newMethod}(\${2:}): \${3:unknown}"] },
207
+
208
+ // === PUBLIC STATIC ===
209
+ "Public static field": { "scope": "typescript", "prefix": "public.static.field", "body": ["public static \${1:newField} = \${2:undefined}"] },
210
+ "Public static readonly field": { "scope": "typescript", "prefix": "public.static.readonly.field", "body": ["public static readonly \${1:newField} = \${2:undefined}"] },
211
+ "Public static getter": { "scope": "typescript", "prefix": "public.static.getter", "body": ["public static get \${1:newProperty}() { return \${2:undefined} }"] },
212
+ "Public static setter": { "scope": "typescript", "prefix": "public.static.setter", "body": ["public static set \${1:newProperty}(\${2:_value}: \${3:unknown}) {\${4:} }"] },
213
+ "Public static method": { "scope": "typescript", "prefix": "public.static.method", "body": ["public static \${1:newMethod}(\${2:}): \${3:void} {\${4:} }"] },
214
+ "Public static async method": { "scope": "typescript", "prefix": "public.static.async.method", "body": ["public static async \${1:newMethod}(\${2:}): \${3:Promise<unknown>} {\${4:} }"] },
215
+
216
+ // === PROTECTED STATIC ===
217
+ "Protected static field": { "scope": "typescript", "prefix": "protected.static.field", "body": ["protected static \${1:newField} = \${2:undefined}"] },
218
+ "Protected static readonly field": { "scope": "typescript", "prefix": "protected.static.readonly.field", "body": ["protected static readonly \${1:newField} = \${2:undefined}"] },
219
+ "Protected static getter": { "scope": "typescript", "prefix": "protected.static.getter", "body": ["protected static get \${1:newProperty}() { return \${2:undefined} }"] },
220
+ "Protected static setter": { "scope": "typescript", "prefix": "protected.static.setter", "body": ["protected static set \${1:newProperty}(\${2:_value}: \${3:unknown}) {\${4:} }"] },
221
+ "Protected static method": { "scope": "typescript", "prefix": "protected.static.method", "body": ["protected static \${1:newMethod}(\${2:}): \${3:void} {\${4:} }"] },
222
+ "Protected static async method": { "scope": "typescript", "prefix": "protected.static.async.method", "body": ["protected static async \${1:newMethod}(\${2:}): \${3:Promise<unknown>} {\${4:} }"] },
223
+
224
+ // === PRIVATE STATIC ===
225
+ "Private static field": { "scope": "typescript", "prefix": "private.static.field", "body": ["private static \${1:newField} = \${2:undefined}"] },
226
+ "Private static readonly field": { "scope": "typescript", "prefix": "private.static.readonly.field", "body": ["private static readonly \${1:newField} = \${2:undefined}"] },
227
+ "Private static getter": { "scope": "typescript", "prefix": "private.static.getter", "body": ["private static get \${1:newProperty}() { return \${2:undefined} }"] },
228
+ "Private static setter": { "scope": "typescript", "prefix": "private.static.setter", "body": ["private static set \${1:newProperty}(\${2:_value}: \${3:unknown}) {\${4:} }"] },
229
+ "Private static method": { "scope": "typescript", "prefix": "private.static.method", "body": ["private static \${1:newMethod}(\${2:}): \${3:void} {\${4:} }"] },
230
+ "Private static async method": { "scope": "typescript", "prefix": "private.static.async.method", "body": ["private static async \${1:newMethod}(\${2:}): \${3:Promise<unknown>} {\${4:} }"] }
231
+ `.replace(/^[ \t]+/gm, '')
232
+
233
+ private readonly indentedSnippets = this.snippets
234
+ .split('\n')
235
+ .map((line) => (line.trim() ? ' ' + line : line))
236
+ .join('\n')
237
+
238
+ private readonly keybindings = `
239
+ // === PUBLIC (Ctrl+1) ===
240
+ { "key": "ctrl+1 f1", "command": "editor.action.insertSnippet", "args": { "name": "Public constructor" } },
241
+ { "key": "ctrl+1 f2", "command": "editor.action.insertSnippet", "args": { "name": "Public field" } },
242
+ { "key": "ctrl+1 f3", "command": "editor.action.insertSnippet", "args": { "name": "Public readonly field" } },
243
+ { "key": "ctrl+1 f4", "command": "editor.action.insertSnippet", "args": { "name": "Public getter" } },
244
+ { "key": "ctrl+1 f5", "command": "editor.action.insertSnippet", "args": { "name": "Public setter" } },
245
+ { "key": "ctrl+1 f6", "command": "editor.action.insertSnippet", "args": { "name": "Public method" } },
246
+ { "key": "ctrl+1 f7", "command": "editor.action.insertSnippet", "args": { "name": "Public async method" } },
247
+ { "key": "ctrl+1 f8", "command": "editor.action.insertSnippet", "args": { "name": "Public abstract method" } },
248
+
249
+ // === PROTECTED (Ctrl+2) ===
250
+ { "key": "ctrl+2 f1", "command": "editor.action.insertSnippet", "args": { "name": "Protected constructor" } },
251
+ { "key": "ctrl+2 f2", "command": "editor.action.insertSnippet", "args": { "name": "Protected field" } },
252
+ { "key": "ctrl+2 f3", "command": "editor.action.insertSnippet", "args": { "name": "Protected readonly field" } },
253
+ { "key": "ctrl+2 f4", "command": "editor.action.insertSnippet", "args": { "name": "Protected getter" } },
254
+ { "key": "ctrl+2 f5", "command": "editor.action.insertSnippet", "args": { "name": "Protected setter" } },
255
+ { "key": "ctrl+2 f6", "command": "editor.action.insertSnippet", "args": { "name": "Protected method" } },
256
+ { "key": "ctrl+2 f7", "command": "editor.action.insertSnippet", "args": { "name": "Protected async method" } },
257
+ { "key": "ctrl+2 f8", "command": "editor.action.insertSnippet", "args": { "name": "Protected abstract method" } },
258
+
259
+ // === PRIVATE (Ctrl+3) ===
260
+ { "key": "ctrl+3 f1", "command": "editor.action.insertSnippet", "args": { "name": "Private constructor" } },
261
+ { "key": "ctrl+3 f2", "command": "editor.action.insertSnippet", "args": { "name": "Private field" } },
262
+ { "key": "ctrl+3 f3", "command": "editor.action.insertSnippet", "args": { "name": "Private readonly field" } },
263
+ { "key": "ctrl+3 f4", "command": "editor.action.insertSnippet", "args": { "name": "Private getter" } },
264
+ { "key": "ctrl+3 f5", "command": "editor.action.insertSnippet", "args": { "name": "Private setter" } },
265
+ { "key": "ctrl+3 f6", "command": "editor.action.insertSnippet", "args": { "name": "Private method" } },
266
+ { "key": "ctrl+3 f7", "command": "editor.action.insertSnippet", "args": { "name": "Private async method" } },
267
+ { "key": "ctrl+3 f8", "command": "editor.action.insertSnippet", "args": { "name": "Private abstract method" } },
268
+
269
+ // === PUBLIC STATIC (Ctrl+1 Alt) ===
270
+ { "key": "ctrl+1 alt+f2", "command": "editor.action.insertSnippet", "args": { "name": "Public static field" } },
271
+ { "key": "ctrl+1 alt+f3", "command": "editor.action.insertSnippet", "args": { "name": "Public static readonly field" } },
272
+ { "key": "ctrl+1 alt+f4", "command": "editor.action.insertSnippet", "args": { "name": "Public static getter" } },
273
+ { "key": "ctrl+1 alt+f5", "command": "editor.action.insertSnippet", "args": { "name": "Public static setter" } },
274
+ { "key": "ctrl+1 alt+f6", "command": "editor.action.insertSnippet", "args": { "name": "Public static method" } },
275
+ { "key": "ctrl+1 alt+f7", "command": "editor.action.insertSnippet", "args": { "name": "Public static async method" } },
276
+
277
+ // === PROTECTED STATIC (Ctrl+2 Alt) ===
278
+ { "key": "ctrl+2 alt+f2", "command": "editor.action.insertSnippet", "args": { "name": "Protected static field" } },
279
+ { "key": "ctrl+2 alt+f3", "command": "editor.action.insertSnippet", "args": { "name": "Protected static readonly field" } },
280
+ { "key": "ctrl+2 alt+f4", "command": "editor.action.insertSnippet", "args": { "name": "Protected static getter" } },
281
+ { "key": "ctrl+2 alt+f5", "command": "editor.action.insertSnippet", "args": { "name": "Protected static setter" } },
282
+ { "key": "ctrl+2 alt+f6", "command": "editor.action.insertSnippet", "args": { "name": "Protected static method" } },
283
+ { "key": "ctrl+2 alt+f7", "command": "editor.action.insertSnippet", "args": { "name": "Protected static async method" } },
284
+
285
+ // === PRIVATE STATIC (Ctrl+3 Alt) ===
286
+ { "key": "ctrl+3 alt+f2", "command": "editor.action.insertSnippet", "args": { "name": "Private static field" } },
287
+ { "key": "ctrl+3 alt+f3", "command": "editor.action.insertSnippet", "args": { "name": "Private static readonly field" } },
288
+ { "key": "ctrl+3 alt+f4", "command": "editor.action.insertSnippet", "args": { "name": "Private static getter" } },
289
+ { "key": "ctrl+3 alt+f5", "command": "editor.action.insertSnippet", "args": { "name": "Private static setter" } },
290
+ { "key": "ctrl+3 alt+f6", "command": "editor.action.insertSnippet", "args": { "name": "Private static method" } },
291
+ { "key": "ctrl+3 alt+f7", "command": "editor.action.insertSnippet", "args": { "name": "Private static async method" } }
292
+ `.replace(/^[ \t]+/gm, '')
293
+
294
+ private readonly indentedKeybindings = this.keybindings
295
+ .split('\n')
296
+ .map((line) => (line.trim() ? ' ' + line : line))
297
+ .join('\n')
9
298
  }
10
299
 
11
- export interface SnippetSuite {}
300
+ export interface SnippetSuite {
301
+ install(): Promise<void>
302
+ }
12
303
 
13
304
  export type SnippetSuiteConstructor = new () => SnippetSuite
@@ -0,0 +1,15 @@
1
+ import TypescriptClassSnippetSuite from '../impl/TypescriptClassSnippetSuite'
2
+
3
+ async function main() {
4
+ console.log('Installing TypeScript Class Snippet Suite...')
5
+
6
+ const instance = TypescriptClassSnippetSuite.Create()
7
+ await instance.install()
8
+
9
+ console.log('Installation complete!')
10
+ }
11
+
12
+ main().catch((err) => {
13
+ console.error(err)
14
+ process.exit(1)
15
+ })
@@ -2,12 +2,18 @@ import { SnippetSuite } from '../../impl/TypescriptClassSnippetSuite'
2
2
 
3
3
  export default class FakeSnippetSuite implements SnippetSuite {
4
4
  public static numCallsToConstructor = 0
5
+ public static numCallsToInstall = 0
5
6
 
6
7
  public constructor() {
7
8
  FakeSnippetSuite.numCallsToConstructor++
8
9
  }
9
10
 
11
+ public async install() {
12
+ FakeSnippetSuite.numCallsToInstall++
13
+ }
14
+
10
15
  public static resetTestDouble() {
11
16
  FakeSnippetSuite.numCallsToConstructor = 0
17
+ FakeSnippetSuite.numCallsToInstall = 0
12
18
  }
13
19
  }