@semacode/cli 3.0.0 → 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agentEntryPoints.js +78 -64
- package/dist/cliGrammar.js +5 -5
- package/dist/cliHelpTexto.js +6 -6
- package/dist/docs.part01.d.ts +4 -1
- package/dist/docs.part01.js +8 -0
- package/dist/docs.part02.js +9 -1
- package/dist/drift.part01.d.ts +8 -2
- package/dist/drift.part04.d.ts +14 -2
- package/dist/drift.part04.js +35 -2
- package/dist/drift.part05.d.ts +6 -0
- package/dist/drift.part05.js +68 -0
- package/dist/drift.part06.js +60 -3
- package/dist/drift.part09.d.ts +2 -2
- package/dist/drift.part09.js +13 -3
- package/dist/drift.part11.js +24 -3
- package/dist/drift.part14.js +4 -0
- package/dist/exemplosOficiais.d.ts +2 -1
- package/dist/exemplosOficiais.js +9 -3
- package/dist/geracaoCore.d.ts +1 -0
- package/dist/geracaoCore.js +5 -0
- package/dist/importador.part01.d.ts +1 -1
- package/dist/importador.part04.d.ts +5 -1
- package/dist/importador.part04.js +40 -2
- package/dist/importador.part06.d.ts +2 -0
- package/dist/importador.part06.js +97 -1
- package/dist/importador.part09.js +17 -2
- package/dist/index.part04.js +13 -6
- package/dist/index.part07.d.ts +8 -2
- package/dist/index.part07.js +146 -4
- package/dist/index.part08.js +3 -3
- package/dist/initCommand.d.ts +1 -0
- package/dist/initCommand.js +15 -3
- package/dist/initTemplatesBase.js +78 -78
- package/dist/projetoConfig.js +5 -0
- package/dist/projetoLegado.js +40 -0
- package/dist/tipos.d.ts +1 -1
- package/dist/typescript-http-body.js +9 -0
- package/dist/typescript-http-modelos.d.ts +1 -1
- package/dist/typescript-http-rotas.d.ts +1 -0
- package/dist/typescript-http-rotas.js +198 -0
- package/dist/typescript-http-semantica.d.ts +3 -3
- package/dist/typescript-http-semantica.js +65 -7
- package/dist/typescript-http.d.ts +1 -1
- package/dist/typescript-http.js +1 -1
- package/dist/verificacaoCache.d.ts +25 -0
- package/dist/verificacaoCache.js +80 -0
- package/docs/syntax.md +14 -0
- package/exemplos/express_crud.sema +139 -0
- package/exemplos/fastify_crud.sema +146 -0
- package/exemplos/koa_crud.sema +95 -0
- package/node_modules/@sema/nucleo/dist/ir/conversor.part01.js +37 -38
- package/node_modules/@sema/nucleo/dist/ir/modelos.d.ts +1 -0
- package/node_modules/@sema/nucleo/dist/semantico/analisador.part01.d.ts +7 -0
- package/node_modules/@sema/nucleo/dist/semantico/analisador.part01.js +33 -10
- package/package.json +1 -1
package/dist/projetoLegado.js
CHANGED
|
@@ -17,6 +17,18 @@ export async function inferirFontesLegado(diretoriosCodigo, baseProjeto, configC
|
|
|
17
17
|
if (/@nestjs\/common|@nestjs\/core/.test(packageJsonRaiz)) {
|
|
18
18
|
encontrados.add("nestjs");
|
|
19
19
|
}
|
|
20
|
+
if (/"express"\s*:/.test(packageJsonRaiz)) {
|
|
21
|
+
encontrados.add("express");
|
|
22
|
+
encontrados.add("typescript");
|
|
23
|
+
}
|
|
24
|
+
if (/"fastify"\s*:/.test(packageJsonRaiz)) {
|
|
25
|
+
encontrados.add("fastify");
|
|
26
|
+
encontrados.add("typescript");
|
|
27
|
+
}
|
|
28
|
+
if (/"koa"\s*:|@koa\/router|"koa-router"/.test(packageJsonRaiz)) {
|
|
29
|
+
encontrados.add("koa");
|
|
30
|
+
encontrados.add("typescript");
|
|
31
|
+
}
|
|
20
32
|
if (/typescript/.test(packageJsonRaiz)) {
|
|
21
33
|
encontrados.add("typescript");
|
|
22
34
|
}
|
|
@@ -43,6 +55,8 @@ export async function inferirFontesLegado(diretoriosCodigo, baseProjeto, configC
|
|
|
43
55
|
const arquivosJava = await listarArquivosRecursivosLimitado(diretorio, [".java"], 5, 20);
|
|
44
56
|
const arquivosGo = await listarArquivosRecursivosLimitado(diretorio, [".go"], 5, 20);
|
|
45
57
|
const arquivosRust = await listarArquivosRecursivosLimitado(diretorio, [".rs"], 5, 20);
|
|
58
|
+
const arquivosVueSvelte = await listarArquivosRecursivosLimitado(diretorio, [".vue", ".svelte"], 5, 40);
|
|
59
|
+
const relacoesVueSvelte = arquivosVueSvelte.map((arquivo) => path.relative(diretorio, arquivo).replace(/\\/g, "/"));
|
|
46
60
|
const arquivosCppBrutos = await listarArquivosRecursivosLimitado(diretorio, [".cpp", ".cc", ".cxx", ".hpp", ".h"], 5, 30);
|
|
47
61
|
const arquivosCpp = arquivosCppBrutos.filter((arquivo) => !/(^|[\\/])(windows|linux|macos|runner|flutter|ephemeral|build|vendor)([\\/]|$)/i.test(arquivo));
|
|
48
62
|
const arquivosPhp = (await listarArquivosRecursivosLimitado(diretorio, [".php"], 5, 30))
|
|
@@ -64,6 +78,12 @@ export async function inferirFontesLegado(diretoriosCodigo, baseProjeto, configC
|
|
|
64
78
|
const relacoesTs = arquivosTsJs.map((arquivo) => path.relative(diretorio, arquivo).replace(/\\/g, "/"));
|
|
65
79
|
const temNest = textosPackage.some((texto) => /@nestjs\/common|@nestjs\/core/.test(texto ?? ""))
|
|
66
80
|
|| amostrasTs.some((texto) => /@nestjs\/common|@nestjs\/core|@Controller\(|@Get\(|@Post\(|@Put\(|@Patch\(|@Delete\(/.test(texto ?? ""));
|
|
81
|
+
const temExpress = textosPackage.some((texto) => /"express"\s*:/.test(texto ?? ""))
|
|
82
|
+
|| amostrasTs.some((texto) => /(?:from\s+["']express["']|require\(\s*["']express["']\s*\)|\bexpress\s*\(\s*\))/.test(texto ?? ""));
|
|
83
|
+
const temFastify = textosPackage.some((texto) => /"fastify"\s*:/.test(texto ?? ""))
|
|
84
|
+
|| amostrasTs.some((texto) => /(?:from\s+["']fastify["']|require\(\s*["']fastify["']\s*\)|\bfastify\s*\(\s*\{)/.test(texto ?? ""));
|
|
85
|
+
const temKoa = textosPackage.some((texto) => /"koa"\s*:|@koa\/router|"koa-router"/.test(texto ?? ""))
|
|
86
|
+
|| amostrasTs.some((texto) => /(?:from\s+["'](?:@koa\/router|koa-router|koa)["']|require\(\s*["'](?:@koa\/router|koa-router|koa)["']\s*\)|new\s+Router\s*\()/.test(texto ?? ""));
|
|
67
87
|
const temNext = textosPackage.some((texto) => /"next"\s*:/.test(texto ?? ""))
|
|
68
88
|
|| nextConfigs.length > 0
|
|
69
89
|
|| relacoesTs.some((relacao) => /(?:^|\/)(?:src\/)?app\/api\/.+\/route\.(?:ts|tsx|js|jsx)$/.test(relacao));
|
|
@@ -77,6 +97,11 @@ export async function inferirFontesLegado(diretoriosCodigo, baseProjeto, configC
|
|
|
77
97
|
const temSuperficieAngularConsumer = relacoesTs.some((relacao) => /(?:^|\/)(?:src\/)?app\/.+\.component\.(?:ts|js)$/.test(relacao))
|
|
78
98
|
|| relacoesTs.some((relacao) => /(?:^|\/)(?:src\/)?app(?:\/.+)?\/[^/]+\.routes\.(?:ts|js)$/.test(relacao));
|
|
79
99
|
const temBridgeAngularConsumer = relacoesTs.some((relacao) => /(?:^|\/)(?:src\/)?app\/(?:sema_consumer_bridge|sema\/.+)\.(?:ts|js)$/.test(relacao));
|
|
100
|
+
const temSvelteKitConsumer = relacoesVueSvelte.some((relacao) => /(?:^|\/)(?:src\/)?routes\/(?:[^/]+\/)*\+(?:page|layout|error|server)\.svelte$/i.test(relacao))
|
|
101
|
+
|| relacoesTs.some((relacao) => /(?:^|\/)(?:src\/)?routes\/(?:[^/]+\/)*\+(?:page|server)\.(?:ts|js)$/i.test(relacao))
|
|
102
|
+
|| textosPackage.some((texto) => /@sveltejs\/kit/.test(texto ?? ""));
|
|
103
|
+
const temNuxtConsumer = relacoesVueSvelte.some((relacao) => /^(?:app\/)?pages\/.+\.vue$/i.test(relacao))
|
|
104
|
+
|| textosPackage.some((texto) => /"nuxt"\s*:/.test(texto ?? ""));
|
|
80
105
|
const temAngularConsumer = temSuperficieAngularConsumer
|
|
81
106
|
|| ((textosPackage.some((texto) => /@angular\/core|@angular\/router/.test(texto ?? "")) || angularConfigs.length > 0) && temBridgeAngularConsumer);
|
|
82
107
|
const temFirebase = marcadoresFirebaseProjeto.length > 0
|
|
@@ -86,6 +111,15 @@ export async function inferirFontesLegado(diretoriosCodigo, baseProjeto, configC
|
|
|
86
111
|
if (temNest) {
|
|
87
112
|
encontrados.add("nestjs");
|
|
88
113
|
}
|
|
114
|
+
if (temExpress) {
|
|
115
|
+
encontrados.add("express");
|
|
116
|
+
}
|
|
117
|
+
if (temFastify) {
|
|
118
|
+
encontrados.add("fastify");
|
|
119
|
+
}
|
|
120
|
+
if (temKoa) {
|
|
121
|
+
encontrados.add("koa");
|
|
122
|
+
}
|
|
89
123
|
if (temNext) {
|
|
90
124
|
encontrados.add("nextjs");
|
|
91
125
|
}
|
|
@@ -98,6 +132,12 @@ export async function inferirFontesLegado(diretoriosCodigo, baseProjeto, configC
|
|
|
98
132
|
if (temAngularConsumer) {
|
|
99
133
|
encontrados.add("angular-consumer");
|
|
100
134
|
}
|
|
135
|
+
if (temSvelteKitConsumer) {
|
|
136
|
+
encontrados.add("sveltekit-consumer");
|
|
137
|
+
}
|
|
138
|
+
if (temNuxtConsumer) {
|
|
139
|
+
encontrados.add("nuxt-consumer");
|
|
140
|
+
}
|
|
101
141
|
if (temFirebase) {
|
|
102
142
|
encontrados.add("firebase");
|
|
103
143
|
}
|
package/dist/tipos.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export type EstruturaSaida = "flat" | "modulos" | "backend";
|
|
2
|
-
export type FonteLegado = "nestjs" | "fastapi" | "flask" | "nextjs" | "nextjs-consumer" | "react-vite-consumer" | "angular-consumer" | "flutter-consumer" | "firebase" | "typescript" | "javascript" | "python" | "dart" | "lua" | "dotnet" | "java" | "go" | "rust" | "cpp" | "php";
|
|
2
|
+
export type FonteLegado = "nestjs" | "express" | "fastify" | "koa" | "fastapi" | "flask" | "nextjs" | "nextjs-consumer" | "react-vite-consumer" | "angular-consumer" | "flutter-consumer" | "sveltekit-consumer" | "nuxt-consumer" | "firebase" | "typescript" | "javascript" | "python" | "dart" | "lua" | "dotnet" | "java" | "go" | "rust" | "cpp" | "php";
|
|
3
3
|
export type ModoAdocao = "incremental";
|
|
@@ -8,6 +8,15 @@ export function resolverMetadadosOrigemBody(expr, sourceFile, requestNames, body
|
|
|
8
8
|
if (ts.isIdentifier(normalizado) && bodyAliases.has(normalizado.text)) {
|
|
9
9
|
return bodyAliases.get(normalizado.text);
|
|
10
10
|
}
|
|
11
|
+
if (ts.isPropertyAccessExpression(normalizado)
|
|
12
|
+
&& normalizado.name.text === "body"
|
|
13
|
+
&& ts.isIdentifier(normalizado.expression)
|
|
14
|
+
&& requestNames.has(normalizado.expression.text)) {
|
|
15
|
+
return {
|
|
16
|
+
tipoTexto: typeNodeDeclarado?.getText(sourceFile),
|
|
17
|
+
campos: [],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
11
20
|
if (ehCallRequest(expr, requestNames, "json")) {
|
|
12
21
|
return extrairMetadadosTipoExplicito(expr, sourceFile, typeNodeDeclarado);
|
|
13
22
|
}
|
|
@@ -4,7 +4,7 @@ export interface ParametroRotaTypeScript {
|
|
|
4
4
|
tipoSema: "Texto" | "Inteiro" | "Decimal" | "Id";
|
|
5
5
|
}
|
|
6
6
|
export interface RotaTypeScriptExtraida {
|
|
7
|
-
origem: "nextjs" | "firebase";
|
|
7
|
+
origem: "nextjs" | "firebase" | "express" | "fastify" | "koa";
|
|
8
8
|
metodo: string;
|
|
9
9
|
caminho: string;
|
|
10
10
|
simbolo: string;
|
|
@@ -6,4 +6,5 @@ export declare function inferirCaminhoNext(relacaoArquivo: string): {
|
|
|
6
6
|
} | undefined;
|
|
7
7
|
export declare function identificarRotasNodeHttp(sourceFile: ts.SourceFile, nomeSimbolo: string, corpo: ts.Block): RotaTypeScriptExtraida[];
|
|
8
8
|
export declare function extrairRotasNodeWorker(sourceFile: ts.SourceFile, relacaoArquivo: string): RotaTypeScriptExtraida[];
|
|
9
|
+
export declare function extrairRotasExpressFastify(sourceFile: ts.SourceFile): RotaTypeScriptExtraida[];
|
|
9
10
|
export declare function extrairRotasTypeScriptHttp(sourceFile: ts.SourceFile, relacaoArquivo: string): RotaTypeScriptExtraida[];
|
|
@@ -109,6 +109,203 @@ export function extrairRotasNodeWorker(sourceFile, relacaoArquivo) {
|
|
|
109
109
|
}
|
|
110
110
|
return deduplicarRotas(rotas);
|
|
111
111
|
}
|
|
112
|
+
function combinarCaminhoPrefixo(base, sufixo) {
|
|
113
|
+
const prefixo = (base ?? "").replace(/\/+$/u, "");
|
|
114
|
+
const caminho = sufixo.startsWith("/") ? sufixo : `/${sufixo}`;
|
|
115
|
+
const combinado = `${prefixo}${caminho}`.replace(/\/{2,}/gu, "/");
|
|
116
|
+
return combinado.length > 1 && combinado.endsWith("/") ? combinado.slice(0, -1) : combinado;
|
|
117
|
+
}
|
|
118
|
+
function extrairPrefixoRegister(node) {
|
|
119
|
+
const opcoes = node.arguments[1];
|
|
120
|
+
if (!opcoes || !ts.isObjectLiteralExpression(opcoes)) {
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
for (const propriedade of opcoes.properties) {
|
|
124
|
+
if (ts.isPropertyAssignment(propriedade)
|
|
125
|
+
&& ts.isIdentifier(propriedade.name)
|
|
126
|
+
&& propriedade.name.text === "prefix"
|
|
127
|
+
&& propriedade.initializer
|
|
128
|
+
&& ts.isStringLiteralLike(propriedade.initializer)) {
|
|
129
|
+
return propriedade.initializer.text;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
export function extrairRotasExpressFastify(sourceFile) {
|
|
135
|
+
const textoArquivo = sourceFile.getFullText();
|
|
136
|
+
const importouExpress = /(?:from\s+["']express["']|require\(\s*["']express["']\s*\))/.test(textoArquivo);
|
|
137
|
+
const importouFastify = /(?:from\s+["']fastify["']|require\(\s*["']fastify["']\s*\))/.test(textoArquivo);
|
|
138
|
+
const importouKoa = /(?:from\s+["'](?:@koa\/router|koa-router|koa)["']|require\(\s*["'](?:@koa\/router|koa-router|koa)["']\s*\))/.test(textoArquivo);
|
|
139
|
+
if (!importouExpress && !importouFastify && !importouKoa) {
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
const arestasMontagem = [];
|
|
143
|
+
const prefixoDireto = new Map();
|
|
144
|
+
const prefixoPorParametro = new Map();
|
|
145
|
+
const prefixoResolvido = new Map();
|
|
146
|
+
const resolverPrefixo = (receptor, profundidade = 0) => {
|
|
147
|
+
if (prefixoResolvido.has(receptor)) {
|
|
148
|
+
return prefixoResolvido.get(receptor);
|
|
149
|
+
}
|
|
150
|
+
if (profundidade > 8) {
|
|
151
|
+
return "";
|
|
152
|
+
}
|
|
153
|
+
let prefixo = prefixoDireto.get(receptor) ?? "";
|
|
154
|
+
const aresta = arestasMontagem.find((item) => item.filho === receptor);
|
|
155
|
+
if (aresta) {
|
|
156
|
+
prefixo = combinarCaminhoPrefixo(resolverPrefixo(aresta.pai, profundidade + 1), aresta.prefixo);
|
|
157
|
+
}
|
|
158
|
+
prefixoResolvido.set(receptor, prefixo);
|
|
159
|
+
return prefixo;
|
|
160
|
+
};
|
|
161
|
+
const visitarMontagens = (node) => {
|
|
162
|
+
if (ts.isVariableStatement(node)) {
|
|
163
|
+
for (const declaracao of node.declarationList.declarations) {
|
|
164
|
+
if (!ts.isIdentifier(declaracao.name) || !declaracao.initializer) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const init = declaracao.initializer;
|
|
168
|
+
const argumentoFastify = ts.isCallExpression(init)
|
|
169
|
+
&& ts.isIdentifier(init.expression)
|
|
170
|
+
&& /fastify/i.test(init.expression.text)
|
|
171
|
+
&& init.arguments[0]
|
|
172
|
+
&& ts.isObjectLiteralExpression(init.arguments[0])
|
|
173
|
+
? init.arguments[0]
|
|
174
|
+
: undefined;
|
|
175
|
+
if (argumentoFastify) {
|
|
176
|
+
for (const propriedade of argumentoFastify.properties) {
|
|
177
|
+
if (ts.isPropertyAssignment(propriedade)
|
|
178
|
+
&& ts.isIdentifier(propriedade.name)
|
|
179
|
+
&& propriedade.name.text === "prefix"
|
|
180
|
+
&& propriedade.initializer
|
|
181
|
+
&& ts.isStringLiteralLike(propriedade.initializer)) {
|
|
182
|
+
prefixoDireto.set(declaracao.name.text, propriedade.initializer.text);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && ts.isIdentifier(node.expression.expression)) {
|
|
189
|
+
const receptor = node.expression.expression.text;
|
|
190
|
+
const nomeMetodo = node.expression.name.text.toLowerCase();
|
|
191
|
+
if (nomeMetodo === "use"
|
|
192
|
+
&& node.arguments[0]
|
|
193
|
+
&& ts.isStringLiteralLike(node.arguments[0])
|
|
194
|
+
&& node.arguments[0].text.startsWith("/")
|
|
195
|
+
&& node.arguments[1]
|
|
196
|
+
&& ts.isIdentifier(node.arguments[1])) {
|
|
197
|
+
arestasMontagem.push({ pai: receptor, filho: node.arguments[1].text, prefixo: node.arguments[0].text });
|
|
198
|
+
}
|
|
199
|
+
else if (nomeMetodo === "prefix"
|
|
200
|
+
&& node.arguments[0]
|
|
201
|
+
&& ts.isStringLiteralLike(node.arguments[0])
|
|
202
|
+
&& node.arguments[0].text.startsWith("/")) {
|
|
203
|
+
prefixoDireto.set(receptor, combinarCaminhoPrefixo(node.arguments[0].text, prefixoDireto.get(receptor) ?? ""));
|
|
204
|
+
}
|
|
205
|
+
else if (nomeMetodo === "register" && importouFastify) {
|
|
206
|
+
const prefixo = extrairPrefixoRegister(node);
|
|
207
|
+
const alvo = node.arguments[0];
|
|
208
|
+
if (prefixo && alvo) {
|
|
209
|
+
const funcao = ts.isFunctionExpression(alvo) || ts.isArrowFunction(alvo) ? alvo : undefined;
|
|
210
|
+
const parametro = funcao?.parameters[0];
|
|
211
|
+
if (parametro && ts.isIdentifier(parametro.name)) {
|
|
212
|
+
prefixoPorParametro.set(parametro.name.text, prefixo);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
node.forEachChild(visitarMontagens);
|
|
218
|
+
};
|
|
219
|
+
visitarMontagens(sourceFile);
|
|
220
|
+
const rotas = [];
|
|
221
|
+
const escolherOrigemChamada = (receptor) => {
|
|
222
|
+
if (/fastify/i.test(receptor)) {
|
|
223
|
+
return "fastify";
|
|
224
|
+
}
|
|
225
|
+
if (importouExpress) {
|
|
226
|
+
return "express";
|
|
227
|
+
}
|
|
228
|
+
if (importouKoa) {
|
|
229
|
+
return "koa";
|
|
230
|
+
}
|
|
231
|
+
if (importouFastify || prefixoPorParametro.has(receptor)) {
|
|
232
|
+
return "fastify";
|
|
233
|
+
}
|
|
234
|
+
return undefined;
|
|
235
|
+
};
|
|
236
|
+
const visitar = (node) => {
|
|
237
|
+
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && ts.isIdentifier(node.expression.expression)) {
|
|
238
|
+
const receptor = node.expression.expression.text;
|
|
239
|
+
const nomeMetodo = node.expression.name.text.toLowerCase();
|
|
240
|
+
const prefixoReceptor = prefixoPorParametro.get(receptor) ?? resolverPrefixo(receptor);
|
|
241
|
+
if (nomeMetodo === "route" && node.arguments[0] && ts.isObjectLiteralExpression(node.arguments[0]) && importouFastify) {
|
|
242
|
+
let metodo;
|
|
243
|
+
let caminho;
|
|
244
|
+
for (const propriedade of node.arguments[0].properties) {
|
|
245
|
+
if (!ts.isPropertyAssignment(propriedade) || !ts.isIdentifier(propriedade.name)) {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
const valor = propriedade.initializer && ts.isStringLiteralLike(propriedade.initializer)
|
|
249
|
+
? propriedade.initializer.text
|
|
250
|
+
: undefined;
|
|
251
|
+
if (propriedade.name.text === "method" && valor) {
|
|
252
|
+
metodo = valor.toUpperCase();
|
|
253
|
+
}
|
|
254
|
+
if (propriedade.name.text === "url" && valor) {
|
|
255
|
+
caminho = valor;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (metodo && caminho?.startsWith("/") && METODOS_HTTP.has(metodo)) {
|
|
259
|
+
rotas.push({
|
|
260
|
+
origem: "fastify",
|
|
261
|
+
metodo,
|
|
262
|
+
caminho: converterCaminhoParametros(combinarCaminhoPrefixo(prefixoReceptor, caminho)),
|
|
263
|
+
simbolo: receptor,
|
|
264
|
+
parametros: extrairParametrosCaminhoDoisPontos(caminho),
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
else if (nomeMetodo === "all" || nomeMetodo === "del" || METODOS_HTTP.has(nomeMetodo.toUpperCase())) {
|
|
269
|
+
const caminho = node.arguments[0] && ts.isStringLiteralLike(node.arguments[0])
|
|
270
|
+
? node.arguments[0].text
|
|
271
|
+
: undefined;
|
|
272
|
+
const origem = escolherOrigemChamada(receptor);
|
|
273
|
+
const handler = node.arguments[1] && ts.isIdentifier(node.arguments[1]) ? node.arguments[1].text : undefined;
|
|
274
|
+
if (caminho?.startsWith("/") && origem) {
|
|
275
|
+
const metodos = nomeMetodo === "all"
|
|
276
|
+
? ["GET", "POST", "PUT", "PATCH", "DELETE"]
|
|
277
|
+
: [nomeMetodo === "del" ? "DELETE" : nomeMetodo.toUpperCase()];
|
|
278
|
+
for (const metodo of metodos) {
|
|
279
|
+
rotas.push({
|
|
280
|
+
origem,
|
|
281
|
+
metodo,
|
|
282
|
+
caminho: converterCaminhoParametros(combinarCaminhoPrefixo(prefixoReceptor, caminho)),
|
|
283
|
+
simbolo: handler ?? receptor,
|
|
284
|
+
parametros: extrairParametrosCaminhoDoisPontos(combinarCaminhoPrefixo(prefixoReceptor, caminho)),
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
node.forEachChild(visitar);
|
|
291
|
+
};
|
|
292
|
+
visitar(sourceFile);
|
|
293
|
+
return deduplicarRotas(rotas);
|
|
294
|
+
}
|
|
295
|
+
function converterCaminhoParametros(caminho) {
|
|
296
|
+
return caminho.replace(/:([A-Za-z_]\w*)/g, "{$1}");
|
|
297
|
+
}
|
|
298
|
+
function extrairParametrosCaminhoDoisPontos(caminho) {
|
|
299
|
+
const parametros = [];
|
|
300
|
+
for (const correspondencia of caminho.matchAll(/:([A-Za-z_]\w*)/g)) {
|
|
301
|
+
const nome = correspondencia[1];
|
|
302
|
+
parametros.push({
|
|
303
|
+
nome,
|
|
304
|
+
tipoSema: /(^|_)id$/i.test(nome) ? "Id" : "Texto",
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
return parametros;
|
|
308
|
+
}
|
|
112
309
|
export function extrairRotasTypeScriptHttp(sourceFile, relacaoArquivo) {
|
|
113
310
|
const rotas = [];
|
|
114
311
|
const caminhoNext = inferirCaminhoNext(relacaoArquivo);
|
|
@@ -124,6 +321,7 @@ export function extrairRotasTypeScriptHttp(sourceFile, relacaoArquivo) {
|
|
|
124
321
|
}
|
|
125
322
|
}
|
|
126
323
|
rotas.push(...extrairRotasNodeWorker(sourceFile, relacaoArquivo));
|
|
324
|
+
rotas.push(...extrairRotasExpressFastify(sourceFile));
|
|
127
325
|
return deduplicarRotas(rotas);
|
|
128
326
|
}
|
|
129
327
|
//# sourceMappingURL=typescript-http-rotas.js.map
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
2
|
import { type CampoInferidoTypeScriptHttp, type ExportacaoTypeScriptHttp, type SemanticaHandlerTypeScriptHttp, type ValorLocalTypeScriptHttp } from "./typescript-http-modelos.js";
|
|
3
|
-
export declare function localizarExportacaoEmStatement(statement: ts.Statement, nomeExportado: string, sourceFile: ts.SourceFile): ExportacaoTypeScriptHttp | undefined;
|
|
3
|
+
export declare function localizarExportacaoEmStatement(statement: ts.Statement, nomeExportado: string, sourceFile: ts.SourceFile, permitirNaoExportada?: boolean): ExportacaoTypeScriptHttp | undefined;
|
|
4
4
|
export declare function inferirTipoPorContexto(node: ts.Node, nomeCampo: string): string | undefined;
|
|
5
5
|
export declare function extrairRetornoHttp(expr: ts.Expression, sourceFile: ts.SourceFile, valoresLocais: Map<string, ValorLocalTypeScriptHttp>): {
|
|
6
6
|
campos: CampoInferidoTypeScriptHttp[];
|
|
7
7
|
tipoTexto?: string;
|
|
8
8
|
status?: number;
|
|
9
9
|
} | undefined;
|
|
10
|
-
export declare function localizarExportacaoTypeScriptHttp(sourceFile: ts.SourceFile, nomeExportado: string): ExportacaoTypeScriptHttp | undefined;
|
|
11
|
-
export declare function inferirSemanticaHandlerTypeScriptHttp(sourceFile: ts.SourceFile, nomeExportado: string): SemanticaHandlerTypeScriptHttp | undefined;
|
|
10
|
+
export declare function localizarExportacaoTypeScriptHttp(sourceFile: ts.SourceFile, nomeExportado: string, permitirNaoExportada?: boolean): ExportacaoTypeScriptHttp | undefined;
|
|
11
|
+
export declare function inferirSemanticaHandlerTypeScriptHttp(sourceFile: ts.SourceFile, nomeExportado: string, permitirNaoExportada?: boolean): SemanticaHandlerTypeScriptHttp | undefined;
|
|
@@ -4,8 +4,10 @@ import ts from "typescript";
|
|
|
4
4
|
import { deduplicarCampos, deduplicarNumeros, desembrulharExpressao, } from "./typescript-http-modelos.js";
|
|
5
5
|
import { ehCallRequest, ehNewUrl, ehSearchParamsSource, extrairCamposObjetoLiteral, extrairMetadadosTipoExplicito, extrairStatusHttp, obterNomeSchema, tipoInferidoPorNomeCampo, } from "./typescript-http-expressoes.js";
|
|
6
6
|
import { extrairCamposBindingPattern, extrairSchemasZodLocais, resolverMetadadosOrigemBody, } from "./typescript-http-body.js";
|
|
7
|
-
export function localizarExportacaoEmStatement(statement, nomeExportado, sourceFile) {
|
|
8
|
-
|
|
7
|
+
export function localizarExportacaoEmStatement(statement, nomeExportado, sourceFile, permitirNaoExportada = false) {
|
|
8
|
+
const declarada = (statement) => permitirNaoExportada
|
|
9
|
+
|| statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) === true;
|
|
10
|
+
if (ts.isFunctionDeclaration(statement) && statement.name?.text === nomeExportado && declarada(statement)) {
|
|
9
11
|
return {
|
|
10
12
|
corpo: statement.body,
|
|
11
13
|
retorno: statement.type?.getText(sourceFile),
|
|
@@ -13,7 +15,7 @@ export function localizarExportacaoEmStatement(statement, nomeExportado, sourceF
|
|
|
13
15
|
declaracao: statement,
|
|
14
16
|
};
|
|
15
17
|
}
|
|
16
|
-
if (!ts.isVariableStatement(statement) || !statement
|
|
18
|
+
if (!ts.isVariableStatement(statement) || !declarada(statement)) {
|
|
17
19
|
return undefined;
|
|
18
20
|
}
|
|
19
21
|
for (const declaracao of statement.declarationList.declarations) {
|
|
@@ -110,17 +112,17 @@ export function extrairRetornoHttp(expr, sourceFile, valoresLocais) {
|
|
|
110
112
|
}
|
|
111
113
|
return undefined;
|
|
112
114
|
}
|
|
113
|
-
export function localizarExportacaoTypeScriptHttp(sourceFile, nomeExportado) {
|
|
115
|
+
export function localizarExportacaoTypeScriptHttp(sourceFile, nomeExportado, permitirNaoExportada = false) {
|
|
114
116
|
for (const node of sourceFile.statements) {
|
|
115
|
-
const localizada = localizarExportacaoEmStatement(node, nomeExportado, sourceFile);
|
|
117
|
+
const localizada = localizarExportacaoEmStatement(node, nomeExportado, sourceFile, permitirNaoExportada);
|
|
116
118
|
if (localizada) {
|
|
117
119
|
return localizada;
|
|
118
120
|
}
|
|
119
121
|
}
|
|
120
122
|
return undefined;
|
|
121
123
|
}
|
|
122
|
-
export function inferirSemanticaHandlerTypeScriptHttp(sourceFile, nomeExportado) {
|
|
123
|
-
const exportacao = localizarExportacaoTypeScriptHttp(sourceFile, nomeExportado);
|
|
124
|
+
export function inferirSemanticaHandlerTypeScriptHttp(sourceFile, nomeExportado, permitirNaoExportada = false) {
|
|
125
|
+
const exportacao = localizarExportacaoTypeScriptHttp(sourceFile, nomeExportado, permitirNaoExportada);
|
|
124
126
|
if (!exportacao?.corpo) {
|
|
125
127
|
return undefined;
|
|
126
128
|
}
|
|
@@ -139,6 +141,12 @@ export function inferirSemanticaHandlerTypeScriptHttp(sourceFile, nomeExportado)
|
|
|
139
141
|
requestNames.add(parametro.name.text);
|
|
140
142
|
}
|
|
141
143
|
}
|
|
144
|
+
const responseNames = new Set();
|
|
145
|
+
for (const [indice, parametro] of exportacao.parametros.entries()) {
|
|
146
|
+
if (indice > 0 && ts.isIdentifier(parametro.name)) {
|
|
147
|
+
responseNames.add(parametro.name.text);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
142
150
|
const visitarDeclaracoes = (node) => {
|
|
143
151
|
if (ts.isVariableDeclaration(node) && node.initializer) {
|
|
144
152
|
const inicializadorNormalizado = desembrulharExpressao(node.initializer);
|
|
@@ -256,6 +264,56 @@ export function inferirSemanticaHandlerTypeScriptHttp(sourceFile, nomeExportado)
|
|
|
256
264
|
bodyTipoTexto ??= tipoTexto;
|
|
257
265
|
};
|
|
258
266
|
const visitarSemantica = (node) => {
|
|
267
|
+
if (ts.isPropertyAccessExpression(node)
|
|
268
|
+
&& ts.isPropertyAccessExpression(node.expression)
|
|
269
|
+
&& ts.isIdentifier(node.expression.expression)
|
|
270
|
+
&& requestNames.has(node.expression.expression.text)) {
|
|
271
|
+
const origemAcesso = node.expression.name.text;
|
|
272
|
+
const nomeCampo = node.name.text;
|
|
273
|
+
const ehQuery = origemAcesso === "query" || origemAcesso === "params"
|
|
274
|
+
|| (origemAcesso === "request" && (nomeCampo === "query" || nomeCampo === "searchParams"));
|
|
275
|
+
const ehBody = origemAcesso === "body" || (origemAcesso === "request" && nomeCampo === "body");
|
|
276
|
+
if (ehQuery && origemAcesso !== "params") {
|
|
277
|
+
query.push({
|
|
278
|
+
nome: nomeCampo,
|
|
279
|
+
tipoTexto: inferirTipoPorContexto(node, nomeCampo) ?? "string",
|
|
280
|
+
obrigatorio: false,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
else if (ehBody) {
|
|
284
|
+
body.push({
|
|
285
|
+
nome: nomeCampo,
|
|
286
|
+
tipoTexto: inferirTipoPorContexto(node, nomeCampo) ?? "string",
|
|
287
|
+
obrigatorio: false,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "json") {
|
|
292
|
+
let alvo = node.expression.expression;
|
|
293
|
+
let status;
|
|
294
|
+
if (ts.isCallExpression(alvo)
|
|
295
|
+
&& ts.isPropertyAccessExpression(alvo.expression)
|
|
296
|
+
&& alvo.expression.name.text === "status"
|
|
297
|
+
&& alvo.arguments[0]
|
|
298
|
+
&& ts.isNumericLiteral(alvo.arguments[0])) {
|
|
299
|
+
status = Number(alvo.arguments[0].text);
|
|
300
|
+
alvo = alvo.expression.expression;
|
|
301
|
+
}
|
|
302
|
+
if (ts.isIdentifier(alvo) && responseNames.has(alvo.text)) {
|
|
303
|
+
const argumento = node.arguments[0];
|
|
304
|
+
const campos = argumento && ts.isObjectLiteralExpression(argumento)
|
|
305
|
+
? extrairCamposObjetoLiteral(argumento, sourceFile)
|
|
306
|
+
: argumento && ts.isIdentifier(argumento) && valoresLocais.has(argumento.text)
|
|
307
|
+
? valoresLocais.get(argumento.text).campos
|
|
308
|
+
: [];
|
|
309
|
+
if (typeof status === "number") {
|
|
310
|
+
statuses.push(status);
|
|
311
|
+
}
|
|
312
|
+
if (status === undefined || status < 400) {
|
|
313
|
+
response.push(...campos);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
259
317
|
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
|
|
260
318
|
const alvo = node.expression.expression;
|
|
261
319
|
const metodo = node.expression.name.text;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export type { CampoInferidoTypeScriptHttp, ExportacaoTypeScriptHttp, ParametroRotaTypeScript, RotaTypeScriptExtraida, SemanticaHandlerTypeScriptHttp, } from "./typescript-http-modelos.js";
|
|
2
|
-
export { extrairRotasTypeScriptHttp } from "./typescript-http-rotas.js";
|
|
2
|
+
export { extrairRotasExpressFastify, extrairRotasTypeScriptHttp } from "./typescript-http-rotas.js";
|
|
3
3
|
export { inferirSemanticaHandlerTypeScriptHttp, localizarExportacaoTypeScriptHttp, } from "./typescript-http-semantica.js";
|
package/dist/typescript-http.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SEMA-GOVERNED: sema.governanca_ia_contexto
|
|
2
2
|
// Descricao: fachada de extracao HTTP TypeScript; consulte contratos/sema/governanca_ia_contexto.sema antes de editar.
|
|
3
|
-
export { extrairRotasTypeScriptHttp } from "./typescript-http-rotas.js";
|
|
3
|
+
export { extrairRotasExpressFastify, extrairRotasTypeScriptHttp } from "./typescript-http-rotas.js";
|
|
4
4
|
export { inferirSemanticaHandlerTypeScriptHttp, localizarExportacaoTypeScriptHttp, } from "./typescript-http-semantica.js";
|
|
5
5
|
//# sourceMappingURL=typescript-http.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export declare const SCHEMA_CACHE_VERIFICACAO = "sema.verificacao-cache/v1";
|
|
2
|
+
export interface EntradaCacheVerificacao {
|
|
3
|
+
schemaVersion: typeof SCHEMA_CACHE_VERIFICACAO;
|
|
4
|
+
chave: string;
|
|
5
|
+
versaoCli: string;
|
|
6
|
+
alvo: string;
|
|
7
|
+
modulo: string;
|
|
8
|
+
sucesso: boolean;
|
|
9
|
+
quantidadeTestes: number;
|
|
10
|
+
testesExecutados: boolean;
|
|
11
|
+
arquivosGerados: string[];
|
|
12
|
+
geradoEm: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function calcularChaveVerificacao(entradas: {
|
|
15
|
+
versaoCli: string;
|
|
16
|
+
versaoNode: string;
|
|
17
|
+
modulo: string;
|
|
18
|
+
alvo: string;
|
|
19
|
+
framework: string;
|
|
20
|
+
estrutura: string;
|
|
21
|
+
conteudoContrato: string;
|
|
22
|
+
}): string;
|
|
23
|
+
export declare function carregarCacheVerificacao(chave: string, pastaAlvo: string, raizCache?: string): Promise<EntradaCacheVerificacao | undefined>;
|
|
24
|
+
export declare function carregarManifestoVerificacao(chave: string, raizCache?: string): Promise<EntradaCacheVerificacao | undefined>;
|
|
25
|
+
export declare function gravarCacheVerificacao(entrada: EntradaCacheVerificacao, raizCache?: string): Promise<void>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// SEMA-GOVERNED: sema.produto.cli_verificacao
|
|
2
|
+
// Descrição: cache incremental de verificação fora do workspace, endereçado por conteúdo do contrato.
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { resolverRaizCacheSema } from "./driftCacheFilesystem.js";
|
|
7
|
+
export const SCHEMA_CACHE_VERIFICACAO = "sema.verificacao-cache/v1";
|
|
8
|
+
export function calcularChaveVerificacao(entradas) {
|
|
9
|
+
return createHash("sha256")
|
|
10
|
+
.update(JSON.stringify({
|
|
11
|
+
schemaVersion: SCHEMA_CACHE_VERIFICACAO,
|
|
12
|
+
versaoCli: entradas.versaoCli,
|
|
13
|
+
versaoNode: entradas.versaoNode,
|
|
14
|
+
modulo: entradas.modulo,
|
|
15
|
+
alvo: entradas.alvo,
|
|
16
|
+
framework: entradas.framework,
|
|
17
|
+
estrutura: entradas.estrutura,
|
|
18
|
+
conteudoContrato: entradas.conteudoContrato,
|
|
19
|
+
}))
|
|
20
|
+
.digest("hex");
|
|
21
|
+
}
|
|
22
|
+
function resolverPastaCacheVerificacao(raizCache) {
|
|
23
|
+
const resolucao = resolverRaizCacheSema(raizCache ? { raizCache } : {});
|
|
24
|
+
return resolucao.disponivel ? path.join(resolucao.raiz, "verificacao") : undefined;
|
|
25
|
+
}
|
|
26
|
+
export async function carregarCacheVerificacao(chave, pastaAlvo, raizCache) {
|
|
27
|
+
const pasta = resolverPastaCacheVerificacao(raizCache);
|
|
28
|
+
if (!pasta) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
let entrada;
|
|
32
|
+
try {
|
|
33
|
+
entrada = JSON.parse(await readFile(path.join(pasta, `${chave}.json`), "utf8"));
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
if (entrada.schemaVersion !== SCHEMA_CACHE_VERIFICACAO || entrada.chave !== chave) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
for (const relativo of entrada.arquivosGerados) {
|
|
42
|
+
try {
|
|
43
|
+
await stat(path.join(pastaAlvo, relativo));
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return entrada;
|
|
50
|
+
}
|
|
51
|
+
export async function carregarManifestoVerificacao(chave, raizCache) {
|
|
52
|
+
const pasta = resolverPastaCacheVerificacao(raizCache);
|
|
53
|
+
if (!pasta) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const entrada = JSON.parse(await readFile(path.join(pasta, `${chave}.json`), "utf8"));
|
|
58
|
+
if (entrada.schemaVersion !== SCHEMA_CACHE_VERIFICACAO || entrada.chave !== chave) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
return entrada;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export async function gravarCacheVerificacao(entrada, raizCache) {
|
|
68
|
+
const pasta = resolverPastaCacheVerificacao(raizCache);
|
|
69
|
+
if (!pasta) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
await mkdir(pasta, { recursive: true });
|
|
74
|
+
await writeFile(path.join(pasta, `${entrada.chave}.json`), `${JSON.stringify(entrada, null, 2)}\n`, "utf8");
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// Cache é aceleração: falha de gravação nunca derruba a verificação.
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=verificacaoCache.js.map
|
package/docs/syntax.md
CHANGED
|
@@ -87,6 +87,20 @@ impl {
|
|
|
87
87
|
|
|
88
88
|
`sema compilar --alvo javascript` defines a generation target. `impl { js: ... }` defines the live-code origin linked to the contract. They are different layers and both are valid.
|
|
89
89
|
|
|
90
|
+
## Layered `impl` Roles
|
|
91
|
+
|
|
92
|
+
Layered code (route + service + persistence in the same language) can declare one `impl` per role. Append a supported role to the origin: `rota`, `servico`, `persistencia` or `repositorio`.
|
|
93
|
+
|
|
94
|
+
```sema
|
|
95
|
+
impl {
|
|
96
|
+
ts_rota: server.routes.monitores.criarMonitor
|
|
97
|
+
ts_servico: server.services.monitores.criarMonitor
|
|
98
|
+
ts_persistencia: server.repositories.monitores.criar
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Each origin-plus-role pair must appear at most once; the bare origin (for example `ts`) stays valid for single-layer tasks, and drift resolves every role independently.
|
|
103
|
+
|
|
90
104
|
## Small Canonical Lists
|
|
91
105
|
|
|
92
106
|
- `effects`: `persistencia`, `consulta`, `evento`, `auditoria`, `db.write`, `queue.publish`, `fs.write`, `network.egress`, `secret.read`, `shell.exec`.
|