kodu 2.2.0 → 3.0.2

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 (234) hide show
  1. package/README.md +24 -3
  2. package/bin/kodu.js +40 -0
  3. package/package.json +12 -67
  4. package/scripts/install.js +68 -0
  5. package/scripts/postinstall.js +22 -0
  6. package/AGENTS.md +0 -214
  7. package/__tests__/core/fs/fs.service.test.ts +0 -72
  8. package/__tests__/core/registry/registry.service.test.ts +0 -82
  9. package/__tests__/shared/cleaner/cleaner.service.test.ts +0 -102
  10. package/__tests__/shared/git/git.service.test.ts +0 -84
  11. package/__tests__/shared/runbook/runbook.service.test.ts +0 -104
  12. package/__tests__/shared/tokenizer/tokenizer.service.test.ts +0 -45
  13. package/biome.json +0 -50
  14. package/dist/package.json +0 -96
  15. package/dist/src/app.module.d.ts +0 -2
  16. package/dist/src/app.module.js +0 -42
  17. package/dist/src/app.module.js.map +0 -1
  18. package/dist/src/commands/clean/clean.command.d.ts +0 -37
  19. package/dist/src/commands/clean/clean.command.js +0 -240
  20. package/dist/src/commands/clean/clean.command.js.map +0 -1
  21. package/dist/src/commands/clean/clean.module.d.ts +0 -2
  22. package/dist/src/commands/clean/clean.module.js +0 -26
  23. package/dist/src/commands/clean/clean.module.js.map +0 -1
  24. package/dist/src/commands/init/init.command.d.ts +0 -10
  25. package/dist/src/commands/init/init.command.js +0 -96
  26. package/dist/src/commands/init/init.command.js.map +0 -1
  27. package/dist/src/commands/init/init.module.d.ts +0 -2
  28. package/dist/src/commands/init/init.module.js +0 -22
  29. package/dist/src/commands/init/init.module.js.map +0 -1
  30. package/dist/src/commands/ops/ops-add.command.d.ts +0 -18
  31. package/dist/src/commands/ops/ops-add.command.js +0 -102
  32. package/dist/src/commands/ops/ops-add.command.js.map +0 -1
  33. package/dist/src/commands/ops/ops-init.command.d.ts +0 -22
  34. package/dist/src/commands/ops/ops-init.command.js +0 -130
  35. package/dist/src/commands/ops/ops-init.command.js.map +0 -1
  36. package/dist/src/commands/ops/ops-list.command.d.ts +0 -12
  37. package/dist/src/commands/ops/ops-list.command.js +0 -73
  38. package/dist/src/commands/ops/ops-list.command.js.map +0 -1
  39. package/dist/src/commands/ops/ops-path.command.d.ts +0 -9
  40. package/dist/src/commands/ops/ops-path.command.js +0 -52
  41. package/dist/src/commands/ops/ops-path.command.js.map +0 -1
  42. package/dist/src/commands/ops/ops-runbook.command.d.ts +0 -12
  43. package/dist/src/commands/ops/ops-runbook.command.js +0 -81
  44. package/dist/src/commands/ops/ops-runbook.command.js.map +0 -1
  45. package/dist/src/commands/ops/ops-status.command.d.ts +0 -11
  46. package/dist/src/commands/ops/ops-status.command.js +0 -62
  47. package/dist/src/commands/ops/ops-status.command.js.map +0 -1
  48. package/dist/src/commands/ops/ops-use.command.d.ts +0 -12
  49. package/dist/src/commands/ops/ops-use.command.js +0 -76
  50. package/dist/src/commands/ops/ops-use.command.js.map +0 -1
  51. package/dist/src/commands/ops/ops.command.d.ts +0 -7
  52. package/dist/src/commands/ops/ops.command.js +0 -56
  53. package/dist/src/commands/ops/ops.command.js.map +0 -1
  54. package/dist/src/commands/ops/ops.helpers.d.ts +0 -2
  55. package/dist/src/commands/ops/ops.helpers.js +0 -11
  56. package/dist/src/commands/ops/ops.helpers.js.map +0 -1
  57. package/dist/src/commands/ops/ops.module.d.ts +0 -2
  58. package/dist/src/commands/ops/ops.module.js +0 -36
  59. package/dist/src/commands/ops/ops.module.js.map +0 -1
  60. package/dist/src/commands/pack/pack.command.d.ts +0 -51
  61. package/dist/src/commands/pack/pack.command.js +0 -355
  62. package/dist/src/commands/pack/pack.command.js.map +0 -1
  63. package/dist/src/commands/pack/pack.module.d.ts +0 -2
  64. package/dist/src/commands/pack/pack.module.js +0 -27
  65. package/dist/src/commands/pack/pack.module.js.map +0 -1
  66. package/dist/src/core/config/config.module.d.ts +0 -2
  67. package/dist/src/core/config/config.module.js +0 -23
  68. package/dist/src/core/config/config.module.js.map +0 -1
  69. package/dist/src/core/config/config.schema.d.ts +0 -19
  70. package/dist/src/core/config/config.schema.js +0 -56
  71. package/dist/src/core/config/config.schema.js.map +0 -1
  72. package/dist/src/core/config/config.service.d.ts +0 -7
  73. package/dist/src/core/config/config.service.js +0 -49
  74. package/dist/src/core/config/config.service.js.map +0 -1
  75. package/dist/src/core/config/prompt.service.d.ts +0 -10
  76. package/dist/src/core/config/prompt.service.js +0 -80
  77. package/dist/src/core/config/prompt.service.js.map +0 -1
  78. package/dist/src/core/file-system/fs.module.d.ts +0 -2
  79. package/dist/src/core/file-system/fs.module.js +0 -21
  80. package/dist/src/core/file-system/fs.module.js.map +0 -1
  81. package/dist/src/core/file-system/fs.service.d.ts +0 -27
  82. package/dist/src/core/file-system/fs.service.js +0 -203
  83. package/dist/src/core/file-system/fs.service.js.map +0 -1
  84. package/dist/src/core/registry/registry.module.d.ts +0 -2
  85. package/dist/src/core/registry/registry.module.js +0 -22
  86. package/dist/src/core/registry/registry.module.js.map +0 -1
  87. package/dist/src/core/registry/registry.schema.d.ts +0 -24
  88. package/dist/src/core/registry/registry.schema.js +0 -21
  89. package/dist/src/core/registry/registry.schema.js.map +0 -1
  90. package/dist/src/core/registry/registry.service.d.ts +0 -16
  91. package/dist/src/core/registry/registry.service.js +0 -91
  92. package/dist/src/core/registry/registry.service.js.map +0 -1
  93. package/dist/src/core/ui/ui.module.d.ts +0 -2
  94. package/dist/src/core/ui/ui.module.js +0 -22
  95. package/dist/src/core/ui/ui.module.js.map +0 -1
  96. package/dist/src/core/ui/ui.service.d.ts +0 -22
  97. package/dist/src/core/ui/ui.service.js +0 -43
  98. package/dist/src/core/ui/ui.service.js.map +0 -1
  99. package/dist/src/main.d.ts +0 -2
  100. package/dist/src/main.js +0 -16
  101. package/dist/src/main.js.map +0 -1
  102. package/dist/src/shared/cleaner/cleaner.service.d.ts +0 -23
  103. package/dist/src/shared/cleaner/cleaner.service.js +0 -223
  104. package/dist/src/shared/cleaner/cleaner.service.js.map +0 -1
  105. package/dist/src/shared/cleaner/cleaner.types.d.ts +0 -21
  106. package/dist/src/shared/cleaner/cleaner.types.js +0 -3
  107. package/dist/src/shared/cleaner/cleaner.types.js.map +0 -1
  108. package/dist/src/shared/constants.d.ts +0 -4
  109. package/dist/src/shared/constants.js +0 -113
  110. package/dist/src/shared/constants.js.map +0 -1
  111. package/dist/src/shared/deps/deps.module.d.ts +0 -2
  112. package/dist/src/shared/deps/deps.module.js +0 -21
  113. package/dist/src/shared/deps/deps.module.js.map +0 -1
  114. package/dist/src/shared/deps/deps.service.d.ts +0 -15
  115. package/dist/src/shared/deps/deps.service.js +0 -114
  116. package/dist/src/shared/deps/deps.service.js.map +0 -1
  117. package/dist/src/shared/git/git.module.d.ts +0 -2
  118. package/dist/src/shared/git/git.module.js +0 -21
  119. package/dist/src/shared/git/git.module.js.map +0 -1
  120. package/dist/src/shared/git/git.service.d.ts +0 -5
  121. package/dist/src/shared/git/git.service.js +0 -56
  122. package/dist/src/shared/git/git.service.js.map +0 -1
  123. package/dist/src/shared/runbook/runbook.module.d.ts +0 -2
  124. package/dist/src/shared/runbook/runbook.module.js +0 -22
  125. package/dist/src/shared/runbook/runbook.module.js.map +0 -1
  126. package/dist/src/shared/runbook/runbook.service.d.ts +0 -20
  127. package/dist/src/shared/runbook/runbook.service.js +0 -118
  128. package/dist/src/shared/runbook/runbook.service.js.map +0 -1
  129. package/dist/src/shared/runbook/runbook.templates.d.ts +0 -6
  130. package/dist/src/shared/runbook/runbook.templates.js +0 -49
  131. package/dist/src/shared/runbook/runbook.templates.js.map +0 -1
  132. package/dist/src/shared/tokenizer/tokenizer.module.d.ts +0 -2
  133. package/dist/src/shared/tokenizer/tokenizer.module.js +0 -21
  134. package/dist/src/shared/tokenizer/tokenizer.module.js.map +0 -1
  135. package/dist/src/shared/tokenizer/tokenizer.service.d.ts +0 -10
  136. package/dist/src/shared/tokenizer/tokenizer.service.js +0 -36
  137. package/dist/src/shared/tokenizer/tokenizer.service.js.map +0 -1
  138. package/dist/tsconfig.build.tsbuildinfo +0 -1
  139. package/docs/todo.md +0 -7
  140. package/knip.json +0 -10
  141. package/kodu.json +0 -63
  142. package/kodu.schema.json +0 -100
  143. package/lefthook.yml +0 -11
  144. package/nest-cli.json +0 -8
  145. package/registry.schema.json +0 -39
  146. package/scripts/generate-json-schema.ts +0 -27
  147. package/skills/ac/SKILL.md +0 -239
  148. package/skills/al/SKILL.md +0 -98
  149. package/skills/audit/SKILL.md +0 -205
  150. package/skills/audit/audit-baseline-template.yml +0 -188
  151. package/skills/audit/runtime-detect.md +0 -64
  152. package/skills/audit/stacks/_generic.md +0 -41
  153. package/skills/audit/stacks/_registry.md +0 -47
  154. package/skills/audit/stacks/go.md +0 -66
  155. package/skills/audit/stacks/java.md +0 -44
  156. package/skills/audit/stacks/node.md +0 -57
  157. package/skills/audit/stacks/python.md +0 -45
  158. package/skills/audit/stacks/rust.md +0 -44
  159. package/skills/audit-api-contracts/SKILL.md +0 -201
  160. package/skills/audit-architecture/SKILL.md +0 -200
  161. package/skills/audit-bugs/SKILL.md +0 -226
  162. package/skills/audit-concurrency/SKILL.md +0 -197
  163. package/skills/audit-deployment/SKILL.md +0 -218
  164. package/skills/audit-docs/SKILL.md +0 -209
  165. package/skills/audit-errors/SKILL.md +0 -216
  166. package/skills/audit-logging/SKILL.md +0 -197
  167. package/skills/audit-matrix/SKILL.md +0 -245
  168. package/skills/audit-meta/SKILL.md +0 -120
  169. package/skills/audit-naming/SKILL.md +0 -200
  170. package/skills/audit-owasp/SKILL.md +0 -223
  171. package/skills/audit-performance/SKILL.md +0 -199
  172. package/skills/audit-reinvention/SKILL.md +0 -214
  173. package/skills/audit-secrets/SKILL.md +0 -198
  174. package/skills/audit-tests/SKILL.md +0 -210
  175. package/skills/audit-validation/SKILL.md +0 -206
  176. package/skills/audit-verify/SKILL.md +0 -139
  177. package/skills/audit-yagni/SKILL.md +0 -188
  178. package/skills/doc-gen/SKILL.md +0 -490
  179. package/skills/doc-gen/scripts/doc_gen.py +0 -911
  180. package/skills/generate-project-docs/SKILL.md +0 -380
  181. package/skills/implement-project/SKILL.md +0 -409
  182. package/skills/liteend-init/SKILL.md +0 -84
  183. package/skills/litefront-init/SKILL.md +0 -96
  184. package/skills/litefront-prototype/SKILL.md +0 -484
  185. package/skills/ops/SKILL.md +0 -94
  186. package/skills/post-call-task-builder/SKILL.md +0 -419
  187. package/skills/project-setup-standardizer/SKILL.md +0 -285
  188. package/skills/skills-best-practices/SKILL.md +0 -415
  189. package/skills/start/SKILL.md +0 -319
  190. package/skills/tech-blueprint/SKILL.md +0 -890
  191. package/skills/tech-blueprint/scripts/blueprint_validator.py +0 -417
  192. package/src/app.module.ts +0 -29
  193. package/src/commands/clean/clean.command.ts +0 -235
  194. package/src/commands/clean/clean.module.ts +0 -13
  195. package/src/commands/init/init.command.ts +0 -92
  196. package/src/commands/init/init.module.ts +0 -9
  197. package/src/commands/ops/ops-add.command.ts +0 -83
  198. package/src/commands/ops/ops-init.command.ts +0 -125
  199. package/src/commands/ops/ops-list.command.ts +0 -57
  200. package/src/commands/ops/ops-path.command.ts +0 -38
  201. package/src/commands/ops/ops-runbook.command.ts +0 -74
  202. package/src/commands/ops/ops-status.command.ts +0 -47
  203. package/src/commands/ops/ops-use.command.ts +0 -76
  204. package/src/commands/ops/ops.command.ts +0 -42
  205. package/src/commands/ops/ops.helpers.ts +0 -20
  206. package/src/commands/ops/ops.module.ts +0 -23
  207. package/src/commands/pack/pack.command.ts +0 -347
  208. package/src/commands/pack/pack.module.ts +0 -14
  209. package/src/core/config/config.module.ts +0 -10
  210. package/src/core/config/config.schema.ts +0 -58
  211. package/src/core/config/config.service.ts +0 -43
  212. package/src/core/config/prompt.service.ts +0 -80
  213. package/src/core/file-system/fs.module.ts +0 -8
  214. package/src/core/file-system/fs.service.ts +0 -248
  215. package/src/core/registry/registry.module.ts +0 -9
  216. package/src/core/registry/registry.schema.ts +0 -46
  217. package/src/core/registry/registry.service.ts +0 -128
  218. package/src/core/ui/ui.module.ts +0 -9
  219. package/src/core/ui/ui.service.ts +0 -39
  220. package/src/main.ts +0 -12
  221. package/src/shared/cleaner/cleaner.service.ts +0 -289
  222. package/src/shared/cleaner/cleaner.types.ts +0 -23
  223. package/src/shared/constants.ts +0 -118
  224. package/src/shared/deps/deps.module.ts +0 -8
  225. package/src/shared/deps/deps.service.ts +0 -175
  226. package/src/shared/git/git.module.ts +0 -8
  227. package/src/shared/git/git.service.ts +0 -47
  228. package/src/shared/runbook/runbook.module.ts +0 -9
  229. package/src/shared/runbook/runbook.service.ts +0 -164
  230. package/src/shared/runbook/runbook.templates.ts +0 -66
  231. package/src/shared/tokenizer/tokenizer.module.ts +0 -8
  232. package/src/shared/tokenizer/tokenizer.service.ts +0 -30
  233. package/tsconfig.build.json +0 -7
  234. package/tsconfig.json +0 -28
