@arcadialdev/arcality 2.6.1 → 2.6.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/package.json +1 -1
- package/scripts/gen-and-run.mjs +117 -46
package/package.json
CHANGED
package/scripts/gen-and-run.mjs
CHANGED
|
@@ -740,19 +740,51 @@ async function main() {
|
|
|
740
740
|
|
|
741
741
|
const finalProjectId = process.env.ARCALITY_PROJECT_ID || selectedProjectId;
|
|
742
742
|
|
|
743
|
-
// ──
|
|
744
|
-
//
|
|
745
|
-
//
|
|
746
|
-
|
|
743
|
+
// ── Cancelación Manual (Ctrl+C) — Estrategia bulletproof ──
|
|
744
|
+
// El problema: @clack/prompts registra su propio SIGINT handler cada tick del spinner,
|
|
745
|
+
// que llama a process.exit(0) directamente. removeAllListeners() no es suficiente porque
|
|
746
|
+
// Clack lo re-registra en cada frame de animación.
|
|
747
|
+
//
|
|
748
|
+
// La solución: interceptar process.exit() antes de que Clack lo llame,
|
|
749
|
+
// y usar prependListener para que NUESTRO handler siempre se ejecute primero.
|
|
750
|
+
|
|
751
|
+
let activeChildProcess = null;
|
|
747
752
|
let userCanceledMission = false;
|
|
748
|
-
|
|
749
|
-
|
|
753
|
+
let sigintCount = 0;
|
|
754
|
+
let _missionActive = true; // Mientras true, bloqueamos el process.exit de Clack
|
|
755
|
+
|
|
756
|
+
// Guardar el process.exit original y reemplazarlo temporalmente
|
|
757
|
+
const _origProcessExit = process.exit.bind(process);
|
|
758
|
+
process.exit = (code) => {
|
|
759
|
+
if (_missionActive) {
|
|
760
|
+
// Clack (u otra lib) está intentando matar el proceso — lo bloqueamos.
|
|
761
|
+
// Nuestro cancelHandler ya habrá sido invocado vía prependListener.
|
|
762
|
+
// El proceso se cerrará naturalmente cuando el child process termine.
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
_origProcessExit(code);
|
|
766
|
+
};
|
|
767
|
+
|
|
768
|
+
const cancelHandler = () => {
|
|
769
|
+
sigintCount++;
|
|
770
|
+
if (sigintCount > 3) {
|
|
771
|
+
console.log(chalk.red('\n🛑 Forzando salida inmediata...'));
|
|
772
|
+
_missionActive = false;
|
|
773
|
+
_origProcessExit(1);
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
if (userCanceledMission) {
|
|
778
|
+
console.log(chalk.yellow('\n⏳ Cancelación en progreso, esperando reporte... (Ctrl+C x4 para forzar cierre)'));
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
750
781
|
userCanceledMission = true;
|
|
751
782
|
|
|
752
783
|
console.log(chalk.yellow('\n\n⚠️ [ARCALITY] Misión cancelada por el usuario.'));
|
|
753
|
-
if (!process.argv.includes('--debug'))
|
|
784
|
+
if (!process.argv.includes('--debug')) {
|
|
785
|
+
try { s.stop(chalk.yellow('⚠️ Misión cancelada.')); } catch { /* ignore */ }
|
|
786
|
+
}
|
|
754
787
|
|
|
755
|
-
// 1. Escribir un agent-log de cancelación para que el reporte lo refleje
|
|
756
788
|
try {
|
|
757
789
|
const ctxDir = process.env.CONTEXT_DIR;
|
|
758
790
|
if (ctxDir && fs.existsSync(ctxDir)) {
|
|
@@ -770,28 +802,15 @@ async function main() {
|
|
|
770
802
|
}
|
|
771
803
|
} catch { /* silencioso */ }
|
|
772
804
|
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
// 3. Matar el proceso hijo de Playwright — hacerlo AL FINAL para dar tiempo
|
|
777
|
-
// a que el agente-log y endMission terminen antes del kill.
|
|
778
|
-
// NO llamar process.exit() aquí: el finally del try/catch principal
|
|
779
|
-
// se encarga de generar el reporte visual y abrirlo en el browser.
|
|
780
|
-
if (activeChildProcess) {
|
|
781
|
-
try {
|
|
782
|
-
if (process.platform === 'win32') {
|
|
783
|
-
spawn('taskkill', ['/pid', activeChildProcess.pid, '/f', '/t'], { stdio: 'ignore', windowsHide: true });
|
|
784
|
-
} else {
|
|
785
|
-
activeChildProcess.kill('SIGTERM');
|
|
786
|
-
}
|
|
787
|
-
} catch { /* silencioso */ }
|
|
788
|
-
}
|
|
789
|
-
// El bloque finally del try/catch de run() tomará el control
|
|
790
|
-
// a partir de aquí (el proceso hijo ya terminó → run() rechaza → catch → finally).
|
|
805
|
+
console.log(chalk.gray(' >> Esperando que Playwright cierre y genere el reporte...'));
|
|
806
|
+
// Playwright también recibe el SIGINT nativamente desde la terminal Windows
|
|
807
|
+
// y cerrará su proceso limpiamente, lo que resolverá la Promise del run() → finally
|
|
791
808
|
};
|
|
792
809
|
|
|
793
|
-
|
|
794
|
-
process.
|
|
810
|
+
// prependListener asegura que nuestro handler se ejecuta ANTES que el de Clack
|
|
811
|
+
process.prependListener('SIGINT', cancelHandler);
|
|
812
|
+
process.prependListener('SIGTERM', cancelHandler);
|
|
813
|
+
|
|
795
814
|
|
|
796
815
|
// Build env for the runner — merge arcality.config values.
|
|
797
816
|
// ARCALITY_API_URL must be explicit — it comes from the library's internal config,
|
|
@@ -831,23 +850,55 @@ async function main() {
|
|
|
831
850
|
const testFile = path.join(PROJECT_ROOT, 'tests', '_helpers', 'agentic-runner.spec.ts');
|
|
832
851
|
const configFile = path.join(PROJECT_ROOT, 'playwright.config.js');
|
|
833
852
|
|
|
834
|
-
// Resolve playwright CLI
|
|
835
|
-
//
|
|
853
|
+
// Resolve playwright CLI — CRITICAL: must use the same @playwright/test instance
|
|
854
|
+
// that the agentic-runner.bundle.spec.js was compiled against. If the user's project
|
|
855
|
+
// has its own @playwright/test, we must use THAT one to avoid the singleton conflict
|
|
856
|
+
// ("Playwright Test did not expect test() to be called here").
|
|
857
|
+
//
|
|
858
|
+
// Resolution order:
|
|
859
|
+
// 1. ORIGINAL_CWD (user's project) — preferred, avoids singleton mismatch
|
|
860
|
+
// 2. PROJECT_ROOT (arcality package) — fallback for clean installs
|
|
836
861
|
let playwrightCli;
|
|
837
862
|
try {
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
863
|
+
const searchPaths = [ORIGINAL_CWD, PROJECT_ROOT];
|
|
864
|
+
let testRunnerPath = null;
|
|
865
|
+
let resolveError = null;
|
|
866
|
+
|
|
867
|
+
for (const searchPath of searchPaths) {
|
|
868
|
+
try {
|
|
869
|
+
testRunnerPath = require.resolve('@playwright/test', { paths: [searchPath] });
|
|
870
|
+
break;
|
|
871
|
+
} catch (e) {
|
|
872
|
+
resolveError = e;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
if (!testRunnerPath) throw resolveError;
|
|
877
|
+
|
|
878
|
+
// Navigate from @playwright/test/index.js → playwright/cli.js (sibling package)
|
|
841
879
|
playwrightCli = path.join(testRunnerPath, '..', '..', '..', 'playwright', 'cli.js');
|
|
880
|
+
|
|
881
|
+
// Some versions nest it differently — try alternate location
|
|
842
882
|
if (!fs.existsSync(playwrightCli)) {
|
|
843
|
-
|
|
883
|
+
const alt = path.join(testRunnerPath, '..', '..', 'playwright', 'cli.js');
|
|
884
|
+
if (fs.existsSync(alt)) playwrightCli = alt;
|
|
885
|
+
else throw new Error(`Playwright CLI not found. Tried:\n ${playwrightCli}\n ${alt}`);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
if (process.argv.includes('--debug')) {
|
|
889
|
+
console.log(chalk.gray(`[DEBUG] Using playwright CLI: ${playwrightCli}`));
|
|
844
890
|
}
|
|
845
891
|
} catch (e) {
|
|
846
|
-
console.error(chalk.red('\n[FATAL] Playwright core not found
|
|
892
|
+
console.error(chalk.red('\n[FATAL] Playwright core not found. Make sure @playwright/test is installed.'));
|
|
847
893
|
console.error(chalk.red(e.message));
|
|
894
|
+
console.error(chalk.yellow('\nTip: Run `npm install @playwright/test` in your project directory.'));
|
|
895
|
+
_missionActive = false;
|
|
896
|
+
process.exit = _origProcessExit;
|
|
848
897
|
process.exit(1);
|
|
849
898
|
}
|
|
850
899
|
|
|
900
|
+
|
|
901
|
+
const postProcessSigint = () => console.log(chalk.yellow('\n⏳ Finalizando tareas de reporte, por favor espera...'));
|
|
851
902
|
let missionResult = null;
|
|
852
903
|
let finalFailReason = null;
|
|
853
904
|
let finalUsageData = undefined;
|
|
@@ -869,9 +920,10 @@ async function main() {
|
|
|
869
920
|
onSpawn: (child) => { activeChildProcess = child; } // ← Capturar referencia para cancelHandler
|
|
870
921
|
});
|
|
871
922
|
|
|
872
|
-
|
|
873
|
-
process.
|
|
874
|
-
process.
|
|
923
|
+
process.removeAllListeners('SIGINT');
|
|
924
|
+
process.removeAllListeners('SIGTERM');
|
|
925
|
+
process.on('SIGINT', postProcessSigint);
|
|
926
|
+
process.on('SIGTERM', postProcessSigint);
|
|
875
927
|
|
|
876
928
|
if (userCanceledMission) {
|
|
877
929
|
missionResult = 'canceled';
|
|
@@ -932,15 +984,10 @@ async function main() {
|
|
|
932
984
|
}
|
|
933
985
|
}
|
|
934
986
|
} catch (e) {
|
|
935
|
-
//
|
|
936
|
-
process.off('SIGINT', cancelHandler);
|
|
937
|
-
process.off('SIGTERM', cancelHandler);
|
|
938
|
-
|
|
987
|
+
// SIGINT ya fue manejado por cancelHandler — solo clasificamos el resultado
|
|
939
988
|
if (userCanceledMission) {
|
|
940
989
|
missionResult = 'canceled';
|
|
941
990
|
finalFailReason = 'user_canceled';
|
|
942
|
-
// El cancelHandler ya registró la cancelación en el servidor.
|
|
943
|
-
// Solo mostramos un mensaje y dejamos que el finally genere el reporte.
|
|
944
991
|
console.log(chalk.yellow('\n⚠️ Misión cancelada — generando reporte de evidencia...'));
|
|
945
992
|
} else {
|
|
946
993
|
missionResult = 'failed';
|
|
@@ -952,14 +999,28 @@ async function main() {
|
|
|
952
999
|
}
|
|
953
1000
|
}
|
|
954
1001
|
} finally {
|
|
1002
|
+
// Desactivar el bloqueo de process.exit y restaurar el original
|
|
1003
|
+
_missionActive = false;
|
|
1004
|
+
process.exit = _origProcessExit;
|
|
1005
|
+
process.removeListener('SIGINT', cancelHandler);
|
|
1006
|
+
process.removeListener('SIGTERM', cancelHandler);
|
|
1007
|
+
|
|
955
1008
|
if (userCanceledMission) {
|
|
956
1009
|
missionResult = 'canceled';
|
|
957
1010
|
finalFailReason = 'user_canceled';
|
|
958
1011
|
await new Promise(r => setTimeout(r, 800));
|
|
959
1012
|
}
|
|
960
1013
|
|
|
1014
|
+
process.on('SIGINT', postProcessSigint);
|
|
1015
|
+
process.on('SIGTERM', postProcessSigint);
|
|
1016
|
+
|
|
961
1017
|
const sRep = spinner();
|
|
962
|
-
|
|
1018
|
+
if (!process.argv.includes('--debug')) {
|
|
1019
|
+
sRep.start('📊 Generating report...');
|
|
1020
|
+
} else {
|
|
1021
|
+
console.log('📊 Generating report...');
|
|
1022
|
+
}
|
|
1023
|
+
|
|
963
1024
|
await run("node", [path.join(PROJECT_ROOT, "scripts/rebrand-report.mjs")]).catch(() => { });
|
|
964
1025
|
|
|
965
1026
|
const reportDir = process.env.REPORTS_DIR || 'tests-report';
|
|
@@ -997,7 +1058,13 @@ async function main() {
|
|
|
997
1058
|
const actualBasePath = sasData?.basePath || sasData?.base_path;
|
|
998
1059
|
|
|
999
1060
|
if (sasData && actualSasUrl && actualBasePath) {
|
|
1000
|
-
if (!process.argv.includes('--debug'))
|
|
1061
|
+
if (!process.argv.includes('--debug')) {
|
|
1062
|
+
sRep.start('☁️ Uploading evidence to Azure...');
|
|
1063
|
+
}
|
|
1064
|
+
process.removeAllListeners('SIGINT');
|
|
1065
|
+
process.removeAllListeners('SIGTERM');
|
|
1066
|
+
process.on('SIGINT', postProcessSigint);
|
|
1067
|
+
process.on('SIGTERM', postProcessSigint);
|
|
1001
1068
|
|
|
1002
1069
|
await uploadEvidence(reportDir, actualSasUrl, actualBasePath);
|
|
1003
1070
|
|
|
@@ -1060,6 +1127,10 @@ async function main() {
|
|
|
1060
1127
|
agentMode = false;
|
|
1061
1128
|
prompt = "";
|
|
1062
1129
|
firstRun = false;
|
|
1130
|
+
|
|
1131
|
+
// Limpiar handlers del post-proceso
|
|
1132
|
+
process.removeListener('SIGINT', postProcessSigint);
|
|
1133
|
+
process.removeListener('SIGTERM', postProcessSigint);
|
|
1063
1134
|
}
|
|
1064
1135
|
}
|
|
1065
1136
|
firstRun = false;
|