diff --git a/deploy/index.html b/deploy/index.html
index b4550a54..fa92a390 100644
--- a/deploy/index.html
+++ b/deploy/index.html
@@ -651,7 +651,6 @@
3 Configure your scripts
} catch {
// best-effort; ignore storage errors
}
- const safeId = escapeHtml(scriptId)
const section = document.getElementById('setup-section-' + scriptId)
if (section) {
section.innerHTML = `⏱ Trigger previously activated.
`
@@ -667,7 +666,7 @@ 3 Configure your scripts
* to activate the hourly trigger.
*/
function buildSetupSource(triggerFn) {
- if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(triggerFn)) {
+ if (!/^[a-zA-Z_]\w*$/.test(triggerFn)) {
throw new Error(
'Invalid trigger function name: ' + JSON.stringify(triggerFn)
)
@@ -1189,14 +1188,11 @@ 3 Configure your scripts
'At approximately ' +
'' +
Array.from({ length: 24 }, (_, h) => {
- const label =
- h === 0
- ? '12 AM'
- : h < 12
- ? h + ' AM'
- : h === 12
- ? '12 PM'
- : h - 12 + ' PM'
+ let label
+ if (h === 0) label = '12 AM'
+ else if (h < 12) label = h + ' AM'
+ else if (h === 12) label = '12 PM'
+ else label = h - 12 + ' PM'
return (
'3Configure your scripts
fields.email ? [fields.email] : []
)
const subject = JSON.stringify(fields.subject || 'Weekly Briefing')
- const lookahead = parseInt(fields.lookaheadDays, 10) || 7
+ const lookahead = Number.parseInt(fields.lookaheadDays, 10) || 7
const freq = JSON.stringify(fields.frequency || 'weekly')
const day = JSON.stringify(fields.triggerDay || 'MONDAY')
- const hour = parseInt(fields.triggerHour, 10) || 7
- const interval = parseInt(fields.triggerInterval, 10) || 1
+ const hourParsed = Number.parseInt(fields.triggerHour, 10)
+ const hour = Number.isNaN(hourParsed) ? 7 : hourParsed
+ const interval = Number.parseInt(fields.triggerInterval, 10) || 1
let scheduleLines =
' scheduleFrequency: ' +
freq +
@@ -1676,11 +1673,11 @@ 3 Configure your scripts
// Tested in src/deploy/tests/index.test.js (escapeHtml suite)
function escapeHtml(str) {
return String(str == null ? '' : str)
- .replace(/&/g, '&')
- .replace(//g, '>')
- .replace(/"/g, '"')
- .replace(/'/g, ''')
+ .replaceAll('&', '&')
+ .replaceAll('<', '<')
+ .replaceAll('>', '>')
+ .replaceAll('"', '"')
+ .replaceAll("'", ''')
}
/**
@@ -2118,7 +2115,7 @@ 3 Configure your scripts
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
- ...(options.headers || {}),
+ ...options.headers,
},
})
if (!res.ok) {
@@ -2133,8 +2130,11 @@ 3 Configure your scripts
? `${json.error.message} — ${JSON.stringify(json.error.details)}`
: json.error.message
}
- } catch (_) {
- /* ignore parse errors */
+ } catch (parseError) {
+ console.debug(
+ 'apiFetch: response body was not valid JSON',
+ parseError
+ )
}
const err = new Error(message)
err.httpStatus = res.status
diff --git a/deploy/tests/ui.spec.js b/deploy/tests/ui.spec.js
index 8001ba55..ac56b68a 100644
--- a/deploy/tests/ui.spec.js
+++ b/deploy/tests/ui.spec.js
@@ -272,9 +272,13 @@ test.describe('deploy index.html', () => {
test('Step 3 stays hidden after sign-in when no projects in Drive', async ({
page,
}) => {
+ // Default mock returns empty files array; wait for the Drive lookup to
+ // actually complete rather than sleeping for a fixed interval.
+ const driveLookup = page.waitForResponse(
+ (resp) => resp.url().includes('drive/v3/files') && resp.ok()
+ )
await signIn(page)
- // Default mock returns empty files array
- await page.waitForTimeout(500)
+ await driveLookup
await expect(page.locator('#step3-card')).toBeHidden()
})
diff --git a/gas-installer/Index.html b/gas-installer/Index.html
index 84f1b3b0..5276cc6e 100644
--- a/gas-installer/Index.html
+++ b/gas-installer/Index.html
@@ -371,10 +371,10 @@ 2 Name your project
function escapeHtml(str) {
return String(str)
- .replace(/&/g, '&')
- .replace(//g, '>')
- .replace(/"/g, '"')
+ .replaceAll('&', '&')
+ .replaceAll('<', '<')
+ .replaceAll('>', '>')
+ .replaceAll('"', '"')
}