@@ -1,248 +0,0 @@
1
- import { promises as fs, type Stats } from 'node:fs';
2
- import path from 'node:path';
3
- import { Injectable } from '@nestjs/common';
4
- import ignore from 'ignore';
5
- import { glob } from 'tinyglobby';
6
- import {
7
- BINARY_EXTENSIONS,
8
- KNOWN_TEXT_EXTENSIONS,
9
- MAX_FILE_SIZE_BYTES,
10
- } from '../../shared/constants';
11
- import { ConfigService } from '../config/config.service';
12
- import { UiService } from '../ui/ui.service';
13
-
14
- const BINARY_PROBE_SIZE = 8192;
15
- const GLOB_IGNORE = ['.git/**'];
16
-
17
- type FindProjectFilesOptions = {
18
- ignore?: string[];
19
- useGitignore?: boolean;
20
- excludeBinary?: boolean;
21
- contentBasedBinaryDetection?: boolean;
22
- maxFileSizeBytes?: number;
23
- rootPaths?: string[];
24
- };
25
-
26
- @Injectable()
27
- export class FsService {
28
- constructor(
29
- private readonly configService: ConfigService,
30
- private readonly ui: UiService,
31
- ) {}
32
-
33
- async findProjectFiles(
34
- options: FindProjectFilesOptions = {},
35
- ): Promise<string[]> {
36
- const { packer } = this.configService.getConfig();
37
- const shouldUseGitignore = options.useGitignore ?? packer.useGitignore;
38
- const gitignorePatterns = shouldUseGitignore
39
- ? await this.readIgnoreFile('.gitignore')
40
- : [];
41
- const koduignorePatterns = await this.readIgnoreFile('.koduignore');
42
-
43
- const baseIgnore = options.ignore ?? packer.ignore ?? [];
44
- const normalizedBase = this.normalizeIgnorePatterns(baseIgnore);
45
- const combinedIgnore = [
46
- ...normalizedBase,
47
- ...gitignorePatterns,
48
- ...koduignorePatterns,
49
- ].map((pattern) => pattern.replace(/\\/g, '/'));
50
-
51
- const ig = ignore();
52
- if (combinedIgnore.length > 0) {
53
- ig.add(combinedIgnore);
54
- }
55
-
56
- const globIgnore = this.buildGlobIgnorePatterns(combinedIgnore);
57
-
58
- const patterns = options.rootPaths?.length
59
- ? options.rootPaths.map((p) => `${p}/**`)
60
- : ['**/*'];
61
-
62
- const entries = await glob(patterns, {
63
- onlyFiles: true,
64
- absolute: true,
65
- dot: true,
66
- ignore: [...GLOB_IGNORE, ...globIgnore],
67
- });
68
-
69
- const relativePaths = entries
70
- .map((entry) => path.relative(process.cwd(), entry))
71
- .map((relative) => this.toPosixPath(relative))
72
- .filter((relative) => relative.length > 0);
73
-
74
- const filtered = ig
75
- .filter(relativePaths)
76
- .sort((a, b) => a.localeCompare(b));
77
-
78
- // By default exclude binary files when collecting project files (so pack will skip them).
79
- // Consumers can override with options.excludeBinary = false.
80
- const excludeBinary = options.excludeBinary ?? true;
81
- const useContentDetection =
82
- options.contentBasedBinaryDetection ??
83
- packer.contentBasedBinaryDetection ??
84
- false;
85
- const maxFileSize = options.maxFileSizeBytes ?? MAX_FILE_SIZE_BYTES;
86
-
87
- const textFiles: string[] = [];
88
-
89
- for (const rel of filtered) {
90
- const abs = path.resolve(process.cwd(), rel);
91
- let stats: Stats;
92
-
93
- try {
94
- stats = await fs.stat(abs);
95
- } catch {
96
- continue;
97
- }
98
-
99
- if (stats.size > maxFileSize) {
100
- this.ui.log.warn(
101
- `Skipping large file: ${rel} (>${(maxFileSize / (1024 * 1024)).toFixed(0)}MB)`,
102
- );
103
- continue;
104
- }
105
-
106
- if (
107
- excludeBinary &&
108
- (await this.shouldExcludeBinary(rel, abs, useContentDetection))
109
- ) {
110
- continue;
111
- }
112
-
113
- textFiles.push(rel);
114
- }
115
-
116
- return textFiles;
117
- }
118
-
119
- async readFileRelative(relativePath: string): Promise<string> {
120
- const absolute = path.resolve(process.cwd(), relativePath);
121
- return fs.readFile(absolute, 'utf8');
122
- }
123
-
124
- private toPosixPath(relativePath: string): string {
125
- return relativePath.split(path.sep).join(path.posix.sep);
126
- }
127
-
128
- private normalizeIgnorePatterns(patterns: string[]): string[] {
129
- return patterns
130
- .map((pattern) => pattern.trim())
131
- .filter((pattern) => pattern.length > 0 && !pattern.startsWith('#'));
132
- }
133
-
134
- private buildGlobIgnorePatterns(patterns: string[]): string[] {
135
- const normalized = patterns
136
- .map((pattern) => pattern.trim())
137
- .filter(
138
- (pattern) =>
139
- pattern.length > 0 &&
140
- !pattern.startsWith('#') &&
141
- !pattern.startsWith('!'),
142
- )
143
- .map((pattern) => pattern.replace(/\\/g, '/'));
144
-
145
- const result = new Set<string>();
146
-
147
- for (const pattern of normalized) {
148
- const trimmed = pattern.replace(/\/+$/, '');
149
- result.add(pattern);
150
-
151
- if (trimmed.length === 0) {
152
- continue;
153
- }
154
-
155
- if (!pattern.includes('*')) {
156
- result.add(`${trimmed}/**`);
157
- result.add(`**/${trimmed}/**`);
158
- }
159
-
160
- if (!pattern.startsWith('**/')) {
161
- result.add(`**/${trimmed}`);
162
- }
163
-
164
- if (pattern.endsWith('/')) {
165
- result.add(`${trimmed}/**`);
166
- }
167
- }
168
-
169
- return [...result];
170
- }
171
-
172
- private async readIgnoreFile(fileName: string): Promise<string[]> {
173
- const target = path.join(process.cwd(), fileName);
174
-
175
- try {
176
- const content = await fs.readFile(target, 'utf8');
177
- return this.parseIgnoreContent(content);
178
- } catch {
179
- return [];
180
- }
181
- }
182
-
183
- private parseIgnoreContent(content: string): string[] {
184
- return content
185
- .split(/\r?\n/)
186
- .map((line) => line.trim())
187
- .filter((line) => line.length > 0 && !line.startsWith('#'));
188
- }
189
-
190
- private isBinaryExtension(relativePath: string): boolean {
191
- const ext = path.extname(relativePath).toLowerCase();
192
- return ext.length > 0 && BINARY_EXTENSIONS.has(ext);
193
- }
194
-
195
- private isKnownTextFile(relativePath: string): boolean {
196
- const ext = path.extname(relativePath).toLowerCase();
197
- if (ext && KNOWN_TEXT_EXTENSIONS.has(ext)) {
198
- return true;
199
- }
200
-
201
- const baseName = path.basename(relativePath).toLowerCase();
202
- return KNOWN_TEXT_EXTENSIONS.has(baseName);
203
- }
204
-
205
- private async shouldExcludeBinary(
206
- relativePath: string,
207
- absolutePath: string,
208
- detectByContent: boolean,
209
- ): Promise<boolean> {
210
- if (this.isKnownTextFile(relativePath)) {
211
- return false;
212
- }
213
-
214
- if (this.isBinaryExtension(relativePath)) {
215
- return true;
216
- }
217
-
218
- if (!detectByContent) {
219
- return false;
220
- }
221
-
222
- return this.hasNullByte(absolutePath);
223
- }
224
-
225
- private async hasNullByte(absolutePath: string): Promise<boolean> {
226
- let handle: fs.FileHandle | undefined;
227
-
228
- try {
229
- handle = await fs.open(absolutePath, 'r');
230
- const buffer = Buffer.alloc(BINARY_PROBE_SIZE);
231
- const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
232
-
233
- for (let i = 0; i < bytesRead; i += 1) {
234
- if (buffer[i] === 0) {
235
- return true;
236
- }
237
- }
238
-
239
- return false;
240
- } catch {
241
- return true;
242
- } finally {
243
- if (handle) {
244
- await handle.close();
245
- }
246
- }
247
- }
248
- }
@@ -1,9 +0,0 @@
1
- import { Global, Module } from '@nestjs/common';
2
- import { RegistryService } from './registry.service';
3
-
4
- @Global()
5
- @Module({
6
- providers: [RegistryService],
7
- exports: [RegistryService],
8
- })
9
- export class RegistryModule {}
@@ -1,46 +0,0 @@
1
- import { z } from 'zod';
2
-
3
- /**
4
- * Стандартный набор стендов. Можно использовать и любые другие имена —
5
- * стенды хранятся как обычные строки, это лишь значение по умолчанию.
6
- */
7
- export const DEFAULT_STANDS = ['local', 'dev', 'stage', 'prod'] as const;
8
-
9
- /** Один проект в глобальном реестре `~/.config/kodu/registry.json`. */
10
- export const projectEntrySchema = z.object({
11
- /** Абсолютный путь к репозиторию проекта на этой машине. */
12
- path: z.string().min(1),
13
- /** URL репозитория (git clone), необязательно. */
14
- repo: z.string().optional(),
15
- /** Доступные стенды проекта. */
16
- stands: z.array(z.string()).default([...DEFAULT_STANDS]),
17
- });
18
-
19
- export type ProjectEntry = z.infer<typeof projectEntrySchema>;
20
-
21
- /**
22
- * Глобальный реестр всех проектов. Ключ объекта `projects` — уникальное имя
23
- * проекта, по которому агент/CLI понимает, с каким репозиторием работать.
24
- */
25
- export const registrySchema = z.object({
26
- $schema: z.string().optional(),
27
- projects: z.record(z.string(), projectEntrySchema).default({}),
28
- });
29
-
30
- export type Registry = z.infer<typeof registrySchema>;
31
-
32
- /**
33
- * Per-project конфиг `.runbook/config.json` (лежит в `.gitignore`).
34
- * Хранит текущий активный стенд конкретного разработчика.
35
- */
36
- export const projectConfigSchema = z.object({
37
- $schema: z.string().optional(),
38
- /** Имя проекта — должно совпадать с ключом в глобальном реестре. */
39
- project: z.string().min(1),
40
- /** Текущий активный стенд. */
41
- activeStand: z.string().default('local'),
42
- /** Доступные стенды проекта. */
43
- stands: z.array(z.string()).default([...DEFAULT_STANDS]),
44
- });
45
-
46
- export type ProjectConfig = z.infer<typeof projectConfigSchema>;
@@ -1,128 +0,0 @@
1
- import { promises as fs } from 'node:fs';
2
- import os from 'node:os';
3
- import path from 'node:path';
4
- import { Injectable } from '@nestjs/common';
5
- import {
6
- type ProjectEntry,
7
- type Registry,
8
- registrySchema,
9
- } from './registry.schema';
10
-
11
- /**
12
- * Читает и пишет глобальный реестр проектов `~/.config/kodu/registry.json`
13
- * (учитывает `$XDG_CONFIG_HOME`). Файл создаётся при первой записи — пока
14
- * проектов нет, ничего в системе не создаётся.
15
- */
16
- @Injectable()
17
- export class RegistryService {
18
- private readonly dir = path.join(
19
- process.env.XDG_CONFIG_HOME?.trim() || path.join(os.homedir(), '.config'),
20
- 'kodu',
21
- );
22
- private readonly file = path.join(this.dir, 'registry.json');
23
-
24
- /** Путь к файлу реестра (для подсказок пользователю). */
25
- getFilePath(): string {
26
- return this.file;
27
- }
28
-
29
- /** Загрузить реестр. Если файла ещё нет — вернуть пустой реестр. */
30
- async load(): Promise<Registry> {
31
- let raw: unknown;
32
-
33
- try {
34
- const content = await fs.readFile(this.file, 'utf8');
35
- raw = JSON.parse(content);
36
- } catch (error) {
37
- if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
38
- return registrySchema.parse({});
39
- }
40
- throw new Error(
41
- `Не удалось прочитать реестр ${this.file}: ${(error as Error).message}`,
42
- );
43
- }
44
-
45
- const parsed = registrySchema.safeParse(raw);
46
-
47
- if (!parsed.success) {
48
- const issues = parsed.error.issues
49
- .map(
50
- (issue) => `- ${issue.path.join('.') || '(root)'}: ${issue.message}`,
51
- )
52
- .join('\n');
53
- throw new Error(`Реестр ${this.file} невалиден:\n${issues}`);
54
- }
55
-
56
- return parsed.data;
57
- }
58
-
59
- /** Сохранить реестр на диск (создаёт директорию при необходимости). */
60
- async save(registry: Registry): Promise<void> {
61
- const validated = registrySchema.parse(registry);
62
- await fs.mkdir(this.dir, { recursive: true });
63
- await fs.writeFile(
64
- this.file,
65
- `${JSON.stringify(validated, null, 2)}\n`,
66
- 'utf8',
67
- );
68
- }
69
-
70
- async list(): Promise<Registry['projects']> {
71
- return (await this.load()).projects;
72
- }
73
-
74
- async get(name: string): Promise<ProjectEntry | undefined> {
75
- return (await this.load()).projects[name];
76
- }
77
-
78
- async has(name: string): Promise<boolean> {
79
- return Boolean(await this.get(name));
80
- }
81
-
82
- /** Добавить проект. По умолчанию запрещает перезапись существующего имени. */
83
- async add(
84
- name: string,
85
- entry: ProjectEntry,
86
- options: { overwrite?: boolean } = {},
87
- ): Promise<void> {
88
- const registry = await this.load();
89
-
90
- if (registry.projects[name] && !options.overwrite) {
91
- throw new Error(
92
- `Проект с именем "${name}" уже есть в реестре. Выбери другое имя или обнови существующий проект.`,
93
- );
94
- }
95
-
96
- registry.projects[name] = entry;
97
- await this.save(registry);
98
- }
99
-
100
- /** Обновить поля существующего проекта. */
101
- async update(
102
- name: string,
103
- patch: Partial<ProjectEntry>,
104
- ): Promise<ProjectEntry> {
105
- const registry = await this.load();
106
- const existing = registry.projects[name];
107
-
108
- if (!existing) {
109
- throw new Error(`Проект "${name}" не найден в реестре.`);
110
- }
111
-
112
- const next: ProjectEntry = { ...existing, ...patch };
113
- registry.projects[name] = next;
114
- await this.save(registry);
115
- return next;
116
- }
117
-
118
- async remove(name: string): Promise<void> {
119
- const registry = await this.load();
120
-
121
- if (!registry.projects[name]) {
122
- throw new Error(`Проект "${name}" не найден в реестре.`);
123
- }
124
-
125
- delete registry.projects[name];
126
- await this.save(registry);
127
- }
128
- }
@@ -1,9 +0,0 @@
1
- import { Global, Module } from '@nestjs/common';
2
- import { UiService } from './ui.service';
3
-
4
- @Global()
5
- @Module({
6
- providers: [UiService],
7
- exports: [UiService],
8
- })
9
- export class UiModule {}
@@ -1,39 +0,0 @@
1
- import confirm from '@inquirer/confirm';
2
- import input from '@inquirer/input';
3
- import select from '@inquirer/select';
4
- import { Injectable } from '@nestjs/common';
5
- import pc from 'picocolors';
6
- import yoctoSpinner, {
7
- type Spinner,
8
- type Options as SpinnerOptions,
9
- } from 'yocto-spinner';
10
-
11
- type InputOptions = Parameters<typeof input>[0];
12
- type ConfirmOptions = Parameters<typeof confirm>[0];
13
- type SelectOptions<TValue> = Parameters<typeof select<TValue>>[0];
14
-
15
- @Injectable()
16
- export class UiService {
17
- readonly log = {
18
- success: (message: string) => console.log(pc.green(`✔ ${message}`)),
19
- warn: (message: string) => console.log(pc.yellow(`⚠ ${message}`)),
20
- error: (message: string) => console.log(pc.red(`✖ ${message}`)),
21
- info: (message: string) => console.log(pc.cyan(`ℹ ${message}`)),
22
- };
23
-
24
- createSpinner(options?: SpinnerOptions & { text?: string }): Spinner {
25
- return yoctoSpinner({ text: options?.text ?? '', ...options });
26
- }
27
-
28
- promptInput(options: InputOptions): Promise<string> {
29
- return input(options);
30
- }
31
-
32
- promptConfirm(options: ConfirmOptions): Promise<boolean> {
33
- return confirm(options);
34
- }
35
-
36
- promptSelect<TValue>(options: SelectOptions<TValue>): Promise<TValue> {
37
- return select<TValue>(options);
38
- }
39
- }
package/src/main.ts DELETED
@@ -1,12 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { CommandFactory } from 'nest-commander';
4
- import packageJson from '../package.json';
5
- import { AppModule } from './app.module';
6
-
7
- async function bootstrap() {
8
- await CommandFactory.run(AppModule, {
9
- version: packageJson.version,
10
- });
11
- }
12
- bootstrap();