kaven-cli 0.3.0 → 0.4.0-alpha.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.
- package/dist/commands/config/features.js +1059 -0
- package/dist/commands/init/index.js +21 -0
- package/dist/commands/module/activate.js +204 -0
- package/dist/core/ModuleDoctor.js +4 -4
- package/dist/core/ProjectInitializer.js +28 -0
- package/dist/core/SchemaActivator.js +257 -0
- package/dist/core/SignatureVerifier.js +7 -4
- package/dist/index.js +63 -1
- package/package.json +1 -1
|
@@ -170,6 +170,21 @@ async function initProject(projectName, options) {
|
|
|
170
170
|
console.error(chalk_1.default.gray(error instanceof Error ? error.message : String(error)));
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
|
+
// Install kaven-squad (optional, non-fatal)
|
|
174
|
+
if (options.withSquad) {
|
|
175
|
+
const squadSpinner = (0, ora_1.default)("Installing kaven-squad...").start();
|
|
176
|
+
const squadResult = await initializer.installSquad(targetDir);
|
|
177
|
+
if (squadResult.installed) {
|
|
178
|
+
squadSpinner.succeed("kaven-squad installed in squads/kaven-squad/");
|
|
179
|
+
}
|
|
180
|
+
else if (squadResult.reason === "already-exists") {
|
|
181
|
+
squadSpinner.info("kaven-squad already installed — skipping");
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
squadSpinner.warn(`Could not install kaven-squad automatically (${squadResult.reason})`);
|
|
185
|
+
console.log(chalk_1.default.yellow(" ⚠ Install manually inside the project: *download-squad kaven-squad"));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
173
188
|
// Health check
|
|
174
189
|
const healthCheckSpinner = (0, ora_1.default)("Running health check...").start();
|
|
175
190
|
const health = await initializer.healthCheck(targetDir);
|
|
@@ -193,6 +208,12 @@ async function initProject(projectName, options) {
|
|
|
193
208
|
console.log(chalk_1.default.cyan(" pnpm dev"));
|
|
194
209
|
console.log();
|
|
195
210
|
console.log(chalk_1.default.gray("For more help, visit: https://docs.kaven.sh/getting-started"));
|
|
211
|
+
if (options.withSquad) {
|
|
212
|
+
console.log();
|
|
213
|
+
console.log(chalk_1.default.bold("AIOX Squad next step:"));
|
|
214
|
+
console.log(chalk_1.default.cyan(" node /path/to/aiox-core/bin/aiox.js install --quiet --merge"));
|
|
215
|
+
console.log(chalk_1.default.gray(" (replace /path/to/aiox-core with your AIOX installation path)"));
|
|
216
|
+
}
|
|
196
217
|
// Save project defaults to config for future use
|
|
197
218
|
await ConfigManager_1.configManager.initialize();
|
|
198
219
|
try {
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.moduleActivate = moduleActivate;
|
|
7
|
+
exports.moduleDeactivate = moduleDeactivate;
|
|
8
|
+
exports.moduleListActivation = moduleListActivation;
|
|
9
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
10
|
+
const ora_1 = __importDefault(require("ora"));
|
|
11
|
+
const SchemaActivator_1 = require("../../core/SchemaActivator");
|
|
12
|
+
const TelemetryBuffer_1 = require("../../infrastructure/TelemetryBuffer");
|
|
13
|
+
// ============================================================
|
|
14
|
+
// Helpers
|
|
15
|
+
// ============================================================
|
|
16
|
+
function findModuleDef(moduleId) {
|
|
17
|
+
return SchemaActivator_1.KAVEN_MODULES.find((m) => m.id === moduleId.toLowerCase());
|
|
18
|
+
}
|
|
19
|
+
function assertSchemaExists(exists) {
|
|
20
|
+
if (!exists) {
|
|
21
|
+
console.error(chalk_1.default.red("\nNão encontrei packages/database/prisma/schema.extended.prisma."));
|
|
22
|
+
console.error(chalk_1.default.gray("Execute este comando na raiz de um projeto Kaven."));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// ============================================================
|
|
27
|
+
// kaven module activate <name>
|
|
28
|
+
// ============================================================
|
|
29
|
+
async function moduleActivate(moduleName, projectRoot) {
|
|
30
|
+
const telemetry = TelemetryBuffer_1.TelemetryBuffer.getInstance();
|
|
31
|
+
const startTime = Date.now();
|
|
32
|
+
telemetry.capture("cli.module.activate.start", { moduleName });
|
|
33
|
+
const root = projectRoot ?? process.cwd();
|
|
34
|
+
const activator = new SchemaActivator_1.SchemaActivator(root);
|
|
35
|
+
const spinner = (0, ora_1.default)(`Ativando módulo ${moduleName}...`).start();
|
|
36
|
+
try {
|
|
37
|
+
// 1. Validar que o schema existe
|
|
38
|
+
const exists = await activator.exists();
|
|
39
|
+
spinner.stop();
|
|
40
|
+
assertSchemaExists(exists);
|
|
41
|
+
// 2. Validar que o módulo é conhecido
|
|
42
|
+
const def = findModuleDef(moduleName);
|
|
43
|
+
if (!def) {
|
|
44
|
+
console.error(chalk_1.default.red(`\nMódulo desconhecido: "${moduleName}".`));
|
|
45
|
+
console.error(chalk_1.default.gray(`Módulos disponíveis: ${SchemaActivator_1.KAVEN_MODULES.map((m) => m.id).join(", ")}`));
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
// 3. Verificar dependências
|
|
49
|
+
if (def.dependsOn.length > 0) {
|
|
50
|
+
spinner.start("Verificando dependências...");
|
|
51
|
+
const depStatuses = [];
|
|
52
|
+
for (const depId of def.dependsOn) {
|
|
53
|
+
const depDef = findModuleDef(depId);
|
|
54
|
+
if (!depDef)
|
|
55
|
+
continue;
|
|
56
|
+
const status = await activator.getModuleStatus(depDef);
|
|
57
|
+
depStatuses.push(status);
|
|
58
|
+
}
|
|
59
|
+
const missing = depStatuses.filter((s) => !s.active);
|
|
60
|
+
if (missing.length > 0) {
|
|
61
|
+
spinner.stop();
|
|
62
|
+
console.error(chalk_1.default.red(`\nDependências inativas para "${moduleName}": ${missing.map((m) => m.id).join(", ")}`));
|
|
63
|
+
console.error(chalk_1.default.gray(`Ative as dependências primeiro:\n${missing.map((m) => ` kaven module activate ${m.id}`).join("\n")}`));
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
spinner.stop();
|
|
67
|
+
}
|
|
68
|
+
// 4. Verificar se já está ativo
|
|
69
|
+
const current = await activator.getModuleStatus(def);
|
|
70
|
+
if (current.active) {
|
|
71
|
+
console.log(chalk_1.default.yellow(`\nMódulo "${def.label}" já está ativo.`));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
// 5. Ativar
|
|
75
|
+
spinner.start(`Descomentando modelos de ${def.label} no schema...`);
|
|
76
|
+
await activator.activateModule(def);
|
|
77
|
+
spinner.succeed(chalk_1.default.green(`\nMódulo ${def.label} ativado. ${def.models.length} models adicionados: ${def.models.join(", ")}`));
|
|
78
|
+
console.log(chalk_1.default.cyan("\nSugestão: Execute `pnpm db:generate && pnpm db:migrate` para aplicar as mudanças."));
|
|
79
|
+
telemetry.capture("cli.module.activate.success", { moduleName: def.id, models: def.models.length }, Date.now() - startTime);
|
|
80
|
+
await telemetry.flush();
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
spinner.fail(chalk_1.default.red(`Falha ao ativar módulo: ${error instanceof Error ? error.message : String(error)}`));
|
|
84
|
+
telemetry.capture("cli.module.activate.error", { moduleName, error: error.message }, Date.now() - startTime);
|
|
85
|
+
await telemetry.flush();
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// ============================================================
|
|
90
|
+
// kaven module deactivate <name>
|
|
91
|
+
// ============================================================
|
|
92
|
+
async function moduleDeactivate(moduleName, projectRoot) {
|
|
93
|
+
const telemetry = TelemetryBuffer_1.TelemetryBuffer.getInstance();
|
|
94
|
+
const startTime = Date.now();
|
|
95
|
+
telemetry.capture("cli.module.deactivate.start", { moduleName });
|
|
96
|
+
const root = projectRoot ?? process.cwd();
|
|
97
|
+
const activator = new SchemaActivator_1.SchemaActivator(root);
|
|
98
|
+
const spinner = (0, ora_1.default)(`Desativando módulo ${moduleName}...`).start();
|
|
99
|
+
try {
|
|
100
|
+
// 1. Validar que o schema existe
|
|
101
|
+
const exists = await activator.exists();
|
|
102
|
+
spinner.stop();
|
|
103
|
+
assertSchemaExists(exists);
|
|
104
|
+
// 2. Validar módulo
|
|
105
|
+
const def = findModuleDef(moduleName);
|
|
106
|
+
if (!def) {
|
|
107
|
+
console.error(chalk_1.default.red(`\nMódulo desconhecido: "${moduleName}".`));
|
|
108
|
+
console.error(chalk_1.default.gray(`Módulos disponíveis: ${SchemaActivator_1.KAVEN_MODULES.map((m) => m.id).join(", ")}`));
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
// 3. Verificar se outros módulos ativos dependem deste
|
|
112
|
+
spinner.start("Verificando dependências reversas...");
|
|
113
|
+
const dependents = [];
|
|
114
|
+
for (const candidate of SchemaActivator_1.KAVEN_MODULES) {
|
|
115
|
+
if (candidate.id === def.id)
|
|
116
|
+
continue;
|
|
117
|
+
if (!candidate.dependsOn.includes(def.id))
|
|
118
|
+
continue;
|
|
119
|
+
const status = await activator.getModuleStatus(candidate);
|
|
120
|
+
if (status.active) {
|
|
121
|
+
dependents.push(candidate.id);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (dependents.length > 0) {
|
|
125
|
+
spinner.stop();
|
|
126
|
+
console.error(chalk_1.default.red(`\nNão é possível desativar "${moduleName}": os seguintes módulos dependem dele e estão ativos:`));
|
|
127
|
+
console.error(chalk_1.default.gray(` ${dependents.join(", ")}`));
|
|
128
|
+
console.error(chalk_1.default.gray(`Desative-os primeiro:\n${dependents.map((d) => ` kaven module deactivate ${d}`).join("\n")}`));
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
// 4. Verificar se já está inativo
|
|
132
|
+
const current = await activator.getModuleStatus(def);
|
|
133
|
+
if (!current.active) {
|
|
134
|
+
spinner.stop();
|
|
135
|
+
console.log(chalk_1.default.yellow(`\nMódulo "${def.label}" já está inativo.`));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
// 5. Desativar
|
|
139
|
+
spinner.start(`Comentando modelos de ${def.label} no schema...`);
|
|
140
|
+
await activator.deactivateModule(def);
|
|
141
|
+
spinner.succeed(chalk_1.default.green(`\nMódulo ${def.label} desativado com sucesso.`));
|
|
142
|
+
console.log(chalk_1.default.cyan("\nSugestão: Execute `pnpm db:generate && pnpm db:migrate` para aplicar as mudanças."));
|
|
143
|
+
telemetry.capture("cli.module.deactivate.success", { moduleName: def.id }, Date.now() - startTime);
|
|
144
|
+
await telemetry.flush();
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
spinner.fail(chalk_1.default.red(`Falha ao desativar módulo: ${error instanceof Error ? error.message : String(error)}`));
|
|
148
|
+
telemetry.capture("cli.module.deactivate.error", { moduleName, error: error.message }, Date.now() - startTime);
|
|
149
|
+
await telemetry.flush();
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// ============================================================
|
|
154
|
+
// kaven module list
|
|
155
|
+
// ============================================================
|
|
156
|
+
async function moduleListActivation(projectRoot) {
|
|
157
|
+
const root = projectRoot ?? process.cwd();
|
|
158
|
+
const activator = new SchemaActivator_1.SchemaActivator(root);
|
|
159
|
+
const schemaExists = await activator.exists();
|
|
160
|
+
// Cabeçalho da tabela
|
|
161
|
+
const COL = {
|
|
162
|
+
module: 14,
|
|
163
|
+
status: 10,
|
|
164
|
+
models: 44,
|
|
165
|
+
deps: 20,
|
|
166
|
+
};
|
|
167
|
+
const header = chalk_1.default.bold("Module".padEnd(COL.module)) +
|
|
168
|
+
chalk_1.default.bold("Status".padEnd(COL.status)) +
|
|
169
|
+
chalk_1.default.bold("Models".padEnd(COL.models)) +
|
|
170
|
+
chalk_1.default.bold("Depends on");
|
|
171
|
+
const divider = "─".repeat(COL.module + COL.status + COL.models + COL.deps);
|
|
172
|
+
console.log();
|
|
173
|
+
console.log(chalk_1.default.blue("Módulos Kaven disponíveis\n"));
|
|
174
|
+
console.log(header);
|
|
175
|
+
console.log(chalk_1.default.gray(divider));
|
|
176
|
+
if (!schemaExists) {
|
|
177
|
+
for (const def of SchemaActivator_1.KAVEN_MODULES) {
|
|
178
|
+
const modCol = def.id.padEnd(COL.module);
|
|
179
|
+
const statusCol = chalk_1.default.gray("unknown".padEnd(COL.status));
|
|
180
|
+
const modelsCol = def.models.join(", ").padEnd(COL.models);
|
|
181
|
+
const depsCol = def.dependsOn.length > 0 ? def.dependsOn.join(", ") : "—";
|
|
182
|
+
console.log(`${modCol}${statusCol}${modelsCol}${depsCol}`);
|
|
183
|
+
}
|
|
184
|
+
console.log();
|
|
185
|
+
console.log(chalk_1.default.yellow("Atenção: schema.extended.prisma não encontrado. Status não pode ser determinado."));
|
|
186
|
+
console.log(chalk_1.default.gray("Execute este comando na raiz de um projeto Kaven para ver o status real."));
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
for (const def of SchemaActivator_1.KAVEN_MODULES) {
|
|
190
|
+
const status = await activator.getModuleStatus(def);
|
|
191
|
+
const modCol = def.id.padEnd(COL.module);
|
|
192
|
+
const statusText = status.active ? "active" : "inactive";
|
|
193
|
+
const statusColored = status.active
|
|
194
|
+
? chalk_1.default.green(statusText.padEnd(COL.status))
|
|
195
|
+
: chalk_1.default.gray(statusText.padEnd(COL.status));
|
|
196
|
+
const modelsCol = def.models.join(", ").padEnd(COL.models);
|
|
197
|
+
const depsCol = def.dependsOn.length > 0 ? def.dependsOn.join(", ") : "—";
|
|
198
|
+
console.log(`${modCol}${statusColored}${modelsCol}${depsCol}`);
|
|
199
|
+
}
|
|
200
|
+
console.log();
|
|
201
|
+
console.log(chalk_1.default.gray("Core (sempre ativo): Tenant, User, Role, Capability, AuthSession, AuditLog"));
|
|
202
|
+
console.log();
|
|
203
|
+
console.log(chalk_1.default.gray("Para ativar: kaven module activate <name> | Para desativar: kaven module deactivate <name>"));
|
|
204
|
+
}
|
|
@@ -28,9 +28,9 @@ class ModuleDoctor {
|
|
|
28
28
|
async checkAnchors() {
|
|
29
29
|
const results = [];
|
|
30
30
|
const expectedAnchors = [
|
|
31
|
-
{ file: "apps/api/src/
|
|
32
|
-
{ file: "apps/api/src/
|
|
33
|
-
{ file: "apps/
|
|
31
|
+
{ file: "apps/api/src/app.ts", anchor: "// [KAVEN_MODULE_IMPORTS]" },
|
|
32
|
+
{ file: "apps/api/src/app.ts", anchor: "// [KAVEN_MODULE_HOOKS]" },
|
|
33
|
+
{ file: "apps/api/src/app.ts", anchor: "// [KAVEN_MODULE_REGISTRATION]" },
|
|
34
34
|
];
|
|
35
35
|
for (const { file, anchor } of expectedAnchors) {
|
|
36
36
|
const filePath = path_1.default.join(this.projectRoot, file);
|
|
@@ -433,7 +433,7 @@ class ModuleDoctor {
|
|
|
433
433
|
return results;
|
|
434
434
|
}
|
|
435
435
|
async readKavenConfig() {
|
|
436
|
-
const configPath = path_1.default.join(this.projectRoot, "kaven.
|
|
436
|
+
const configPath = path_1.default.join(this.projectRoot, "kaven.json");
|
|
437
437
|
if (!(await fs_extra_1.default.pathExists(configPath))) {
|
|
438
438
|
return { modules: [] };
|
|
439
439
|
}
|
|
@@ -8,6 +8,7 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const child_process_1 = require("child_process");
|
|
10
10
|
const TEMPLATE_REPO = "https://github.com/kaven-co/kaven-template.git";
|
|
11
|
+
const KAVEN_SQUAD_REPO = "https://github.com/bychrisr/kaven-squad";
|
|
11
12
|
/** Run a shell command via spawn, returning exit code. */
|
|
12
13
|
function runCommand(cmd, args, cwd, onData) {
|
|
13
14
|
return new Promise((resolve, reject) => {
|
|
@@ -93,6 +94,33 @@ class ProjectInitializer {
|
|
|
93
94
|
await runCommand("git", ["add", "."], targetDir);
|
|
94
95
|
await runCommand("git", ["commit", "-m", "chore: initial kaven setup"], targetDir);
|
|
95
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Clone kaven-squad into squads/kaven-squad/ inside the project.
|
|
99
|
+
* Returns { installed: true } on success, { installed: false, reason } on failure.
|
|
100
|
+
* Never throws — squad installation is non-fatal.
|
|
101
|
+
*/
|
|
102
|
+
async installSquad(targetDir) {
|
|
103
|
+
const squadsDir = path_1.default.join(targetDir, "squads");
|
|
104
|
+
const squadDir = path_1.default.join(squadsDir, "kaven-squad");
|
|
105
|
+
// Squad already present — skip
|
|
106
|
+
if (await fs_extra_1.default.pathExists(squadDir)) {
|
|
107
|
+
return { installed: false, reason: "already-exists" };
|
|
108
|
+
}
|
|
109
|
+
await fs_extra_1.default.ensureDir(squadsDir);
|
|
110
|
+
const exitCode = await runCommand("git", ["clone", "--depth", "1", KAVEN_SQUAD_REPO, squadDir], process.cwd());
|
|
111
|
+
if (exitCode !== 0) {
|
|
112
|
+
return {
|
|
113
|
+
installed: false,
|
|
114
|
+
reason: `git clone exited with code ${exitCode}`,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
// Remove .git — squad history not needed in user project
|
|
118
|
+
const squadGitDir = path_1.default.join(squadDir, ".git");
|
|
119
|
+
if (await fs_extra_1.default.pathExists(squadGitDir)) {
|
|
120
|
+
await fs_extra_1.default.remove(squadGitDir);
|
|
121
|
+
}
|
|
122
|
+
return { installed: true };
|
|
123
|
+
}
|
|
96
124
|
/** Health check after project initialization. */
|
|
97
125
|
async healthCheck(targetDir) {
|
|
98
126
|
const issues = [];
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SchemaActivator = exports.KAVEN_MODULES = void 0;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
exports.KAVEN_MODULES = [
|
|
10
|
+
{
|
|
11
|
+
id: "billing",
|
|
12
|
+
label: "Billing",
|
|
13
|
+
description: "Faturamento, assinaturas e pagamentos",
|
|
14
|
+
models: ["Invoice", "Order", "Subscription", "Plan", "Payment", "Product"],
|
|
15
|
+
enums: [],
|
|
16
|
+
dependsOn: [],
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: "projects",
|
|
20
|
+
label: "Projects",
|
|
21
|
+
description: "Gestão de projetos e tasks",
|
|
22
|
+
models: ["Project", "Task"],
|
|
23
|
+
enums: ["ProjectStatus", "TaskStatus", "TaskPriority"],
|
|
24
|
+
dependsOn: [],
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "notifications",
|
|
28
|
+
label: "Notifications",
|
|
29
|
+
description: "Notificações e preferências de usuário",
|
|
30
|
+
models: ["Notification", "UserPreference"],
|
|
31
|
+
enums: [],
|
|
32
|
+
dependsOn: [],
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
// ============================================================
|
|
36
|
+
// Marcadores de seção no schema
|
|
37
|
+
// ============================================================
|
|
38
|
+
const BEGIN_MARKER = (moduleId) => `// [KAVEN_MODULE:${moduleId.toUpperCase()} BEGIN]`;
|
|
39
|
+
const END_MARKER = (moduleId) => `// [KAVEN_MODULE:${moduleId.toUpperCase()} END]`;
|
|
40
|
+
// ============================================================
|
|
41
|
+
// SchemaActivator — lê/escreve schema.extended.prisma
|
|
42
|
+
// ============================================================
|
|
43
|
+
class SchemaActivator {
|
|
44
|
+
constructor(projectRoot) {
|
|
45
|
+
this.schemaPath = path_1.default.join(projectRoot, "packages", "database", "prisma", "schema.extended.prisma");
|
|
46
|
+
}
|
|
47
|
+
/** Verifica se o schema existe no projeto */
|
|
48
|
+
async exists() {
|
|
49
|
+
return fs_extra_1.default.pathExists(this.schemaPath);
|
|
50
|
+
}
|
|
51
|
+
/** Caminho absoluto do schema */
|
|
52
|
+
get path() {
|
|
53
|
+
return this.schemaPath;
|
|
54
|
+
}
|
|
55
|
+
/** Lê o conteúdo atual do schema */
|
|
56
|
+
async readSchema() {
|
|
57
|
+
return fs_extra_1.default.readFile(this.schemaPath, "utf-8");
|
|
58
|
+
}
|
|
59
|
+
/** Persiste o conteúdo no schema */
|
|
60
|
+
async writeSchema(content) {
|
|
61
|
+
await fs_extra_1.default.writeFile(this.schemaPath, content, "utf-8");
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Detecta se um módulo está ativo no schema.
|
|
65
|
+
*
|
|
66
|
+
* Estratégia:
|
|
67
|
+
* 1. Se existirem marcadores BEGIN/END: verifica se o conteúdo dentro
|
|
68
|
+
* dos marcadores NÃO está completamente comentado.
|
|
69
|
+
* 2. Se não houver marcadores: verifica se algum dos models principais
|
|
70
|
+
* do módulo está presente e não comentado no arquivo.
|
|
71
|
+
*/
|
|
72
|
+
async getModuleStatus(def) {
|
|
73
|
+
const content = await this.readSchema();
|
|
74
|
+
const begin = BEGIN_MARKER(def.id);
|
|
75
|
+
const end = END_MARKER(def.id);
|
|
76
|
+
const hasMarkers = content.includes(begin) && content.includes(end);
|
|
77
|
+
let active = false;
|
|
78
|
+
if (hasMarkers) {
|
|
79
|
+
const block = this.extractBlock(content, def.id);
|
|
80
|
+
active = block !== null && this.isBlockActive(block);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
// Sem marcadores: verifica presença de pelo menos um model descomentado
|
|
84
|
+
active = def.models.some((modelName) => this.isModelActive(content, modelName));
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
id: def.id,
|
|
88
|
+
label: def.label,
|
|
89
|
+
description: def.description,
|
|
90
|
+
models: def.models,
|
|
91
|
+
dependsOn: def.dependsOn,
|
|
92
|
+
active,
|
|
93
|
+
hasMarkers,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/** Ativa um módulo: se tem marcadores, descomenta o bloco; senão, injeta o bloco */
|
|
97
|
+
async activateModule(def) {
|
|
98
|
+
const content = await this.readSchema();
|
|
99
|
+
const begin = BEGIN_MARKER(def.id);
|
|
100
|
+
const end = END_MARKER(def.id);
|
|
101
|
+
if (content.includes(begin) && content.includes(end)) {
|
|
102
|
+
const updated = this.uncommentBlock(content, def.id);
|
|
103
|
+
await this.writeSchema(updated);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// Módulo não tem seção marcada — sem template para injetar
|
|
107
|
+
throw new Error(`O módulo "${def.id}" não possui uma seção marcada (BEGIN/END) no schema.\n` +
|
|
108
|
+
`Adicione o bloco do módulo manualmente com os marcadores:\n` +
|
|
109
|
+
` ${begin}\n` +
|
|
110
|
+
` ... models do módulo ...\n` +
|
|
111
|
+
` ${end}`);
|
|
112
|
+
}
|
|
113
|
+
/** Desativa um módulo: comenta todos os models do bloco */
|
|
114
|
+
async deactivateModule(def) {
|
|
115
|
+
const content = await this.readSchema();
|
|
116
|
+
const begin = BEGIN_MARKER(def.id);
|
|
117
|
+
const end = END_MARKER(def.id);
|
|
118
|
+
if (!content.includes(begin) || !content.includes(end)) {
|
|
119
|
+
throw new Error(`O módulo "${def.id}" não possui marcadores BEGIN/END no schema. ` +
|
|
120
|
+
`Não é possível desativar automaticamente.`);
|
|
121
|
+
}
|
|
122
|
+
const updated = this.commentBlock(content, def.id);
|
|
123
|
+
await this.writeSchema(updated);
|
|
124
|
+
}
|
|
125
|
+
// ──────────────────────────────────────────────
|
|
126
|
+
// Helpers de manipulação de blocos
|
|
127
|
+
// ──────────────────────────────────────────────
|
|
128
|
+
/**
|
|
129
|
+
* Extrai o conteúdo entre os marcadores BEGIN e END (exclusive).
|
|
130
|
+
* Retorna null se os marcadores não forem encontrados.
|
|
131
|
+
*/
|
|
132
|
+
extractBlock(content, moduleId) {
|
|
133
|
+
const begin = BEGIN_MARKER(moduleId);
|
|
134
|
+
const end = END_MARKER(moduleId);
|
|
135
|
+
const lines = content.split("\n");
|
|
136
|
+
let beginIdx = -1;
|
|
137
|
+
let endIdx = -1;
|
|
138
|
+
for (let i = 0; i < lines.length; i++) {
|
|
139
|
+
if (lines[i].includes(begin))
|
|
140
|
+
beginIdx = i;
|
|
141
|
+
if (lines[i].includes(end) && beginIdx !== -1) {
|
|
142
|
+
endIdx = i;
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (beginIdx === -1 || endIdx === -1)
|
|
147
|
+
return null;
|
|
148
|
+
return lines.slice(beginIdx + 1, endIdx).join("\n");
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Verifica se um bloco tem ao menos uma linha não-comentada relevante
|
|
152
|
+
* (ignora linhas vazias e comentários simples).
|
|
153
|
+
*/
|
|
154
|
+
isBlockActive(block) {
|
|
155
|
+
return block.split("\n").some((line) => {
|
|
156
|
+
const trimmed = line.trim();
|
|
157
|
+
return (trimmed.length > 0 &&
|
|
158
|
+
!trimmed.startsWith("//") &&
|
|
159
|
+
!trimmed.startsWith("/*") &&
|
|
160
|
+
!trimmed.startsWith("*"));
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Verifica se um model específico está ativo (não comentado) no schema.
|
|
165
|
+
* Procura pela linha `model ModelName {` sem `//` antes.
|
|
166
|
+
*/
|
|
167
|
+
isModelActive(content, modelName) {
|
|
168
|
+
const lines = content.split("\n");
|
|
169
|
+
for (const line of lines) {
|
|
170
|
+
const trimmed = line.trim();
|
|
171
|
+
if (trimmed.startsWith(`model ${modelName}`) &&
|
|
172
|
+
!line.trimStart().startsWith("//")) {
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Comenta todas as linhas não-comentadas dentro do bloco BEGIN/END.
|
|
180
|
+
*/
|
|
181
|
+
commentBlock(content, moduleId) {
|
|
182
|
+
const begin = BEGIN_MARKER(moduleId);
|
|
183
|
+
const end = END_MARKER(moduleId);
|
|
184
|
+
const lines = content.split("\n");
|
|
185
|
+
let inBlock = false;
|
|
186
|
+
const result = [];
|
|
187
|
+
for (const line of lines) {
|
|
188
|
+
if (line.includes(begin)) {
|
|
189
|
+
inBlock = true;
|
|
190
|
+
result.push(line);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (line.includes(end)) {
|
|
194
|
+
inBlock = false;
|
|
195
|
+
result.push(line);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (inBlock) {
|
|
199
|
+
const trimmed = line.trim();
|
|
200
|
+
if (trimmed.length === 0) {
|
|
201
|
+
result.push(line);
|
|
202
|
+
}
|
|
203
|
+
else if (trimmed.startsWith("//")) {
|
|
204
|
+
result.push(line); // já comentado
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
result.push(`// ${line}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
result.push(line);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return result.join("\n");
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Remove `// ` do início das linhas dentro do bloco BEGIN/END.
|
|
218
|
+
*/
|
|
219
|
+
uncommentBlock(content, moduleId) {
|
|
220
|
+
const begin = BEGIN_MARKER(moduleId);
|
|
221
|
+
const end = END_MARKER(moduleId);
|
|
222
|
+
const lines = content.split("\n");
|
|
223
|
+
let inBlock = false;
|
|
224
|
+
const result = [];
|
|
225
|
+
for (const line of lines) {
|
|
226
|
+
if (line.includes(begin)) {
|
|
227
|
+
inBlock = true;
|
|
228
|
+
result.push(line);
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (line.includes(end)) {
|
|
232
|
+
inBlock = false;
|
|
233
|
+
result.push(line);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (inBlock) {
|
|
237
|
+
// Remove exatamente um nível de comentário `// ` ou `//`
|
|
238
|
+
if (line.trimStart().startsWith("// ")) {
|
|
239
|
+
const indent = line.length - line.trimStart().length;
|
|
240
|
+
result.push(line.slice(0, indent) + line.trimStart().slice(3));
|
|
241
|
+
}
|
|
242
|
+
else if (line.trimStart().startsWith("//")) {
|
|
243
|
+
const indent = line.length - line.trimStart().length;
|
|
244
|
+
result.push(line.slice(0, indent) + line.trimStart().slice(2));
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
result.push(line);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
result.push(line);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return result.join("\n");
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
exports.SchemaActivator = SchemaActivator;
|
|
@@ -33,9 +33,8 @@ function decodeSignature(encoded) {
|
|
|
33
33
|
* Verify Ed25519 signature against a SHA-256 checksum.
|
|
34
34
|
*
|
|
35
35
|
* Accepts signature in either hex or base64 encoding.
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* in the release metadata.
|
|
36
|
+
* Tolerates signatures made over checksum with trailing newline
|
|
37
|
+
* (common when signing via `echo checksum > file && openssl sign`).
|
|
39
38
|
*/
|
|
40
39
|
function verifyEd25519Signature(checksum, signature, publicKeyBase64) {
|
|
41
40
|
try {
|
|
@@ -45,7 +44,11 @@ function verifyEd25519Signature(checksum, signature, publicKeyBase64) {
|
|
|
45
44
|
format: "der",
|
|
46
45
|
});
|
|
47
46
|
const sigBuffer = decodeSignature(signature);
|
|
48
|
-
|
|
47
|
+
if (crypto_1.default.verify(null, Buffer.from(checksum), publicKey, sigBuffer)) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
// Tolerate trailing newline from shell-based signing
|
|
51
|
+
return crypto_1.default.verify(null, Buffer.from(checksum + "\n"), publicKey, sigBuffer);
|
|
49
52
|
}
|
|
50
53
|
catch {
|
|
51
54
|
return false;
|