- Refactor monolithic index.js (2646 lines) into src/{webex,integrations,
cards,flows,commands,services} modules; replace node-fetch/form-data
with native fetch/FormData; move all secrets to .env via dotenv
- Add dockerized remote SIW agent (docker/remote-agent/) with cross-arch
buildx packaging (arm64 Mac -> linux/amd64), idempotent install.sh
deploy bundle, and docker-free ZIP inspector for arch verification
- Bot hosts a WebSocket server; agent proxies SIW requests with a
per-request insecure:true flag, replacing the process-wide
NODE_TLS_REJECT_UNAUTHORIZED bypass
- Add ESLint flat config + Prettier, rewrite Dockerfile as non-root
multi-stage node:22-alpine build, README covering setup / deploy /
remote agent workflow
- Fix parseStoreArg to read trigger.prompt correctly (was indexing past
the framework's post-match slice); register /help as regex (string
matcher only compares the first token); switch catch-all to /.+/
(previous /.*/gim was stateful due to the g flag); remove
/fixDisplayNames command and its flow/card
Co-authored-by: Cursor <cursoragent@cursor.com>
92 lines
3.5 KiB
JavaScript
92 lines
3.5 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Merged replacement for the legacy get911.js and storeAddress.js scripts.
|
|
* Pulls the store list from SIW, validates each address with Google, looks up
|
|
* phone numbers with Twilio, and writes buildingFile.csv + locationFile.csv
|
|
* suitable for the E911 uploader.
|
|
*
|
|
* Usage:
|
|
* node scripts/generate911Csv.js [--env dev|prod]
|
|
*
|
|
* Defaults to prod. The SIW filter endpoint takes /Store/Filter/{page}/{size}/{sort}/{dir}.
|
|
*/
|
|
import fs from 'node:fs/promises';
|
|
import { config } from '../src/config.js';
|
|
import { getSIWData } from '../src/integrations/siw.js';
|
|
import { validatePhoneNumber } from '../src/integrations/twilio.js';
|
|
import { formatE911Address, formatSuite, validateAddress } from '../src/integrations/google.js';
|
|
import { logger } from '../src/logger.js';
|
|
|
|
const BUILDING_HEADER =
|
|
'"Building Name *","Address *","Supplemental Info (20)","Organization Name Override (50)"\n';
|
|
const LOCATION_HEADER =
|
|
'"Building Name *","Location Name *","Phone Number (10)","Alternate ID (50)","Callback Number (10)","Location Info (20)","Organization Name Override (50)","HTML Link Name (64)","HTML Link URL (1024)"\n';
|
|
|
|
function brandFor(brandCode) {
|
|
switch (brandCode) {
|
|
case 'AE':
|
|
return 'American Eagle';
|
|
case 'AR':
|
|
return 'Aerie';
|
|
default:
|
|
return 'American Eagle';
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
const args = process.argv.slice(2);
|
|
const envIndex = args.indexOf('--env');
|
|
const envOverride = envIndex >= 0 ? args[envIndex + 1] : undefined;
|
|
const baseUrl =
|
|
envOverride === 'dev' ? config.siw.baseUrl.replace('-prod.', '-dev.') : config.siw.baseUrl;
|
|
const listUrl = `${baseUrl}/api/Store/Filter/1/9999/store_number/1`;
|
|
|
|
logger.info(`Fetching stores from ${listUrl}`);
|
|
const stores = await getSIWData(listUrl);
|
|
|
|
let buildingCsv = BUILDING_HEADER;
|
|
let locationCsv = LOCATION_HEADER;
|
|
let liveStores = 0;
|
|
|
|
for (const store of stores.stores ?? []) {
|
|
const openInUsProd =
|
|
store.status_id === 2 &&
|
|
store.environment_code === 'PRD' &&
|
|
store.country_code === 'US' &&
|
|
store.brand_code !== 'TS' &&
|
|
store.store_number > 1000;
|
|
if (!openInUsProd) continue;
|
|
|
|
try {
|
|
const googleAddress = await validateAddress(
|
|
`${store.address ?? ''} ${store.address2 ?? ''}`.trim(),
|
|
store.city,
|
|
store.state,
|
|
store.postal_code,
|
|
store.country_code,
|
|
);
|
|
const formatted = formatE911Address(googleAddress);
|
|
const suite = formatSuite(googleAddress);
|
|
const phone = await validatePhoneNumber(store.phone);
|
|
const brand = brandFor(store.brand_code);
|
|
const padded = String(store.store_number).padStart(4, '0');
|
|
|
|
buildingCsv += `"Store ${padded}","${formatted}",,"${brand}"\n`;
|
|
locationCsv += `"Store ${padded}","Store ${padded}","${phone.phone_number}",,,"${suite}","${brand}",,\n`;
|
|
liveStores++;
|
|
} catch (error) {
|
|
logger.error(`Failed store ${store.store_number}:`, error);
|
|
}
|
|
}
|
|
|
|
logger.info(`Live stores processed: ${liveStores}`);
|
|
await fs.writeFile('./buildingFile.csv', buildingCsv);
|
|
logger.info('buildingFile.csv saved.');
|
|
await fs.writeFile('./locationFile.csv', locationCsv);
|
|
logger.info('locationFile.csv saved.');
|
|
}
|
|
|
|
main().catch((error) => {
|
|
logger.error('generate911Csv failed:', error);
|
|
process.exit(1);
|
|
});
|