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.
- package/README.md +24 -3
- package/bin/kodu.js +40 -0
- package/package.json +12 -67
- package/scripts/install.js +68 -0
- package/scripts/postinstall.js +22 -0
- package/AGENTS.md +0 -214
- package/__tests__/core/fs/fs.service.test.ts +0 -72
- package/__tests__/core/registry/registry.service.test.ts +0 -82
- package/__tests__/shared/cleaner/cleaner.service.test.ts +0 -102
- package/__tests__/shared/git/git.service.test.ts +0 -84
- package/__tests__/shared/runbook/runbook.service.test.ts +0 -104
- package/__tests__/shared/tokenizer/tokenizer.service.test.ts +0 -45
- package/biome.json +0 -50
- package/dist/package.json +0 -96
- package/dist/src/app.module.d.ts +0 -2
- package/dist/src/app.module.js +0 -42
- package/dist/src/app.module.js.map +0 -1
- package/dist/src/commands/clean/clean.command.d.ts +0 -37
- package/dist/src/commands/clean/clean.command.js +0 -240
- package/dist/src/commands/clean/clean.command.js.map +0 -1
- package/dist/src/commands/clean/clean.module.d.ts +0 -2
- package/dist/src/commands/clean/clean.module.js +0 -26
- package/dist/src/commands/clean/clean.module.js.map +0 -1
- package/dist/src/commands/init/init.command.d.ts +0 -10
- package/dist/src/commands/init/init.command.js +0 -96
- package/dist/src/commands/init/init.command.js.map +0 -1
- package/dist/src/commands/init/init.module.d.ts +0 -2
- package/dist/src/commands/init/init.module.js +0 -22
- package/dist/src/commands/init/init.module.js.map +0 -1
- package/dist/src/commands/ops/ops-add.command.d.ts +0 -18
- package/dist/src/commands/ops/ops-add.command.js +0 -102
- package/dist/src/commands/ops/ops-add.command.js.map +0 -1
- package/dist/src/commands/ops/ops-init.command.d.ts +0 -22
- package/dist/src/commands/ops/ops-init.command.js +0 -130
- package/dist/src/commands/ops/ops-init.command.js.map +0 -1
- package/dist/src/commands/ops/ops-list.command.d.ts +0 -12
- package/dist/src/commands/ops/ops-list.command.js +0 -73
- package/dist/src/commands/ops/ops-list.command.js.map +0 -1
- package/dist/src/commands/ops/ops-path.command.d.ts +0 -9
- package/dist/src/commands/ops/ops-path.command.js +0 -52
- package/dist/src/commands/ops/ops-path.command.js.map +0 -1
- package/dist/src/commands/ops/ops-runbook.command.d.ts +0 -12
- package/dist/src/commands/ops/ops-runbook.command.js +0 -81
- package/dist/src/commands/ops/ops-runbook.command.js.map +0 -1
- package/dist/src/commands/ops/ops-status.command.d.ts +0 -11
- package/dist/src/commands/ops/ops-status.command.js +0 -62
- package/dist/src/commands/ops/ops-status.command.js.map +0 -1
- package/dist/src/commands/ops/ops-use.command.d.ts +0 -12
- package/dist/src/commands/ops/ops-use.command.js +0 -76
- package/dist/src/commands/ops/ops-use.command.js.map +0 -1
- package/dist/src/commands/ops/ops.command.d.ts +0 -7
- package/dist/src/commands/ops/ops.command.js +0 -56
- package/dist/src/commands/ops/ops.command.js.map +0 -1
- package/dist/src/commands/ops/ops.helpers.d.ts +0 -2
- package/dist/src/commands/ops/ops.helpers.js +0 -11
- package/dist/src/commands/ops/ops.helpers.js.map +0 -1
- package/dist/src/commands/ops/ops.module.d.ts +0 -2
- package/dist/src/commands/ops/ops.module.js +0 -36
- package/dist/src/commands/ops/ops.module.js.map +0 -1
- package/dist/src/commands/pack/pack.command.d.ts +0 -51
- package/dist/src/commands/pack/pack.command.js +0 -355
- package/dist/src/commands/pack/pack.command.js.map +0 -1
- package/dist/src/commands/pack/pack.module.d.ts +0 -2
- package/dist/src/commands/pack/pack.module.js +0 -27
- package/dist/src/commands/pack/pack.module.js.map +0 -1
- package/dist/src/core/config/config.module.d.ts +0 -2
- package/dist/src/core/config/config.module.js +0 -23
- package/dist/src/core/config/config.module.js.map +0 -1
- package/dist/src/core/config/config.schema.d.ts +0 -19
- package/dist/src/core/config/config.schema.js +0 -56
- package/dist/src/core/config/config.schema.js.map +0 -1
- package/dist/src/core/config/config.service.d.ts +0 -7
- package/dist/src/core/config/config.service.js +0 -49
- package/dist/src/core/config/config.service.js.map +0 -1
- package/dist/src/core/config/prompt.service.d.ts +0 -10
- package/dist/src/core/config/prompt.service.js +0 -80
- package/dist/src/core/config/prompt.service.js.map +0 -1
- package/dist/src/core/file-system/fs.module.d.ts +0 -2
- package/dist/src/core/file-system/fs.module.js +0 -21
- package/dist/src/core/file-system/fs.module.js.map +0 -1
- package/dist/src/core/file-system/fs.service.d.ts +0 -27
- package/dist/src/core/file-system/fs.service.js +0 -203
- package/dist/src/core/file-system/fs.service.js.map +0 -1
- package/dist/src/core/registry/registry.module.d.ts +0 -2
- package/dist/src/core/registry/registry.module.js +0 -22
- package/dist/src/core/registry/registry.module.js.map +0 -1
- package/dist/src/core/registry/registry.schema.d.ts +0 -24
- package/dist/src/core/registry/registry.schema.js +0 -21
- package/dist/src/core/registry/registry.schema.js.map +0 -1
- package/dist/src/core/registry/registry.service.d.ts +0 -16
- package/dist/src/core/registry/registry.service.js +0 -91
- package/dist/src/core/registry/registry.service.js.map +0 -1
- package/dist/src/core/ui/ui.module.d.ts +0 -2
- package/dist/src/core/ui/ui.module.js +0 -22
- package/dist/src/core/ui/ui.module.js.map +0 -1
- package/dist/src/core/ui/ui.service.d.ts +0 -22
- package/dist/src/core/ui/ui.service.js +0 -43
- package/dist/src/core/ui/ui.service.js.map +0 -1
- package/dist/src/main.d.ts +0 -2
- package/dist/src/main.js +0 -16
- package/dist/src/main.js.map +0 -1
- package/dist/src/shared/cleaner/cleaner.service.d.ts +0 -23
- package/dist/src/shared/cleaner/cleaner.service.js +0 -223
- package/dist/src/shared/cleaner/cleaner.service.js.map +0 -1
- package/dist/src/shared/cleaner/cleaner.types.d.ts +0 -21
- package/dist/src/shared/cleaner/cleaner.types.js +0 -3
- package/dist/src/shared/cleaner/cleaner.types.js.map +0 -1
- package/dist/src/shared/constants.d.ts +0 -4
- package/dist/src/shared/constants.js +0 -113
- package/dist/src/shared/constants.js.map +0 -1
- package/dist/src/shared/deps/deps.module.d.ts +0 -2
- package/dist/src/shared/deps/deps.module.js +0 -21
- package/dist/src/shared/deps/deps.module.js.map +0 -1
- package/dist/src/shared/deps/deps.service.d.ts +0 -15
- package/dist/src/shared/deps/deps.service.js +0 -114
- package/dist/src/shared/deps/deps.service.js.map +0 -1
- package/dist/src/shared/git/git.module.d.ts +0 -2
- package/dist/src/shared/git/git.module.js +0 -21
- package/dist/src/shared/git/git.module.js.map +0 -1
- package/dist/src/shared/git/git.service.d.ts +0 -5
- package/dist/src/shared/git/git.service.js +0 -56
- package/dist/src/shared/git/git.service.js.map +0 -1
- package/dist/src/shared/runbook/runbook.module.d.ts +0 -2
- package/dist/src/shared/runbook/runbook.module.js +0 -22
- package/dist/src/shared/runbook/runbook.module.js.map +0 -1
- package/dist/src/shared/runbook/runbook.service.d.ts +0 -20
- package/dist/src/shared/runbook/runbook.service.js +0 -118
- package/dist/src/shared/runbook/runbook.service.js.map +0 -1
- package/dist/src/shared/runbook/runbook.templates.d.ts +0 -6
- package/dist/src/shared/runbook/runbook.templates.js +0 -49
- package/dist/src/shared/runbook/runbook.templates.js.map +0 -1
- package/dist/src/shared/tokenizer/tokenizer.module.d.ts +0 -2
- package/dist/src/shared/tokenizer/tokenizer.module.js +0 -21
- package/dist/src/shared/tokenizer/tokenizer.module.js.map +0 -1
- package/dist/src/shared/tokenizer/tokenizer.service.d.ts +0 -10
- package/dist/src/shared/tokenizer/tokenizer.service.js +0 -36
- package/dist/src/shared/tokenizer/tokenizer.service.js.map +0 -1
- package/dist/tsconfig.build.tsbuildinfo +0 -1
- package/docs/todo.md +0 -7
- package/knip.json +0 -10
- package/kodu.json +0 -63
- package/kodu.schema.json +0 -100
- package/lefthook.yml +0 -11
- package/nest-cli.json +0 -8
- package/registry.schema.json +0 -39
- package/scripts/generate-json-schema.ts +0 -27
- package/skills/ac/SKILL.md +0 -239
- package/skills/al/SKILL.md +0 -98
- package/skills/audit/SKILL.md +0 -205
- package/skills/audit/audit-baseline-template.yml +0 -188
- package/skills/audit/runtime-detect.md +0 -64
- package/skills/audit/stacks/_generic.md +0 -41
- package/skills/audit/stacks/_registry.md +0 -47
- package/skills/audit/stacks/go.md +0 -66
- package/skills/audit/stacks/java.md +0 -44
- package/skills/audit/stacks/node.md +0 -57
- package/skills/audit/stacks/python.md +0 -45
- package/skills/audit/stacks/rust.md +0 -44
- package/skills/audit-api-contracts/SKILL.md +0 -201
- package/skills/audit-architecture/SKILL.md +0 -200
- package/skills/audit-bugs/SKILL.md +0 -226
- package/skills/audit-concurrency/SKILL.md +0 -197
- package/skills/audit-deployment/SKILL.md +0 -218
- package/skills/audit-docs/SKILL.md +0 -209
- package/skills/audit-errors/SKILL.md +0 -216
- package/skills/audit-logging/SKILL.md +0 -197
- package/skills/audit-matrix/SKILL.md +0 -245
- package/skills/audit-meta/SKILL.md +0 -120
- package/skills/audit-naming/SKILL.md +0 -200
- package/skills/audit-owasp/SKILL.md +0 -223
- package/skills/audit-performance/SKILL.md +0 -199
- package/skills/audit-reinvention/SKILL.md +0 -214
- package/skills/audit-secrets/SKILL.md +0 -198
- package/skills/audit-tests/SKILL.md +0 -210
- package/skills/audit-validation/SKILL.md +0 -206
- package/skills/audit-verify/SKILL.md +0 -139
- package/skills/audit-yagni/SKILL.md +0 -188
- package/skills/doc-gen/SKILL.md +0 -490
- package/skills/doc-gen/scripts/doc_gen.py +0 -911
- package/skills/generate-project-docs/SKILL.md +0 -380
- package/skills/implement-project/SKILL.md +0 -409
- package/skills/liteend-init/SKILL.md +0 -84
- package/skills/litefront-init/SKILL.md +0 -96
- package/skills/litefront-prototype/SKILL.md +0 -484
- package/skills/ops/SKILL.md +0 -94
- package/skills/post-call-task-builder/SKILL.md +0 -419
- package/skills/project-setup-standardizer/SKILL.md +0 -285
- package/skills/skills-best-practices/SKILL.md +0 -415
- package/skills/start/SKILL.md +0 -319
- package/skills/tech-blueprint/SKILL.md +0 -890
- package/skills/tech-blueprint/scripts/blueprint_validator.py +0 -417
- package/src/app.module.ts +0 -29
- package/src/commands/clean/clean.command.ts +0 -235
- package/src/commands/clean/clean.module.ts +0 -13
- package/src/commands/init/init.command.ts +0 -92
- package/src/commands/init/init.module.ts +0 -9
- package/src/commands/ops/ops-add.command.ts +0 -83
- package/src/commands/ops/ops-init.command.ts +0 -125
- package/src/commands/ops/ops-list.command.ts +0 -57
- package/src/commands/ops/ops-path.command.ts +0 -38
- package/src/commands/ops/ops-runbook.command.ts +0 -74
- package/src/commands/ops/ops-status.command.ts +0 -47
- package/src/commands/ops/ops-use.command.ts +0 -76
- package/src/commands/ops/ops.command.ts +0 -42
- package/src/commands/ops/ops.helpers.ts +0 -20
- package/src/commands/ops/ops.module.ts +0 -23
- package/src/commands/pack/pack.command.ts +0 -347
- package/src/commands/pack/pack.module.ts +0 -14
- package/src/core/config/config.module.ts +0 -10
- package/src/core/config/config.schema.ts +0 -58
- package/src/core/config/config.service.ts +0 -43
- package/src/core/config/prompt.service.ts +0 -80
- package/src/core/file-system/fs.module.ts +0 -8
- package/src/core/file-system/fs.service.ts +0 -248
- package/src/core/registry/registry.module.ts +0 -9
- package/src/core/registry/registry.schema.ts +0 -46
- package/src/core/registry/registry.service.ts +0 -128
- package/src/core/ui/ui.module.ts +0 -9
- package/src/core/ui/ui.service.ts +0 -39
- package/src/main.ts +0 -12
- package/src/shared/cleaner/cleaner.service.ts +0 -289
- package/src/shared/cleaner/cleaner.types.ts +0 -23
- package/src/shared/constants.ts +0 -118
- package/src/shared/deps/deps.module.ts +0 -8
- package/src/shared/deps/deps.service.ts +0 -175
- package/src/shared/git/git.module.ts +0 -8
- package/src/shared/git/git.service.ts +0 -47
- package/src/shared/runbook/runbook.module.ts +0 -9
- package/src/shared/runbook/runbook.service.ts +0 -164
- package/src/shared/runbook/runbook.templates.ts +0 -66
- package/src/shared/tokenizer/tokenizer.module.ts +0 -8
- package/src/shared/tokenizer/tokenizer.service.ts +0 -30
- package/tsconfig.build.json +0 -7
- 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,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
|
-
}
|
package/src/core/ui/ui.module.ts
DELETED
|
@@ -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();
|