Initial commit

This commit is contained in:
Josias Castro
2026-06-18 17:12:16 -03:00
commit 790e67d5d7
8 changed files with 1070 additions and 0 deletions
+123
View File
@@ -0,0 +1,123 @@
(async () => {
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
const getAllDocuments = () => {
const docs = [document];
const scan = doc => {
[...doc.querySelectorAll("iframe, frame")].forEach(frame => {
try {
if (frame.contentDocument) {
docs.push(frame.contentDocument);
scan(frame.contentDocument);
}
} catch (e) {}
});
};
scan(document);
return docs;
};
const waitForElement = async (selector, timeout = 30000) => {
const start = Date.now();
while (Date.now() - start < timeout) {
for (const doc of getAllDocuments()) {
const el = doc.querySelector(selector);
if (el) return el;
}
await sleep(300);
}
throw new Error(`No se encontró: ${selector}`);
};
const waitForEnabledDownload = async (timeout = 10 * 60 * 1000) => {
const start = Date.now();
while (Date.now() - start < timeout) {
for (const doc of getAllDocuments()) {
const button = doc.querySelector("#id_pub_button_download");
if (
button &&
button.offsetParent !== null &&
!button.classList.contains("disabled") &&
button.getAttribute("disabled") === null
) {
return button;
}
}
await sleep(1000);
}
throw new Error("El botón Download no se habilitó dentro del tiempo esperado");
};
const click = async el => {
el.scrollIntoView({ block: "center", inline: "center" });
el.focus();
await sleep(500);
const view = el.ownerDocument.defaultView || window;
["mouseover", "mousedown", "mouseup", "click"].forEach(type => {
el.dispatchEvent(
new view.MouseEvent(type, {
bubbles: true,
cancelable: true,
view
})
);
});
el.click();
};
const clickAndWait = async (selector, nextSelector) => {
const el = await waitForElement(selector);
await click(el);
await sleep(1200);
if (nextSelector) {
await waitForElement(nextSelector);
}
};
const deploy = await waitForElement("#id_menu_prj_deploy");
await click(deploy);
await sleep(1500);
await clickAndWait(
'input[name="avancar"][value="Next"][onclick="nm_next()"]',
'input[name="avancar"][value="Next"][onclick*="esquema"]'
);
await clickAndWait(
'input[name="avancar"][value="Next"][onclick*="esquema"]',
'input[name="avancar"][value="Next"][onclick*="perfil"]'
);
await clickAndWait(
'input[name="avancar"][value="Next"][onclick*="perfil"]',
'input[name="avancar"][value="Next"][onclick*="create_dir"]'
);
const lastNext = await waitForElement(
'input[name="avancar"][value="Next"][onclick*="create_dir"]'
);
await click(lastNext);
console.log("Esperando que se habilite Download...");
const downloadButton = await waitForEnabledDownload();
console.log("Download habilitado:", downloadButton);
await sleep(1500);
await click(await waitForEnabledDownload(30000));
console.log("Download clickeado.");
})();
+50
View File
@@ -0,0 +1,50 @@
(async () => {
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
const getAllDocuments = () => {
const docs = [document];
const scan = doc => {
[...doc.querySelectorAll("iframe, frame")].forEach(frame => {
try {
if (frame.contentDocument) {
docs.push(frame.contentDocument);
scan(frame.contentDocument);
}
} catch (e) {}
});
};
scan(document);
return docs;
};
const waitForElement = async (selector, timeout = 20000) => {
const start = Date.now();
while (Date.now() - start < timeout) {
for (const doc of getAllDocuments()) {
const el = doc.querySelector(selector);
if (el) return el;
}
await sleep(300);
}
throw new Error(`No se encontró: ${selector}`);
};
const clickElement = async selector => {
const el = await waitForElement(selector);
el.scrollIntoView({ block: "center", inline: "center" });
await sleep(300);
el.click();
return el;
};
await clickElement("#id_menu_prj_generate");
await sleep(1500);
await clickElement('input[name="avancar"][value="Generate"]');
})();
+50
View File
@@ -0,0 +1,50 @@
(async () => {
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
const getAllDocuments = () => {
const docs = [document];
const scan = doc => {
[...doc.querySelectorAll("iframe, frame")].forEach(frame => {
try {
if (frame.contentDocument) {
docs.push(frame.contentDocument);
scan(frame.contentDocument);
}
} catch (e) {}
});
};
scan(document);
return docs;
};
const waitForElement = async (selector, timeout = 15000) => {
const start = Date.now();
while (Date.now() - start < timeout) {
for (const doc of getAllDocuments()) {
const el = doc.querySelector(selector);
if (el) return el;
}
await sleep(300);
}
throw new Error(`No se encontró: ${selector}`);
};
const clickElement = async selector => {
const el = await waitForElement(selector);
el.scrollIntoView({ block: "center", inline: "center" });
await sleep(300);
el.click();
return el;
};
await clickElement("#id_menu_prj_version_increment");
await sleep(1000);
await clickElement('input[name="incrementar"][value="Increment"]');
})();
+50
View File
@@ -0,0 +1,50 @@
(async () => {
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
const getAllDocuments = () => {
const docs = [document];
const scan = doc => {
[...doc.querySelectorAll("iframe, frame")].forEach(frame => {
try {
if (frame.contentDocument) {
docs.push(frame.contentDocument);
scan(frame.contentDocument);
}
} catch (e) {}
});
};
scan(document);
return docs;
};
const waitForElement = async (selector, timeout = 15000) => {
const start = Date.now();
while (Date.now() - start < timeout) {
for (const doc of getAllDocuments()) {
const el = doc.querySelector(selector);
if (el) return el;
}
await sleep(300);
}
throw new Error(`No se encontró: ${selector}`);
};
const clickElement = async selector => {
const el = await waitForElement(selector);
el.scrollIntoView({ block: "center", inline: "center" });
await sleep(300);
el.click();
return el;
};
await clickElement("#id_menu_prj_version_increment");
await sleep(1000);
await clickElement('input[name="incrementar"][value="Increment"]');
})();
+11
View File
@@ -0,0 +1,11 @@
{
"manifest_version": 3,
"name": "Deploy Master",
"version": "1.2",
"description": "Ejecuta los pasos de deploy de Scriptcase desde archivos configurables.",
"permissions": ["activeTab", "scripting", "storage"],
"action": {
"default_popup": "popup.html",
"default_title": "Deploy Master"
}
}
+401
View File
@@ -0,0 +1,401 @@
:root {
color-scheme: dark;
--bg: #111820;
--panel: #20303f;
--panel-2: #192633;
--line: #425569;
--line-soft: rgba(148, 163, 184, 0.24);
--text: #eef5ff;
--muted: #9aaabd;
--green: #36d787;
--blue: #3f9dff;
--blue-dark: #1b72d8;
--error: #ff6b6b;
}
* {
box-sizing: border-box;
}
body {
width: 322px;
margin: 0;
font-family: Inter, "Segoe UI", Arial, sans-serif;
background:
radial-gradient(circle at 20% 0%, rgba(73, 130, 190, 0.28), transparent 28%),
linear-gradient(180deg, #182230 0%, #111820 100%);
color: var(--text);
}
button,
input,
summary {
font: inherit;
}
.shell {
min-height: 100vh;
padding-bottom: 8px;
}
.titlebar {
padding: 8px 12px 10px;
background: linear-gradient(180deg, #253244 0%, #172232 100%);
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
}
.brand {
display: flex;
align-items: center;
gap: 8px;
}
.brand h1 {
display: inline;
margin: 0;
font-size: 21px;
line-height: 1;
font-weight: 750;
letter-spacing: 0;
}
.brand span {
margin-left: 4px;
color: var(--muted);
font-size: 12px;
}
.rocket {
width: 27px;
height: 27px;
fill: #a9bdd2;
}
.panel {
margin: 10px 6px;
padding: 12px 14px 10px;
border: 1px solid rgba(148, 163, 184, 0.24);
border-radius: 8px;
background:
linear-gradient(145deg, rgba(42, 60, 78, 0.92), rgba(25, 38, 51, 0.94)),
var(--panel);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04), 0 6px 18px rgba(0, 0, 0, 0.22);
}
.section-title {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
color: #f5f8fc;
font-size: 17px;
}
.section-title::after {
content: "";
flex: 1;
height: 1px;
background: linear-gradient(90deg, #7b8da1, transparent);
}
.switch-row {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 14px;
font-size: 15px;
cursor: pointer;
}
.switch-row input,
.option input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.switch {
position: relative;
width: 39px;
height: 21px;
border-radius: 999px;
background: #526171;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.18);
transition: background 160ms ease;
}
.switch::after {
content: "";
position: absolute;
top: 3px;
left: 4px;
width: 15px;
height: 15px;
border-radius: 50%;
background: #f8fbff;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);
transition: transform 160ms ease;
}
.switch-row input:checked + .switch,
.option input:checked + .mini-check {
background: linear-gradient(180deg, #65b8ff, #227cdb);
}
.switch-row input:checked + .switch::after {
transform: translateX(16px);
}
.steps {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
margin: 4px 12px 16px;
position: relative;
}
.step {
display: grid;
justify-items: center;
gap: 6px;
min-height: 78px;
color: var(--muted);
text-align: center;
font-size: 13px;
line-height: 1.12;
position: relative;
}
.step::after {
content: "";
position: absolute;
top: 19px;
left: calc(50% + 22px);
width: calc(100% - 18px);
height: 3px;
background: var(--line);
border-radius: 999px;
}
.step:nth-child(2)::after {
top: 19px;
left: calc(50% + 22px);
width: calc(100% - 18px);
height: 3px;
}
.step:nth-child(3)::after {
display: none;
}
.step:nth-child(3)::before {
display: none;
}
.step.done::after,
.step.running::after,
.step.done::before,
.step.running::before {
background: var(--green);
box-shadow: 0 0 8px rgba(54, 215, 135, 0.28);
}
.step-dot {
display: grid;
place-items: center;
width: 38px;
height: 38px;
border-radius: 50%;
background: #5b6470;
color: rgba(255, 255, 255, 0.65);
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
}
.step-dot svg {
width: 23px;
height: 23px;
fill: currentColor;
}
.step.done,
.step.running {
color: #eefaf4;
}
.step.done .step-dot,
.step.running .step-dot {
background: linear-gradient(180deg, #46d98b, #23b86a);
color: #153927;
box-shadow: 0 0 14px rgba(54, 215, 135, 0.45);
}
.step.running .step-dot {
animation: pulse 900ms ease-in-out infinite;
}
.step.error .step-dot {
background: var(--error);
color: #391414;
}
.step.skipped .step-dot {
background: #4b5563;
color: #a9b1bd;
}
.primary-button {
width: 100%;
min-height: 43px;
border: 1px solid rgba(180, 220, 255, 0.38);
border-radius: 7px;
color: white;
font-size: 18px;
font-weight: 650;
cursor: pointer;
background: linear-gradient(180deg, #5bb2ff 0%, #267de1 100%);
box-shadow: 0 0 12px rgba(63, 157, 255, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.26);
}
.primary-button:disabled,
.secondary-button:disabled {
opacity: 0.58;
cursor: default;
}
.step-options {
margin-top: 10px;
color: var(--muted);
font-size: 13px;
}
.step-options summary {
cursor: pointer;
list-style-position: inside;
}
.options-grid {
display: grid;
gap: 8px;
margin-top: 9px;
}
.option {
display: flex;
align-items: center;
gap: 8px;
color: #d7e2ed;
cursor: pointer;
}
.mini-check {
display: grid;
place-items: center;
width: 18px;
height: 18px;
border-radius: 5px;
background: #55616e;
}
.mini-check::after {
content: "";
width: 8px;
height: 4px;
border-left: 2px solid white;
border-bottom: 2px solid white;
transform: rotate(-45deg) translateY(-1px);
}
.tools {
padding-bottom: 11px;
}
.secondary-button {
display: flex;
align-items: center;
justify-content: center;
gap: 9px;
width: 100%;
min-height: 40px;
margin-top: 10px;
border: 1px solid rgba(148, 163, 184, 0.26);
border-radius: 7px;
color: #edf4fa;
background: rgba(30, 46, 61, 0.78);
cursor: pointer;
}
.secondary-button svg {
width: 23px;
height: 23px;
fill: #d8e4ef;
}
.console {
margin: 8px 0 0;
border-top: 1px solid rgba(255, 255, 255, 0.07);
background: #1b1f22;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
}
.console-head {
display: flex;
align-items: center;
justify-content: space-between;
height: 28px;
padding: 0 8px 0 12px;
color: #d4d7da;
font-size: 13px;
border-bottom: 1px solid rgba(255, 255, 255, 0.07);
}
.console-head::before {
content: "";
width: 0;
height: 0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 5px solid #aab0b5;
margin-right: 7px;
}
.console-head span {
flex: 1;
}
.icon-button {
width: 22px;
height: 22px;
border: 0;
color: #b7bdc3;
background: transparent;
cursor: pointer;
font-size: 18px;
line-height: 1;
}
#logs {
min-height: 76px;
max-height: 130px;
margin: 0;
padding: 8px 12px;
overflow: auto;
color: #d7d7d7;
font: 12px/1.22 Consolas, "Courier New", monospace;
white-space: pre-wrap;
}
.log-success {
color: #63dd8d;
}
@keyframes pulse {
0%,
100% {
transform: scale(1);
}
50% {
transform: scale(1.08);
}
}
+73
View File
@@ -0,0 +1,73 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Deploy Master</title>
<link rel="stylesheet" href="popup.css">
</head>
<body>
<main class="shell">
<header class="titlebar">
<div class="brand">
<svg class="rocket" viewBox="0 0 24 24" aria-hidden="true">
<path d="M14.6 3.2c2.1-.9 4.4-1 6.2-.7.3 1.8.2 4.1-.7 6.2-1 2.4-2.9 4.6-5.9 6.7l-1.7-1.7-2.6 2.6-2.2-2.2 2.6-2.6-1.7-1.7c2.1-3 4.3-4.9 6.7-5.8ZM16 8.5a1.7 1.7 0 1 0 3.4 0A1.7 1.7 0 0 0 16 8.5ZM6.4 15.9l1.7 1.7c-.8 1.8-2.5 3.3-5.1 4 .7-2.6 2.2-4.3 4-5.1ZM4.6 9.7c1.2-2.3 2.7-3.9 4.8-4.8-.9 1.2-1.7 2.5-2.4 4L4.6 9.7Zm9.7 9.7.8-2.4c1.5-.7 2.8-1.5 4-2.4-.9 2.1-2.5 3.6-4.8 4.8Z"/>
</svg>
<div>
<h1>Deploy Master</h1>
<span>v1.2</span>
</div>
</div>
</header>
<section class="panel">
<div class="section-title">
<span>Deploy Configuration</span>
</div>
<label class="switch-row">
<input id="toggle-increment-version" type="checkbox" checked>
<span class="switch"></span>
<span>Incrementar y bloquear versión</span>
</label>
<div id="steps" class="steps" aria-live="polite"></div>
<button id="run-deploy" class="primary-button" type="button">Ejecutar Deploy</button>
<details class="step-options">
<summary>Pasos a ejecutar</summary>
<div id="step-options" class="options-grid"></div>
</details>
</section>
<section class="panel tools">
<div class="section-title">
<span>Herramientas de Desarrollador</span>
</div>
<button id="generate-test-id" class="secondary-button" type="button">
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M4 5.5A2.5 2.5 0 0 1 6.5 3h11A2.5 2.5 0 0 1 20 5.5v13A2.5 2.5 0 0 1 17.5 21h-11A2.5 2.5 0 0 1 4 18.5v-13Zm2 0v13c0 .3.2.5.5.5h11c.3 0 .5-.2.5-.5v-13c0-.3-.2-.5-.5-.5h-11c-.3 0-.5.2-.5.5Zm2 2h5v2H8v-2Zm0 4h8v2H8v-2Zm0 4h8v2H8v-2Zm10-7h-3V7h3v1.5Zm0 3h-3v-1.5h3V11.5Z"/>
</svg>
Generar ID para prueba
</button>
<button id="download-reports-folder" class="secondary-button" type="button">
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M12 3a5.8 5.8 0 0 1 5.6 4.4A5.3 5.3 0 0 1 17.2 18H15v-2h2.2a3.3 3.3 0 0 0 .1-6.6l-1.4-.1-.3-1.3A3.9 3.9 0 0 0 8 7.4l-.5 1.2-1.3.2A3.1 3.1 0 0 0 6.7 15H9v2H6.7A5.1 5.1 0 0 1 6 6.9 5.8 5.8 0 0 1 12 3Zm1 8v5.2l1.8-1.8 1.4 1.4L12 20l-4.2-4.2 1.4-1.4 1.8 1.8V11h2Z"/>
</svg>
Descargar carpeta de reportes
</button>
</section>
<section class="console">
<div class="console-head">
<span>Consola de Logs</span>
<button id="clear-logs" class="icon-button" type="button" aria-label="Limpiar consola">x</button>
</div>
<pre id="logs"></pre>
</section>
</main>
<script src="popup.js"></script>
</body>
</html>
+312
View File
@@ -0,0 +1,312 @@
const DEPLOY_STEPS = [
{
id: "export-project",
label: "Exportar proyecto",
file: "export-project.js",
enabled: true
},
{
id: "increment-version",
label: "Incrementar y bloquear version",
files: ["increment-version.js", "lock-version-history.js"],
enabled: true,
linkedToggle: "toggle-increment-version"
},
{
id: "generate-source-code",
label: "Generar codigo",
file: "generate-source-code.js",
enabled: true
}
];
const REPORTS_DOWNLOAD_URL = "http://172.27.197.229:8092/scriptcase/app/GT_ARIES/blank_PROCESAR_DESCARGAR_REPORTES/";
const SCRIPT_DELAY_MS = 2500;
const CHECK_ICON = `
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M9.2 16.6 4.8 12.2l-1.6 1.6 6 6L21 7.8l-1.6-1.6L9.2 16.6Z"/>
</svg>`;
const PLAY_ICON = `
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M8 5v14l11-7L8 5Z"/>
</svg>`;
const SKIP_ICON = `
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M5 7.4 6.4 6 12 11.6 17.6 6 19 7.4 13.4 13 19 18.6 17.6 20 12 14.4 6.4 20 5 18.6 10.6 13 5 7.4Z"/>
</svg>`;
const state = {
running: false,
stepStatus: Object.fromEntries(DEPLOY_STEPS.map(step => [step.id, "pending"])),
logs: []
};
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
const stepsEl = document.getElementById("steps");
const optionsEl = document.getElementById("step-options");
const logsEl = document.getElementById("logs");
const runButton = document.getElementById("run-deploy");
const generateIdButton = document.getElementById("generate-test-id");
const downloadReportsButton = document.getElementById("download-reports-folder");
const clearLogsButton = document.getElementById("clear-logs");
const incrementToggle = document.getElementById("toggle-increment-version");
document.addEventListener("DOMContentLoaded", init);
async function init() {
renderOptions();
renderSteps();
bindEvents();
await restoreOptions();
syncIncrementOption();
renderSteps();
log("INFO", "Deploy Master listo.");
}
function bindEvents() {
runButton.addEventListener("click", runDeploy);
generateIdButton.addEventListener("click", generateTestId);
downloadReportsButton.addEventListener("click", openReportsFolder);
clearLogsButton.addEventListener("click", () => {
state.logs = [];
renderLogs();
});
incrementToggle.addEventListener("change", () => {
syncIncrementOption();
persistOptions();
renderSteps();
});
}
function renderOptions() {
optionsEl.innerHTML = DEPLOY_STEPS.map(step => `
<label class="option" title="${getStepFiles(step).join(", ")}">
<input type="checkbox" data-step-option="${step.id}" ${step.enabled ? "checked" : ""}>
<span class="mini-check"></span>
<span>${step.label}</span>
</label>
`).join("");
optionsEl.querySelectorAll("[data-step-option]").forEach(input => {
input.addEventListener("change", () => {
const step = DEPLOY_STEPS.find(item => item.id === input.dataset.stepOption);
step.enabled = input.checked;
if (step.linkedToggle === "toggle-increment-version") {
incrementToggle.checked = input.checked;
}
persistOptions();
renderSteps();
});
});
}
function renderSteps() {
stepsEl.innerHTML = DEPLOY_STEPS.map(step => {
const status = step.enabled ? state.stepStatus[step.id] : "skipped";
return `
<div class="step ${status}">
<div class="step-dot">${getStepIcon(status)}</div>
<div>${step.label}</div>
</div>
`;
}).join("");
}
function getStepIcon(status) {
if (status === "running") return PLAY_ICON;
if (status === "skipped") return SKIP_ICON;
return CHECK_ICON;
}
async function runDeploy() {
if (state.running) return;
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab?.id) {
log("ERROR", "No se pudo detectar la pestaña activa.");
return;
}
const selectedSteps = DEPLOY_STEPS.filter(step => step.enabled);
if (!selectedSteps.length) {
log("WARN", "No hay pasos seleccionados para ejecutar.");
return;
}
setRunning(true);
resetStepStatus();
log("INFO", "Deploy iniciado...");
try {
for (const step of DEPLOY_STEPS) {
if (!step.enabled) {
setStepStatus(step.id, "skipped");
log("INFO", `Saltando ${getStepFiles(step).join(", ")}.`);
continue;
}
setStepStatus(step.id, "running");
log("INFO", `Ejecutando ${getStepFiles(step).join(", ")}...`);
const stepFiles = getStepFiles(step);
for (const [index, file] of stepFiles.entries()) {
await chrome.scripting.executeScript({
target: { tabId: tab.id, allFrames: false },
files: [file]
});
if (index < stepFiles.length - 1) {
log("INFO", `Esperando ${SCRIPT_DELAY_MS / 1000}s antes del siguiente script...`);
await sleep(SCRIPT_DELAY_MS);
}
}
setStepStatus(step.id, "done");
log("INFO", `${step.label} finalizado.`);
}
log("SUCCESS", "Deploy completado exitosamente!");
} catch (error) {
const runningStep = DEPLOY_STEPS.find(step => state.stepStatus[step.id] === "running");
if (runningStep) {
setStepStatus(runningStep.id, "error");
}
log("ERROR", error?.message || "Ocurrio un error al ejecutar el deploy.");
} finally {
setRunning(false);
}
}
async function openReportsFolder() {
await chrome.tabs.create({ url: REPORTS_DOWNLOAD_URL });
log("INFO", "Abriendo descarga de carpeta de reportes.");
}
async function generateTestId() {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab?.id) {
log("ERROR", "No se pudo detectar la pestaña activa.");
return;
}
try {
const [{ result }] = await chrome.scripting.executeScript({
target: { tabId: tab.id },
func: generateAndCopyTestId
});
log("INFO", `ID generado: ${result}`);
} catch (error) {
log("ERROR", error?.message || "No se pudo generar el ID de prueba.");
}
}
function generateAndCopyTestId() {
const now = new Date();
const pad = value => String(value).padStart(2, "0");
const id = `TEST-${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
const textarea = document.createElement("textarea");
textarea.value = id;
textarea.style.position = "fixed";
textarea.style.top = "0";
textarea.style.left = "0";
textarea.style.opacity = "0";
document.body.appendChild(textarea);
textarea.select();
document.execCommand("copy");
document.body.removeChild(textarea);
return id;
}
function resetStepStatus() {
DEPLOY_STEPS.forEach(step => {
state.stepStatus[step.id] = step.enabled ? "pending" : "skipped";
});
renderSteps();
}
function setStepStatus(stepId, status) {
state.stepStatus[stepId] = status;
renderSteps();
}
function setRunning(isRunning) {
state.running = isRunning;
runButton.disabled = isRunning;
generateIdButton.disabled = isRunning;
downloadReportsButton.disabled = isRunning;
runButton.textContent = isRunning ? "Ejecutando..." : "Ejecutar Deploy";
}
function syncIncrementOption() {
const incrementStep = DEPLOY_STEPS.find(step => step.id === "increment-version");
incrementStep.enabled = incrementToggle.checked;
const option = optionsEl.querySelector('[data-step-option="increment-version"]');
if (option) {
option.checked = incrementToggle.checked;
}
}
async function restoreOptions() {
const { deployStepOptions } = await chrome.storage.local.get("deployStepOptions");
if (!deployStepOptions) return;
DEPLOY_STEPS.forEach(step => {
if (typeof deployStepOptions[step.id] === "boolean") {
step.enabled = deployStepOptions[step.id];
}
});
incrementToggle.checked = DEPLOY_STEPS.find(step => step.id === "increment-version").enabled;
optionsEl.querySelectorAll("[data-step-option]").forEach(input => {
const step = DEPLOY_STEPS.find(item => item.id === input.dataset.stepOption);
input.checked = step.enabled;
});
}
async function persistOptions() {
const deployStepOptions = Object.fromEntries(DEPLOY_STEPS.map(step => [step.id, step.enabled]));
await chrome.storage.local.set({ deployStepOptions });
}
function getStepFiles(step) {
return step.files || [step.file];
}
function log(level, message) {
const line = `[${level}] ${message}`;
state.logs.push({ level, line });
renderLogs();
}
function renderLogs() {
logsEl.innerHTML = state.logs
.map(entry => {
const className = entry.level === "SUCCESS" ? "log-success" : "";
return `<span class="${className}">${escapeHtml(entry.line)}</span>`;
})
.join("\n");
logsEl.scrollTop = logsEl.scrollHeight;
}
function escapeHtml(value) {
return value
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#039;");
}