@arcadialdev/arcality 2.4.48 → 2.4.49
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/package.json +3 -2
- package/scripts/gen-and-run.mjs +137 -64
- package/scripts/init.mjs +8 -3
- package/tests/_helpers/ArcalityReporter.js +729 -706
- package/tests/_helpers/agentic-runner.bundle.spec.js +67 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arcadialdev/arcality",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.49",
|
|
4
4
|
"description": "El primer ingeniero QA Autónomo integrado al CI/CD. Creado por Arcadial.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -51,7 +51,8 @@
|
|
|
51
51
|
"@types/figlet": "^1.7.0",
|
|
52
52
|
"@types/node": "^25.0.9",
|
|
53
53
|
"@types/prompts": "^2.4.9",
|
|
54
|
-
"esbuild": "^0.20.2"
|
|
54
|
+
"esbuild": "^0.20.2",
|
|
55
|
+
"typescript": "^6.0.3"
|
|
55
56
|
},
|
|
56
57
|
"dependencies": {
|
|
57
58
|
"@azure/storage-blob": "^12.31.0",
|
package/scripts/gen-and-run.mjs
CHANGED
|
@@ -34,7 +34,7 @@ const PROJECT_ROOT = process.env.ARCALITY_ROOT || path.join(__dirname, "..");
|
|
|
34
34
|
const ORIGINAL_CWD = process.cwd();
|
|
35
35
|
|
|
36
36
|
// ── Load arcality.config if present ──
|
|
37
|
-
|
|
37
|
+
let projectConfig = loadProjectConfig(ORIGINAL_CWD) || loadProjectConfig(PROJECT_ROOT);
|
|
38
38
|
if (projectConfig) {
|
|
39
39
|
injectConfigToEnv(projectConfig);
|
|
40
40
|
}
|
|
@@ -324,6 +324,9 @@ function run(cmd, args, options = {}) {
|
|
|
324
324
|
env,
|
|
325
325
|
});
|
|
326
326
|
|
|
327
|
+
// Exponer el proceso hijo para que el cancelHandler pueda matarlo
|
|
328
|
+
if (options.onSpawn) options.onSpawn(p);
|
|
329
|
+
|
|
327
330
|
let outputBuffer = '';
|
|
328
331
|
const handleData = (data) => {
|
|
329
332
|
const str = data.toString();
|
|
@@ -467,7 +470,6 @@ async function main() {
|
|
|
467
470
|
...(savedMissions.length ? [{ label: '🚀 Launch Saved Mission (.yaml)', value: 'launch_saved' }] : []),
|
|
468
471
|
...(yamlOutputFiles.length ? [{ label: '♻️ Reuse YAML + Auto-Guide (if prev. run exists)', value: 'reuse_yaml' }] : []),
|
|
469
472
|
...(rootYamlFiles.length ? [{ label: '📂 Choose Root YAML (Templates)', value: 'yaml_list' }] : []),
|
|
470
|
-
{ label: '📋 View Current Configuration', value: 'view_config' },
|
|
471
473
|
{ label: '🔄 Reconfigure (arcality init)', value: 'reconfigure' },
|
|
472
474
|
{ label: '❌ Exit', value: 'exit' }
|
|
473
475
|
];
|
|
@@ -484,28 +486,6 @@ async function main() {
|
|
|
484
486
|
|
|
485
487
|
if (action === 'agent') {
|
|
486
488
|
agentMode = true;
|
|
487
|
-
} else if (action === 'view_config') {
|
|
488
|
-
if (projectConfig) {
|
|
489
|
-
note(
|
|
490
|
-
chalk.bold.white('📋 Project: ') + chalk.cyan(projectConfig.project?.name) + '\n' +
|
|
491
|
-
chalk.bold.white('🌐 Base URL: ') + chalk.cyan(projectConfig.project?.baseUrl) + '\n' +
|
|
492
|
-
chalk.bold.white('🛠️ Framework: ') + chalk.magenta(projectConfig.project?.frameworkDetected || 'N/A') + '\n' +
|
|
493
|
-
chalk.bold.white('🆔 Project ID: ') + chalk.gray(projectConfig.projectId) + '\n' +
|
|
494
|
-
chalk.bold.white('🏢 Org ID: ') + chalk.gray(projectConfig.organizationId || 'N/A') + '\n' +
|
|
495
|
-
chalk.bold.white('👤 User: ') + chalk.cyan(projectConfig.auth?.username) + '\n' +
|
|
496
|
-
chalk.bold.white('📂 YAML Dir: ') + chalk.yellow(projectConfig.runtime?.yamlOutputDir) + '\n' +
|
|
497
|
-
chalk.bold.white('♻️ Reuse YAMLs: ') + chalk.cyan(projectConfig.runtime?.reuseSuccessfulYamls ? '✅' : '❌') + '\n' +
|
|
498
|
-
chalk.bold.white('📦 Version: ') + chalk.yellow(projectConfig.meta?.arcalityVersion || 'N/A'),
|
|
499
|
-
'arcality.config'
|
|
500
|
-
);
|
|
501
|
-
} else {
|
|
502
|
-
note(
|
|
503
|
-
chalk.yellow('No arcality.config found.') + '\n' +
|
|
504
|
-
chalk.gray('Run `arcality init` to configure this project.'),
|
|
505
|
-
'No Configuration'
|
|
506
|
-
);
|
|
507
|
-
}
|
|
508
|
-
continue;
|
|
509
489
|
} else if (action === 'reconfigure') {
|
|
510
490
|
// Launch arcality init in the user's project directory
|
|
511
491
|
const initScript = path.join(PROJECT_ROOT, 'scripts', 'init.mjs');
|
|
@@ -519,7 +499,20 @@ async function main() {
|
|
|
519
499
|
child.on('exit', code => code === 0 ? resolve() : reject(new Error(`Init exited with ${code}`)));
|
|
520
500
|
child.on('error', reject);
|
|
521
501
|
});
|
|
522
|
-
|
|
502
|
+
|
|
503
|
+
// ── REFRESH CONFIG ──
|
|
504
|
+
// Recargar el archivo que init.mjs acaba de escribir
|
|
505
|
+
projectConfig = loadProjectConfig(ORIGINAL_CWD) || loadProjectConfig(PROJECT_ROOT);
|
|
506
|
+
if (projectConfig) {
|
|
507
|
+
injectConfigToEnv(projectConfig);
|
|
508
|
+
}
|
|
509
|
+
setupEnvironment();
|
|
510
|
+
|
|
511
|
+
} catch (e) {
|
|
512
|
+
// Mostrar el error claramente antes de que console.clear() lo borre
|
|
513
|
+
console.log(chalk.red(`\n❌ Reconfigure failed: ${e.message}`));
|
|
514
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
515
|
+
}
|
|
523
516
|
continue;
|
|
524
517
|
} else if (action === 'launch_saved') {
|
|
525
518
|
const sel = await select({
|
|
@@ -747,6 +740,63 @@ async function main() {
|
|
|
747
740
|
|
|
748
741
|
const finalProjectId = process.env.ARCALITY_PROJECT_ID || selectedProjectId;
|
|
749
742
|
|
|
743
|
+
// ── SIGINT Handler: Cancelación manual (Ctrl+C / ESC) ──
|
|
744
|
+
// Se registra AQUÍ, justo antes del spawn, para que solo aplique durante la ejecución.
|
|
745
|
+
// Al cancelar: mata el proceso hijo, guarda estado "canceled", llama endMission y genera reporte.
|
|
746
|
+
let activeChildProcess = null; // Referencia al proceso hijo de Playwright
|
|
747
|
+
let userCanceledMission = false;
|
|
748
|
+
const cancelHandler = async () => {
|
|
749
|
+
if (userCanceledMission) return; // Evitar doble disparo
|
|
750
|
+
userCanceledMission = true;
|
|
751
|
+
|
|
752
|
+
console.log(chalk.yellow('\n\n⚠️ [ARCALITY] Misión cancelada por el usuario.'));
|
|
753
|
+
if (!process.argv.includes('--debug')) s.stop(chalk.yellow('⚠️ Misión cancelada por el usuario.'));
|
|
754
|
+
|
|
755
|
+
// 1. Escribir un agent-log de cancelación para que el reporte lo refleje
|
|
756
|
+
try {
|
|
757
|
+
const ctxDir = process.env.CONTEXT_DIR;
|
|
758
|
+
if (ctxDir && fs.existsSync(ctxDir)) {
|
|
759
|
+
const cancelLog = {
|
|
760
|
+
prompt,
|
|
761
|
+
history: ['[CANCELADO POR USUARIO] La prueba fue detenida manualmente antes de completarse.'],
|
|
762
|
+
steps: 0,
|
|
763
|
+
success: false,
|
|
764
|
+
error: true,
|
|
765
|
+
fail_reason: 'user_canceled',
|
|
766
|
+
canceled_by_user: true,
|
|
767
|
+
timestamp: new Date().toISOString()
|
|
768
|
+
};
|
|
769
|
+
fs.writeFileSync(path.join(ctxDir, `agent-log-${Date.now()}.json`), JSON.stringify(cancelLog, null, 2));
|
|
770
|
+
}
|
|
771
|
+
} catch { /* silencioso */ }
|
|
772
|
+
|
|
773
|
+
// 2. Llamar endMission con estado 'canceled'
|
|
774
|
+
try {
|
|
775
|
+
const { endMission: endM } = await import('../src/arcalityClient.mjs');
|
|
776
|
+
await endM(mission.mission_id, 'canceled', null, 'user_canceled');
|
|
777
|
+
console.log(chalk.gray(' >> Misión registrada como "cancelada" en el servidor.'));
|
|
778
|
+
} catch { /* silencioso si el backend no está disponible */ }
|
|
779
|
+
|
|
780
|
+
// 3. Matar el proceso hijo de Playwright — hacerlo AL FINAL para dar tiempo
|
|
781
|
+
// a que el agente-log y endMission terminen antes del kill.
|
|
782
|
+
// NO llamar process.exit() aquí: el finally del try/catch principal
|
|
783
|
+
// se encarga de generar el reporte visual y abrirlo en el browser.
|
|
784
|
+
if (activeChildProcess) {
|
|
785
|
+
try {
|
|
786
|
+
if (process.platform === 'win32') {
|
|
787
|
+
spawn('taskkill', ['/pid', activeChildProcess.pid, '/f', '/t'], { stdio: 'ignore', windowsHide: true });
|
|
788
|
+
} else {
|
|
789
|
+
activeChildProcess.kill('SIGTERM');
|
|
790
|
+
}
|
|
791
|
+
} catch { /* silencioso */ }
|
|
792
|
+
}
|
|
793
|
+
// El bloque finally del try/catch de run() tomará el control
|
|
794
|
+
// a partir de aquí (el proceso hijo ya terminó → run() rechaza → catch → finally).
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
process.once('SIGINT', cancelHandler);
|
|
798
|
+
process.once('SIGTERM', cancelHandler);
|
|
799
|
+
|
|
750
800
|
// Build env for the runner — merge arcality.config values.
|
|
751
801
|
// ARCALITY_API_URL must be explicit — it comes from the library's internal config,
|
|
752
802
|
// not from the user's project .env, so we inject it directly via getApiUrl().
|
|
@@ -812,10 +862,17 @@ async function main() {
|
|
|
812
862
|
'--headed',
|
|
813
863
|
'--config', 'playwright.config.js'
|
|
814
864
|
], {
|
|
815
|
-
cwd: PROJECT_ROOT,
|
|
865
|
+
cwd: PROJECT_ROOT,
|
|
816
866
|
env: mergedEnv,
|
|
817
|
-
onStatus: (msg) => { if (!process.argv.includes('--debug')) s.message(msg); else console.log(chalk.gray(`>> Status: ${msg}`)); }
|
|
867
|
+
onStatus: (msg) => { if (!process.argv.includes('--debug')) s.message(msg); else console.log(chalk.gray(`>> Status: ${msg}`)); },
|
|
868
|
+
onSpawn: (child) => { activeChildProcess = child; } // ← Capturar referencia para cancelHandler
|
|
818
869
|
});
|
|
870
|
+
|
|
871
|
+
// Eliminar handlers SIGINT ahora que el proceso terminó normalmente
|
|
872
|
+
process.off('SIGINT', cancelHandler);
|
|
873
|
+
process.off('SIGTERM', cancelHandler);
|
|
874
|
+
|
|
875
|
+
if (userCanceledMission) return; // El handler ya tomó el control
|
|
819
876
|
if (!process.argv.includes('--debug')) s.stop(chalk.green('✅ Mission completed successfully.'));
|
|
820
877
|
else console.log(chalk.green('✅ Mission completed successfully.'));
|
|
821
878
|
|
|
@@ -905,50 +962,66 @@ async function main() {
|
|
|
905
962
|
}
|
|
906
963
|
}
|
|
907
964
|
} catch (e) {
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
965
|
+
// Eliminar handlers SIGINT en el path de error también
|
|
966
|
+
process.off('SIGINT', cancelHandler);
|
|
967
|
+
process.off('SIGTERM', cancelHandler);
|
|
968
|
+
|
|
969
|
+
if (userCanceledMission) {
|
|
970
|
+
// El cancelHandler ya registró la cancelación en el servidor.
|
|
971
|
+
// Solo mostramos un mensaje y dejamos que el finally genere el reporte.
|
|
972
|
+
console.log(chalk.yellow('\n⚠️ Misión cancelada — generando reporte de evidencia...'));
|
|
973
|
+
} else {
|
|
974
|
+
s.stop(chalk.red(`❌ Mission could not be completed.`));
|
|
975
|
+
console.error(chalk.red(`\nError Details: ${e.message}`));
|
|
976
|
+
if (e.stack && process.argv.includes('--debug')) {
|
|
977
|
+
console.error(chalk.gray(e.stack));
|
|
978
|
+
}
|
|
913
979
|
|
|
914
|
-
|
|
915
|
-
try {
|
|
916
|
-
let finalUsage = undefined;
|
|
917
|
-
let failReason = "timeout_or_crash";
|
|
980
|
+
// Si NO fue cancelación de usuario, cerrar la misión como fallida en el servidor
|
|
918
981
|
try {
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
const
|
|
923
|
-
if (
|
|
924
|
-
const
|
|
925
|
-
const
|
|
926
|
-
|
|
927
|
-
|
|
982
|
+
let finalUsage = undefined;
|
|
983
|
+
let failReason = "timeout_or_crash";
|
|
984
|
+
try {
|
|
985
|
+
const ctxDir = process.env.CONTEXT_DIR;
|
|
986
|
+
if (fs.existsSync(ctxDir)) {
|
|
987
|
+
const files = fs.readdirSync(ctxDir);
|
|
988
|
+
const logFiles = files.filter(f => f.startsWith('agent-log-') && f.endsWith('.json')).sort();
|
|
989
|
+
if (logFiles.length > 0) {
|
|
990
|
+
const lastLog = path.join(ctxDir, logFiles[logFiles.length - 1]);
|
|
991
|
+
const logData = JSON.parse(fs.readFileSync(lastLog, 'utf8'));
|
|
992
|
+
finalUsage = logData.usage;
|
|
993
|
+
failReason = logData.fail_reason || failReason;
|
|
994
|
+
}
|
|
928
995
|
}
|
|
929
|
-
}
|
|
930
|
-
} catch(e) {}
|
|
996
|
+
} catch(e) {}
|
|
931
997
|
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
998
|
+
// ── Upload Evidence ──
|
|
999
|
+
const { endMission, getEvidenceSasToken } = await import('../src/arcalityClient.mjs');
|
|
1000
|
+
const { uploadEvidence } = await import('../src/EvidenceUploader.mjs');
|
|
935
1001
|
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
await run("node", [path.join(PROJECT_ROOT, "scripts/rebrand-report.mjs")]).catch(() => { });
|
|
943
|
-
|
|
944
|
-
await uploadEvidence(reportsDir, sasData.sasUrl, sasData.basePath);
|
|
945
|
-
if (!process.argv.includes('--debug')) s.stop(chalk.green('✅ Evidence uploaded successfully.'));
|
|
946
|
-
}
|
|
947
|
-
} catch (e) {}
|
|
1002
|
+
try {
|
|
1003
|
+
const sasData = await getEvidenceSasToken(mission.mission_id, finalProjectId);
|
|
1004
|
+
if (sasData && sasData.sasUrl) {
|
|
1005
|
+
if (!process.argv.includes('--debug')) s.start('☁️ Uploading evidence to Azure...');
|
|
1006
|
+
const reportsDir = process.env.REPORTS_DIR || path.join(ORIGINAL_CWD, 'tests-report');
|
|
948
1007
|
|
|
949
|
-
|
|
950
|
-
|
|
1008
|
+
await run("node", [path.join(PROJECT_ROOT, "scripts/rebrand-report.mjs")]).catch(() => { });
|
|
1009
|
+
|
|
1010
|
+
await uploadEvidence(reportsDir, sasData.sasUrl, sasData.basePath);
|
|
1011
|
+
if (!process.argv.includes('--debug')) s.stop(chalk.green('✅ Evidence uploaded successfully.'));
|
|
1012
|
+
}
|
|
1013
|
+
} catch (e) {}
|
|
1014
|
+
|
|
1015
|
+
await endMission(mission.mission_id, 'failed', finalUsage, failReason);
|
|
1016
|
+
} catch { }
|
|
1017
|
+
}
|
|
951
1018
|
} finally {
|
|
1019
|
+
// Si fue cancelación de usuario, dar un breve margen para que el cancelHandler
|
|
1020
|
+
// async (endMission, escritura de log) termine antes de generar el reporte.
|
|
1021
|
+
if (userCanceledMission) {
|
|
1022
|
+
await new Promise(r => setTimeout(r, 800));
|
|
1023
|
+
}
|
|
1024
|
+
|
|
952
1025
|
const sRep = spinner();
|
|
953
1026
|
sRep.start('📊 Generating report...');
|
|
954
1027
|
await run("node", [path.join(PROJECT_ROOT, "scripts/rebrand-report.mjs")]).catch(() => { });
|
package/scripts/init.mjs
CHANGED
|
@@ -143,8 +143,12 @@ async function main() {
|
|
|
143
143
|
401: '❌ Invalid API Key. Please check your key and try again.',
|
|
144
144
|
403: `❌ Authentication Failed (HTTP 403). Server says: ${errText.slice(0, 150)}`,
|
|
145
145
|
};
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
const msg = errorMap[res.status] || `❌ Server error (HTTP ${res.status}): ${errText.slice(0, 150)}`;
|
|
147
|
+
s.stop(chalk.red(msg));
|
|
148
|
+
console.log(chalk.yellow('\n Press Enter to return to menu...'));
|
|
149
|
+
// Salida limpia (código 0) para que el menú padre no interprete esto como crash
|
|
150
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
151
|
+
process.exit(0);
|
|
148
152
|
}
|
|
149
153
|
|
|
150
154
|
const data = await res.json();
|
|
@@ -162,7 +166,8 @@ async function main() {
|
|
|
162
166
|
} catch (err) {
|
|
163
167
|
s.stop(chalk.red(`❌ Could not connect to Arcality backend: ${err.message}`));
|
|
164
168
|
console.log(chalk.yellow(' Make sure the backend is running and accessible.'));
|
|
165
|
-
|
|
169
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
170
|
+
process.exit(0);
|
|
166
171
|
}
|
|
167
172
|
|
|
168
173
|
// ── Step 3: Project details ──
|