From 5e53757d43d0508466a210ffee2141beafbc3a5d Mon Sep 17 00:00:00 2001
From: priyankmodiPM <32540484+priyankmodiPM@users.noreply.github.com>
Date: Fri, 26 Jun 2026 00:30:08 +0530
Subject: [PATCH 01/20] Update app.js
---
app.js | 88 +++++++++++++++++++++-------------------------------------
1 file changed, 32 insertions(+), 56 deletions(-)
diff --git a/app.js b/app.js
index 5ab128e4b4..66de816cab 100644
--- a/app.js
+++ b/app.js
@@ -1,61 +1,37 @@
-const express = require("express");
+// Import Express.js
+const express = require('express');
+
+// Create an Express app
const app = express();
-const port = process.env.PORT || 3001;
-app.get("/", (req, res) => res.type('html').send(html));
+// Middleware to parse JSON bodies
+app.use(express.json());
+
+// Set port and verify_token
+const port = process.env.PORT || 3000;
+const verifyToken = process.env.VERIFY_TOKEN;
+
+// Route for GET requests
+app.get('/', (req, res) => {
+ const { 'hub.mode': mode, 'hub.challenge': challenge, 'hub.verify_token': token } = req.query;
-const server = app.listen(port, () => console.log(`Example app listening on port ${port}!`));
+ if (mode === 'subscribe' && token === verifyToken) {
+ console.log('WEBHOOK VERIFIED');
+ res.status(200).send(challenge);
+ } else {
+ res.status(403).end();
+ }
+});
-server.keepAliveTimeout = 120 * 1000;
-server.headersTimeout = 120 * 1000;
+// Route for POST requests
+app.post('/', (req, res) => {
+ const timestamp = new Date().toISOString().replace('T', ' ').slice(0, 19);
+ console.log(`\n\nWebhook received ${timestamp}\n`);
+ console.log(JSON.stringify(req.body, null, 2));
+ res.status(200).end();
+});
-const html = `
-
-
-
- Hello from Render!
-
-
-
-
-
-
-
-
-`
+// Start the server
+app.listen(port, () => {
+ console.log(`\nListening on port ${port}\n`);
+});
From fdd95724d9d36cc50039d3c50dd2f0881c16c7d6 Mon Sep 17 00:00:00 2001
From: Priyank Modi
Date: Fri, 26 Jun 2026 19:10:59 +0530
Subject: [PATCH 02/20] Add discount image auto-reply for WhatsApp messages
When an incoming WhatsApp message contains "update the discount", the webhook
automatically replies to the sender with the discount image via the WhatsApp
Cloud API. Phone number ID and token are read from env vars.
Co-Authored-By: Claude Sonnet 4.6
---
app.js | 50 +++++++++++++++++++++++++++++++++++++++++++++++++-
render.yaml | 6 ++++++
2 files changed, 55 insertions(+), 1 deletion(-)
diff --git a/app.js b/app.js
index 66de816cab..356f1ddf80 100644
--- a/app.js
+++ b/app.js
@@ -10,6 +10,36 @@ app.use(express.json());
// Set port and verify_token
const port = process.env.PORT || 3000;
const verifyToken = process.env.VERIFY_TOKEN;
+const whatsappPhoneNumberId = process.env.WHATSAPP_PHONE_NUMBER_ID;
+const whatsappToken = process.env.WHATSAPP_TOKEN;
+
+const DISCOUNT_IMAGE_URL = 'https://s7ap1.scene7.com/is/image/varun/diwali-offer2-updated';
+
+async function sendImageMessage(to) {
+ const url = `https://graph.facebook.com/v19.0/${whatsappPhoneNumberId}/messages`;
+ const body = {
+ messaging_product: 'whatsapp',
+ to,
+ type: 'image',
+ image: { link: DISCOUNT_IMAGE_URL },
+ };
+
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: {
+ Authorization: `Bearer ${whatsappToken}`,
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(body),
+ });
+
+ if (!response.ok) {
+ const text = await response.text();
+ throw new Error(`WhatsApp API error ${response.status}: ${text}`);
+ }
+
+ return response.json();
+}
// Route for GET requests
app.get('/', (req, res) => {
@@ -24,11 +54,29 @@ app.get('/', (req, res) => {
});
// Route for POST requests
-app.post('/', (req, res) => {
+app.post('/', async (req, res) => {
const timestamp = new Date().toISOString().replace('T', ' ').slice(0, 19);
console.log(`\n\nWebhook received ${timestamp}\n`);
console.log(JSON.stringify(req.body, null, 2));
+
+ // Acknowledge immediately so WhatsApp doesn't retry
res.status(200).end();
+
+ try {
+ const messages = req.body?.entry?.[0]?.changes?.[0]?.value?.messages;
+ if (!messages?.length) return;
+
+ for (const message of messages) {
+ const text = message?.text?.body ?? '';
+ if (text.toLowerCase().includes('update the discount')) {
+ console.log(`Sending discount image to ${message.from}`);
+ await sendImageMessage(message.from);
+ console.log(`Discount image sent to ${message.from}`);
+ }
+ }
+ } catch (err) {
+ console.error('Error handling message:', err.message);
+ }
});
// Start the server
diff --git a/render.yaml b/render.yaml
index 9583d53df2..5a4e5ac55d 100644
--- a/render.yaml
+++ b/render.yaml
@@ -8,3 +8,9 @@ services:
envVars:
- key: NODE_ENV
value: production
+ - key: VERIFY_TOKEN
+ sync: false
+ - key: WHATSAPP_PHONE_NUMBER_ID
+ sync: false
+ - key: WHATSAPP_TOKEN
+ sync: false
From 4a26ed66c7fb06a023069c5718f6fab58415d1bb Mon Sep 17 00:00:00 2001
From: priyankmodiPM <32540484+priyankmodiPM@users.noreply.github.com>
Date: Mon, 6 Jul 2026 18:20:33 +0530
Subject: [PATCH 03/20] feat: replace static discount auto-reply with GPT
tool-calling assistant (#1)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Adds a GPT decision loop (with per-phone conversation history) that
routes inbound WhatsApp messages to campaign-graphic actions —
listing graphics, checking allowed edits, editing via Adobe Express,
and bulk generation from an uploaded file — instead of only replying
to a hardcoded "update the discount" phrase.
Co-authored-by: Priyank Modi
---
app.js | 218 ++++++++++--
package-lock.json | 881 ++++++++++++++++++++++++++++++++++++++++++++++
package.json | 3 +-
yarn.lock | 249 +++++++------
4 files changed, 1196 insertions(+), 155 deletions(-)
create mode 100644 package-lock.json
diff --git a/app.js b/app.js
index 356f1ddf80..0eb789d47d 100644
--- a/app.js
+++ b/app.js
@@ -1,29 +1,34 @@
-// Import Express.js
const express = require('express');
+const OpenAI = require('openai');
-// Create an Express app
const app = express();
-
-// Middleware to parse JSON bodies
app.use(express.json());
-// Set port and verify_token
const port = process.env.PORT || 3000;
const verifyToken = process.env.VERIFY_TOKEN;
const whatsappPhoneNumberId = process.env.WHATSAPP_PHONE_NUMBER_ID;
const whatsappToken = process.env.WHATSAPP_TOKEN;
-const DISCOUNT_IMAGE_URL = 'https://s7ap1.scene7.com/is/image/varun/diwali-offer2-updated';
+const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
-async function sendImageMessage(to) {
- const url = `https://graph.facebook.com/v19.0/${whatsappPhoneNumberId}/messages`;
- const body = {
- messaging_product: 'whatsapp',
- to,
- type: 'image',
- image: { link: DISCOUNT_IMAGE_URL },
- };
+// In-memory conversation history per phone number (last 20 messages kept)
+const conversationHistory = new Map();
+
+function getHistory(phoneNumber) {
+ return conversationHistory.get(phoneNumber) || [];
+}
+
+function appendHistory(phoneNumber, role, content) {
+ const history = conversationHistory.get(phoneNumber) || [];
+ history.push({ role, content });
+ if (history.length > 20) history.shift();
+ conversationHistory.set(phoneNumber, history);
+}
+
+// ── WhatsApp helpers ─────────────────────────────────────────────────────────
+async function whatsappPost(body) {
+ const url = `https://graph.facebook.com/v19.0/${whatsappPhoneNumberId}/messages`;
const response = await fetch(url, {
method: 'POST',
headers: {
@@ -32,19 +37,140 @@ async function sendImageMessage(to) {
},
body: JSON.stringify(body),
});
-
if (!response.ok) {
const text = await response.text();
throw new Error(`WhatsApp API error ${response.status}: ${text}`);
}
-
return response.json();
}
-// Route for GET requests
+function sendText(to, text) {
+ return whatsappPost({ messaging_product: 'whatsapp', to, type: 'text', text: { body: text } });
+}
+
+function sendImage(to, link) {
+ return whatsappPost({ messaging_product: 'whatsapp', to, type: 'image', image: { link } });
+}
+
+// ── GPT tool definitions ─────────────────────────────────────────────────────
+
+const tools = [
+ {
+ type: 'function',
+ function: {
+ name: 'list_campaign_graphics',
+ description: 'List all graphics available in the current campaign',
+ parameters: { type: 'object', properties: {} },
+ },
+ },
+ {
+ type: 'function',
+ function: {
+ name: 'ask_for_more_information',
+ description: 'Ask the user a clarifying question when the request is ambiguous or incomplete',
+ parameters: {
+ type: 'object',
+ properties: {
+ question: { type: 'string', description: 'The clarifying question to send to the user' },
+ },
+ required: ['question'],
+ },
+ },
+ },
+ {
+ type: 'function',
+ function: {
+ name: 'check_allowed_edits',
+ description: 'Check what edits are permitted on the current graphic',
+ parameters: { type: 'object', properties: {} },
+ },
+ },
+ {
+ type: 'function',
+ function: {
+ name: 'edit_graphic',
+ description: 'Edit the current graphic via Adobe Express API (e.g. change discount text, colors)',
+ parameters: {
+ type: 'object',
+ properties: {
+ edits: {
+ type: 'object',
+ description: 'Key-value pairs of edits to apply, e.g. { "discount_text": "70%" }',
+ },
+ },
+ required: ['edits'],
+ },
+ },
+ },
+ {
+ type: 'function',
+ function: {
+ name: 'generate_bulk_graphics',
+ description: 'Generate multiple graphics from an uploaded CSV or Excel file',
+ parameters: {
+ type: 'object',
+ properties: {
+ filename: { type: 'string', description: 'Name of the uploaded CSV or Excel file' },
+ },
+ },
+ },
+ },
+];
+
+// ── Action handlers (stubs — wire real APIs here) ────────────────────────────
+
+async function actionListCampaignGraphics() {
+ // TODO: fetch from campaign API
+ return 'Graphics in your current campaign:\n1. Diwali Offer Banner\n2. Summer Sale Flyer\n3. New Arrival Poster';
+}
+
+async function actionCheckAllowedEdits() {
+ // TODO: fetch from Adobe Express API
+ return 'Edits allowed on the current graphic:\n- Discount percentage\n- Headline text\n- Background color\n- Font color';
+}
+
+async function actionEditGraphic(edits) {
+ // TODO: call Adobe Express API
+ const summary = Object.entries(edits).map(([k, v]) => `• ${k}: ${v}`).join('\n');
+ return `Graphic updated successfully:\n${summary}`;
+}
+
+async function actionGenerateBulkGraphics(filename) {
+ // TODO: parse CSV/Excel and call Adobe Express API per row
+ return `Bulk generation complete! Graphics created from ${filename || 'your uploaded file'}.`;
+}
+
+// ── GPT decision engine ──────────────────────────────────────────────────────
+
+async function decideAction(phoneNumber, userMessage) {
+ const last3 = getHistory(phoneNumber).slice(-3);
+
+ const messages = [
+ {
+ role: 'system',
+ content: `You are a WhatsApp assistant for managing marketing campaign graphics via Adobe Express.
+Analyze the user's message and conversation history, then call the appropriate tool.
+Always call exactly one tool — never reply with plain text.
+If the request is ambiguous or missing details, use ask_for_more_information.`,
+ },
+ ...last3,
+ { role: 'user', content: userMessage },
+ ];
+
+ const response = await openai.chat.completions.create({
+ model: 'gpt-4o',
+ messages,
+ tools,
+ tool_choice: 'required',
+ });
+
+ return response.choices[0].message;
+}
+
+// ── Webhook routes ───────────────────────────────────────────────────────────
+
app.get('/', (req, res) => {
const { 'hub.mode': mode, 'hub.challenge': challenge, 'hub.verify_token': token } = req.query;
-
if (mode === 'subscribe' && token === verifyToken) {
console.log('WEBHOOK VERIFIED');
res.status(200).send(challenge);
@@ -53,13 +179,11 @@ app.get('/', (req, res) => {
}
});
-// Route for POST requests
app.post('/', async (req, res) => {
const timestamp = new Date().toISOString().replace('T', ' ').slice(0, 19);
console.log(`\n\nWebhook received ${timestamp}\n`);
console.log(JSON.stringify(req.body, null, 2));
- // Acknowledge immediately so WhatsApp doesn't retry
res.status(200).end();
try {
@@ -67,19 +191,61 @@ app.post('/', async (req, res) => {
if (!messages?.length) return;
for (const message of messages) {
- const text = message?.text?.body ?? '';
- if (text.toLowerCase().includes('update the discount')) {
- console.log(`Sending discount image to ${message.from}`);
- await sendImageMessage(message.from);
- console.log(`Discount image sent to ${message.from}`);
+ const userText = message?.text?.body;
+ if (!userText) continue;
+
+ const phoneNumber = message.from;
+ console.log(`Message from ${phoneNumber}: ${userText}`);
+
+ appendHistory(phoneNumber, 'user', userText);
+
+ const gptMessage = await decideAction(phoneNumber, userText);
+ const toolCall = gptMessage.tool_calls?.[0];
+ if (!toolCall) continue;
+
+ const action = toolCall.function.name;
+ const args = JSON.parse(toolCall.function.arguments || '{}');
+ console.log(`GPT chose action: ${action}`, args);
+
+ let replyText;
+
+ switch (action) {
+ case 'list_campaign_graphics':
+ await sendText(phoneNumber, '⏳ Fetching campaign graphics...');
+ replyText = await actionListCampaignGraphics();
+ break;
+
+ case 'ask_for_more_information':
+ replyText = args.question;
+ break;
+
+ case 'check_allowed_edits':
+ await sendText(phoneNumber, '⏳ Checking allowed edits...');
+ replyText = await actionCheckAllowedEdits();
+ break;
+
+ case 'edit_graphic':
+ await sendText(phoneNumber, '⏳ Applying edits to your graphic...');
+ replyText = await actionEditGraphic(args.edits);
+ break;
+
+ case 'generate_bulk_graphics':
+ await sendText(phoneNumber, '⏳ Generating graphics from your file, this may take a moment...');
+ replyText = await actionGenerateBulkGraphics(args.filename);
+ break;
+
+ default:
+ replyText = "Sorry, I couldn't figure out how to handle that request.";
}
+
+ await sendText(phoneNumber, replyText);
+ appendHistory(phoneNumber, 'assistant', replyText);
}
} catch (err) {
console.error('Error handling message:', err.message);
}
});
-// Start the server
app.listen(port, () => {
console.log(`\nListening on port ${port}\n`);
});
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000000..05cf00a448
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,881 @@
+{
+ "name": "express-hello-world",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "express-hello-world",
+ "version": "1.0.0",
+ "license": "MIT",
+ "dependencies": {
+ "express": "^5.0.0",
+ "openai": "^6.45.0"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "dependencies": {
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/array-flatten": {
+ "version": "3.0.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/array-flatten/-/array-flatten-3.0.0.tgz",
+ "integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA=="
+ },
+ "node_modules/body-parser": {
+ "version": "2.0.1",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/body-parser/-/body-parser-2.0.1.tgz",
+ "integrity": "sha512-PagxbjvuPH6tv0f/kdVbFGcb79D236SLcDTs6DrQ7GizJ88S1UWP4nMXFEo/I4fdhGRGabvFfFjVGm3M7U8JwA==",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "content-type": "~1.0.5",
+ "debug": "3.1.0",
+ "destroy": "1.2.0",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.5.2",
+ "on-finished": "2.4.1",
+ "qs": "6.13.0",
+ "raw-body": "^3.0.0",
+ "type-is": "~1.6.18",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/body-parser/node_modules/debug": {
+ "version": "3.1.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/body-parser/node_modules/media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/body-parser/node_modules/mime-db": {
+ "version": "1.40.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/mime-db/-/mime-db-1.40.0.tgz",
+ "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/body-parser/node_modules/mime-types": {
+ "version": "2.1.24",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/mime-types/-/mime-types-2.1.24.tgz",
+ "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
+ "dependencies": {
+ "mime-db": "1.40.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/body-parser/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ },
+ "node_modules/body-parser/node_modules/type-is": {
+ "version": "1.6.18",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.7",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/call-bind/-/call-bind-1.0.7.tgz",
+ "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "set-function-length": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "1.0.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/content-disposition/-/content-disposition-1.0.0.tgz",
+ "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.6.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.2.1",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/cookie-signature/-/cookie-signature-1.2.1.tgz",
+ "integrity": "sha512-78KWk9T26NhzXtuL26cIJ8/qNHANyJ/ZYrmEXFzUmhZdjpBv+DlWlOANRTGBt48YcyslsLrj0bMLFTmXvLRCOw==",
+ "engines": {
+ "node": ">=6.6.0"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.3.6",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/debug/-/debug-4.3.6.tgz",
+ "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/define-data-property": {
+ "version": "1.1.4",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/define-data-property/-/define-data-property-1.1.4.tgz",
+ "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/es-define-property/-/es-define-property-1.0.0.tgz",
+ "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
+ "dependencies": {
+ "get-intrinsic": "^1.2.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express": {
+ "version": "5.0.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/express/-/express-5.0.0.tgz",
+ "integrity": "sha512-V4UkHQc+B7ldh1YC84HCXHwf60M4BOMvp9rkvTUWCK5apqDC1Esnbid4wm6nFyVuDy8XMfETsJw5lsIGBWyo0A==",
+ "dependencies": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.0.1",
+ "content-disposition": "^1.0.0",
+ "content-type": "~1.0.4",
+ "cookie": "0.6.0",
+ "cookie-signature": "^1.2.1",
+ "debug": "4.3.6",
+ "depd": "2.0.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "^2.0.0",
+ "fresh": "2.0.0",
+ "http-errors": "2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "methods": "~1.1.2",
+ "mime-types": "^3.0.0",
+ "on-finished": "2.4.1",
+ "once": "1.4.0",
+ "parseurl": "~1.3.3",
+ "proxy-addr": "~2.0.7",
+ "qs": "6.13.0",
+ "range-parser": "~1.2.1",
+ "router": "^2.0.0",
+ "safe-buffer": "5.2.1",
+ "send": "^1.1.0",
+ "serve-static": "^2.1.0",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "type-is": "^2.0.0",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "2.0.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/finalhandler/-/finalhandler-2.0.0.tgz",
+ "integrity": "sha512-MX6Zo2adDViYh+GcxxB1dpO43eypOGUOL12rLCOTMQv/DfIbpSJUy4oQIIZhVZkH9e+bZWKMon0XHFEju16tkQ==",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "2.0.1",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/finalhandler/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/finalhandler/node_modules/encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/finalhandler/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "2.0.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.2.4",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
+ "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3",
+ "hasown": "^2.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.0.1",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/gopd/-/gopd-1.0.1.tgz",
+ "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
+ "dependencies": {
+ "get-intrinsic": "^1.1.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.2",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+ "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+ "dependencies": {
+ "es-define-property": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-proto": {
+ "version": "1.0.3",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/has-proto/-/has-proto-1.0.3.tgz",
+ "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.0.3",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/has-symbols/-/has-symbols-1.0.3.tgz",
+ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "dependencies": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.5.2",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/iconv-lite/-/iconv-lite-0.5.2.tgz",
+ "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/is-promise": {
+ "version": "4.0.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="
+ },
+ "node_modules/media-typer": {
+ "version": "1.1.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/media-typer/-/media-typer-1.1.0.tgz",
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "2.0.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.53.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/mime-db/-/mime-db-1.53.0.tgz",
+ "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "3.0.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/mime-types/-/mime-types-3.0.0.tgz",
+ "integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==",
+ "dependencies": {
+ "mime-db": "^1.53.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "node_modules/negotiator": {
+ "version": "1.0.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.2",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/object-inspect/-/object-inspect-1.13.2.tgz",
+ "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/openai": {
+ "version": "6.45.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/openai/-/openai-6.45.0.tgz",
+ "integrity": "sha512-5DQVNErssk0afNpTTHUm/qZPU4iKR9OYdNid8Ib4puq4gHNNvGWZht2zY4h9a8JMF949Ik6m8gQutllVPbjdnw==",
+ "peerDependencies": {
+ "@aws-sdk/credential-provider-node": ">=3.972.0 <4",
+ "@smithy/hash-node": ">=4.3.0 <5",
+ "@smithy/signature-v4": ">=5.4.0 <6",
+ "ws": "^8.18.0",
+ "zod": "^3.25 || ^4.0"
+ },
+ "peerDependenciesMeta": {
+ "@aws-sdk/credential-provider-node": {
+ "optional": true
+ },
+ "@smithy/hash-node": {
+ "optional": true
+ },
+ "@smithy/signature-v4": {
+ "optional": true
+ },
+ "ws": {
+ "optional": true
+ },
+ "zod": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "8.1.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/path-to-regexp/-/path-to-regexp-8.1.0.tgz",
+ "integrity": "sha512-Bqn3vc8CMHty6zuD+tG23s6v2kwxslHEhTj4eYaVKGIEB+YX/2wd0/rgXLFD9G9id9KCtbVy/3ZgmvZjpa0UdQ==",
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.13.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/qs/-/qs-6.13.0.tgz",
+ "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
+ "dependencies": {
+ "side-channel": "^1.0.6"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "3.0.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/raw-body/-/raw-body-3.0.0.tgz",
+ "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.6.3",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/raw-body/node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/router": {
+ "version": "2.0.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/router/-/router-2.0.0.tgz",
+ "integrity": "sha512-dIM5zVoG8xhC6rnSN8uoAgFARwTE7BQs8YwHEvK0VCmfxQXMaOuA1uiR1IPwsW7JyK5iTt7Od/TC9StasS2NPQ==",
+ "dependencies": {
+ "array-flatten": "3.0.0",
+ "is-promise": "4.0.0",
+ "methods": "~1.1.2",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "^8.0.0",
+ "setprototypeof": "1.2.0",
+ "utils-merge": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+ },
+ "node_modules/send": {
+ "version": "1.1.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/send/-/send-1.1.0.tgz",
+ "integrity": "sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==",
+ "dependencies": {
+ "debug": "^4.3.5",
+ "destroy": "^1.2.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^0.5.2",
+ "http-errors": "^2.0.0",
+ "mime-types": "^2.1.35",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/send/node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/send/node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/send/node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/send/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ },
+ "node_modules/serve-static": {
+ "version": "2.1.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/serve-static/-/serve-static-2.1.0.tgz",
+ "integrity": "sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA==",
+ "dependencies": {
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/set-function-length": {
+ "version": "1.2.2",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/set-function-length/-/set-function-length-1.2.2.tgz",
+ "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "gopd": "^1.0.1",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
+ },
+ "node_modules/side-channel": {
+ "version": "1.0.6",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/side-channel/-/side-channel-1.0.6.tgz",
+ "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.4",
+ "object-inspect": "^1.13.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.1",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "2.0.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/type-is/-/type-is-2.0.0.tgz",
+ "integrity": "sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==",
+ "dependencies": {
+ "content-type": "^1.0.5",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
+ }
+ }
+}
diff --git a/package.json b/package.json
index 43954fe280..567801e154 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,7 @@
"start": "node app.js"
},
"dependencies": {
- "express": "^5.0.0"
+ "express": "^5.0.0",
+ "openai": "^6.45.0"
}
}
diff --git a/yarn.lock b/yarn.lock
index 35c2bede4d..f4940dc9e4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4,7 +4,7 @@
accepts@^2.0.0:
version "2.0.0"
- resolved "https://registry.yarnpkg.com/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/accepts/-/accepts-2.0.0.tgz"
integrity sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==
dependencies:
mime-types "^3.0.0"
@@ -12,12 +12,12 @@ accepts@^2.0.0:
array-flatten@3.0.0:
version "3.0.0"
- resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-3.0.0.tgz#6428ca2ee52c7b823192ec600fa3ed2f157cd541"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/array-flatten/-/array-flatten-3.0.0.tgz"
integrity sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==
body-parser@^2.0.1:
version "2.0.1"
- resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.0.1.tgz#979de4a43468c5624403457fd6d45f797faffbaf"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/body-parser/-/body-parser-2.0.1.tgz"
integrity sha512-PagxbjvuPH6tv0f/kdVbFGcb79D236SLcDTs6DrQ7GizJ88S1UWP4nMXFEo/I4fdhGRGabvFfFjVGm3M7U8JwA==
dependencies:
bytes "3.1.2"
@@ -34,12 +34,12 @@ body-parser@^2.0.1:
bytes@3.1.2:
version "3.1.2"
- resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/bytes/-/bytes-3.1.2.tgz"
integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
call-bind@^1.0.7:
version "1.0.7"
- resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/call-bind/-/call-bind-1.0.7.tgz"
integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==
dependencies:
es-define-property "^1.0.0"
@@ -50,62 +50,50 @@ call-bind@^1.0.7:
content-disposition@^1.0.0:
version "1.0.0"
- resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-1.0.0.tgz#844426cb398f934caefcbb172200126bc7ceace2"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/content-disposition/-/content-disposition-1.0.0.tgz"
integrity sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==
dependencies:
safe-buffer "5.2.1"
-content-type@^1.0.5, content-type@~1.0.5:
+content-type@^1.0.5, content-type@~1.0.4, content-type@~1.0.5:
version "1.0.5"
- resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/content-type/-/content-type-1.0.5.tgz"
integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
-content-type@~1.0.4:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
- integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
-
cookie-signature@^1.2.1:
version "1.2.1"
- resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.1.tgz#790dea2cce64638c7ae04d9fabed193bd7ccf3b4"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/cookie-signature/-/cookie-signature-1.2.1.tgz"
integrity sha512-78KWk9T26NhzXtuL26cIJ8/qNHANyJ/ZYrmEXFzUmhZdjpBv+DlWlOANRTGBt48YcyslsLrj0bMLFTmXvLRCOw==
cookie@0.6.0:
version "0.6.0"
- resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/cookie/-/cookie-0.6.0.tgz"
integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==
+debug@^4.3.5, debug@4.3.6:
+ version "4.3.6"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/debug/-/debug-4.3.6.tgz"
+ integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==
+ dependencies:
+ ms "2.1.2"
+
debug@2.6.9:
version "2.6.9"
- resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/debug/-/debug-2.6.9.tgz"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
dependencies:
ms "2.0.0"
debug@3.1.0:
version "3.1.0"
- resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/debug/-/debug-3.1.0.tgz"
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
dependencies:
ms "2.0.0"
-debug@4.3.6:
- version "4.3.6"
- resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b"
- integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==
- dependencies:
- ms "2.1.2"
-
-debug@^4.3.5:
- version "4.3.7"
- resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"
- integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
- dependencies:
- ms "^2.1.3"
-
define-data-property@^1.1.4:
version "1.1.4"
- resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/define-data-property/-/define-data-property-1.1.4.tgz"
integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==
dependencies:
es-define-property "^1.0.0"
@@ -114,54 +102,54 @@ define-data-property@^1.1.4:
depd@2.0.0:
version "2.0.0"
- resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/depd/-/depd-2.0.0.tgz"
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
-destroy@1.2.0, destroy@^1.2.0:
+destroy@^1.2.0, destroy@1.2.0:
version "1.2.0"
- resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/destroy/-/destroy-1.2.0.tgz"
integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
ee-first@1.1.1:
version "1.1.1"
- resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
- integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/ee-first/-/ee-first-1.1.1.tgz"
+ integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
encodeurl@^2.0.0, encodeurl@~2.0.0:
version "2.0.0"
- resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/encodeurl/-/encodeurl-2.0.0.tgz"
integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==
encodeurl@~1.0.2:
version "1.0.2"
- resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
- integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/encodeurl/-/encodeurl-1.0.2.tgz"
+ integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
es-define-property@^1.0.0:
version "1.0.0"
- resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/es-define-property/-/es-define-property-1.0.0.tgz"
integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==
dependencies:
get-intrinsic "^1.2.4"
es-errors@^1.3.0:
version "1.3.0"
- resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/es-errors/-/es-errors-1.3.0.tgz"
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
escape-html@^1.0.3, escape-html@~1.0.3:
version "1.0.3"
- resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
- integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/escape-html/-/escape-html-1.0.3.tgz"
+ integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
etag@^1.8.1, etag@~1.8.1:
version "1.8.1"
- resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
- integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/etag/-/etag-1.8.1.tgz"
+ integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
express@^5.0.0:
version "5.0.0"
- resolved "https://registry.yarnpkg.com/express/-/express-5.0.0.tgz#744f9ec86025a01aeca99e4300aa4fc050d493c7"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/express/-/express-5.0.0.tgz"
integrity sha512-V4UkHQc+B7ldh1YC84HCXHwf60M4BOMvp9rkvTUWCK5apqDC1Esnbid4wm6nFyVuDy8XMfETsJw5lsIGBWyo0A==
dependencies:
accepts "^2.0.0"
@@ -199,7 +187,7 @@ express@^5.0.0:
finalhandler@^2.0.0:
version "2.0.0"
- resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-2.0.0.tgz#9d3c79156dfa798069db7de7dd53bc37546f564b"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/finalhandler/-/finalhandler-2.0.0.tgz"
integrity sha512-MX6Zo2adDViYh+GcxxB1dpO43eypOGUOL12rLCOTMQv/DfIbpSJUy4oQIIZhVZkH9e+bZWKMon0XHFEju16tkQ==
dependencies:
debug "2.6.9"
@@ -212,27 +200,27 @@ finalhandler@^2.0.0:
forwarded@0.2.0:
version "0.2.0"
- resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/forwarded/-/forwarded-0.2.0.tgz"
integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
-fresh@2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/fresh/-/fresh-2.0.0.tgz#8dd7df6a1b3a1b3a5cf186c05a5dd267622635a4"
- integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==
-
fresh@^0.5.2:
version "0.5.2"
- resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/fresh/-/fresh-0.5.2.tgz"
integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
+fresh@2.0.0:
+ version "2.0.0"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/fresh/-/fresh-2.0.0.tgz"
+ integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==
+
function-bind@^1.1.2:
version "1.1.2"
- resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/function-bind/-/function-bind-1.1.2.tgz"
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
get-intrinsic@^1.1.3, get-intrinsic@^1.2.4:
version "1.2.4"
- resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/get-intrinsic/-/get-intrinsic-1.2.4.tgz"
integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==
dependencies:
es-errors "^1.3.0"
@@ -243,38 +231,38 @@ get-intrinsic@^1.1.3, get-intrinsic@^1.2.4:
gopd@^1.0.1:
version "1.0.1"
- resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/gopd/-/gopd-1.0.1.tgz"
integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==
dependencies:
get-intrinsic "^1.1.3"
has-property-descriptors@^1.0.2:
version "1.0.2"
- resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz"
integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==
dependencies:
es-define-property "^1.0.0"
has-proto@^1.0.1:
version "1.0.3"
- resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/has-proto/-/has-proto-1.0.3.tgz"
integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==
has-symbols@^1.0.3:
version "1.0.3"
- resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/has-symbols/-/has-symbols-1.0.3.tgz"
integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
hasown@^2.0.0:
version "2.0.2"
- resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/hasown/-/hasown-2.0.2.tgz"
integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
dependencies:
function-bind "^1.1.2"
-http-errors@2.0.0, http-errors@^2.0.0:
+http-errors@^2.0.0, http-errors@2.0.0:
version "2.0.0"
- resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/http-errors/-/http-errors-2.0.0.tgz"
integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==
dependencies:
depd "2.0.0"
@@ -285,141 +273,146 @@ http-errors@2.0.0, http-errors@^2.0.0:
iconv-lite@0.5.2:
version "0.5.2"
- resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.5.2.tgz#af6d628dccfb463b7364d97f715e4b74b8c8c2b8"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/iconv-lite/-/iconv-lite-0.5.2.tgz"
integrity sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==
dependencies:
safer-buffer ">= 2.1.2 < 3"
iconv-lite@0.6.3:
version "0.6.3"
- resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/iconv-lite/-/iconv-lite-0.6.3.tgz"
integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
inherits@2.0.4:
version "2.0.4"
- resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/inherits/-/inherits-2.0.4.tgz"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
ipaddr.js@1.9.1:
version "1.9.1"
- resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/ipaddr.js/-/ipaddr.js-1.9.1.tgz"
integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
is-promise@4.0.0:
version "4.0.0"
- resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/is-promise/-/is-promise-4.0.0.tgz"
integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==
-media-typer@0.3.0:
- version "0.3.0"
- resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
- integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
-
media-typer@^1.1.0:
version "1.1.0"
- resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-1.1.0.tgz#6ab74b8f2d3320f2064b2a87a38e7931ff3a5561"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/media-typer/-/media-typer-1.1.0.tgz"
integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==
+media-typer@0.3.0:
+ version "0.3.0"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/media-typer/-/media-typer-0.3.0.tgz"
+ integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
+
merge-descriptors@^2.0.0:
version "2.0.0"
- resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz#ea922f660635a2249ee565e0449f951e6b603808"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/merge-descriptors/-/merge-descriptors-2.0.0.tgz"
integrity sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==
methods@~1.1.2:
version "1.1.2"
- resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
- integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/methods/-/methods-1.1.2.tgz"
+ integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==
+
+mime-db@^1.53.0:
+ version "1.53.0"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/mime-db/-/mime-db-1.53.0.tgz"
+ integrity sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==
mime-db@1.40.0:
version "1.40.0"
- resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/mime-db/-/mime-db-1.40.0.tgz"
integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==
mime-db@1.52.0:
version "1.52.0"
- resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/mime-db/-/mime-db-1.52.0.tgz"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
-mime-db@^1.53.0:
- version "1.53.0"
- resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.53.0.tgz#3cb63cd820fc29896d9d4e8c32ab4fcd74ccb447"
- integrity sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==
-
mime-types@^2.1.35:
version "2.1.35"
- resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/mime-types/-/mime-types-2.1.35.tgz"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
dependencies:
mime-db "1.52.0"
mime-types@^3.0.0:
version "3.0.0"
- resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-3.0.0.tgz#148453a900475522d095a445355c074cca4f5217"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/mime-types/-/mime-types-3.0.0.tgz"
integrity sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==
dependencies:
mime-db "^1.53.0"
mime-types@~2.1.24:
version "2.1.24"
- resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/mime-types/-/mime-types-2.1.24.tgz"
integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==
dependencies:
mime-db "1.40.0"
+ms@^2.1.3:
+ version "2.1.3"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/ms/-/ms-2.1.3.tgz"
+ integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
+
ms@2.0.0:
version "2.0.0"
- resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
- integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/ms/-/ms-2.0.0.tgz"
+ integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
ms@2.1.2:
version "2.1.2"
- resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/ms/-/ms-2.1.2.tgz"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
-ms@^2.1.3:
- version "2.1.3"
- resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
- integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
-
negotiator@^1.0.0:
version "1.0.0"
- resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/negotiator/-/negotiator-1.0.0.tgz"
integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==
object-inspect@^1.13.1:
version "1.13.2"
- resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/object-inspect/-/object-inspect-1.13.2.tgz"
integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==
-on-finished@2.4.1, on-finished@^2.4.1:
+on-finished@^2.4.1, on-finished@2.4.1:
version "2.4.1"
- resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/on-finished/-/on-finished-2.4.1.tgz"
integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
dependencies:
ee-first "1.1.1"
once@1.4.0:
version "1.4.0"
- resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/once/-/once-1.4.0.tgz"
integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
dependencies:
wrappy "1"
+openai@^6.45.0:
+ version "6.45.0"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/openai/-/openai-6.45.0.tgz"
+ integrity sha512-5DQVNErssk0afNpTTHUm/qZPU4iKR9OYdNid8Ib4puq4gHNNvGWZht2zY4h9a8JMF949Ik6m8gQutllVPbjdnw==
+
parseurl@^1.3.3, parseurl@~1.3.3:
version "1.3.3"
- resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/parseurl/-/parseurl-1.3.3.tgz"
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
path-to-regexp@^8.0.0:
version "8.1.0"
- resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.1.0.tgz#4d687606ed0be8ed512ba802eb94d620cb1a86f0"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/path-to-regexp/-/path-to-regexp-8.1.0.tgz"
integrity sha512-Bqn3vc8CMHty6zuD+tG23s6v2kwxslHEhTj4eYaVKGIEB+YX/2wd0/rgXLFD9G9id9KCtbVy/3ZgmvZjpa0UdQ==
proxy-addr@~2.0.7:
version "2.0.7"
- resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/proxy-addr/-/proxy-addr-2.0.7.tgz"
integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==
dependencies:
forwarded "0.2.0"
@@ -427,19 +420,19 @@ proxy-addr@~2.0.7:
qs@6.13.0:
version "6.13.0"
- resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/qs/-/qs-6.13.0.tgz"
integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==
dependencies:
side-channel "^1.0.6"
range-parser@^1.2.1, range-parser@~1.2.1:
version "1.2.1"
- resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/range-parser/-/range-parser-1.2.1.tgz"
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
raw-body@^3.0.0:
version "3.0.0"
- resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-3.0.0.tgz#25b3476f07a51600619dae3fe82ddc28a36e5e0f"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/raw-body/-/raw-body-3.0.0.tgz"
integrity sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==
dependencies:
bytes "3.1.2"
@@ -449,7 +442,7 @@ raw-body@^3.0.0:
router@^2.0.0:
version "2.0.0"
- resolved "https://registry.yarnpkg.com/router/-/router-2.0.0.tgz#8692720b95de83876870d7bc638dd3c7e1ae8a27"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/router/-/router-2.0.0.tgz"
integrity sha512-dIM5zVoG8xhC6rnSN8uoAgFARwTE7BQs8YwHEvK0VCmfxQXMaOuA1uiR1IPwsW7JyK5iTt7Od/TC9StasS2NPQ==
dependencies:
array-flatten "3.0.0"
@@ -462,17 +455,17 @@ router@^2.0.0:
safe-buffer@5.2.1:
version "5.2.1"
- resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/safe-buffer/-/safe-buffer-5.2.1.tgz"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0":
version "2.1.2"
- resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/safer-buffer/-/safer-buffer-2.1.2.tgz"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
send@^1.0.0, send@^1.1.0:
version "1.1.0"
- resolved "https://registry.yarnpkg.com/send/-/send-1.1.0.tgz#4efe6ff3bb2139b0e5b2648d8b18d4dec48fc9c5"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/send/-/send-1.1.0.tgz"
integrity sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==
dependencies:
debug "^4.3.5"
@@ -490,7 +483,7 @@ send@^1.0.0, send@^1.1.0:
serve-static@^2.1.0:
version "2.1.0"
- resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-2.1.0.tgz#1b4eacbe93006b79054faa4d6d0a501d7f0e84e2"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/serve-static/-/serve-static-2.1.0.tgz"
integrity sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA==
dependencies:
encodeurl "^2.0.0"
@@ -500,7 +493,7 @@ serve-static@^2.1.0:
set-function-length@^1.2.1:
version "1.2.2"
- resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/set-function-length/-/set-function-length-1.2.2.tgz"
integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==
dependencies:
define-data-property "^1.1.4"
@@ -512,12 +505,12 @@ set-function-length@^1.2.1:
setprototypeof@1.2.0:
version "1.2.0"
- resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/setprototypeof/-/setprototypeof-1.2.0.tgz"
integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
side-channel@^1.0.6:
version "1.0.6"
- resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/side-channel/-/side-channel-1.0.6.tgz"
integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==
dependencies:
call-bind "^1.0.7"
@@ -525,19 +518,19 @@ side-channel@^1.0.6:
get-intrinsic "^1.2.4"
object-inspect "^1.13.1"
-statuses@2.0.1, statuses@^2.0.1:
+statuses@^2.0.1, statuses@2.0.1:
version "2.0.1"
- resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/statuses/-/statuses-2.0.1.tgz"
integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
toidentifier@1.0.1:
version "1.0.1"
- resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/toidentifier/-/toidentifier-1.0.1.tgz"
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
type-is@^2.0.0:
version "2.0.0"
- resolved "https://registry.yarnpkg.com/type-is/-/type-is-2.0.0.tgz#7d249c2e2af716665cc149575dadb8b3858653af"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/type-is/-/type-is-2.0.0.tgz"
integrity sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==
dependencies:
content-type "^1.0.5"
@@ -546,28 +539,28 @@ type-is@^2.0.0:
type-is@~1.6.18:
version "1.6.18"
- resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/type-is/-/type-is-1.6.18.tgz"
integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
dependencies:
media-typer "0.3.0"
mime-types "~2.1.24"
-unpipe@1.0.0, unpipe@~1.0.0:
+unpipe@~1.0.0, unpipe@1.0.0:
version "1.0.0"
- resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
- integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/unpipe/-/unpipe-1.0.0.tgz"
+ integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
utils-merge@1.0.1:
version "1.0.1"
- resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
- integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/utils-merge/-/utils-merge-1.0.1.tgz"
+ integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
vary@~1.1.2:
version "1.1.2"
- resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
- integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/vary/-/vary-1.1.2.tgz"
+ integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
wrappy@1:
version "1.0.2"
- resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+ resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/wrappy/-/wrappy-1.0.2.tgz"
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
From b1ed8d852219a647563fe745d217545717439205 Mon Sep 17 00:00:00 2001
From: priyankmodiPM <32540484+priyankmodiPM@users.noreply.github.com>
Date: Mon, 6 Jul 2026 18:45:36 +0530
Subject: [PATCH 04/20] fix: regenerate lockfiles against the public npm
registry (#2)
Co-authored-by: Priyank Modi
---
yarn.lock | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/yarn.lock b/yarn.lock
index f4940dc9e4..50df762342 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -113,7 +113,7 @@ destroy@^1.2.0, destroy@1.2.0:
ee-first@1.1.1:
version "1.1.1"
resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/ee-first/-/ee-first-1.1.1.tgz"
- integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
+ integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
encodeurl@^2.0.0, encodeurl@~2.0.0:
version "2.0.0"
@@ -123,7 +123,7 @@ encodeurl@^2.0.0, encodeurl@~2.0.0:
encodeurl@~1.0.2:
version "1.0.2"
resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/encodeurl/-/encodeurl-1.0.2.tgz"
- integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
+ integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
es-define-property@^1.0.0:
version "1.0.0"
@@ -140,12 +140,12 @@ es-errors@^1.3.0:
escape-html@^1.0.3, escape-html@~1.0.3:
version "1.0.3"
resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/escape-html/-/escape-html-1.0.3.tgz"
- integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
+ integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
etag@^1.8.1, etag@~1.8.1:
version "1.8.1"
resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/etag/-/etag-1.8.1.tgz"
- integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
+ integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
express@^5.0.0:
version "5.0.0"
@@ -308,7 +308,7 @@ media-typer@^1.1.0:
media-typer@0.3.0:
version "0.3.0"
resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/media-typer/-/media-typer-0.3.0.tgz"
- integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
+ integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
merge-descriptors@^2.0.0:
version "2.0.0"
@@ -318,7 +318,7 @@ merge-descriptors@^2.0.0:
methods@~1.1.2:
version "1.1.2"
resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/methods/-/methods-1.1.2.tgz"
- integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==
+ integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==
mime-db@^1.53.0:
version "1.53.0"
@@ -364,7 +364,7 @@ ms@^2.1.3:
ms@2.0.0:
version "2.0.0"
resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/ms/-/ms-2.0.0.tgz"
- integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
+ integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
ms@2.1.2:
version "2.1.2"
@@ -548,17 +548,17 @@ type-is@~1.6.18:
unpipe@~1.0.0, unpipe@1.0.0:
version "1.0.0"
resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/unpipe/-/unpipe-1.0.0.tgz"
- integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
+ integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
utils-merge@1.0.1:
version "1.0.1"
resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/utils-merge/-/utils-merge-1.0.1.tgz"
- integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
+ integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
vary@~1.1.2:
version "1.1.2"
resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/vary/-/vary-1.1.2.tgz"
- integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
+ integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
wrappy@1:
version "1.0.2"
From f5acca1d365388b0a862f96ada46b46916529991 Mon Sep 17 00:00:00 2001
From: priyankmodiPM <32540484+priyankmodiPM@users.noreply.github.com>
Date: Mon, 6 Jul 2026 18:55:42 +0530
Subject: [PATCH 05/20] fix: regenerate yarn.lock against public npm registry
(#3)
yarn.lock resolved every package from Adobe's internal Artifactory
(picked up via local ~/.npmrc), which Render's build servers can't
reach or validate certs for, causing "unable to get local issuer
certificate" during deploy. Regenerated against registry.npmjs.org
and dropped package-lock.json since render.yaml only runs yarn.
Co-authored-by: Priyank Modi
---
package-lock.json | 881 ----------------------------------------------
yarn.lock | 671 ++++++++++++++++-------------------
2 files changed, 297 insertions(+), 1255 deletions(-)
delete mode 100644 package-lock.json
diff --git a/package-lock.json b/package-lock.json
deleted file mode 100644
index 05cf00a448..0000000000
--- a/package-lock.json
+++ /dev/null
@@ -1,881 +0,0 @@
-{
- "name": "express-hello-world",
- "version": "1.0.0",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "name": "express-hello-world",
- "version": "1.0.0",
- "license": "MIT",
- "dependencies": {
- "express": "^5.0.0",
- "openai": "^6.45.0"
- }
- },
- "node_modules/accepts": {
- "version": "2.0.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/accepts/-/accepts-2.0.0.tgz",
- "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
- "dependencies": {
- "mime-types": "^3.0.0",
- "negotiator": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/array-flatten": {
- "version": "3.0.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/array-flatten/-/array-flatten-3.0.0.tgz",
- "integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA=="
- },
- "node_modules/body-parser": {
- "version": "2.0.1",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/body-parser/-/body-parser-2.0.1.tgz",
- "integrity": "sha512-PagxbjvuPH6tv0f/kdVbFGcb79D236SLcDTs6DrQ7GizJ88S1UWP4nMXFEo/I4fdhGRGabvFfFjVGm3M7U8JwA==",
- "dependencies": {
- "bytes": "3.1.2",
- "content-type": "~1.0.5",
- "debug": "3.1.0",
- "destroy": "1.2.0",
- "http-errors": "2.0.0",
- "iconv-lite": "0.5.2",
- "on-finished": "2.4.1",
- "qs": "6.13.0",
- "raw-body": "^3.0.0",
- "type-is": "~1.6.18",
- "unpipe": "1.0.0"
- },
- "engines": {
- "node": ">= 0.10"
- }
- },
- "node_modules/body-parser/node_modules/debug": {
- "version": "3.1.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/debug/-/debug-3.1.0.tgz",
- "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
- "dependencies": {
- "ms": "2.0.0"
- }
- },
- "node_modules/body-parser/node_modules/media-typer": {
- "version": "0.3.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/media-typer/-/media-typer-0.3.0.tgz",
- "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/body-parser/node_modules/mime-db": {
- "version": "1.40.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/mime-db/-/mime-db-1.40.0.tgz",
- "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/body-parser/node_modules/mime-types": {
- "version": "2.1.24",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/mime-types/-/mime-types-2.1.24.tgz",
- "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
- "dependencies": {
- "mime-db": "1.40.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/body-parser/node_modules/ms": {
- "version": "2.0.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
- },
- "node_modules/body-parser/node_modules/type-is": {
- "version": "1.6.18",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/type-is/-/type-is-1.6.18.tgz",
- "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
- "dependencies": {
- "media-typer": "0.3.0",
- "mime-types": "~2.1.24"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/bytes": {
- "version": "3.1.2",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/bytes/-/bytes-3.1.2.tgz",
- "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/call-bind": {
- "version": "1.0.7",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/call-bind/-/call-bind-1.0.7.tgz",
- "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
- "dependencies": {
- "es-define-property": "^1.0.0",
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2",
- "get-intrinsic": "^1.2.4",
- "set-function-length": "^1.2.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/content-disposition": {
- "version": "1.0.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/content-disposition/-/content-disposition-1.0.0.tgz",
- "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
- "dependencies": {
- "safe-buffer": "5.2.1"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/content-type": {
- "version": "1.0.5",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/content-type/-/content-type-1.0.5.tgz",
- "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/cookie": {
- "version": "0.6.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/cookie/-/cookie-0.6.0.tgz",
- "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/cookie-signature": {
- "version": "1.2.1",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/cookie-signature/-/cookie-signature-1.2.1.tgz",
- "integrity": "sha512-78KWk9T26NhzXtuL26cIJ8/qNHANyJ/ZYrmEXFzUmhZdjpBv+DlWlOANRTGBt48YcyslsLrj0bMLFTmXvLRCOw==",
- "engines": {
- "node": ">=6.6.0"
- }
- },
- "node_modules/debug": {
- "version": "4.3.6",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/debug/-/debug-4.3.6.tgz",
- "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
- "dependencies": {
- "ms": "2.1.2"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/define-data-property": {
- "version": "1.1.4",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/define-data-property/-/define-data-property-1.1.4.tgz",
- "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
- "dependencies": {
- "es-define-property": "^1.0.0",
- "es-errors": "^1.3.0",
- "gopd": "^1.0.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/depd": {
- "version": "2.0.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/depd/-/depd-2.0.0.tgz",
- "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/destroy": {
- "version": "1.2.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/destroy/-/destroy-1.2.0.tgz",
- "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
- "engines": {
- "node": ">= 0.8",
- "npm": "1.2.8000 || >= 1.4.16"
- }
- },
- "node_modules/ee-first": {
- "version": "1.1.1",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/ee-first/-/ee-first-1.1.1.tgz",
- "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
- },
- "node_modules/encodeurl": {
- "version": "2.0.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/encodeurl/-/encodeurl-2.0.0.tgz",
- "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/es-define-property": {
- "version": "1.0.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/es-define-property/-/es-define-property-1.0.0.tgz",
- "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
- "dependencies": {
- "get-intrinsic": "^1.2.4"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-errors": {
- "version": "1.3.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/es-errors/-/es-errors-1.3.0.tgz",
- "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/escape-html": {
- "version": "1.0.3",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/escape-html/-/escape-html-1.0.3.tgz",
- "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
- },
- "node_modules/etag": {
- "version": "1.8.1",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/etag/-/etag-1.8.1.tgz",
- "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/express": {
- "version": "5.0.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/express/-/express-5.0.0.tgz",
- "integrity": "sha512-V4UkHQc+B7ldh1YC84HCXHwf60M4BOMvp9rkvTUWCK5apqDC1Esnbid4wm6nFyVuDy8XMfETsJw5lsIGBWyo0A==",
- "dependencies": {
- "accepts": "^2.0.0",
- "body-parser": "^2.0.1",
- "content-disposition": "^1.0.0",
- "content-type": "~1.0.4",
- "cookie": "0.6.0",
- "cookie-signature": "^1.2.1",
- "debug": "4.3.6",
- "depd": "2.0.0",
- "encodeurl": "~2.0.0",
- "escape-html": "~1.0.3",
- "etag": "~1.8.1",
- "finalhandler": "^2.0.0",
- "fresh": "2.0.0",
- "http-errors": "2.0.0",
- "merge-descriptors": "^2.0.0",
- "methods": "~1.1.2",
- "mime-types": "^3.0.0",
- "on-finished": "2.4.1",
- "once": "1.4.0",
- "parseurl": "~1.3.3",
- "proxy-addr": "~2.0.7",
- "qs": "6.13.0",
- "range-parser": "~1.2.1",
- "router": "^2.0.0",
- "safe-buffer": "5.2.1",
- "send": "^1.1.0",
- "serve-static": "^2.1.0",
- "setprototypeof": "1.2.0",
- "statuses": "2.0.1",
- "type-is": "^2.0.0",
- "utils-merge": "1.0.1",
- "vary": "~1.1.2"
- },
- "engines": {
- "node": ">= 18"
- }
- },
- "node_modules/finalhandler": {
- "version": "2.0.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/finalhandler/-/finalhandler-2.0.0.tgz",
- "integrity": "sha512-MX6Zo2adDViYh+GcxxB1dpO43eypOGUOL12rLCOTMQv/DfIbpSJUy4oQIIZhVZkH9e+bZWKMon0XHFEju16tkQ==",
- "dependencies": {
- "debug": "2.6.9",
- "encodeurl": "~1.0.2",
- "escape-html": "~1.0.3",
- "on-finished": "2.4.1",
- "parseurl": "~1.3.3",
- "statuses": "2.0.1",
- "unpipe": "~1.0.0"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/finalhandler/node_modules/debug": {
- "version": "2.6.9",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dependencies": {
- "ms": "2.0.0"
- }
- },
- "node_modules/finalhandler/node_modules/encodeurl": {
- "version": "1.0.2",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/encodeurl/-/encodeurl-1.0.2.tgz",
- "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/finalhandler/node_modules/ms": {
- "version": "2.0.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
- },
- "node_modules/forwarded": {
- "version": "0.2.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/forwarded/-/forwarded-0.2.0.tgz",
- "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/fresh": {
- "version": "2.0.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/fresh/-/fresh-2.0.0.tgz",
- "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/function-bind": {
- "version": "1.1.2",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/function-bind/-/function-bind-1.1.2.tgz",
- "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/get-intrinsic": {
- "version": "1.2.4",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
- "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
- "dependencies": {
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2",
- "has-proto": "^1.0.1",
- "has-symbols": "^1.0.3",
- "hasown": "^2.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/gopd": {
- "version": "1.0.1",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/gopd/-/gopd-1.0.1.tgz",
- "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
- "dependencies": {
- "get-intrinsic": "^1.1.3"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-property-descriptors": {
- "version": "1.0.2",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
- "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
- "dependencies": {
- "es-define-property": "^1.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-proto": {
- "version": "1.0.3",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/has-proto/-/has-proto-1.0.3.tgz",
- "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-symbols": {
- "version": "1.0.3",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/has-symbols/-/has-symbols-1.0.3.tgz",
- "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/hasown": {
- "version": "2.0.2",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/hasown/-/hasown-2.0.2.tgz",
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
- "dependencies": {
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/http-errors": {
- "version": "2.0.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/http-errors/-/http-errors-2.0.0.tgz",
- "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
- "dependencies": {
- "depd": "2.0.0",
- "inherits": "2.0.4",
- "setprototypeof": "1.2.0",
- "statuses": "2.0.1",
- "toidentifier": "1.0.1"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/iconv-lite": {
- "version": "0.5.2",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/iconv-lite/-/iconv-lite-0.5.2.tgz",
- "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==",
- "dependencies": {
- "safer-buffer": ">= 2.1.2 < 3"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/inherits": {
- "version": "2.0.4",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
- },
- "node_modules/ipaddr.js": {
- "version": "1.9.1",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
- "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
- "engines": {
- "node": ">= 0.10"
- }
- },
- "node_modules/is-promise": {
- "version": "4.0.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/is-promise/-/is-promise-4.0.0.tgz",
- "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="
- },
- "node_modules/media-typer": {
- "version": "1.1.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/media-typer/-/media-typer-1.1.0.tgz",
- "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/merge-descriptors": {
- "version": "2.0.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
- "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/methods": {
- "version": "1.1.2",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/methods/-/methods-1.1.2.tgz",
- "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/mime-db": {
- "version": "1.53.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/mime-db/-/mime-db-1.53.0.tgz",
- "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/mime-types": {
- "version": "3.0.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/mime-types/-/mime-types-3.0.0.tgz",
- "integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==",
- "dependencies": {
- "mime-db": "^1.53.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/ms": {
- "version": "2.1.2",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
- },
- "node_modules/negotiator": {
- "version": "1.0.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/negotiator/-/negotiator-1.0.0.tgz",
- "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/object-inspect": {
- "version": "1.13.2",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/object-inspect/-/object-inspect-1.13.2.tgz",
- "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/on-finished": {
- "version": "2.4.1",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/on-finished/-/on-finished-2.4.1.tgz",
- "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
- "dependencies": {
- "ee-first": "1.1.1"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/once": {
- "version": "1.4.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/once/-/once-1.4.0.tgz",
- "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
- "dependencies": {
- "wrappy": "1"
- }
- },
- "node_modules/openai": {
- "version": "6.45.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/openai/-/openai-6.45.0.tgz",
- "integrity": "sha512-5DQVNErssk0afNpTTHUm/qZPU4iKR9OYdNid8Ib4puq4gHNNvGWZht2zY4h9a8JMF949Ik6m8gQutllVPbjdnw==",
- "peerDependencies": {
- "@aws-sdk/credential-provider-node": ">=3.972.0 <4",
- "@smithy/hash-node": ">=4.3.0 <5",
- "@smithy/signature-v4": ">=5.4.0 <6",
- "ws": "^8.18.0",
- "zod": "^3.25 || ^4.0"
- },
- "peerDependenciesMeta": {
- "@aws-sdk/credential-provider-node": {
- "optional": true
- },
- "@smithy/hash-node": {
- "optional": true
- },
- "@smithy/signature-v4": {
- "optional": true
- },
- "ws": {
- "optional": true
- },
- "zod": {
- "optional": true
- }
- }
- },
- "node_modules/parseurl": {
- "version": "1.3.3",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/parseurl/-/parseurl-1.3.3.tgz",
- "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/path-to-regexp": {
- "version": "8.1.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/path-to-regexp/-/path-to-regexp-8.1.0.tgz",
- "integrity": "sha512-Bqn3vc8CMHty6zuD+tG23s6v2kwxslHEhTj4eYaVKGIEB+YX/2wd0/rgXLFD9G9id9KCtbVy/3ZgmvZjpa0UdQ==",
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/proxy-addr": {
- "version": "2.0.7",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/proxy-addr/-/proxy-addr-2.0.7.tgz",
- "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
- "dependencies": {
- "forwarded": "0.2.0",
- "ipaddr.js": "1.9.1"
- },
- "engines": {
- "node": ">= 0.10"
- }
- },
- "node_modules/qs": {
- "version": "6.13.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/qs/-/qs-6.13.0.tgz",
- "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
- "dependencies": {
- "side-channel": "^1.0.6"
- },
- "engines": {
- "node": ">=0.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/range-parser": {
- "version": "1.2.1",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/range-parser/-/range-parser-1.2.1.tgz",
- "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/raw-body": {
- "version": "3.0.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/raw-body/-/raw-body-3.0.0.tgz",
- "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
- "dependencies": {
- "bytes": "3.1.2",
- "http-errors": "2.0.0",
- "iconv-lite": "0.6.3",
- "unpipe": "1.0.0"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/raw-body/node_modules/iconv-lite": {
- "version": "0.6.3",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/iconv-lite/-/iconv-lite-0.6.3.tgz",
- "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
- "dependencies": {
- "safer-buffer": ">= 2.1.2 < 3.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/router": {
- "version": "2.0.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/router/-/router-2.0.0.tgz",
- "integrity": "sha512-dIM5zVoG8xhC6rnSN8uoAgFARwTE7BQs8YwHEvK0VCmfxQXMaOuA1uiR1IPwsW7JyK5iTt7Od/TC9StasS2NPQ==",
- "dependencies": {
- "array-flatten": "3.0.0",
- "is-promise": "4.0.0",
- "methods": "~1.1.2",
- "parseurl": "~1.3.3",
- "path-to-regexp": "^8.0.0",
- "setprototypeof": "1.2.0",
- "utils-merge": "1.0.1"
- },
- "engines": {
- "node": ">= 0.10"
- }
- },
- "node_modules/safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ]
- },
- "node_modules/safer-buffer": {
- "version": "2.1.2",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/safer-buffer/-/safer-buffer-2.1.2.tgz",
- "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
- },
- "node_modules/send": {
- "version": "1.1.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/send/-/send-1.1.0.tgz",
- "integrity": "sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==",
- "dependencies": {
- "debug": "^4.3.5",
- "destroy": "^1.2.0",
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "etag": "^1.8.1",
- "fresh": "^0.5.2",
- "http-errors": "^2.0.0",
- "mime-types": "^2.1.35",
- "ms": "^2.1.3",
- "on-finished": "^2.4.1",
- "range-parser": "^1.2.1",
- "statuses": "^2.0.1"
- },
- "engines": {
- "node": ">= 18"
- }
- },
- "node_modules/send/node_modules/fresh": {
- "version": "0.5.2",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/fresh/-/fresh-0.5.2.tgz",
- "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/send/node_modules/mime-db": {
- "version": "1.52.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/mime-db/-/mime-db-1.52.0.tgz",
- "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/send/node_modules/mime-types": {
- "version": "2.1.35",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/mime-types/-/mime-types-2.1.35.tgz",
- "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
- "dependencies": {
- "mime-db": "1.52.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/send/node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
- },
- "node_modules/serve-static": {
- "version": "2.1.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/serve-static/-/serve-static-2.1.0.tgz",
- "integrity": "sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA==",
- "dependencies": {
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "parseurl": "^1.3.3",
- "send": "^1.0.0"
- },
- "engines": {
- "node": ">= 18"
- }
- },
- "node_modules/set-function-length": {
- "version": "1.2.2",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/set-function-length/-/set-function-length-1.2.2.tgz",
- "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
- "dependencies": {
- "define-data-property": "^1.1.4",
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2",
- "get-intrinsic": "^1.2.4",
- "gopd": "^1.0.1",
- "has-property-descriptors": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/setprototypeof": {
- "version": "1.2.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/setprototypeof/-/setprototypeof-1.2.0.tgz",
- "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
- },
- "node_modules/side-channel": {
- "version": "1.0.6",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/side-channel/-/side-channel-1.0.6.tgz",
- "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
- "dependencies": {
- "call-bind": "^1.0.7",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.4",
- "object-inspect": "^1.13.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/statuses": {
- "version": "2.0.1",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/statuses/-/statuses-2.0.1.tgz",
- "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/toidentifier": {
- "version": "1.0.1",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/toidentifier/-/toidentifier-1.0.1.tgz",
- "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
- "engines": {
- "node": ">=0.6"
- }
- },
- "node_modules/type-is": {
- "version": "2.0.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/type-is/-/type-is-2.0.0.tgz",
- "integrity": "sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==",
- "dependencies": {
- "content-type": "^1.0.5",
- "media-typer": "^1.1.0",
- "mime-types": "^3.0.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/unpipe": {
- "version": "1.0.0",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/unpipe/-/unpipe-1.0.0.tgz",
- "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/utils-merge": {
- "version": "1.0.1",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/utils-merge/-/utils-merge-1.0.1.tgz",
- "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
- "engines": {
- "node": ">= 0.4.0"
- }
- },
- "node_modules/vary": {
- "version": "1.1.2",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/vary/-/vary-1.1.2.tgz",
- "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/wrappy": {
- "version": "1.0.2",
- "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
- }
- }
-}
diff --git a/yarn.lock b/yarn.lock
index 50df762342..e4212e3db7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4,563 +4,486 @@
accepts@^2.0.0:
version "2.0.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/accepts/-/accepts-2.0.0.tgz"
+ resolved "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895"
integrity sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==
dependencies:
mime-types "^3.0.0"
negotiator "^1.0.0"
-array-flatten@3.0.0:
- version "3.0.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/array-flatten/-/array-flatten-3.0.0.tgz"
- integrity sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==
-
-body-parser@^2.0.1:
- version "2.0.1"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/body-parser/-/body-parser-2.0.1.tgz"
- integrity sha512-PagxbjvuPH6tv0f/kdVbFGcb79D236SLcDTs6DrQ7GizJ88S1UWP4nMXFEo/I4fdhGRGabvFfFjVGm3M7U8JwA==
+body-parser@^2.2.1:
+ version "2.3.0"
+ resolved "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz#6d8662f4d8c336028b8ac9aa24251b0ca64ba437"
+ integrity sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==
dependencies:
- bytes "3.1.2"
- content-type "~1.0.5"
- debug "3.1.0"
- destroy "1.2.0"
- http-errors "2.0.0"
- iconv-lite "0.5.2"
- on-finished "2.4.1"
- qs "6.13.0"
- raw-body "^3.0.0"
- type-is "~1.6.18"
- unpipe "1.0.0"
-
-bytes@3.1.2:
+ bytes "^3.1.2"
+ content-type "^2.0.0"
+ debug "^4.4.3"
+ http-errors "^2.0.1"
+ iconv-lite "^0.7.2"
+ on-finished "^2.4.1"
+ qs "^6.15.2"
+ raw-body "^3.0.2"
+ type-is "^2.1.0"
+
+bytes@^3.1.2, bytes@~3.1.2:
version "3.1.2"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/bytes/-/bytes-3.1.2.tgz"
+ resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
-call-bind@^1.0.7:
- version "1.0.7"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/call-bind/-/call-bind-1.0.7.tgz"
- integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==
+call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6"
+ integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==
dependencies:
- es-define-property "^1.0.0"
es-errors "^1.3.0"
function-bind "^1.1.2"
- get-intrinsic "^1.2.4"
- set-function-length "^1.2.1"
-content-disposition@^1.0.0:
- version "1.0.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/content-disposition/-/content-disposition-1.0.0.tgz"
- integrity sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==
+call-bound@^1.0.2:
+ version "1.0.4"
+ resolved "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a"
+ integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==
dependencies:
- safe-buffer "5.2.1"
+ call-bind-apply-helpers "^1.0.2"
+ get-intrinsic "^1.3.0"
-content-type@^1.0.5, content-type@~1.0.4, content-type@~1.0.5:
+content-disposition@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz#f3db789c752d45564cc7e9e1e0b31790d4a38e17"
+ integrity sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==
+
+content-type@^1.0.5:
version "1.0.5"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/content-type/-/content-type-1.0.5.tgz"
+ resolved "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
-cookie-signature@^1.2.1:
- version "1.2.1"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/cookie-signature/-/cookie-signature-1.2.1.tgz"
- integrity sha512-78KWk9T26NhzXtuL26cIJ8/qNHANyJ/ZYrmEXFzUmhZdjpBv+DlWlOANRTGBt48YcyslsLrj0bMLFTmXvLRCOw==
-
-cookie@0.6.0:
- version "0.6.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/cookie/-/cookie-0.6.0.tgz"
- integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==
-
-debug@^4.3.5, debug@4.3.6:
- version "4.3.6"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/debug/-/debug-4.3.6.tgz"
- integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==
- dependencies:
- ms "2.1.2"
+content-type@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz#2fb3ede69dffa0af78ca7c4ce7589680638b56df"
+ integrity sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==
-debug@2.6.9:
- version "2.6.9"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/debug/-/debug-2.6.9.tgz"
- integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
- dependencies:
- ms "2.0.0"
+cookie-signature@^1.2.1:
+ version "1.2.2"
+ resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793"
+ integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==
-debug@3.1.0:
- version "3.1.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/debug/-/debug-3.1.0.tgz"
- integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
- dependencies:
- ms "2.0.0"
+cookie@^0.7.1:
+ version "0.7.2"
+ resolved "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7"
+ integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==
-define-data-property@^1.1.4:
- version "1.1.4"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/define-data-property/-/define-data-property-1.1.4.tgz"
- integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==
+debug@^4.4.0, debug@^4.4.3:
+ version "4.4.3"
+ resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
+ integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
dependencies:
- es-define-property "^1.0.0"
- es-errors "^1.3.0"
- gopd "^1.0.1"
+ ms "^2.1.3"
-depd@2.0.0:
+depd@^2.0.0, depd@~2.0.0:
version "2.0.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/depd/-/depd-2.0.0.tgz"
+ resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
-destroy@^1.2.0, destroy@1.2.0:
- version "1.2.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/destroy/-/destroy-1.2.0.tgz"
- integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
+dunder-proto@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a"
+ integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==
+ dependencies:
+ call-bind-apply-helpers "^1.0.1"
+ es-errors "^1.3.0"
+ gopd "^1.2.0"
ee-first@1.1.1:
version "1.1.1"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/ee-first/-/ee-first-1.1.1.tgz"
+ resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
-encodeurl@^2.0.0, encodeurl@~2.0.0:
+encodeurl@^2.0.0:
version "2.0.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/encodeurl/-/encodeurl-2.0.0.tgz"
+ resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58"
integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==
-encodeurl@~1.0.2:
- version "1.0.2"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/encodeurl/-/encodeurl-1.0.2.tgz"
- integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
-
-es-define-property@^1.0.0:
- version "1.0.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/es-define-property/-/es-define-property-1.0.0.tgz"
- integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==
- dependencies:
- get-intrinsic "^1.2.4"
+es-define-property@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa"
+ integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==
es-errors@^1.3.0:
version "1.3.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/es-errors/-/es-errors-1.3.0.tgz"
+ resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
-escape-html@^1.0.3, escape-html@~1.0.3:
+es-object-atoms@^1.0.0, es-object-atoms@^1.1.1:
+ version "1.1.2"
+ resolved "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz#a2d0b373205724dfa525d23b0c3e1b1ca582c99b"
+ integrity sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==
+ dependencies:
+ es-errors "^1.3.0"
+
+escape-html@^1.0.3:
version "1.0.3"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/escape-html/-/escape-html-1.0.3.tgz"
+ resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
-etag@^1.8.1, etag@~1.8.1:
+etag@^1.8.1:
version "1.8.1"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/etag/-/etag-1.8.1.tgz"
+ resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
express@^5.0.0:
- version "5.0.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/express/-/express-5.0.0.tgz"
- integrity sha512-V4UkHQc+B7ldh1YC84HCXHwf60M4BOMvp9rkvTUWCK5apqDC1Esnbid4wm6nFyVuDy8XMfETsJw5lsIGBWyo0A==
+ version "5.2.1"
+ resolved "https://registry.npmjs.org/express/-/express-5.2.1.tgz#8f21d15b6d327f92b4794ecf8cb08a72f956ac04"
+ integrity sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==
dependencies:
accepts "^2.0.0"
- body-parser "^2.0.1"
+ body-parser "^2.2.1"
content-disposition "^1.0.0"
- content-type "~1.0.4"
- cookie "0.6.0"
+ content-type "^1.0.5"
+ cookie "^0.7.1"
cookie-signature "^1.2.1"
- debug "4.3.6"
- depd "2.0.0"
- encodeurl "~2.0.0"
- escape-html "~1.0.3"
- etag "~1.8.1"
- finalhandler "^2.0.0"
- fresh "2.0.0"
- http-errors "2.0.0"
+ debug "^4.4.0"
+ depd "^2.0.0"
+ encodeurl "^2.0.0"
+ escape-html "^1.0.3"
+ etag "^1.8.1"
+ finalhandler "^2.1.0"
+ fresh "^2.0.0"
+ http-errors "^2.0.0"
merge-descriptors "^2.0.0"
- methods "~1.1.2"
mime-types "^3.0.0"
- on-finished "2.4.1"
- once "1.4.0"
- parseurl "~1.3.3"
- proxy-addr "~2.0.7"
- qs "6.13.0"
- range-parser "~1.2.1"
- router "^2.0.0"
- safe-buffer "5.2.1"
+ on-finished "^2.4.1"
+ once "^1.4.0"
+ parseurl "^1.3.3"
+ proxy-addr "^2.0.7"
+ qs "^6.14.0"
+ range-parser "^1.2.1"
+ router "^2.2.0"
send "^1.1.0"
- serve-static "^2.1.0"
- setprototypeof "1.2.0"
- statuses "2.0.1"
- type-is "^2.0.0"
- utils-merge "1.0.1"
- vary "~1.1.2"
-
-finalhandler@^2.0.0:
- version "2.0.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/finalhandler/-/finalhandler-2.0.0.tgz"
- integrity sha512-MX6Zo2adDViYh+GcxxB1dpO43eypOGUOL12rLCOTMQv/DfIbpSJUy4oQIIZhVZkH9e+bZWKMon0XHFEju16tkQ==
+ serve-static "^2.2.0"
+ statuses "^2.0.1"
+ type-is "^2.0.1"
+ vary "^1.1.2"
+
+finalhandler@^2.1.0:
+ version "2.1.1"
+ resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz#a2c517a6559852bcdb06d1f8bd7f51b68fad8099"
+ integrity sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==
dependencies:
- debug "2.6.9"
- encodeurl "~1.0.2"
- escape-html "~1.0.3"
- on-finished "2.4.1"
- parseurl "~1.3.3"
- statuses "2.0.1"
- unpipe "~1.0.0"
+ debug "^4.4.0"
+ encodeurl "^2.0.0"
+ escape-html "^1.0.3"
+ on-finished "^2.4.1"
+ parseurl "^1.3.3"
+ statuses "^2.0.1"
forwarded@0.2.0:
version "0.2.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/forwarded/-/forwarded-0.2.0.tgz"
+ resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
-fresh@^0.5.2:
- version "0.5.2"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/fresh/-/fresh-0.5.2.tgz"
- integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
-
-fresh@2.0.0:
+fresh@^2.0.0:
version "2.0.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/fresh/-/fresh-2.0.0.tgz"
+ resolved "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz#8dd7df6a1b3a1b3a5cf186c05a5dd267622635a4"
integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==
function-bind@^1.1.2:
version "1.1.2"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/function-bind/-/function-bind-1.1.2.tgz"
+ resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
-get-intrinsic@^1.1.3, get-intrinsic@^1.2.4:
- version "1.2.4"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/get-intrinsic/-/get-intrinsic-1.2.4.tgz"
- integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==
+get-intrinsic@^1.2.5, get-intrinsic@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01"
+ integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==
dependencies:
+ call-bind-apply-helpers "^1.0.2"
+ es-define-property "^1.0.1"
es-errors "^1.3.0"
+ es-object-atoms "^1.1.1"
function-bind "^1.1.2"
- has-proto "^1.0.1"
- has-symbols "^1.0.3"
- hasown "^2.0.0"
+ get-proto "^1.0.1"
+ gopd "^1.2.0"
+ has-symbols "^1.1.0"
+ hasown "^2.0.2"
+ math-intrinsics "^1.1.0"
-gopd@^1.0.1:
+get-proto@^1.0.1:
version "1.0.1"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/gopd/-/gopd-1.0.1.tgz"
- integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==
+ resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1"
+ integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==
dependencies:
- get-intrinsic "^1.1.3"
+ dunder-proto "^1.0.1"
+ es-object-atoms "^1.0.0"
-has-property-descriptors@^1.0.2:
- version "1.0.2"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz"
- integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==
- dependencies:
- es-define-property "^1.0.0"
-
-has-proto@^1.0.1:
- version "1.0.3"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/has-proto/-/has-proto-1.0.3.tgz"
- integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==
+gopd@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1"
+ integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
-has-symbols@^1.0.3:
- version "1.0.3"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/has-symbols/-/has-symbols-1.0.3.tgz"
- integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
+has-symbols@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338"
+ integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==
-hasown@^2.0.0:
- version "2.0.2"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/hasown/-/hasown-2.0.2.tgz"
- integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
+hasown@^2.0.2:
+ version "2.0.4"
+ resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz#8c62d8cb90beb2aad5d0a5b67581ad9854c3f003"
+ integrity sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==
dependencies:
function-bind "^1.1.2"
-http-errors@^2.0.0, http-errors@2.0.0:
- version "2.0.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/http-errors/-/http-errors-2.0.0.tgz"
- integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==
- dependencies:
- depd "2.0.0"
- inherits "2.0.4"
- setprototypeof "1.2.0"
- statuses "2.0.1"
- toidentifier "1.0.1"
-
-iconv-lite@0.5.2:
- version "0.5.2"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/iconv-lite/-/iconv-lite-0.5.2.tgz"
- integrity sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==
+http-errors@^2.0.0, http-errors@^2.0.1, http-errors@~2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz#36d2f65bc909c8790018dd36fb4d93da6caae06b"
+ integrity sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==
dependencies:
- safer-buffer ">= 2.1.2 < 3"
+ depd "~2.0.0"
+ inherits "~2.0.4"
+ setprototypeof "~1.2.0"
+ statuses "~2.0.2"
+ toidentifier "~1.0.1"
-iconv-lite@0.6.3:
- version "0.6.3"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/iconv-lite/-/iconv-lite-0.6.3.tgz"
- integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
+iconv-lite@^0.7.2, iconv-lite@~0.7.0:
+ version "0.7.3"
+ resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz#84ee12f963e7de50bc01a13e160a078b3b0f415f"
+ integrity sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
-inherits@2.0.4:
+inherits@~2.0.4:
version "2.0.4"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/inherits/-/inherits-2.0.4.tgz"
+ resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
ipaddr.js@1.9.1:
version "1.9.1"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/ipaddr.js/-/ipaddr.js-1.9.1.tgz"
+ resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
-is-promise@4.0.0:
+is-promise@^4.0.0:
version "4.0.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/is-promise/-/is-promise-4.0.0.tgz"
+ resolved "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3"
integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==
+math-intrinsics@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9"
+ integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==
+
media-typer@^1.1.0:
version "1.1.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/media-typer/-/media-typer-1.1.0.tgz"
+ resolved "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz#6ab74b8f2d3320f2064b2a87a38e7931ff3a5561"
integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==
-media-typer@0.3.0:
- version "0.3.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/media-typer/-/media-typer-0.3.0.tgz"
- integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
-
merge-descriptors@^2.0.0:
version "2.0.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/merge-descriptors/-/merge-descriptors-2.0.0.tgz"
+ resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz#ea922f660635a2249ee565e0449f951e6b603808"
integrity sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==
-methods@~1.1.2:
- version "1.1.2"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/methods/-/methods-1.1.2.tgz"
- integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==
-
-mime-db@^1.53.0:
- version "1.53.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/mime-db/-/mime-db-1.53.0.tgz"
- integrity sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==
-
-mime-db@1.40.0:
- version "1.40.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/mime-db/-/mime-db-1.40.0.tgz"
- integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==
-
-mime-db@1.52.0:
- version "1.52.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/mime-db/-/mime-db-1.52.0.tgz"
- integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
-
-mime-types@^2.1.35:
- version "2.1.35"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/mime-types/-/mime-types-2.1.35.tgz"
- integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
- dependencies:
- mime-db "1.52.0"
-
-mime-types@^3.0.0:
- version "3.0.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/mime-types/-/mime-types-3.0.0.tgz"
- integrity sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==
- dependencies:
- mime-db "^1.53.0"
+mime-db@^1.54.0:
+ version "1.54.0"
+ resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5"
+ integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==
-mime-types@~2.1.24:
- version "2.1.24"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/mime-types/-/mime-types-2.1.24.tgz"
- integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==
+mime-types@^3.0.0, mime-types@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz#39002d4182575d5af036ffa118100f2524b2e2ab"
+ integrity sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==
dependencies:
- mime-db "1.40.0"
+ mime-db "^1.54.0"
ms@^2.1.3:
version "2.1.3"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/ms/-/ms-2.1.3.tgz"
+ resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
-ms@2.0.0:
- version "2.0.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/ms/-/ms-2.0.0.tgz"
- integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
-
-ms@2.1.2:
- version "2.1.2"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/ms/-/ms-2.1.2.tgz"
- integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
-
negotiator@^1.0.0:
version "1.0.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/negotiator/-/negotiator-1.0.0.tgz"
+ resolved "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a"
integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==
-object-inspect@^1.13.1:
- version "1.13.2"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/object-inspect/-/object-inspect-1.13.2.tgz"
- integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==
+object-inspect@^1.13.3, object-inspect@^1.13.4:
+ version "1.13.4"
+ resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213"
+ integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==
-on-finished@^2.4.1, on-finished@2.4.1:
+on-finished@^2.4.1:
version "2.4.1"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/on-finished/-/on-finished-2.4.1.tgz"
+ resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
dependencies:
ee-first "1.1.1"
-once@1.4.0:
+once@^1.4.0:
version "1.4.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/once/-/once-1.4.0.tgz"
+ resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
dependencies:
wrappy "1"
openai@^6.45.0:
version "6.45.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/openai/-/openai-6.45.0.tgz"
+ resolved "https://registry.npmjs.org/openai/-/openai-6.45.0.tgz#53840c5c5848884dfbff47006f839b27d1d955b9"
integrity sha512-5DQVNErssk0afNpTTHUm/qZPU4iKR9OYdNid8Ib4puq4gHNNvGWZht2zY4h9a8JMF949Ik6m8gQutllVPbjdnw==
-parseurl@^1.3.3, parseurl@~1.3.3:
+parseurl@^1.3.3:
version "1.3.3"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/parseurl/-/parseurl-1.3.3.tgz"
+ resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
path-to-regexp@^8.0.0:
- version "8.1.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/path-to-regexp/-/path-to-regexp-8.1.0.tgz"
- integrity sha512-Bqn3vc8CMHty6zuD+tG23s6v2kwxslHEhTj4eYaVKGIEB+YX/2wd0/rgXLFD9G9id9KCtbVy/3ZgmvZjpa0UdQ==
+ version "8.4.2"
+ resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz#795c420c4f7ca45c5b887366f622ee0c9852cccd"
+ integrity sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==
-proxy-addr@~2.0.7:
+proxy-addr@^2.0.7:
version "2.0.7"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/proxy-addr/-/proxy-addr-2.0.7.tgz"
+ resolved "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==
dependencies:
forwarded "0.2.0"
ipaddr.js "1.9.1"
-qs@6.13.0:
- version "6.13.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/qs/-/qs-6.13.0.tgz"
- integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==
+qs@^6.14.0, qs@^6.15.2:
+ version "6.15.3"
+ resolved "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz#76852132a58ed5c7c0ef67e4441b9bb5d6061b3b"
+ integrity sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==
dependencies:
- side-channel "^1.0.6"
+ es-define-property "^1.0.1"
+ side-channel "^1.1.1"
-range-parser@^1.2.1, range-parser@~1.2.1:
- version "1.2.1"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/range-parser/-/range-parser-1.2.1.tgz"
- integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
+range-parser@^1.2.1:
+ version "1.3.0"
+ resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz#d7f19be812bb62721472b45d3be219ef09572b47"
+ integrity sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==
-raw-body@^3.0.0:
- version "3.0.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/raw-body/-/raw-body-3.0.0.tgz"
- integrity sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==
+raw-body@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz#3e3ada5ae5568f9095d84376fd3a49b8fb000a51"
+ integrity sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==
dependencies:
- bytes "3.1.2"
- http-errors "2.0.0"
- iconv-lite "0.6.3"
- unpipe "1.0.0"
+ bytes "~3.1.2"
+ http-errors "~2.0.1"
+ iconv-lite "~0.7.0"
+ unpipe "~1.0.0"
-router@^2.0.0:
- version "2.0.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/router/-/router-2.0.0.tgz"
- integrity sha512-dIM5zVoG8xhC6rnSN8uoAgFARwTE7BQs8YwHEvK0VCmfxQXMaOuA1uiR1IPwsW7JyK5iTt7Od/TC9StasS2NPQ==
+router@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.npmjs.org/router/-/router-2.2.0.tgz#019be620b711c87641167cc79b99090f00b146ef"
+ integrity sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==
dependencies:
- array-flatten "3.0.0"
- is-promise "4.0.0"
- methods "~1.1.2"
- parseurl "~1.3.3"
+ debug "^4.4.0"
+ depd "^2.0.0"
+ is-promise "^4.0.0"
+ parseurl "^1.3.3"
path-to-regexp "^8.0.0"
- setprototypeof "1.2.0"
- utils-merge "1.0.1"
-safe-buffer@5.2.1:
- version "5.2.1"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/safe-buffer/-/safe-buffer-5.2.1.tgz"
- integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
-
-"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0":
+"safer-buffer@>= 2.1.2 < 3.0.0":
version "2.1.2"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/safer-buffer/-/safer-buffer-2.1.2.tgz"
+ resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
-send@^1.0.0, send@^1.1.0:
- version "1.1.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/send/-/send-1.1.0.tgz"
- integrity sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==
+send@^1.1.0, send@^1.2.0:
+ version "1.2.1"
+ resolved "https://registry.npmjs.org/send/-/send-1.2.1.tgz#9eab743b874f3550f40a26867bf286ad60d3f3ed"
+ integrity sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==
dependencies:
- debug "^4.3.5"
- destroy "^1.2.0"
+ debug "^4.4.3"
encodeurl "^2.0.0"
escape-html "^1.0.3"
etag "^1.8.1"
- fresh "^0.5.2"
- http-errors "^2.0.0"
- mime-types "^2.1.35"
+ fresh "^2.0.0"
+ http-errors "^2.0.1"
+ mime-types "^3.0.2"
ms "^2.1.3"
on-finished "^2.4.1"
range-parser "^1.2.1"
- statuses "^2.0.1"
+ statuses "^2.0.2"
-serve-static@^2.1.0:
- version "2.1.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/serve-static/-/serve-static-2.1.0.tgz"
- integrity sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA==
+serve-static@^2.2.0:
+ version "2.2.1"
+ resolved "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz#7f186a4a4e5f5b663ad7a4294ff1bf37cf0e98a9"
+ integrity sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==
dependencies:
encodeurl "^2.0.0"
escape-html "^1.0.3"
parseurl "^1.3.3"
- send "^1.0.0"
+ send "^1.2.0"
-set-function-length@^1.2.1:
- version "1.2.2"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/set-function-length/-/set-function-length-1.2.2.tgz"
- integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==
+setprototypeof@~1.2.0:
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
+ integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
+
+side-channel-list@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz#c2e0b5a14a540aebee3bbc6c3f8666cc9b509127"
+ integrity sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==
dependencies:
- define-data-property "^1.1.4"
es-errors "^1.3.0"
- function-bind "^1.1.2"
- get-intrinsic "^1.2.4"
- gopd "^1.0.1"
- has-property-descriptors "^1.0.2"
+ object-inspect "^1.13.4"
-setprototypeof@1.2.0:
- version "1.2.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/setprototypeof/-/setprototypeof-1.2.0.tgz"
- integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
+side-channel-map@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42"
+ integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==
+ dependencies:
+ call-bound "^1.0.2"
+ es-errors "^1.3.0"
+ get-intrinsic "^1.2.5"
+ object-inspect "^1.13.3"
-side-channel@^1.0.6:
- version "1.0.6"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/side-channel/-/side-channel-1.0.6.tgz"
- integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==
+side-channel-weakmap@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea"
+ integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==
dependencies:
- call-bind "^1.0.7"
+ call-bound "^1.0.2"
es-errors "^1.3.0"
- get-intrinsic "^1.2.4"
- object-inspect "^1.13.1"
+ get-intrinsic "^1.2.5"
+ object-inspect "^1.13.3"
+ side-channel-map "^1.0.1"
-statuses@^2.0.1, statuses@2.0.1:
- version "2.0.1"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/statuses/-/statuses-2.0.1.tgz"
- integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
+side-channel@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz#ea02c62e05dc4bea67d4442f0fb71ee192f8e0ab"
+ integrity sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==
+ dependencies:
+ es-errors "^1.3.0"
+ object-inspect "^1.13.4"
+ side-channel-list "^1.0.1"
+ side-channel-map "^1.0.1"
+ side-channel-weakmap "^1.0.2"
+
+statuses@^2.0.1, statuses@^2.0.2, statuses@~2.0.2:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382"
+ integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==
-toidentifier@1.0.1:
+toidentifier@~1.0.1:
version "1.0.1"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/toidentifier/-/toidentifier-1.0.1.tgz"
+ resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
-type-is@^2.0.0:
- version "2.0.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/type-is/-/type-is-2.0.0.tgz"
- integrity sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==
+type-is@^2.0.1, type-is@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz#71d1a7053293582e16ac9f3ebaf1ab9aa49e5570"
+ integrity sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==
dependencies:
- content-type "^1.0.5"
+ content-type "^2.0.0"
media-typer "^1.1.0"
mime-types "^3.0.0"
-type-is@~1.6.18:
- version "1.6.18"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/type-is/-/type-is-1.6.18.tgz"
- integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
- dependencies:
- media-typer "0.3.0"
- mime-types "~2.1.24"
-
-unpipe@~1.0.0, unpipe@1.0.0:
+unpipe@~1.0.0:
version "1.0.0"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/unpipe/-/unpipe-1.0.0.tgz"
+ resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
-utils-merge@1.0.1:
- version "1.0.1"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/utils-merge/-/utils-merge-1.0.1.tgz"
- integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
-
-vary@~1.1.2:
+vary@^1.1.2:
version "1.1.2"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/vary/-/vary-1.1.2.tgz"
+ resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
wrappy@1:
version "1.0.2"
- resolved "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/wrappy/-/wrappy-1.0.2.tgz"
+ resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
From 4fc86418f5f904e42fad9f96c75b338422ff67de Mon Sep 17 00:00:00 2001
From: Priyank Modi
Date: Mon, 6 Jul 2026 19:05:52 +0530
Subject: [PATCH 06/20] fix: declare OPENAI_API_KEY in render.yaml envVars
app.js reads process.env.OPENAI_API_KEY but the Blueprint never
declared the key, so a value set in the Render dashboard was never
wired into the service, causing "Missing credentials" at boot.
---
render.yaml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/render.yaml b/render.yaml
index 5a4e5ac55d..f4b937f914 100644
--- a/render.yaml
+++ b/render.yaml
@@ -14,3 +14,5 @@ services:
sync: false
- key: WHATSAPP_TOKEN
sync: false
+ - key: OPENAI_API_KEY
+ sync: false
From f7ee32e1a6371b6fe2806236b7c6bde00eb21608 Mon Sep 17 00:00:00 2001
From: Priyank Modi
Date: Mon, 6 Jul 2026 20:37:24 +0530
Subject: [PATCH 07/20] fix: make OpenAI model/baseURL configurable and log
request/error details
Model was hardcoded to gpt-4o while the configured Azure OpenAI resource
only has a gpt-5.4-mini deployment, causing every request to fail with a
404 "deployment does not exist" error. Reads OPENAI_MODEL/OPENAI_BASE_URL
from env instead, and logs the resolved config plus full error details on
failure to make future mismatches easy to diagnose.
Co-Authored-By: Claude Sonnet 5
---
app.js | 24 +++++++++++++++++++++---
render.yaml | 4 ++++
2 files changed, 25 insertions(+), 3 deletions(-)
diff --git a/app.js b/app.js
index 0eb789d47d..3f5ad89e3f 100644
--- a/app.js
+++ b/app.js
@@ -8,8 +8,10 @@ const port = process.env.PORT || 3000;
const verifyToken = process.env.VERIFY_TOKEN;
const whatsappPhoneNumberId = process.env.WHATSAPP_PHONE_NUMBER_ID;
const whatsappToken = process.env.WHATSAPP_TOKEN;
+const openaiBaseURL = process.env.OPENAI_BASE_URL || undefined;
+const openaiModel = process.env.OPENAI_MODEL || 'gpt-4o';
-const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
+const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, baseURL: openaiBaseURL });
// In-memory conversation history per phone number (last 20 messages kept)
const conversationHistory = new Map();
@@ -157,8 +159,12 @@ If the request is ambiguous or missing details, use ask_for_more_information.`,
{ role: 'user', content: userMessage },
];
+ console.log(
+ `[decideAction] calling chat.completions.create — model: ${openaiModel}, baseURL: ${openai.baseURL}, phone: ${phoneNumber}`
+ );
+
const response = await openai.chat.completions.create({
- model: 'gpt-4o',
+ model: openaiModel,
messages,
tools,
tool_choice: 'required',
@@ -242,10 +248,22 @@ app.post('/', async (req, res) => {
appendHistory(phoneNumber, 'assistant', replyText);
}
} catch (err) {
- console.error('Error handling message:', err.message);
+ console.error('Error handling message:', {
+ message: err.message,
+ status: err.status,
+ code: err.code,
+ type: err.type,
+ requestID: err.requestID,
+ error: err.error,
+ model: openaiModel,
+ baseURL: openai.baseURL,
+ });
}
});
app.listen(port, () => {
console.log(`\nListening on port ${port}\n`);
+ console.log(`[startup] OPENAI_BASE_URL: ${process.env.OPENAI_BASE_URL ?? '(unset, defaults to api.openai.com)'}`);
+ console.log(`[startup] OPENAI_MODEL: ${openaiModel}`);
+ console.log(`[startup] OPENAI_API_KEY set: ${Boolean(process.env.OPENAI_API_KEY)}`);
});
diff --git a/render.yaml b/render.yaml
index f4b937f914..55fe1154ed 100644
--- a/render.yaml
+++ b/render.yaml
@@ -16,3 +16,7 @@ services:
sync: false
- key: OPENAI_API_KEY
sync: false
+ - key: OPENAI_BASE_URL
+ sync: false
+ - key: OPENAI_MODEL
+ sync: false
From 272e14ae2fb920eb771d26c0641be77bb72f267f Mon Sep 17 00:00:00 2001
From: priyankmodiPM <32540484+priyankmodiPM@users.noreply.github.com>
Date: Mon, 13 Jul 2026 17:51:28 +0530
Subject: [PATCH 08/20] Feat/express edit flow (#4)
* docs: add design spec for mocked Adobe Express image-edit flow
* docs: add implementation plan for image-edit flow
* feat: add in-memory tracked-image store
* feat: add mock Adobe Express API module
* feat: add mock Meta media upload module
* feat: extract GPT action handlers and add image-edit validation flow
* feat: wire image-edit flow into the webhook and GPT tool schema
* feat: map Croma earbuds graphic to Price/Address/Product Image/Partner Logo edits
Replaces the New Arrival Poster mock seed image with the real Croma
earbuds graphic and its allowed-edit fields.
* feat: return fixed updated Croma earbuds image for any edit
Any allowed edit on the Croma earbuds graphic now resolves to the
fixed croma1-earbuds-updated image, sent as a new WhatsApp message,
instead of a randomized mock render/upload URL.
---------
Co-authored-by: Priyank Modi
---
actions.js | 61 ++
actions.test.js | 66 ++
app.js | 58 +-
.../plans/2026-07-13-express-edit-flow.md | 705 ++++++++++++++++++
.../2026-07-13-express-edit-flow-design.md | 132 ++++
expressApi.js | 33 +
expressApi.test.js | 39 +
imageStore.js | 28 +
imageStore.test.js | 35 +
metaUpload.js | 10 +
metaUpload.test.js | 19 +
package.json | 3 +-
12 files changed, 1158 insertions(+), 31 deletions(-)
create mode 100644 actions.js
create mode 100644 actions.test.js
create mode 100644 docs/superpowers/plans/2026-07-13-express-edit-flow.md
create mode 100644 docs/superpowers/specs/2026-07-13-express-edit-flow-design.md
create mode 100644 expressApi.js
create mode 100644 expressApi.test.js
create mode 100644 imageStore.js
create mode 100644 imageStore.test.js
create mode 100644 metaUpload.js
create mode 100644 metaUpload.test.js
diff --git a/actions.js b/actions.js
new file mode 100644
index 0000000000..a990e7b913
--- /dev/null
+++ b/actions.js
@@ -0,0 +1,61 @@
+const { getTrackedImages, findTrackedImage } = require('./imageStore');
+const { getTemplateInfo, applyEdit } = require('./expressApi');
+const { uploadImageToMeta } = require('./metaUpload');
+
+function formatUnknownImageMessage(phoneNumber) {
+ const images = getTrackedImages(phoneNumber);
+ const list = images.map((image) => `- ${image.name}`).join('\n');
+ return `I couldn't find that image. Here's what I have:\n${list}`;
+}
+
+async function actionListCampaignGraphics() {
+ // TODO: fetch from campaign API
+ return 'Graphics in your current campaign:\n1. Diwali Offer Banner\n2. Summer Sale Flyer\n3. Croma Earbuds';
+}
+
+async function actionCheckAllowedEdits(phoneNumber, imageId) {
+ const image = findTrackedImage(phoneNumber, imageId);
+ if (!image) {
+ return formatUnknownImageMessage(phoneNumber);
+ }
+ const { unlockedLayers } = getTemplateInfo(image.templateId);
+ const layerList = unlockedLayers.map((layer) => `- ${layer}`).join('\n');
+ return `Edits allowed on "${image.name}":\n${layerList}`;
+}
+
+async function actionEditGraphic(phoneNumber, imageId, edits, { sendImage }) {
+ const image = findTrackedImage(phoneNumber, imageId);
+ if (!image) {
+ return formatUnknownImageMessage(phoneNumber);
+ }
+
+ const { unlockedLayers } = getTemplateInfo(image.templateId);
+ const requestedKeys = Object.keys(edits || {});
+ const disallowedKeys = requestedKeys.filter((key) => !unlockedLayers.includes(key));
+
+ if (disallowedKeys.length > 0) {
+ const allowedList = unlockedLayers.map((layer) => `- ${layer}`).join('\n');
+ return `I can't edit ${disallowedKeys.join(', ')} on "${image.name}". Allowed edits:\n${allowedList}`;
+ }
+
+ const { mergedEdits, renderedImageUrl } = applyEdit(image.templateId, image.currentEdits, edits);
+ image.currentEdits = mergedEdits;
+
+ const uploadedUrl = await uploadImageToMeta(renderedImageUrl);
+ await sendImage(phoneNumber, uploadedUrl);
+
+ const summary = Object.entries(edits).map(([key, value]) => `• ${key}: ${value}`).join('\n');
+ return `Updated "${image.name}":\n${summary}`;
+}
+
+async function actionGenerateBulkGraphics(filename) {
+ // TODO: parse CSV/Excel and call Adobe Express API per row
+ return `Bulk generation complete! Graphics created from ${filename || 'your uploaded file'}.`;
+}
+
+module.exports = {
+ actionListCampaignGraphics,
+ actionCheckAllowedEdits,
+ actionEditGraphic,
+ actionGenerateBulkGraphics,
+};
diff --git a/actions.test.js b/actions.test.js
new file mode 100644
index 0000000000..fa88280a39
--- /dev/null
+++ b/actions.test.js
@@ -0,0 +1,66 @@
+const test = require('node:test');
+const assert = require('node:assert/strict');
+const { actionCheckAllowedEdits, actionEditGraphic } = require('./actions');
+const { getTrackedImages } = require('./imageStore');
+
+test('actionCheckAllowedEdits lists the unlocked layers for a known image', async () => {
+ const reply = await actionCheckAllowedEdits('phone-1', 'img_1');
+ assert.match(reply, /Diwali Offer Banner/);
+ assert.match(reply, /discount_text/);
+});
+
+test('actionCheckAllowedEdits lists Price, Address, Product Image, Partner Logo for the Croma earbuds image', async () => {
+ const reply = await actionCheckAllowedEdits('phone-croma', 'img_3');
+ assert.match(reply, /Croma Earbuds/);
+ assert.match(reply, /- Price/);
+ assert.match(reply, /- Address/);
+ assert.match(reply, /- Product Image/);
+ assert.match(reply, /- Partner Logo/);
+});
+
+test('actionCheckAllowedEdits reports unknown images without throwing', async () => {
+ const reply = await actionCheckAllowedEdits('phone-2', 'img_nope');
+ assert.match(reply, /couldn't find that image/);
+});
+
+test('actionEditGraphic rejects edits outside the unlocked layers and sends nothing', async () => {
+ let sendImageCalled = false;
+ const sendImage = async () => {
+ sendImageCalled = true;
+ };
+
+ const reply = await actionEditGraphic('phone-3', 'img_3', { background_color: 'red' }, { sendImage });
+
+ assert.match(reply, /can't edit background_color/);
+ assert.equal(sendImageCalled, false);
+});
+
+test('actionEditGraphic sends the fixed updated Croma earbuds image for any allowed edit', async () => {
+ const sentCalls = [];
+ const sendImage = async (to, link) => {
+ sentCalls.push({ to, link });
+ };
+
+ const reply = await actionEditGraphic('phone-5', 'img_3', { Price: '999' }, { sendImage });
+
+ assert.match(reply, /Updated "Croma Earbuds"/);
+ assert.equal(sentCalls.length, 1);
+ assert.equal(sentCalls[0].link, 'https://s7ap1.scene7.com/is/image/varun/croma1-earbuds-updated');
+});
+
+test('actionEditGraphic applies an allowed edit, sends the updated image, and remembers the edit', async () => {
+ const sentCalls = [];
+ const sendImage = async (to, link) => {
+ sentCalls.push({ to, link });
+ };
+
+ const reply = await actionEditGraphic('phone-4', 'img_2', { headline: 'Flash Sale' }, { sendImage });
+
+ assert.match(reply, /Updated "Summer Sale Flyer"/);
+ assert.equal(sentCalls.length, 1);
+ assert.equal(sentCalls[0].to, 'phone-4');
+ assert.match(sentCalls[0].link, /^https:\/\/mock-meta-cdn\.local\//);
+
+ const image = getTrackedImages('phone-4').find((img) => img.id === 'img_2');
+ assert.equal(image.currentEdits.headline, 'Flash Sale');
+});
diff --git a/app.js b/app.js
index 3f5ad89e3f..1d703d50e1 100644
--- a/app.js
+++ b/app.js
@@ -1,5 +1,12 @@
const express = require('express');
const OpenAI = require('openai');
+const { getTrackedImages } = require('./imageStore');
+const {
+ actionListCampaignGraphics,
+ actionCheckAllowedEdits,
+ actionEditGraphic,
+ actionGenerateBulkGraphics,
+} = require('./actions');
const app = express();
app.use(express.json());
@@ -83,24 +90,33 @@ const tools = [
type: 'function',
function: {
name: 'check_allowed_edits',
- description: 'Check what edits are permitted on the current graphic',
- parameters: { type: 'object', properties: {} },
+ description:
+ 'Check what edits are permitted on a specific graphic. Pick image_id from the "Images previously sent to this user" list in the system prompt that best matches what the user is referring to.',
+ parameters: {
+ type: 'object',
+ properties: {
+ image_id: { type: 'string', description: 'The id of the image the user is asking about, from the tracked images list' },
+ },
+ required: ['image_id'],
+ },
},
},
{
type: 'function',
function: {
name: 'edit_graphic',
- description: 'Edit the current graphic via Adobe Express API (e.g. change discount text, colors)',
+ description:
+ 'Edit a specific graphic via Adobe Express API (e.g. change discount text, colors). Pick image_id from the "Images previously sent to this user" list in the system prompt that best matches what the user is referring to.',
parameters: {
type: 'object',
properties: {
+ image_id: { type: 'string', description: 'The id of the image to edit, from the tracked images list' },
edits: {
type: 'object',
description: 'Key-value pairs of edits to apply, e.g. { "discount_text": "70%" }',
},
},
- required: ['edits'],
+ required: ['image_id', 'edits'],
},
},
},
@@ -119,33 +135,12 @@ const tools = [
},
];
-// ── Action handlers (stubs — wire real APIs here) ────────────────────────────
-
-async function actionListCampaignGraphics() {
- // TODO: fetch from campaign API
- return 'Graphics in your current campaign:\n1. Diwali Offer Banner\n2. Summer Sale Flyer\n3. New Arrival Poster';
-}
-
-async function actionCheckAllowedEdits() {
- // TODO: fetch from Adobe Express API
- return 'Edits allowed on the current graphic:\n- Discount percentage\n- Headline text\n- Background color\n- Font color';
-}
-
-async function actionEditGraphic(edits) {
- // TODO: call Adobe Express API
- const summary = Object.entries(edits).map(([k, v]) => `• ${k}: ${v}`).join('\n');
- return `Graphic updated successfully:\n${summary}`;
-}
-
-async function actionGenerateBulkGraphics(filename) {
- // TODO: parse CSV/Excel and call Adobe Express API per row
- return `Bulk generation complete! Graphics created from ${filename || 'your uploaded file'}.`;
-}
-
// ── GPT decision engine ──────────────────────────────────────────────────────
async function decideAction(phoneNumber, userMessage) {
const last3 = getHistory(phoneNumber).slice(-3);
+ const trackedImages = getTrackedImages(phoneNumber);
+ const imagesList = trackedImages.map((image) => `- ${image.id}: ${image.name}`).join('\n');
const messages = [
{
@@ -153,7 +148,10 @@ async function decideAction(phoneNumber, userMessage) {
content: `You are a WhatsApp assistant for managing marketing campaign graphics via Adobe Express.
Analyze the user's message and conversation history, then call the appropriate tool.
Always call exactly one tool — never reply with plain text.
-If the request is ambiguous or missing details, use ask_for_more_information.`,
+If the request is ambiguous or missing details, use ask_for_more_information.
+
+Images previously sent to this user (reference by id):
+${imagesList}`,
},
...last3,
{ role: 'user', content: userMessage },
@@ -227,12 +225,12 @@ app.post('/', async (req, res) => {
case 'check_allowed_edits':
await sendText(phoneNumber, '⏳ Checking allowed edits...');
- replyText = await actionCheckAllowedEdits();
+ replyText = await actionCheckAllowedEdits(phoneNumber, args.image_id);
break;
case 'edit_graphic':
await sendText(phoneNumber, '⏳ Applying edits to your graphic...');
- replyText = await actionEditGraphic(args.edits);
+ replyText = await actionEditGraphic(phoneNumber, args.image_id, args.edits, { sendImage });
break;
case 'generate_bulk_graphics':
diff --git a/docs/superpowers/plans/2026-07-13-express-edit-flow.md b/docs/superpowers/plans/2026-07-13-express-edit-flow.md
new file mode 100644
index 0000000000..b36d079cae
--- /dev/null
+++ b/docs/superpowers/plans/2026-07-13-express-edit-flow.md
@@ -0,0 +1,705 @@
+# Express Image Edit Flow Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Let a WhatsApp user ask what edits are allowed on a previously-sent graphic, request an edit, have it validated against the graphic's unlocked layers, applied via a mocked Adobe Express API, uploaded via a mocked Meta upload, and re-sent as an updated image — all with placeholder Express/Meta calls that can be swapped for real ones later.
+
+**Architecture:** Three new small, dependency-free modules (`imageStore.js` for per-phone-number tracked-image state, `expressApi.js` for mock template/edit calls, `metaUpload.js` for the mock media upload) get unit tests via Node's built-in test runner. A fourth new module, `actions.js`, extracts all GPT tool-call action handlers (including the two existing ones, `list_campaign_graphics` and `generate_bulk_graphics`, moved unchanged) so the new `edit_graphic` logic can be unit-tested with a fake `sendImage` — no real WhatsApp/OpenAI credentials needed for tests. `app.js` is left as the thin orchestrator: WhatsApp API calls, GPT tool schemas/system prompt, and the webhook route wiring args into `actions.js`.
+
+**Tech Stack:** Node.js 20, Express 5, `openai` SDK, Node's built-in `node:test` + `node:assert/strict` (no new dependencies).
+
+## Global Constraints
+
+- No real Adobe Express or Meta API calls — all such calls are placeholder/mock functions returning mock values (per spec, these get swapped for real implementations later).
+- Actually sending the initial graphic image is out of scope — tracked images are pre-seeded in memory at first access per phone number (spec: 3 fixed seed entries: Diwali Offer Banner / Summer Sale Flyer / New Arrival Poster).
+- State is in-memory only, no persistence across restarts (matches existing `conversationHistory` pattern in app.js).
+- No new npm dependencies — use Node's built-in `node:test` test runner.
+- Follow existing code style: CommonJS `require`, 2-space indentation, semicolons.
+
+---
+
+## Task 1: `imageStore.js` — tracked-image state
+
+**Files:**
+- Create: `imageStore.js`
+- Test: `imageStore.test.js`
+- Modify: `package.json` (add a `test` script)
+
+**Interfaces:**
+- Produces: `getTrackedImages(phoneNumber) -> Array<{ id: string, name: string, templateId: string, currentEdits: object }>` (seeds 3 fixed entries on first call for a phone number, returns the same array reference on later calls).
+- Produces: `findTrackedImage(phoneNumber, imageId) -> object | undefined`.
+
+- [ ] **Step 1: Write the failing test**
+
+Create `imageStore.test.js`:
+
+```js
+const test = require('node:test');
+const assert = require('node:assert/strict');
+const { getTrackedImages, findTrackedImage } = require('./imageStore');
+
+test('getTrackedImages seeds 3 images on first access', () => {
+ const images = getTrackedImages('111');
+ assert.equal(images.length, 3);
+ assert.deepEqual(images.map((img) => img.id), ['img_1', 'img_2', 'img_3']);
+ assert.deepEqual(images[0].currentEdits, {});
+});
+
+test('getTrackedImages returns the same array on repeated calls for the same phone number', () => {
+ const first = getTrackedImages('222');
+ first[0].currentEdits.headline = 'Flash Sale';
+ const second = getTrackedImages('222');
+ assert.equal(second[0].currentEdits.headline, 'Flash Sale');
+});
+
+test('getTrackedImages seeds independently per phone number', () => {
+ getTrackedImages('333')[0].currentEdits.headline = 'Only for 333';
+ const other = getTrackedImages('444');
+ assert.deepEqual(other[0].currentEdits, {});
+});
+
+test('findTrackedImage returns the matching image', () => {
+ getTrackedImages('555');
+ const image = findTrackedImage('555', 'img_2');
+ assert.equal(image.name, 'Summer Sale Flyer');
+});
+
+test('findTrackedImage returns undefined for an unknown id', () => {
+ getTrackedImages('666');
+ const image = findTrackedImage('666', 'img_999');
+ assert.equal(image, undefined);
+});
+```
+
+- [ ] **Step 2: Add the test script and run to verify failure**
+
+Modify `package.json` scripts section to:
+
+```json
+"scripts": {
+ "start": "node app.js",
+ "test": "node --test"
+},
+```
+
+Run: `node --test imageStore.test.js`
+Expected: FAIL — `Cannot find module './imageStore'`
+
+- [ ] **Step 3: Write the implementation**
+
+Create `imageStore.js`:
+
+```js
+const SEED_IMAGES = [
+ { id: 'img_1', name: 'Diwali Offer Banner', templateId: 'tpl_diwali' },
+ { id: 'img_2', name: 'Summer Sale Flyer', templateId: 'tpl_summer' },
+ { id: 'img_3', name: 'New Arrival Poster', templateId: 'tpl_newarrival' },
+];
+
+const trackedImages = new Map();
+
+function getTrackedImages(phoneNumber) {
+ if (!trackedImages.has(phoneNumber)) {
+ trackedImages.set(
+ phoneNumber,
+ SEED_IMAGES.map((image) => ({ ...image, currentEdits: {} }))
+ );
+ }
+ return trackedImages.get(phoneNumber);
+}
+
+function findTrackedImage(phoneNumber, imageId) {
+ return getTrackedImages(phoneNumber).find((image) => image.id === imageId);
+}
+
+module.exports = { getTrackedImages, findTrackedImage };
+```
+
+- [ ] **Step 4: Run test to verify it passes**
+
+Run: `node --test imageStore.test.js`
+Expected: PASS — 5 tests passing
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add imageStore.js imageStore.test.js package.json
+git commit -m "feat: add in-memory tracked-image store"
+```
+
+---
+
+## Task 2: `expressApi.js` — mock Adobe Express calls
+
+**Files:**
+- Create: `expressApi.js`
+- Test: `expressApi.test.js`
+
+**Interfaces:**
+- Consumes: nothing (standalone module).
+- Produces: `getTemplateInfo(templateId) -> { templateId: string, unlockedLayers: string[] }`.
+- Produces: `applyEdit(templateId, currentEdits, newEdits) -> { mergedEdits: object, renderedImageUrl: string }`.
+
+- [ ] **Step 1: Write the failing test**
+
+Create `expressApi.test.js`:
+
+```js
+const test = require('node:test');
+const assert = require('node:assert/strict');
+const { getTemplateInfo, applyEdit } = require('./expressApi');
+
+test('getTemplateInfo returns the unlocked layers for a known template', () => {
+ const info = getTemplateInfo('tpl_diwali');
+ assert.deepEqual(info, {
+ templateId: 'tpl_diwali',
+ unlockedLayers: ['discount_text', 'headline', 'background_color'],
+ });
+});
+
+test('getTemplateInfo returns an empty layer list for an unknown template', () => {
+ const info = getTemplateInfo('tpl_does_not_exist');
+ assert.deepEqual(info.unlockedLayers, []);
+});
+
+test('applyEdit merges new edits on top of current edits', () => {
+ const result = applyEdit('tpl_diwali', { headline: 'Old Headline' }, { discount_text: '70%' });
+ assert.deepEqual(result.mergedEdits, { headline: 'Old Headline', discount_text: '70%' });
+});
+
+test('applyEdit returns a rendered image url that references the template', () => {
+ const result = applyEdit('tpl_summer', {}, { headline: 'Flash Sale' });
+ assert.match(result.renderedImageUrl, /^https:\/\/mock-express\.local\/render\/tpl_summer\?rev=\d+$/);
+});
+```
+
+- [ ] **Step 2: Run test to verify it fails**
+
+Run: `node --test expressApi.test.js`
+Expected: FAIL — `Cannot find module './expressApi'`
+
+- [ ] **Step 3: Write the implementation**
+
+Create `expressApi.js`:
+
+```js
+const TEMPLATE_LAYERS = {
+ tpl_diwali: ['discount_text', 'headline', 'background_color'],
+ tpl_summer: ['headline', 'font_color'],
+ tpl_newarrival: ['headline'],
+};
+
+function getTemplateInfo(templateId) {
+ return {
+ templateId,
+ unlockedLayers: TEMPLATE_LAYERS[templateId] || [],
+ };
+}
+
+let renderRevision = 0;
+
+function applyEdit(templateId, currentEdits, newEdits) {
+ const mergedEdits = { ...currentEdits, ...newEdits };
+ renderRevision += 1;
+ return {
+ mergedEdits,
+ renderedImageUrl: `https://mock-express.local/render/${templateId}?rev=${renderRevision}`,
+ };
+}
+
+module.exports = { getTemplateInfo, applyEdit };
+```
+
+- [ ] **Step 4: Run test to verify it passes**
+
+Run: `node --test expressApi.test.js`
+Expected: PASS — 4 tests passing
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add expressApi.js expressApi.test.js
+git commit -m "feat: add mock Adobe Express API module"
+```
+
+---
+
+## Task 3: `metaUpload.js` — mock Meta media upload
+
+**Files:**
+- Create: `metaUpload.js`
+- Test: `metaUpload.test.js`
+
+**Interfaces:**
+- Consumes: nothing (standalone module).
+- Produces: `uploadImageToMeta(renderedImageUrl) -> Promise` (mock CDN URL).
+
+- [ ] **Step 1: Write the failing test**
+
+Create `metaUpload.test.js`:
+
+```js
+const test = require('node:test');
+const assert = require('node:assert/strict');
+const { uploadImageToMeta } = require('./metaUpload');
+
+test('uploadImageToMeta returns a mock CDN url', async () => {
+ const url = await uploadImageToMeta('https://mock-express.local/render/tpl_diwali?rev=1');
+ assert.match(url, /^https:\/\/mock-meta-cdn\.local\/media\/[0-9a-f-]+\.png$/);
+});
+
+test('uploadImageToMeta returns a different url on each call', async () => {
+ const first = await uploadImageToMeta('https://mock-express.local/render/tpl_diwali?rev=1');
+ const second = await uploadImageToMeta('https://mock-express.local/render/tpl_diwali?rev=2');
+ assert.notEqual(first, second);
+});
+```
+
+- [ ] **Step 2: Run test to verify it fails**
+
+Run: `node --test metaUpload.test.js`
+Expected: FAIL — `Cannot find module './metaUpload'`
+
+- [ ] **Step 3: Write the implementation**
+
+Create `metaUpload.js`:
+
+```js
+const { randomUUID } = require('node:crypto');
+
+async function uploadImageToMeta(renderedImageUrl) {
+ return `https://mock-meta-cdn.local/media/${randomUUID()}.png`;
+}
+
+module.exports = { uploadImageToMeta };
+```
+
+- [ ] **Step 4: Run test to verify it passes**
+
+Run: `node --test metaUpload.test.js`
+Expected: PASS — 2 tests passing
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add metaUpload.js metaUpload.test.js
+git commit -m "feat: add mock Meta media upload module"
+```
+
+---
+
+## Task 4: `actions.js` — extract and extend GPT tool-call action handlers
+
+**Files:**
+- Create: `actions.js`
+- Test: `actions.test.js`
+
+**Interfaces:**
+- Consumes: `getTrackedImages`, `findTrackedImage` from `./imageStore` (Task 1); `getTemplateInfo`, `applyEdit` from `./expressApi` (Task 2); `uploadImageToMeta` from `./metaUpload` (Task 3).
+- Produces: `actionListCampaignGraphics() -> Promise` (unchanged behavior, moved from app.js).
+- Produces: `actionCheckAllowedEdits(phoneNumber, imageId) -> Promise`.
+- Produces: `actionEditGraphic(phoneNumber, imageId, edits, { sendImage }) -> Promise` — `sendImage` is injected `(to, link) => Promise` so tests don't need real WhatsApp calls.
+- Produces: `actionGenerateBulkGraphics(filename) -> Promise` (unchanged behavior, moved from app.js).
+
+- [ ] **Step 1: Write the failing test**
+
+Create `actions.test.js`:
+
+```js
+const test = require('node:test');
+const assert = require('node:assert/strict');
+const { actionCheckAllowedEdits, actionEditGraphic } = require('./actions');
+const { getTrackedImages } = require('./imageStore');
+
+test('actionCheckAllowedEdits lists the unlocked layers for a known image', async () => {
+ const reply = await actionCheckAllowedEdits('phone-1', 'img_1');
+ assert.match(reply, /Diwali Offer Banner/);
+ assert.match(reply, /discount_text/);
+});
+
+test('actionCheckAllowedEdits reports unknown images without throwing', async () => {
+ const reply = await actionCheckAllowedEdits('phone-2', 'img_nope');
+ assert.match(reply, /couldn't find that image/);
+});
+
+test('actionEditGraphic rejects edits outside the unlocked layers and sends nothing', async () => {
+ let sendImageCalled = false;
+ const sendImage = async () => {
+ sendImageCalled = true;
+ };
+
+ const reply = await actionEditGraphic('phone-3', 'img_3', { background_color: 'red' }, { sendImage });
+
+ assert.match(reply, /can't edit background_color/);
+ assert.equal(sendImageCalled, false);
+});
+
+test('actionEditGraphic applies an allowed edit, sends the updated image, and remembers the edit', async () => {
+ const sentCalls = [];
+ const sendImage = async (to, link) => {
+ sentCalls.push({ to, link });
+ };
+
+ const reply = await actionEditGraphic('phone-4', 'img_2', { headline: 'Flash Sale' }, { sendImage });
+
+ assert.match(reply, /Updated "Summer Sale Flyer"/);
+ assert.equal(sentCalls.length, 1);
+ assert.equal(sentCalls[0].to, 'phone-4');
+ assert.match(sentCalls[0].link, /^https:\/\/mock-meta-cdn\.local\//);
+
+ const image = getTrackedImages('phone-4').find((img) => img.id === 'img_2');
+ assert.equal(image.currentEdits.headline, 'Flash Sale');
+});
+```
+
+- [ ] **Step 2: Run test to verify it fails**
+
+Run: `node --test actions.test.js`
+Expected: FAIL — `Cannot find module './actions'`
+
+- [ ] **Step 3: Write the implementation**
+
+Create `actions.js`:
+
+```js
+const { getTrackedImages, findTrackedImage } = require('./imageStore');
+const { getTemplateInfo, applyEdit } = require('./expressApi');
+const { uploadImageToMeta } = require('./metaUpload');
+
+function formatUnknownImageMessage(phoneNumber) {
+ const images = getTrackedImages(phoneNumber);
+ const list = images.map((image) => `- ${image.name}`).join('\n');
+ return `I couldn't find that image. Here's what I have:\n${list}`;
+}
+
+async function actionListCampaignGraphics() {
+ // TODO: fetch from campaign API
+ return 'Graphics in your current campaign:\n1. Diwali Offer Banner\n2. Summer Sale Flyer\n3. New Arrival Poster';
+}
+
+async function actionCheckAllowedEdits(phoneNumber, imageId) {
+ const image = findTrackedImage(phoneNumber, imageId);
+ if (!image) {
+ return formatUnknownImageMessage(phoneNumber);
+ }
+ const { unlockedLayers } = getTemplateInfo(image.templateId);
+ const layerList = unlockedLayers.map((layer) => `- ${layer}`).join('\n');
+ return `Edits allowed on "${image.name}":\n${layerList}`;
+}
+
+async function actionEditGraphic(phoneNumber, imageId, edits, { sendImage }) {
+ const image = findTrackedImage(phoneNumber, imageId);
+ if (!image) {
+ return formatUnknownImageMessage(phoneNumber);
+ }
+
+ const { unlockedLayers } = getTemplateInfo(image.templateId);
+ const requestedKeys = Object.keys(edits || {});
+ const disallowedKeys = requestedKeys.filter((key) => !unlockedLayers.includes(key));
+
+ if (disallowedKeys.length > 0) {
+ const allowedList = unlockedLayers.map((layer) => `- ${layer}`).join('\n');
+ return `I can't edit ${disallowedKeys.join(', ')} on "${image.name}". Allowed edits:\n${allowedList}`;
+ }
+
+ const { mergedEdits, renderedImageUrl } = applyEdit(image.templateId, image.currentEdits, edits);
+ image.currentEdits = mergedEdits;
+
+ const uploadedUrl = await uploadImageToMeta(renderedImageUrl);
+ await sendImage(phoneNumber, uploadedUrl);
+
+ const summary = Object.entries(edits).map(([key, value]) => `• ${key}: ${value}`).join('\n');
+ return `Updated "${image.name}":\n${summary}`;
+}
+
+async function actionGenerateBulkGraphics(filename) {
+ // TODO: parse CSV/Excel and call Adobe Express API per row
+ return `Bulk generation complete! Graphics created from ${filename || 'your uploaded file'}.`;
+}
+
+module.exports = {
+ actionListCampaignGraphics,
+ actionCheckAllowedEdits,
+ actionEditGraphic,
+ actionGenerateBulkGraphics,
+};
+```
+
+- [ ] **Step 4: Run test to verify it passes**
+
+Run: `node --test actions.test.js`
+Expected: PASS — 4 tests passing
+
+- [ ] **Step 5: Run the full test suite**
+
+Run: `node --test`
+Expected: PASS — all tests across `imageStore.test.js`, `expressApi.test.js`, `metaUpload.test.js`, `actions.test.js` (15 tests total)
+
+- [ ] **Step 6: Commit**
+
+```bash
+git add actions.js actions.test.js
+git commit -m "feat: extract GPT action handlers and add image-edit validation flow"
+```
+
+---
+
+## Task 5: Wire `app.js` to the new modules
+
+**Files:**
+- Modify: `app.js:1-2` (imports)
+- Modify: `app.js:59-120` (tool definitions)
+- Modify: `app.js:122-143` (delete — action handlers now live in `actions.js`)
+- Modify: `app.js:147-174` (`decideAction` system prompt)
+- Modify: `app.js:218-245` (webhook switch statement)
+
+**Interfaces:**
+- Consumes: `getTrackedImages` from `./imageStore` (Task 1); `actionListCampaignGraphics`, `actionCheckAllowedEdits`, `actionEditGraphic`, `actionGenerateBulkGraphics` from `./actions` (Task 4).
+- Produces: nothing new — this task is pure wiring, verified manually (no unit test; this is the orchestration layer that touches the real WhatsApp/OpenAI SDKs already excluded from automated testing per the spec).
+
+- [ ] **Step 1: Update imports**
+
+At the top of `app.js`, replace:
+
+```js
+const express = require('express');
+const OpenAI = require('openai');
+```
+
+with:
+
+```js
+const express = require('express');
+const OpenAI = require('openai');
+const { getTrackedImages } = require('./imageStore');
+const {
+ actionListCampaignGraphics,
+ actionCheckAllowedEdits,
+ actionEditGraphic,
+ actionGenerateBulkGraphics,
+} = require('./actions');
+```
+
+- [ ] **Step 2: Add `image_id` to the `check_allowed_edits` and `edit_graphic` tool definitions**
+
+In the `tools` array, replace the `check_allowed_edits` entry:
+
+```js
+ {
+ type: 'function',
+ function: {
+ name: 'check_allowed_edits',
+ description: 'Check what edits are permitted on the current graphic',
+ parameters: { type: 'object', properties: {} },
+ },
+ },
+```
+
+with:
+
+```js
+ {
+ type: 'function',
+ function: {
+ name: 'check_allowed_edits',
+ description:
+ 'Check what edits are permitted on a specific graphic. Pick image_id from the "Images previously sent to this user" list in the system prompt that best matches what the user is referring to.',
+ parameters: {
+ type: 'object',
+ properties: {
+ image_id: { type: 'string', description: 'The id of the image the user is asking about, from the tracked images list' },
+ },
+ required: ['image_id'],
+ },
+ },
+ },
+```
+
+And replace the `edit_graphic` entry:
+
+```js
+ {
+ type: 'function',
+ function: {
+ name: 'edit_graphic',
+ description: 'Edit the current graphic via Adobe Express API (e.g. change discount text, colors)',
+ parameters: {
+ type: 'object',
+ properties: {
+ edits: {
+ type: 'object',
+ description: 'Key-value pairs of edits to apply, e.g. { "discount_text": "70%" }',
+ },
+ },
+ required: ['edits'],
+ },
+ },
+ },
+```
+
+with:
+
+```js
+ {
+ type: 'function',
+ function: {
+ name: 'edit_graphic',
+ description:
+ 'Edit a specific graphic via Adobe Express API (e.g. change discount text, colors). Pick image_id from the "Images previously sent to this user" list in the system prompt that best matches what the user is referring to.',
+ parameters: {
+ type: 'object',
+ properties: {
+ image_id: { type: 'string', description: 'The id of the image to edit, from the tracked images list' },
+ edits: {
+ type: 'object',
+ description: 'Key-value pairs of edits to apply, e.g. { "discount_text": "70%" }',
+ },
+ },
+ required: ['image_id', 'edits'],
+ },
+ },
+ },
+```
+
+- [ ] **Step 3: Delete the old inline action handlers**
+
+Delete this entire block (now provided by `actions.js`):
+
+```js
+// ── Action handlers (stubs — wire real APIs here) ────────────────────────────
+
+async function actionListCampaignGraphics() {
+ // TODO: fetch from campaign API
+ return 'Graphics in your current campaign:\n1. Diwali Offer Banner\n2. Summer Sale Flyer\n3. New Arrival Poster';
+}
+
+async function actionCheckAllowedEdits() {
+ // TODO: fetch from Adobe Express API
+ return 'Edits allowed on the current graphic:\n- Discount percentage\n- Headline text\n- Background color\n- Font color';
+}
+
+async function actionEditGraphic(edits) {
+ // TODO: call Adobe Express API
+ const summary = Object.entries(edits).map(([k, v]) => `• ${k}: ${v}`).join('\n');
+ return `Graphic updated successfully:\n${summary}`;
+}
+
+async function actionGenerateBulkGraphics(filename) {
+ // TODO: parse CSV/Excel and call Adobe Express API per row
+ return `Bulk generation complete! Graphics created from ${filename || 'your uploaded file'}.`;
+}
+```
+
+- [ ] **Step 4: Add the tracked-images list to the `decideAction` system prompt**
+
+Replace:
+
+```js
+async function decideAction(phoneNumber, userMessage) {
+ const last3 = getHistory(phoneNumber).slice(-3);
+
+ const messages = [
+ {
+ role: 'system',
+ content: `You are a WhatsApp assistant for managing marketing campaign graphics via Adobe Express.
+Analyze the user's message and conversation history, then call the appropriate tool.
+Always call exactly one tool — never reply with plain text.
+If the request is ambiguous or missing details, use ask_for_more_information.`,
+ },
+ ...last3,
+ { role: 'user', content: userMessage },
+ ];
+```
+
+with:
+
+```js
+async function decideAction(phoneNumber, userMessage) {
+ const last3 = getHistory(phoneNumber).slice(-3);
+ const trackedImages = getTrackedImages(phoneNumber);
+ const imagesList = trackedImages.map((image) => `- ${image.id}: ${image.name}`).join('\n');
+
+ const messages = [
+ {
+ role: 'system',
+ content: `You are a WhatsApp assistant for managing marketing campaign graphics via Adobe Express.
+Analyze the user's message and conversation history, then call the appropriate tool.
+Always call exactly one tool — never reply with plain text.
+If the request is ambiguous or missing details, use ask_for_more_information.
+
+Images previously sent to this user (reference by id):
+${imagesList}`,
+ },
+ ...last3,
+ { role: 'user', content: userMessage },
+ ];
+```
+
+- [ ] **Step 5: Pass `phoneNumber`/`image_id`/`sendImage` through the webhook switch**
+
+Replace:
+
+```js
+ case 'check_allowed_edits':
+ await sendText(phoneNumber, '⏳ Checking allowed edits...');
+ replyText = await actionCheckAllowedEdits();
+ break;
+
+ case 'edit_graphic':
+ await sendText(phoneNumber, '⏳ Applying edits to your graphic...');
+ replyText = await actionEditGraphic(args.edits);
+ break;
+```
+
+with:
+
+```js
+ case 'check_allowed_edits':
+ await sendText(phoneNumber, '⏳ Checking allowed edits...');
+ replyText = await actionCheckAllowedEdits(phoneNumber, args.image_id);
+ break;
+
+ case 'edit_graphic':
+ await sendText(phoneNumber, '⏳ Applying edits to your graphic...');
+ replyText = await actionEditGraphic(phoneNumber, args.image_id, args.edits, { sendImage });
+ break;
+```
+
+- [ ] **Step 6: Verify the file is syntactically valid**
+
+Run: `node --check app.js`
+Expected: no output (silent success)
+
+- [ ] **Step 7: Verify the server still boots and the webhook-verification route still works**
+
+Run:
+
+```bash
+VERIFY_TOKEN=test WHATSAPP_PHONE_NUMBER_ID=123 WHATSAPP_TOKEN=test OPENAI_API_KEY=test node app.js &
+SERVER_PID=$!
+sleep 1
+curl -s "http://localhost:3000/?hub.mode=subscribe&hub.verify_token=test&hub.challenge=hello123"
+kill $SERVER_PID
+```
+
+Expected: `hello123` printed by curl, followed by `WEBHOOK VERIFIED` in the server's stdout before it's killed.
+
+- [ ] **Step 8: Run the full test suite once more to confirm nothing broke**
+
+Run: `node --test`
+Expected: PASS — all 15 tests passing
+
+- [ ] **Step 9: Commit**
+
+```bash
+git add app.js
+git commit -m "feat: wire image-edit flow into the webhook and GPT tool schema"
+```
+
+---
+
+## Self-Review Notes
+
+- **Spec coverage:** template/layer lookup (Task 2), allowed-edit validation (Task 4 `actionEditGraphic`), Express edit + Meta upload + re-send (Task 4 + Task 5 wiring), image reference inferred by GPT from a system-prompt-provided list (Task 5 Step 4), pre-seeded mock images since real sending is out of scope (Task 1) — all covered.
+- **Placeholder scan:** no TBDs; the two `// TODO: fetch from campaign API` / `// TODO: parse CSV/Excel...` comments are carried over unchanged from the existing code (out of scope for this feature) and are intentional, not gaps in this plan.
+- **Type consistency:** `sendImage` signature `(to, link) => Promise` is consistent between `actions.js`'s `actionEditGraphic` (Task 4) and its call site in `app.js` (Task 5 Step 5), which passes the existing `sendImage(to, link)` from `app.js`. `image.id`/`image.name`/`image.templateId`/`image.currentEdits` field names are consistent across `imageStore.js`, `expressApi.js` consumers, and `actions.js`.
diff --git a/docs/superpowers/specs/2026-07-13-express-edit-flow-design.md b/docs/superpowers/specs/2026-07-13-express-edit-flow-design.md
new file mode 100644
index 0000000000..272d63626f
--- /dev/null
+++ b/docs/superpowers/specs/2026-07-13-express-edit-flow-design.md
@@ -0,0 +1,132 @@
+# Design: Image edit flow (Adobe Express, mocked)
+
+## Goal
+
+Support this WhatsApp conversation flow:
+
+1. User has previously been sent a message containing an image (graphic).
+2. User asks what edits are allowed on one of those images.
+3. System looks up which template the image used and which layers are unlocked (via Adobe Express API).
+4. User asks for a specific edit.
+5. System checks whether the requested edit is one of the allowed edits.
+6. System calls the Adobe Express API to apply the edit.
+7. System uploads the resulting image to Meta and gets back a URL.
+8. System sends a new WhatsApp message with the updated image.
+
+Adobe Express API calls are out of scope for this iteration — all Express/Meta-upload calls are placeholder functions returning mock values, structured so they can be swapped for real implementations later without changing the surrounding flow.
+
+Actually sending the *initial* graphic image (step 1) is out of scope. Instead, a small set of mock "sent images" is pre-seeded at server startup for any phone number, so the rest of the flow has something to operate against.
+
+## Data model
+
+New in-memory store, `imageStore.js`, following the same per-phone-number Map pattern as the existing `conversationHistory`:
+
+```js
+// Map>
+// Seeded lazily on first access for a phone number — same 3 mock entries every time.
+{
+ id: 'img_1', // stable id GPT references in tool calls
+ name: 'Diwali Offer Banner', // human-readable, shown to GPT so it can match user text
+ templateId: 'tpl_diwali', // Express template id
+ currentEdits: {}, // accumulates applied edits across calls
+}
+```
+
+Three seed entries (mirroring the existing `list_campaign_graphics` mock names): Diwali Offer Banner / Summer Sale Flyer / New Arrival Poster, each with a distinct mock `templateId`.
+
+`getTrackedImages(phoneNumber)` returns (seeding if needed) the array; `findTrackedImage(phoneNumber, imageId)` returns one entry or `undefined`.
+
+## GPT tool changes
+
+GPT is responsible for figuring out *which* tracked image a user's message refers to (per your choice of "GPT infers from text"), so the system prompt must include the phone number's tracked images (id + name) on every call to `decideAction`. Both relevant tools gain an `image_id` parameter:
+
+```js
+check_allowed_edits({ image_id })
+edit_graphic({ image_id, edits })
+```
+
+`image_id` is required on both. Tool descriptions instruct GPT to pick the id from the list provided in the system prompt, matching the user's description (e.g. "the Diwali one") to the closest tracked image name.
+
+System prompt gains a section like:
+
+```
+Images previously sent to this user (reference by id):
+- img_1: Diwali Offer Banner
+- img_2: Summer Sale Flyer
+- img_3: New Arrival Poster
+```
+
+## Mock Adobe Express API (`expressApi.js`)
+
+Placeholder module, no network calls:
+
+```js
+function getTemplateInfo(templateId) {
+ // Mock: returns different unlocked layers per template so behavior isn't uniform.
+ // e.g. tpl_diwali -> ['discount_text', 'headline', 'background_color']
+ // tpl_summer -> ['headline', 'font_color']
+ // tpl_newarrival -> ['headline']
+}
+
+function applyEdit(templateId, currentEdits, newEdits) {
+ // Mock: merges edits, returns a fake rendered-image reference
+ // e.g. { renderedImageUrl: 'https://mock-express.local/render/tpl_diwali?rev=3' }
+}
+```
+
+Both are synchronous or trivially async (`Promise.resolve`) mocks — no real HTTP calls yet.
+
+## Mock Meta upload
+
+Added alongside the existing WhatsApp helpers in app.js (or a small `whatsapp.js` if we split further — not required for this scope):
+
+```js
+async function uploadImageToMeta(renderedImageUrl) {
+ // Mock: pretend to upload to Meta's media endpoint, return a fake CDN url
+ // e.g. `https://mock-meta-cdn.local/media/.png`
+}
+```
+
+`sendImage(to, link)` already exists and takes a link — the mock URL from `uploadImageToMeta` is passed straight into it.
+
+## Action handlers
+
+### `actionCheckAllowedEdits(phoneNumber, imageId)`
+
+1. `findTrackedImage(phoneNumber, imageId)` — if not found, return an error message ("I couldn't find that image — here's what I have: ...").
+2. `getTemplateInfo(image.templateId)` → `unlockedLayers`.
+3. Return a formatted list of allowed edits for that specific image (not the generic hardcoded string currently returned).
+
+### `actionEditGraphic(phoneNumber, imageId, edits)`
+
+1. `findTrackedImage(phoneNumber, imageId)` — same not-found handling as above.
+2. `getTemplateInfo(image.templateId)` → `unlockedLayers`.
+3. Validate: every key in `edits` must be in `unlockedLayers`. If any key isn't allowed, return a message naming the rejected field(s) and listing the actually-allowed fields — **do not call Express or Meta**. This is the "system checks if this is one of the allowed edits" step, done in code, not by GPT.
+4. `applyEdit(templateId, image.currentEdits, edits)` → mock render; merge `edits` into `image.currentEdits`.
+5. `uploadImageToMeta(renderedImageUrl)` → mock URL.
+6. `sendImage(phoneNumber, mockUrl)` — new WhatsApp message with the updated image.
+7. Return a short confirming text (e.g. "Updated: discount_text → 70%") which is sent as a follow-up text message via the existing `sendText` call in the webhook handler.
+
+## Webhook wiring
+
+In `app.post('/')`, the `check_allowed_edits` and `edit_graphic` cases pass `args.image_id` (and `args.edits`) through to the updated action handlers. No change to the outer loop structure.
+
+## Error handling
+
+- Unknown `image_id` (GPT hallucination): friendly text response listing currently tracked images, no crash.
+- Disallowed edit field(s): friendly text response naming what's not allowed and what is, no Express/Meta calls made.
+- Mock functions never throw — real error handling (network failures, Express API errors) is deferred to when real API calls replace the mocks.
+
+## Testing
+
+Manual verification only for this mocked iteration (no live Express/Meta credentials exist yet):
+- Simulate incoming webhook payloads for: "what edits can I make to the summer flyer", followed by "change the headline to Flash Sale" — confirm the reply lists the right mock unlocked layers and then confirms the edit + attempts a mock image send.
+- Simulate an edit request for a field not in `unlockedLayers` — confirm it's rejected with the allowed-fields message and no image is sent.
+- Simulate a reference to a nonexistent image — confirm the not-found message.
+
+## Out of scope
+
+- Real Adobe Express API integration.
+- Real Meta media upload.
+- Actually sending the initial graphic image that seeds the flow (images are pre-seeded in memory instead).
+- Persistence across server restarts (in-memory only, matches existing `conversationHistory` pattern).
diff --git a/expressApi.js b/expressApi.js
new file mode 100644
index 0000000000..0a352c054d
--- /dev/null
+++ b/expressApi.js
@@ -0,0 +1,33 @@
+const TEMPLATE_LAYERS = {
+ tpl_diwali: ['discount_text', 'headline', 'background_color'],
+ tpl_summer: ['headline', 'font_color'],
+ tpl_croma_earbuds: ['Price', 'Address', 'Product Image', 'Partner Logo'],
+};
+
+function getTemplateInfo(templateId) {
+ return {
+ templateId,
+ unlockedLayers: TEMPLATE_LAYERS[templateId] || [],
+ };
+}
+
+let renderRevision = 0;
+
+function applyEdit(templateId, currentEdits, newEdits) {
+ const mergedEdits = { ...currentEdits, ...newEdits };
+
+ if (templateId === 'tpl_croma_earbuds') {
+ return {
+ mergedEdits,
+ renderedImageUrl: 'https://s7ap1.scene7.com/is/image/varun/croma1-earbuds-updated',
+ };
+ }
+
+ renderRevision += 1;
+ return {
+ mergedEdits,
+ renderedImageUrl: `https://mock-express.local/render/${templateId}?rev=${renderRevision}`,
+ };
+}
+
+module.exports = { getTemplateInfo, applyEdit };
diff --git a/expressApi.test.js b/expressApi.test.js
new file mode 100644
index 0000000000..c9b3126535
--- /dev/null
+++ b/expressApi.test.js
@@ -0,0 +1,39 @@
+const test = require('node:test');
+const assert = require('node:assert/strict');
+const { getTemplateInfo, applyEdit } = require('./expressApi');
+
+test('getTemplateInfo returns the unlocked layers for a known template', () => {
+ const info = getTemplateInfo('tpl_diwali');
+ assert.deepEqual(info, {
+ templateId: 'tpl_diwali',
+ unlockedLayers: ['discount_text', 'headline', 'background_color'],
+ });
+});
+
+test('getTemplateInfo returns the unlocked layers for the Croma earbuds template', () => {
+ const info = getTemplateInfo('tpl_croma_earbuds');
+ assert.deepEqual(info, {
+ templateId: 'tpl_croma_earbuds',
+ unlockedLayers: ['Price', 'Address', 'Product Image', 'Partner Logo'],
+ });
+});
+
+test('getTemplateInfo returns an empty layer list for an unknown template', () => {
+ const info = getTemplateInfo('tpl_does_not_exist');
+ assert.deepEqual(info.unlockedLayers, []);
+});
+
+test('applyEdit merges new edits on top of current edits', () => {
+ const result = applyEdit('tpl_diwali', { headline: 'Old Headline' }, { discount_text: '70%' });
+ assert.deepEqual(result.mergedEdits, { headline: 'Old Headline', discount_text: '70%' });
+});
+
+test('applyEdit returns a rendered image url that references the template', () => {
+ const result = applyEdit('tpl_summer', {}, { headline: 'Flash Sale' });
+ assert.match(result.renderedImageUrl, /^https:\/\/mock-express\.local\/render\/tpl_summer\?rev=\d+$/);
+});
+
+test('applyEdit returns the fixed updated Croma earbuds image for any edit', () => {
+ const result = applyEdit('tpl_croma_earbuds', {}, { Price: '999' });
+ assert.equal(result.renderedImageUrl, 'https://s7ap1.scene7.com/is/image/varun/croma1-earbuds-updated');
+});
diff --git a/imageStore.js b/imageStore.js
new file mode 100644
index 0000000000..136d365bc8
--- /dev/null
+++ b/imageStore.js
@@ -0,0 +1,28 @@
+const SEED_IMAGES = [
+ { id: 'img_1', name: 'Diwali Offer Banner', templateId: 'tpl_diwali' },
+ { id: 'img_2', name: 'Summer Sale Flyer', templateId: 'tpl_summer' },
+ {
+ id: 'img_3',
+ name: 'Croma Earbuds',
+ templateId: 'tpl_croma_earbuds',
+ url: 'https://s7ap1.scene7.com/is/image/varun/croma1-earbuds',
+ },
+];
+
+const trackedImages = new Map();
+
+function getTrackedImages(phoneNumber) {
+ if (!trackedImages.has(phoneNumber)) {
+ trackedImages.set(
+ phoneNumber,
+ SEED_IMAGES.map((image) => ({ ...image, currentEdits: {} }))
+ );
+ }
+ return trackedImages.get(phoneNumber);
+}
+
+function findTrackedImage(phoneNumber, imageId) {
+ return getTrackedImages(phoneNumber).find((image) => image.id === imageId);
+}
+
+module.exports = { getTrackedImages, findTrackedImage };
diff --git a/imageStore.test.js b/imageStore.test.js
new file mode 100644
index 0000000000..841acf9d78
--- /dev/null
+++ b/imageStore.test.js
@@ -0,0 +1,35 @@
+const test = require('node:test');
+const assert = require('node:assert/strict');
+const { getTrackedImages, findTrackedImage } = require('./imageStore');
+
+test('getTrackedImages seeds 3 images on first access', () => {
+ const images = getTrackedImages('111');
+ assert.equal(images.length, 3);
+ assert.deepEqual(images.map((img) => img.id), ['img_1', 'img_2', 'img_3']);
+ assert.deepEqual(images[0].currentEdits, {});
+});
+
+test('getTrackedImages returns the same array on repeated calls for the same phone number', () => {
+ const first = getTrackedImages('222');
+ first[0].currentEdits.headline = 'Flash Sale';
+ const second = getTrackedImages('222');
+ assert.equal(second[0].currentEdits.headline, 'Flash Sale');
+});
+
+test('getTrackedImages seeds independently per phone number', () => {
+ getTrackedImages('333')[0].currentEdits.headline = 'Only for 333';
+ const other = getTrackedImages('444');
+ assert.deepEqual(other[0].currentEdits, {});
+});
+
+test('findTrackedImage returns the matching image', () => {
+ getTrackedImages('555');
+ const image = findTrackedImage('555', 'img_2');
+ assert.equal(image.name, 'Summer Sale Flyer');
+});
+
+test('findTrackedImage returns undefined for an unknown id', () => {
+ getTrackedImages('666');
+ const image = findTrackedImage('666', 'img_999');
+ assert.equal(image, undefined);
+});
diff --git a/metaUpload.js b/metaUpload.js
new file mode 100644
index 0000000000..4a6a68402b
--- /dev/null
+++ b/metaUpload.js
@@ -0,0 +1,10 @@
+const { randomUUID } = require('node:crypto');
+
+async function uploadImageToMeta(renderedImageUrl) {
+ if (!renderedImageUrl.startsWith('https://mock-express.local/')) {
+ return renderedImageUrl;
+ }
+ return `https://mock-meta-cdn.local/media/${randomUUID()}.png`;
+}
+
+module.exports = { uploadImageToMeta };
diff --git a/metaUpload.test.js b/metaUpload.test.js
new file mode 100644
index 0000000000..e4aed1b9ca
--- /dev/null
+++ b/metaUpload.test.js
@@ -0,0 +1,19 @@
+const test = require('node:test');
+const assert = require('node:assert/strict');
+const { uploadImageToMeta } = require('./metaUpload');
+
+test('uploadImageToMeta returns a mock CDN url', async () => {
+ const url = await uploadImageToMeta('https://mock-express.local/render/tpl_diwali?rev=1');
+ assert.match(url, /^https:\/\/mock-meta-cdn\.local\/media\/[0-9a-f-]+\.png$/);
+});
+
+test('uploadImageToMeta returns a different url on each call', async () => {
+ const first = await uploadImageToMeta('https://mock-express.local/render/tpl_diwali?rev=1');
+ const second = await uploadImageToMeta('https://mock-express.local/render/tpl_diwali?rev=2');
+ assert.notEqual(first, second);
+});
+
+test('uploadImageToMeta passes through urls that are already publicly hosted', async () => {
+ const url = await uploadImageToMeta('https://s7ap1.scene7.com/is/image/varun/croma1-earbuds-updated');
+ assert.equal(url, 'https://s7ap1.scene7.com/is/image/varun/croma1-earbuds-updated');
+});
diff --git a/package.json b/package.json
index 567801e154..854ad81ab5 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,8 @@
"license": "MIT",
"private": false,
"scripts": {
- "start": "node app.js"
+ "start": "node app.js",
+ "test": "node --test"
},
"dependencies": {
"express": "^5.0.0",
From db81c24de6ae063eb12f3dc018c70f036bb8048e Mon Sep 17 00:00:00 2001
From: Priyank Modi
Date: Mon, 13 Jul 2026 19:22:47 +0530
Subject: [PATCH 09/20] Add design spec for global fixed edit-flow responses
Whenever an edit is requested, always send the fixed Croma earbuds
image; whenever allowed-edits is asked, always reply with the fixed
layer list. Removes per-template edit-key validation.
Co-Authored-By: Claude Sonnet 5
---
...07-13-global-fixed-edit-response-design.md | 52 +++++++++++++++++++
1 file changed, 52 insertions(+)
create mode 100644 docs/superpowers/specs/2026-07-13-global-fixed-edit-response-design.md
diff --git a/docs/superpowers/specs/2026-07-13-global-fixed-edit-response-design.md b/docs/superpowers/specs/2026-07-13-global-fixed-edit-response-design.md
new file mode 100644
index 0000000000..afcdc7cabb
--- /dev/null
+++ b/docs/superpowers/specs/2026-07-13-global-fixed-edit-response-design.md
@@ -0,0 +1,52 @@
+# Design: Global fixed responses for edit-graphic and check-allowed-edits
+
+Amends [2026-07-13-express-edit-flow-design.md](./2026-07-13-express-edit-flow-design.md).
+
+## Goal
+
+Simplify two of the existing edit-flow actions in `actions.js` so their outputs are fixed, regardless of which image/template the request is about:
+
+1. **`edit_graphic`** — after any successful edit, always send the same hardcoded image (`https://s7ap1.scene7.com/is/image/varun/croma1-earbuds-updated`) as a new WhatsApp image message to the requesting user. No more per-template mock render or Meta upload.
+2. **`check_allowed_edits`** — always reply with the fixed text `Price, Partner logo, Partner's Address, product image`, regardless of which image is asked about.
+
+As a consequence, the "is this edit key allowed for this template" validation in `actionEditGraphic` is removed: any edit key is now accepted for any image.
+
+## Scope
+
+Only `actions.js` changes. `expressApi.js`, `imageStore.js`, `metaUpload.js`, and `app.js` are untouched — `expressApi.js` and `metaUpload.js` become unused by application code (no longer called from `actions.js`) but are left in place, along with their existing tests, as placeholders for a future real Adobe Express / Meta upload integration.
+
+## Changes to `actions.js`
+
+Two new constants:
+
+```js
+const EDITED_IMAGE_URL = 'https://s7ap1.scene7.com/is/image/varun/croma1-earbuds-updated';
+const ALLOWED_EDITS_TEXT = "Price, Partner logo, Partner's Address, product image";
+```
+
+`actionCheckAllowedEdits(phoneNumber, imageId)`:
+- Keeps the unknown-image guard (`formatUnknownImageMessage`).
+- For any known image, returns `ALLOWED_EDITS_TEXT` unconditionally — no more per-template `getTemplateInfo` lookup.
+
+`actionEditGraphic(phoneNumber, imageId, edits, { sendImage })`:
+- Keeps the unknown-image guard.
+- Drops the disallowed-key validation entirely (no `getTemplateInfo` call, no rejection path).
+- Merges `edits` into `image.currentEdits` directly (`{ ...image.currentEdits, ...edits }`) instead of calling `expressApi.applyEdit` — the rendered URL it used to return is no longer needed since we always send `EDITED_IMAGE_URL`.
+- Sends `EDITED_IMAGE_URL` via `sendImage` (no more `metaUpload.uploadImageToMeta` call, since this URL is already publicly hosted).
+- Returns the same `Updated "":\n` confirmation text as before.
+
+Unused imports (`getTemplateInfo`, `applyEdit` from `expressApi.js`; `uploadImageToMeta` from `metaUpload.js`) are removed from `actions.js`.
+
+## Error handling
+
+Unchanged from the existing flow: an unknown `image_id` (GPT hallucination) still gets the friendly "I couldn't find that image" message in both actions, with no crash. There is no other error path left in `actionEditGraphic` now that key validation is removed.
+
+## Testing
+
+Update `actions.test.js`:
+- `actionCheckAllowedEdits` tests for known images (Diwali, Croma) now assert the reply equals `ALLOWED_EDITS_TEXT`, for any tracked image — not a per-template layer list.
+- The "rejects edits outside the unlocked layers" test is replaced with a test confirming a previously-disallowed key (e.g. `background_color` on the Croma image) is now accepted and sends the image.
+- The edit tests (Croma image, Summer Sale Flyer image) both assert `sendImage` is called with `EDITED_IMAGE_URL`, for every template — not just Croma.
+- The "remembers the edit" assertion (`image.currentEdits`) is unchanged.
+
+`expressApi.test.js` and `metaUpload.test.js` are untouched — those modules' own behavior isn't changing, only their (lack of) callers.
From 8bbb22ad482b3f258087260bb263297fdac75bbb Mon Sep 17 00:00:00 2001
From: Priyank Modi
Date: Thu, 16 Jul 2026 12:29:59 +0530
Subject: [PATCH 10/20] Add design spec for real Adobe Express API integration
Replaces the mocked expressApi.js/imageStore.js/metaUpload.js flow with
real tagged-documents/generate-variation/status calls against a shared
docID catalog, per the brainstorming session.
---
...-16-real-express-api-integration-design.md | 188 ++++++++++++++++++
1 file changed, 188 insertions(+)
create mode 100644 docs/superpowers/specs/2026-07-16-real-express-api-integration-design.md
diff --git a/docs/superpowers/specs/2026-07-16-real-express-api-integration-design.md b/docs/superpowers/specs/2026-07-16-real-express-api-integration-design.md
new file mode 100644
index 0000000000..5505b2d7f0
--- /dev/null
+++ b/docs/superpowers/specs/2026-07-16-real-express-api-integration-design.md
@@ -0,0 +1,188 @@
+# Design: Real Adobe Express API integration for the image-edit flow
+
+Amends [2026-07-13-express-edit-flow-design.md](./2026-07-13-express-edit-flow-design.md) and [2026-07-13-global-fixed-edit-response-design.md](./2026-07-13-global-fixed-edit-response-design.md), replacing the mocked/fixed behavior those introduced with real Adobe Express API calls.
+
+## Goal
+
+Replace all hardcoding and mocked responses in the image-edit flow with real calls to the Adobe Express API:
+
+1. `GET /beta/tagged-documents/` to discover what's editable on a graphic and show the user a simplified list.
+2. `POST /beta/generate-variation` to apply the user's requested edits.
+3. `GET /status/` to poll until the variation is ready.
+4. Send the resulting `thumbnailUrl` back to the user as a new WhatsApp image message.
+
+The identity of *which* Express document a tracked image maps to comes from a JSON catalog shared with the separate UI repo (out of scope for this change), not from anything invented in this backend.
+
+## Shared catalog: `data/express-templates.json`
+
+Committed to this repo (not gitignored), maintained in step with an equivalent file in the UI repo:
+
+```json
+[
+ { "id": "img_1", "name": "Diwali Offer Banner", "docId": "urn:aaid:sc:AP:..." },
+ { "id": "img_2", "name": "Summer Sale Flyer", "docId": "urn:aaid:sc:AP:..." },
+ { "id": "img_3", "name": "Croma Earbuds", "docId": "urn:aaid:sc:AP:aaed427c-b4e4-55e4-b924-74d375f91684" }
+]
+```
+
+- Flat array, `id`/`name`/`docId` only — no `phoneNumber`. It's a global catalog of known templates/images, not a per-customer sent-images log (the UI app's `sendWhatsAppTemplateMessage` already carries `docId` alongside `templateName` on its own send calls; this file is the read side this backend needs).
+- Path is overridable via `EXPRESS_TEMPLATES_FILE` env var, defaulting to `data/express-templates.json`.
+- Keeping both repos' copies of this file in sync (git submodule, manual copy, CI step, etc.) is out of scope for this change — flagged, not solved, here.
+
+## New module: `expressAuth.js`
+
+```js
+async function getAccessToken() // returns a cached IMS access token, refreshing near expiry
+async function buildAuthHeaders() // -> { Authorization: 'Bearer ', 'X-API-KEY': }
+```
+
+- `POST https://ims-na1.adobelogin.com/ims/token/v3` (overridable via `EXPRESS_IMS_TOKEN_URL`), `grant_type=client_credentials`, `client_id`/`client_secret` from `EXPRESS_CLIENT_ID`/`EXPRESS_CLIENT_SECRET`, `scope` from `EXPRESS_API_SCOPE` (default `ee.express_api,openid,AdobeID,read_organizations,additional_info.projectedProductContext`, taken from a decoded sample token).
+- `X-API-KEY` is `EXPRESS_CLIENT_ID` (confirmed identical to the sample token's `client_id` claim).
+- Token cached in memory with its expiry; refetched once within 60s of expiry.
+- **Assumption to verify on first real call:** Adobe IMS v3 token responses have historically returned `expires_in` in **milliseconds**, not seconds (unlike v2). This module treats `expires_in` as milliseconds directly (`expiresAt = Date.now() + Number(expires_in)`). If the real response turns out to be in seconds, this is a one-line fix isolated to this function.
+
+## Rewritten module: `expressApi.js`
+
+No more `TEMPLATE_LAYERS`/mock render. Real HTTP calls against `EXPRESS_API_BASE_URL` (default `https://express-api.adobe.io`), using `expressAuth.buildAuthHeaders()`:
+
+```js
+async function getTaggedDocument(docId)
+// GET /beta/tagged-documents/
+// -> { name, id, documentPages: [{ pageNumber, taggedElements: [{ name, type, value?, position, size }] }] }
+
+async function generateVariation(docId, tagMappings, pages, preferredDocumentName)
+// POST /beta/generate-variation
+// body: { id: docId, variationDetails: { pages, preferredDocumentName, tagMappings } }
+// -> { jobId, statusUrl }
+
+async function getJobStatus(jobId)
+// GET /status/
+// -> { jobId, status, document?: { name, id, thumbnailUrl } }
+
+async function pollJobStatus(jobId, { intervalMs, timeoutMs } = {})
+// Polls getJobStatus every intervalMs (default EXPRESS_STATUS_POLL_INTERVAL_MS=2000)
+// until status === 'succeeded' (returns the full result) or 'failed' (throws),
+// or until timeoutMs elapses (default EXPRESS_STATUS_POLL_TIMEOUT_MS=60000, throws).
+```
+
+Non-2xx responses from any of the three GET/POST calls throw with the HTTP status and response body included in the error message, for logging by the caller.
+
+### Helpers (co-located in `expressApi.js`)
+
+```js
+function collectTaggedElements(taggedDocument)
+// Flattens documentPages[].taggedElements[] into one array, each tagged with its pageNumber.
+
+function formatAllowedEdits(name, elements)
+// Simplified user-facing text, e.g.:
+// Edits allowed on "Croma Earbuds":
+// - heading: currently "The X-Phone Pro is here!"
+// - cta: currently "Available at our store starting 15 Aug 20XX."
+// Tell me what you'd like to change and to what, e.g. "change cta to ...".
+// Non-text element types are listed as "- ()" without a current value.
+
+function pagesForEdits(elements, editKeys)
+// -> comma-joined, ascending page numbers whose taggedElements include any of editKeys, e.g. "1" or "1,2".
+
+function buildPreferredDocumentName(baseName)
+// -> `${baseName}-edit-${Date.now()}`
+```
+
+## Rewritten module: `imageStore.js`
+
+```js
+function getTrackedImages(phoneNumber)
+// Reads data/express-templates.json fresh on every call (no caching of the catalog itself),
+// merges in this conversation's accumulated tagMappings.
+// -> Array<{ id, name, docId, currentEdits }>
+
+function findTrackedImage(phoneNumber, imageId)
+// -> single entry from getTrackedImages(phoneNumber), or undefined
+
+function recordEdits(phoneNumber, imageId, newEdits)
+// Merges newEdits into the in-memory Map<"phone:id", tagMappings> and returns the merged object.
+// Only called after a generate-variation call succeeds (see actions.js below) —
+// a failed/timed-out edit does not get committed, so a retry starts from the last-known-good state.
+```
+
+Catalog read failures (missing/invalid file) are logged and treated as an empty catalog (no images tracked) rather than crashing the process.
+
+## `metaUpload.js`
+
+Left in place, untouched, exports unchanged — but no longer imported by `actions.js`. `generate-variation`'s `status` response already returns a public, directly-fetchable `thumbnailUrl`, so there's no upload hop needed. `metaUpload.test.js` stays as-is since the module's own behavior isn't changing.
+
+## Rewritten `actions.js`
+
+```js
+async function actionCheckAllowedEdits(phoneNumber, imageId) {
+ // unknown-image guard unchanged (formatUnknownImageMessage)
+ // getTaggedDocument(image.docId) -> collectTaggedElements -> formatAllowedEdits(image.name, elements)
+ // Express API errors: log technical detail, return a generic friendly retry message (no crash, no raw error to the user)
+}
+
+async function actionEditGraphic(phoneNumber, imageId, edits, { sendImage }) {
+ // unknown-image guard unchanged
+ // getTaggedDocument(image.docId) -> collectTaggedElements -> allowedNames
+ // requestedKeys not in allowedNames -> same "I can't edit X, allowed edits are: " rejection as before, no Express/generate call made
+ // mergedEdits = { ...image.currentEdits, ...edits }
+ // pages = pagesForEdits(elements, Object.keys(mergedEdits))
+ // preferredDocumentName = buildPreferredDocumentName(image.name)
+ // generateVariation(image.docId, mergedEdits, pages, preferredDocumentName) -> jobId
+ // pollJobStatus(jobId) -> result
+ // recordEdits(phoneNumber, imageId, edits) // only on success
+ // sendImage(phoneNumber, result.document.thumbnailUrl)
+ // generate/poll errors: log technical detail, return a generic friendly retry message
+ // Returns the same "Updated \"\":\n" confirmation text as before on success
+}
+```
+
+`actionListCampaignGraphics` and `actionGenerateBulkGraphics` are unchanged (still out of scope — their existing `// TODO` mocks stay as-is).
+
+## `app.js` — webhook logging only
+
+No behavior change to the GPT tool flow. In the incoming-message loop, before the existing `if (!userText) continue`, add labeled logging for fields that might carry the UI app's per-message metadata, so a real test send lets you find where (if anywhere) a docID rides along in this app's webhook payload, independent of the `data/express-templates.json` catalog:
+
+```js
+if (message.context) console.log('[webhook] message.context:', JSON.stringify(message.context));
+if (message.referral) console.log('[webhook] message.referral:', JSON.stringify(message.referral));
+if (message.image) console.log('[webhook] message.image:', JSON.stringify(message.image));
+```
+
+This is observability only — no new logic acts on these fields. Actually handling incoming image messages (or any other message type beyond text) stays out of scope, same as the original design.
+
+## Environment variables
+
+New, added to `render.yaml` (`sync: false` for secrets, matching existing `OPENAI_API_KEY` pattern):
+
+- `EXPRESS_CLIENT_ID` (required, secret)
+- `EXPRESS_CLIENT_SECRET` (required, secret)
+- `EXPRESS_API_SCOPE` (optional, has default)
+- `EXPRESS_IMS_TOKEN_URL` (optional, has default)
+- `EXPRESS_API_BASE_URL` (optional, has default)
+- `EXPRESS_TEMPLATES_FILE` (optional, has default)
+- `EXPRESS_STATUS_POLL_INTERVAL_MS` (optional, has default)
+- `EXPRESS_STATUS_POLL_TIMEOUT_MS` (optional, has default)
+
+## Error handling
+
+- Unknown `image_id` (GPT hallucination): unchanged friendly message, no crash.
+- Disallowed edit field(s): unchanged rejection listing what's actually allowed, no Express calls made — now driven by real `taggedElements` instead of hardcoded layers.
+- Any Adobe Express HTTP error (auth failure, 404 doc not found, network error), and any poll timeout/failure: caught in `actions.js`, logged with technical detail (status, docId/jobId, message), and surfaced to the user as a short generic retry message — never a raw stack trace or API error body over WhatsApp.
+- A failed/timed-out edit does not get merged into the conversation's `tagMappings`, so retrying re-sends the last-known-good edit set plus the new attempt, rather than compounding a bad state.
+
+## Testing
+
+No new npm dependencies — tests stub `global.fetch` directly (Node's built-in `node:test`, matching the existing pattern; `app.js` already relies on global `fetch` for WhatsApp calls).
+
+- `expressAuth.test.js` (new): fetches and caches a token; refetches once near-expiry; builds the right headers.
+- `expressApi.test.js` (rewritten): `getTaggedDocument`/`generateVariation`/`getJobStatus` send the right URL/method/body/headers and parse responses correctly; `pollJobStatus` resolves on `succeeded`, throws on `failed`, throws on timeout without exceeding it; `collectTaggedElements`/`formatAllowedEdits`/`pagesForEdits`/`buildPreferredDocumentName` unit-tested directly against sample API response shapes.
+- `imageStore.test.js` (rewritten): reads a fixture catalog file; `recordEdits` accumulates correctly per `(phoneNumber, imageId)`; missing/invalid catalog file degrades to an empty list without throwing.
+- `actions.test.js` (rewritten): known-image allowed-edits listing; unknown-image guard; disallowed-field rejection (no generate call); happy path (generate → poll → `sendImage` called with `thumbnailUrl`, edits recorded); generate/poll failure path (friendly message, `sendImage` not called, edits not recorded).
+- `metaUpload.test.js`: untouched.
+
+## Out of scope
+
+- Keeping the two repos' `data/express-templates.json` copies in sync.
+- Any new logic acting on `message.context`/`message.referral`/`message.image` beyond logging them.
+- `actionListCampaignGraphics` / `actionGenerateBulkGraphics` real implementations.
+- Handling of non-text incoming WhatsApp message types beyond the added logging.
From 97c5088447ea555e31c304496254080f6ea30ee9 Mon Sep 17 00:00:00 2001
From: Priyank Modi
Date: Thu, 16 Jul 2026 13:01:21 +0530
Subject: [PATCH 11/20] Ignore .superpowers scratch directory
Co-Authored-By: Claude Sonnet 5
---
.gitignore | 1 +
1 file changed, 1 insertion(+)
diff --git a/.gitignore b/.gitignore
index fd4f2b066b..84888f4ca4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
node_modules
.DS_Store
+.superpowers
From af478c2842df414a78a999312554c3eeffb67574 Mon Sep 17 00:00:00 2001
From: Priyank Modi
Date: Thu, 16 Jul 2026 13:03:50 +0530
Subject: [PATCH 12/20] feat: add Adobe IMS token fetch/cache module
Implement expressAuth.js with getAccessToken and buildAuthHeaders functions.
Provides IMS token caching with 60s refresh margin per Adobe IMS v3 convention.
Co-Authored-By: Claude Sonnet 5
---
expressAuth.js | 44 ++++++++++++++++++++++
expressAuth.test.js | 90 +++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 134 insertions(+)
create mode 100644 expressAuth.js
create mode 100644 expressAuth.test.js
diff --git a/expressAuth.js b/expressAuth.js
new file mode 100644
index 0000000000..a5dfb262c6
--- /dev/null
+++ b/expressAuth.js
@@ -0,0 +1,44 @@
+const IMS_TOKEN_URL = process.env.EXPRESS_IMS_TOKEN_URL || 'https://ims-na1.adobelogin.com/ims/token/v3';
+const DEFAULT_SCOPE = 'ee.express_api,openid,AdobeID,read_organizations,additional_info.projectedProductContext';
+const REFRESH_MARGIN_MS = 60_000;
+
+let cachedToken = null; // { accessToken, expiresAt }
+
+async function getAccessToken() {
+ if (cachedToken && cachedToken.expiresAt - Date.now() > REFRESH_MARGIN_MS) {
+ return cachedToken.accessToken;
+ }
+
+ const response = await fetch(IMS_TOKEN_URL, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
+ body: new URLSearchParams({
+ grant_type: 'client_credentials',
+ client_id: process.env.EXPRESS_CLIENT_ID,
+ client_secret: process.env.EXPRESS_CLIENT_SECRET,
+ scope: process.env.EXPRESS_API_SCOPE || DEFAULT_SCOPE,
+ }),
+ });
+
+ if (!response.ok) {
+ const text = await response.text();
+ throw new Error(`IMS token request failed ${response.status}: ${text}`);
+ }
+
+ const data = await response.json();
+ cachedToken = {
+ accessToken: data.access_token,
+ expiresAt: Date.now() + Number(data.expires_in),
+ };
+ return cachedToken.accessToken;
+}
+
+async function buildAuthHeaders() {
+ const token = await getAccessToken();
+ return {
+ Authorization: `Bearer ${token}`,
+ 'X-API-KEY': process.env.EXPRESS_CLIENT_ID,
+ };
+}
+
+module.exports = { getAccessToken, buildAuthHeaders };
diff --git a/expressAuth.test.js b/expressAuth.test.js
new file mode 100644
index 0000000000..b9b7012386
--- /dev/null
+++ b/expressAuth.test.js
@@ -0,0 +1,90 @@
+const test = require('node:test');
+const assert = require('node:assert/strict');
+
+const originalFetch = global.fetch;
+
+function freshExpressAuth() {
+ delete require.cache[require.resolve('./expressAuth')];
+ return require('./expressAuth');
+}
+
+test('getAccessToken fetches a token from the IMS endpoint using client credentials', async () => {
+ process.env.EXPRESS_CLIENT_ID = 'client-123';
+ process.env.EXPRESS_CLIENT_SECRET = 'secret-456';
+ global.fetch = async (url, options) => {
+ assert.equal(url, 'https://ims-na1.adobelogin.com/ims/token/v3');
+ assert.equal(options.method, 'POST');
+ assert.equal(options.headers['Content-Type'], 'application/x-www-form-urlencoded');
+ const body = options.body.toString();
+ assert.match(body, /grant_type=client_credentials/);
+ assert.match(body, /client_id=client-123/);
+ assert.match(body, /client_secret=secret-456/);
+ return { ok: true, json: async () => ({ access_token: 'tok-abc', expires_in: 86400000, token_type: 'bearer' }) };
+ };
+
+ const { getAccessToken } = freshExpressAuth();
+ const token = await getAccessToken();
+
+ assert.equal(token, 'tok-abc');
+ global.fetch = originalFetch;
+});
+
+test('getAccessToken caches the token and does not refetch on a second call', async () => {
+ process.env.EXPRESS_CLIENT_ID = 'client-123';
+ process.env.EXPRESS_CLIENT_SECRET = 'secret-456';
+ let fetchCalls = 0;
+ global.fetch = async () => {
+ fetchCalls += 1;
+ return { ok: true, json: async () => ({ access_token: 'tok-cached', expires_in: 86400000 }) };
+ };
+
+ const { getAccessToken } = freshExpressAuth();
+ const first = await getAccessToken();
+ const second = await getAccessToken();
+
+ assert.equal(first, 'tok-cached');
+ assert.equal(second, 'tok-cached');
+ assert.equal(fetchCalls, 1);
+ global.fetch = originalFetch;
+});
+
+test('getAccessToken refetches once the cached token is within 60s of expiring', async () => {
+ process.env.EXPRESS_CLIENT_ID = 'client-123';
+ process.env.EXPRESS_CLIENT_SECRET = 'secret-456';
+ let fetchCalls = 0;
+ global.fetch = async () => {
+ fetchCalls += 1;
+ return { ok: true, json: async () => ({ access_token: `tok-${fetchCalls}`, expires_in: 30000 }) };
+ };
+
+ const { getAccessToken } = freshExpressAuth();
+ const first = await getAccessToken();
+ const second = await getAccessToken();
+
+ assert.equal(first, 'tok-1');
+ assert.equal(second, 'tok-2');
+ assert.equal(fetchCalls, 2);
+ global.fetch = originalFetch;
+});
+
+test('getAccessToken throws with the status and body when the IMS endpoint errors', async () => {
+ process.env.EXPRESS_CLIENT_ID = 'client-123';
+ process.env.EXPRESS_CLIENT_SECRET = 'secret-456';
+ global.fetch = async () => ({ ok: false, status: 401, text: async () => 'invalid client' });
+
+ const { getAccessToken } = freshExpressAuth();
+ await assert.rejects(() => getAccessToken(), /401/);
+ global.fetch = originalFetch;
+});
+
+test('buildAuthHeaders returns Authorization and X-API-KEY headers', async () => {
+ process.env.EXPRESS_CLIENT_ID = 'client-789';
+ process.env.EXPRESS_CLIENT_SECRET = 'secret-000';
+ global.fetch = async () => ({ ok: true, json: async () => ({ access_token: 'tok-xyz', expires_in: 86400000 }) });
+
+ const { buildAuthHeaders } = freshExpressAuth();
+ const headers = await buildAuthHeaders();
+
+ assert.deepEqual(headers, { Authorization: 'Bearer tok-xyz', 'X-API-KEY': 'client-789' });
+ global.fetch = originalFetch;
+});
From 0ac9427154b5c790781a680f94b41847a0f533ab Mon Sep 17 00:00:00 2001
From: Priyank Modi
Date: Thu, 16 Jul 2026 13:08:21 +0530
Subject: [PATCH 13/20] feat: read the shared docID catalog instead of
hardcoded seed images
Co-Authored-By: Claude Sonnet 5
---
data/express-templates.json | 3 ++
imageStore.js | 48 +++++++++++++----------
imageStore.test.js | 76 ++++++++++++++++++++++++++-----------
3 files changed, 86 insertions(+), 41 deletions(-)
create mode 100644 data/express-templates.json
diff --git a/data/express-templates.json b/data/express-templates.json
new file mode 100644
index 0000000000..79fb4b86fc
--- /dev/null
+++ b/data/express-templates.json
@@ -0,0 +1,3 @@
+[
+ { "id": "img_1", "name": "Croma Earbuds", "docId": "urn:aaid:sc:AP:aaed427c-b4e4-55e4-b924-74d375f91684" }
+]
diff --git a/imageStore.js b/imageStore.js
index 136d365bc8..6ebac789cb 100644
--- a/imageStore.js
+++ b/imageStore.js
@@ -1,28 +1,38 @@
-const SEED_IMAGES = [
- { id: 'img_1', name: 'Diwali Offer Banner', templateId: 'tpl_diwali' },
- { id: 'img_2', name: 'Summer Sale Flyer', templateId: 'tpl_summer' },
- {
- id: 'img_3',
- name: 'Croma Earbuds',
- templateId: 'tpl_croma_earbuds',
- url: 'https://s7ap1.scene7.com/is/image/varun/croma1-earbuds',
- },
-];
+const fs = require('node:fs');
+const path = require('node:path');
-const trackedImages = new Map();
+function catalogPath() {
+ return process.env.EXPRESS_TEMPLATES_FILE || path.join(__dirname, 'data', 'express-templates.json');
+}
-function getTrackedImages(phoneNumber) {
- if (!trackedImages.has(phoneNumber)) {
- trackedImages.set(
- phoneNumber,
- SEED_IMAGES.map((image) => ({ ...image, currentEdits: {} }))
- );
+function loadCatalog() {
+ try {
+ const raw = fs.readFileSync(catalogPath(), 'utf8');
+ return JSON.parse(raw);
+ } catch (err) {
+ console.error('[imageStore] failed to read catalog', { path: catalogPath(), message: err.message });
+ return [];
}
- return trackedImages.get(phoneNumber);
+}
+
+const conversationEdits = new Map();
+
+function getTrackedImages(phoneNumber) {
+ return loadCatalog().map((entry) => ({
+ ...entry,
+ currentEdits: conversationEdits.get(`${phoneNumber}:${entry.id}`) || {},
+ }));
}
function findTrackedImage(phoneNumber, imageId) {
return getTrackedImages(phoneNumber).find((image) => image.id === imageId);
}
-module.exports = { getTrackedImages, findTrackedImage };
+function recordEdits(phoneNumber, imageId, newEdits) {
+ const key = `${phoneNumber}:${imageId}`;
+ const merged = { ...(conversationEdits.get(key) || {}), ...newEdits };
+ conversationEdits.set(key, merged);
+ return merged;
+}
+
+module.exports = { getTrackedImages, findTrackedImage, recordEdits };
diff --git a/imageStore.test.js b/imageStore.test.js
index 841acf9d78..7d16d2d180 100644
--- a/imageStore.test.js
+++ b/imageStore.test.js
@@ -1,35 +1,67 @@
const test = require('node:test');
const assert = require('node:assert/strict');
-const { getTrackedImages, findTrackedImage } = require('./imageStore');
+const fs = require('node:fs');
+const path = require('node:path');
+const os = require('node:os');
+const { getTrackedImages, findTrackedImage, recordEdits } = require('./imageStore');
-test('getTrackedImages seeds 3 images on first access', () => {
- const images = getTrackedImages('111');
- assert.equal(images.length, 3);
- assert.deepEqual(images.map((img) => img.id), ['img_1', 'img_2', 'img_3']);
- assert.deepEqual(images[0].currentEdits, {});
-});
+function writeFixtureCatalog(entries) {
+ const fixturePath = path.join(os.tmpdir(), `express-templates-${Date.now()}-${Math.random().toString(36).slice(2)}.json`);
+ fs.writeFileSync(fixturePath, JSON.stringify(entries));
+ process.env.EXPRESS_TEMPLATES_FILE = fixturePath;
+}
+
+test('getTrackedImages reads the catalog from EXPRESS_TEMPLATES_FILE', () => {
+ writeFixtureCatalog([
+ { id: 'img_1', name: 'Diwali Offer Banner', docId: 'urn:doc:1' },
+ { id: 'img_2', name: 'Summer Sale Flyer', docId: 'urn:doc:2' },
+ ]);
+
+ const images = getTrackedImages('phone-1');
-test('getTrackedImages returns the same array on repeated calls for the same phone number', () => {
- const first = getTrackedImages('222');
- first[0].currentEdits.headline = 'Flash Sale';
- const second = getTrackedImages('222');
- assert.equal(second[0].currentEdits.headline, 'Flash Sale');
+ assert.equal(images.length, 2);
+ assert.deepEqual(images[0], { id: 'img_1', name: 'Diwali Offer Banner', docId: 'urn:doc:1', currentEdits: {} });
});
-test('getTrackedImages seeds independently per phone number', () => {
- getTrackedImages('333')[0].currentEdits.headline = 'Only for 333';
- const other = getTrackedImages('444');
- assert.deepEqual(other[0].currentEdits, {});
+test('getTrackedImages returns an empty list when the catalog file is missing', () => {
+ process.env.EXPRESS_TEMPLATES_FILE = path.join(os.tmpdir(), 'does-not-exist.json');
+
+ const images = getTrackedImages('phone-2');
+
+ assert.deepEqual(images, []);
});
-test('findTrackedImage returns the matching image', () => {
- getTrackedImages('555');
- const image = findTrackedImage('555', 'img_2');
- assert.equal(image.name, 'Summer Sale Flyer');
+test('findTrackedImage returns the matching image by id', () => {
+ writeFixtureCatalog([{ id: 'img_3', name: 'Croma Earbuds', docId: 'urn:doc:3' }]);
+
+ const image = findTrackedImage('phone-3', 'img_3');
+
+ assert.equal(image.name, 'Croma Earbuds');
});
test('findTrackedImage returns undefined for an unknown id', () => {
- getTrackedImages('666');
- const image = findTrackedImage('666', 'img_999');
+ writeFixtureCatalog([{ id: 'img_3', name: 'Croma Earbuds', docId: 'urn:doc:3' }]);
+
+ const image = findTrackedImage('phone-4', 'img_nope');
+
assert.equal(image, undefined);
});
+
+test('recordEdits merges edits per phone number and image id, visible via findTrackedImage', () => {
+ writeFixtureCatalog([{ id: 'img_1', name: 'Diwali Offer Banner', docId: 'urn:doc:1' }]);
+
+ recordEdits('phone-5', 'img_1', { headline: 'Flash Sale' });
+ recordEdits('phone-5', 'img_1', { discount_text: '70%' });
+
+ const image = findTrackedImage('phone-5', 'img_1');
+ assert.deepEqual(image.currentEdits, { headline: 'Flash Sale', discount_text: '70%' });
+});
+
+test('recordEdits keeps edits independent per phone number', () => {
+ writeFixtureCatalog([{ id: 'img_1', name: 'Diwali Offer Banner', docId: 'urn:doc:1' }]);
+
+ recordEdits('phone-6', 'img_1', { headline: 'Only for phone-6' });
+
+ const otherPhoneImage = findTrackedImage('phone-7', 'img_1');
+ assert.deepEqual(otherPhoneImage.currentEdits, {});
+});
From 93dacd937a814a2a1c56abd058d0b5817445e533 Mon Sep 17 00:00:00 2001
From: Priyank Modi
Date: Thu, 16 Jul 2026 13:12:37 +0530
Subject: [PATCH 14/20] fix: validate catalog is an array in loadCatalog()
If the catalog file parses successfully but isn't an array (e.g., `{}` or `42`),
treat it as an invalid catalog, log the error, and return empty array instead of
crashing on `.map()`. Aligns with design spec requirement to degrade gracefully
on catalog read failures.
Co-Authored-By: Claude Sonnet 5
---
imageStore.js | 7 ++++++-
imageStore.test.js | 8 ++++++++
2 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/imageStore.js b/imageStore.js
index 6ebac789cb..36fd95f818 100644
--- a/imageStore.js
+++ b/imageStore.js
@@ -8,7 +8,12 @@ function catalogPath() {
function loadCatalog() {
try {
const raw = fs.readFileSync(catalogPath(), 'utf8');
- return JSON.parse(raw);
+ const parsed = JSON.parse(raw);
+ if (!Array.isArray(parsed)) {
+ console.error('[imageStore] failed to read catalog', { path: catalogPath(), message: 'catalog is not an array' });
+ return [];
+ }
+ return parsed;
} catch (err) {
console.error('[imageStore] failed to read catalog', { path: catalogPath(), message: err.message });
return [];
diff --git a/imageStore.test.js b/imageStore.test.js
index 7d16d2d180..f175afb835 100644
--- a/imageStore.test.js
+++ b/imageStore.test.js
@@ -31,6 +31,14 @@ test('getTrackedImages returns an empty list when the catalog file is missing',
assert.deepEqual(images, []);
});
+test('getTrackedImages returns an empty list when the catalog is not an array', () => {
+ writeFixtureCatalog({});
+
+ const images = getTrackedImages('phone-2-non-array');
+
+ assert.deepEqual(images, []);
+});
+
test('findTrackedImage returns the matching image by id', () => {
writeFixtureCatalog([{ id: 'img_3', name: 'Croma Earbuds', docId: 'urn:doc:3' }]);
From 6de616a31d3a172fc84b994e0d5300dceb6d5936 Mon Sep 17 00:00:00 2001
From: Priyank Modi
Date: Thu, 16 Jul 2026 13:16:14 +0530
Subject: [PATCH 15/20] feat: replace mocked Express API with real
tagged-documents/generate-variation/status calls
Co-Authored-By: Claude Sonnet 5
---
expressApi.js | 117 ++++++++++++++++++++------
expressApi.test.js | 201 +++++++++++++++++++++++++++++++++++++++------
2 files changed, 269 insertions(+), 49 deletions(-)
diff --git a/expressApi.js b/expressApi.js
index 0a352c054d..1d43d2ef4d 100644
--- a/expressApi.js
+++ b/expressApi.js
@@ -1,33 +1,102 @@
-const TEMPLATE_LAYERS = {
- tpl_diwali: ['discount_text', 'headline', 'background_color'],
- tpl_summer: ['headline', 'font_color'],
- tpl_croma_earbuds: ['Price', 'Address', 'Product Image', 'Partner Logo'],
-};
+const { buildAuthHeaders } = require('./expressAuth');
+
+function apiBaseUrl() {
+ return process.env.EXPRESS_API_BASE_URL || 'https://express-api.adobe.io';
+}
+
+function sleep(ms) {
+ return new Promise((resolve) => setTimeout(resolve, ms));
+}
+
+async function getTaggedDocument(docId) {
+ const headers = await buildAuthHeaders();
+ const response = await fetch(`${apiBaseUrl()}/beta/tagged-documents/${encodeURIComponent(docId)}`, { headers });
+ if (!response.ok) {
+ const text = await response.text();
+ throw new Error(`getTaggedDocument failed ${response.status}: ${text}`);
+ }
+ return response.json();
+}
+
+async function generateVariation(docId, tagMappings, pages, preferredDocumentName) {
+ const headers = await buildAuthHeaders();
+ const response = await fetch(`${apiBaseUrl()}/beta/generate-variation`, {
+ method: 'POST',
+ headers: { ...headers, 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ id: docId,
+ variationDetails: { pages, preferredDocumentName, tagMappings },
+ }),
+ });
+ if (!response.ok) {
+ const text = await response.text();
+ throw new Error(`generateVariation failed ${response.status}: ${text}`);
+ }
+ return response.json();
+}
-function getTemplateInfo(templateId) {
- return {
- templateId,
- unlockedLayers: TEMPLATE_LAYERS[templateId] || [],
- };
+async function getJobStatus(jobId) {
+ const headers = await buildAuthHeaders();
+ const response = await fetch(`${apiBaseUrl()}/status/${encodeURIComponent(jobId)}`, { headers });
+ if (!response.ok) {
+ const text = await response.text();
+ throw new Error(`getJobStatus failed ${response.status}: ${text}`);
+ }
+ return response.json();
}
-let renderRevision = 0;
+async function pollJobStatus(jobId, { intervalMs, timeoutMs } = {}) {
+ const interval = intervalMs ?? Number(process.env.EXPRESS_STATUS_POLL_INTERVAL_MS || 2000);
+ const timeout = timeoutMs ?? Number(process.env.EXPRESS_STATUS_POLL_TIMEOUT_MS || 60000);
+ const deadline = Date.now() + timeout;
-function applyEdit(templateId, currentEdits, newEdits) {
- const mergedEdits = { ...currentEdits, ...newEdits };
+ for (;;) {
+ const result = await getJobStatus(jobId);
+ if (result.status === 'succeeded') return result;
+ if (result.status === 'failed') throw new Error(`Express job ${jobId} failed`);
+ if (Date.now() >= deadline) throw new Error(`Express job ${jobId} timed out after ${timeout}ms`);
+ await sleep(interval);
+ }
+}
- if (templateId === 'tpl_croma_earbuds') {
- return {
- mergedEdits,
- renderedImageUrl: 'https://s7ap1.scene7.com/is/image/varun/croma1-earbuds-updated',
- };
+function collectTaggedElements(taggedDocument) {
+ const elements = [];
+ for (const page of taggedDocument.documentPages || []) {
+ for (const element of page.taggedElements || []) {
+ elements.push({ ...element, pageNumber: page.pageNumber });
+ }
}
+ return elements;
+}
- renderRevision += 1;
- return {
- mergedEdits,
- renderedImageUrl: `https://mock-express.local/render/${templateId}?rev=${renderRevision}`,
- };
+function formatAllowedEdits(name, elements) {
+ const lines = elements.map((element) =>
+ element.type === 'text'
+ ? `- ${element.name}: currently "${element.value}"`
+ : `- ${element.name} (${element.type})`
+ );
+ const example = elements[0]?.name || 'a field';
+ return `Edits allowed on "${name}":\n${lines.join('\n')}\nTell me what you'd like to change and to what, e.g. "change ${example} to ...".`;
}
-module.exports = { getTemplateInfo, applyEdit };
+function pagesForEdits(elements, editKeys) {
+ const pageNumbers = new Set(
+ elements.filter((element) => editKeys.includes(element.name)).map((element) => element.pageNumber)
+ );
+ return [...pageNumbers].sort((a, b) => a - b).join(',');
+}
+
+function buildPreferredDocumentName(baseName) {
+ return `${baseName}-edit-${Date.now()}`;
+}
+
+module.exports = {
+ getTaggedDocument,
+ generateVariation,
+ getJobStatus,
+ pollJobStatus,
+ collectTaggedElements,
+ formatAllowedEdits,
+ pagesForEdits,
+ buildPreferredDocumentName,
+};
diff --git a/expressApi.test.js b/expressApi.test.js
index c9b3126535..e3fe77ed75 100644
--- a/expressApi.test.js
+++ b/expressApi.test.js
@@ -1,39 +1,190 @@
const test = require('node:test');
const assert = require('node:assert/strict');
-const { getTemplateInfo, applyEdit } = require('./expressApi');
+const {
+ getTaggedDocument,
+ generateVariation,
+ getJobStatus,
+ pollJobStatus,
+ collectTaggedElements,
+ formatAllowedEdits,
+ pagesForEdits,
+ buildPreferredDocumentName,
+} = require('./expressApi');
-test('getTemplateInfo returns the unlocked layers for a known template', () => {
- const info = getTemplateInfo('tpl_diwali');
- assert.deepEqual(info, {
- templateId: 'tpl_diwali',
- unlockedLayers: ['discount_text', 'headline', 'background_color'],
- });
+const originalFetch = global.fetch;
+
+function stubFetch(handlers) {
+ global.fetch = async (url, options) => {
+ if (url.includes('ims-na1.adobelogin.com')) {
+ return { ok: true, json: async () => ({ access_token: 'tok-test', expires_in: 86400000 }) };
+ }
+ for (const [pattern, handler] of handlers) {
+ if (pattern.test(url)) return handler(url, options);
+ }
+ throw new Error(`Unexpected fetch call: ${url}`);
+ };
+}
+
+test('getTaggedDocument fetches and returns the tagged document', async () => {
+ process.env.EXPRESS_CLIENT_ID = 'client-1';
+ process.env.EXPRESS_CLIENT_SECRET = 'secret-1';
+ stubFetch([
+ [/\/beta\/tagged-documents\//, async (url, options) => {
+ assert.match(url, /\/beta\/tagged-documents\/urn%3Aaaid%3Asc%3AAP%3Aabc$/);
+ assert.equal(options.headers.Authorization, 'Bearer tok-test');
+ assert.equal(options.headers['X-API-KEY'], 'client-1');
+ return { ok: true, json: async () => ({ name: 'Croma2-Doc', id: 'urn:aaid:sc:AP:abc', documentPages: [] }) };
+ }],
+ ]);
+
+ const doc = await getTaggedDocument('urn:aaid:sc:AP:abc');
+
+ assert.equal(doc.name, 'Croma2-Doc');
+ global.fetch = originalFetch;
+});
+
+test('getTaggedDocument throws with the status and body on a non-ok response', async () => {
+ process.env.EXPRESS_CLIENT_ID = 'client-1';
+ process.env.EXPRESS_CLIENT_SECRET = 'secret-1';
+ stubFetch([
+ [/\/beta\/tagged-documents\//, async () => ({ ok: false, status: 404, text: async () => 'not found' })],
+ ]);
+
+ await assert.rejects(() => getTaggedDocument('urn:missing'), /404/);
+ global.fetch = originalFetch;
+});
+
+test('generateVariation posts the right body and returns jobId/statusUrl', async () => {
+ process.env.EXPRESS_CLIENT_ID = 'client-1';
+ process.env.EXPRESS_CLIENT_SECRET = 'secret-1';
+ stubFetch([
+ [/\/beta\/generate-variation$/, async (url, options) => {
+ assert.equal(options.method, 'POST');
+ const body = JSON.parse(options.body);
+ assert.deepEqual(body, {
+ id: 'urn:doc:1',
+ variationDetails: {
+ pages: '1',
+ preferredDocumentName: 'Croma Earbuds-edit-123',
+ tagMappings: { cta: '20% off' },
+ },
+ });
+ return { ok: true, json: async () => ({ jobId: 'job-1', statusUrl: 'https://express-api.adobe.io/status/job-1' }) };
+ }],
+ ]);
+
+ const result = await generateVariation('urn:doc:1', { cta: '20% off' }, '1', 'Croma Earbuds-edit-123');
+
+ assert.deepEqual(result, { jobId: 'job-1', statusUrl: 'https://express-api.adobe.io/status/job-1' });
+ global.fetch = originalFetch;
+});
+
+test('getJobStatus returns the parsed status response', async () => {
+ process.env.EXPRESS_CLIENT_ID = 'client-1';
+ process.env.EXPRESS_CLIENT_SECRET = 'secret-1';
+ stubFetch([
+ [/\/status\//, async () => ({
+ ok: true,
+ json: async () => ({ jobId: 'job-1', status: 'succeeded', document: { name: 'GD2.express', id: 'urn:doc:2', thumbnailUrl: 'https://example.com/thumb.png' } }),
+ })],
+ ]);
+
+ const result = await getJobStatus('job-1');
+
+ assert.equal(result.status, 'succeeded');
+ assert.equal(result.document.thumbnailUrl, 'https://example.com/thumb.png');
+ global.fetch = originalFetch;
+});
+
+test('pollJobStatus resolves once status is succeeded', async () => {
+ process.env.EXPRESS_CLIENT_ID = 'client-1';
+ process.env.EXPRESS_CLIENT_SECRET = 'secret-1';
+ let calls = 0;
+ stubFetch([
+ [/\/status\//, async () => {
+ calls += 1;
+ const status = calls < 2 ? 'running' : 'succeeded';
+ return {
+ ok: true,
+ json: async () => ({
+ jobId: 'job-2',
+ status,
+ document: status === 'succeeded' ? { thumbnailUrl: 'https://example.com/thumb2.png' } : undefined,
+ }),
+ };
+ }],
+ ]);
+
+ const result = await pollJobStatus('job-2', { intervalMs: 1, timeoutMs: 1000 });
+
+ assert.equal(result.status, 'succeeded');
+ assert.equal(calls, 2);
+ global.fetch = originalFetch;
+});
+
+test('pollJobStatus throws when status is failed', async () => {
+ process.env.EXPRESS_CLIENT_ID = 'client-1';
+ process.env.EXPRESS_CLIENT_SECRET = 'secret-1';
+ stubFetch([
+ [/\/status\//, async () => ({ ok: true, json: async () => ({ jobId: 'job-3', status: 'failed' }) })],
+ ]);
+
+ await assert.rejects(() => pollJobStatus('job-3', { intervalMs: 1, timeoutMs: 1000 }), /failed/);
+ global.fetch = originalFetch;
});
-test('getTemplateInfo returns the unlocked layers for the Croma earbuds template', () => {
- const info = getTemplateInfo('tpl_croma_earbuds');
- assert.deepEqual(info, {
- templateId: 'tpl_croma_earbuds',
- unlockedLayers: ['Price', 'Address', 'Product Image', 'Partner Logo'],
- });
+test('pollJobStatus throws once the timeout elapses without succeeding', async () => {
+ process.env.EXPRESS_CLIENT_ID = 'client-1';
+ process.env.EXPRESS_CLIENT_SECRET = 'secret-1';
+ stubFetch([
+ [/\/status\//, async () => ({ ok: true, json: async () => ({ jobId: 'job-4', status: 'running' }) })],
+ ]);
+
+ await assert.rejects(() => pollJobStatus('job-4', { intervalMs: 5, timeoutMs: 20 }), /timed out/);
+ global.fetch = originalFetch;
});
-test('getTemplateInfo returns an empty layer list for an unknown template', () => {
- const info = getTemplateInfo('tpl_does_not_exist');
- assert.deepEqual(info.unlockedLayers, []);
+test('collectTaggedElements flattens taggedElements across all pages with pageNumber attached', () => {
+ const doc = {
+ documentPages: [
+ { pageNumber: 1, taggedElements: [{ name: 'heading', type: 'text', value: 'Hi' }] },
+ { pageNumber: 2, taggedElements: [{ name: 'footer', type: 'text', value: 'Bye' }] },
+ ],
+ };
+
+ const elements = collectTaggedElements(doc);
+
+ assert.deepEqual(elements, [
+ { name: 'heading', type: 'text', value: 'Hi', pageNumber: 1 },
+ { name: 'footer', type: 'text', value: 'Bye', pageNumber: 2 },
+ ]);
});
-test('applyEdit merges new edits on top of current edits', () => {
- const result = applyEdit('tpl_diwali', { headline: 'Old Headline' }, { discount_text: '70%' });
- assert.deepEqual(result.mergedEdits, { headline: 'Old Headline', discount_text: '70%' });
+test('formatAllowedEdits lists text elements with their current value and non-text elements with just their type', () => {
+ const elements = [
+ { name: 'heading', type: 'text', value: 'Hi', pageNumber: 1 },
+ { name: 'logo', type: 'image', pageNumber: 1 },
+ ];
+
+ const message = formatAllowedEdits('Croma Earbuds', elements);
+
+ assert.match(message, /Edits allowed on "Croma Earbuds":/);
+ assert.match(message, /- heading: currently "Hi"/);
+ assert.match(message, /- logo \(image\)/);
});
-test('applyEdit returns a rendered image url that references the template', () => {
- const result = applyEdit('tpl_summer', {}, { headline: 'Flash Sale' });
- assert.match(result.renderedImageUrl, /^https:\/\/mock-express\.local\/render\/tpl_summer\?rev=\d+$/);
+test('pagesForEdits returns the sorted, comma-joined page numbers containing the edited fields', () => {
+ const elements = [
+ { name: 'heading', pageNumber: 2 },
+ { name: 'cta', pageNumber: 1 },
+ { name: 'footer', pageNumber: 1 },
+ ];
+
+ assert.equal(pagesForEdits(elements, ['cta']), '1');
+ assert.equal(pagesForEdits(elements, ['cta', 'heading']), '1,2');
});
-test('applyEdit returns the fixed updated Croma earbuds image for any edit', () => {
- const result = applyEdit('tpl_croma_earbuds', {}, { Price: '999' });
- assert.equal(result.renderedImageUrl, 'https://s7ap1.scene7.com/is/image/varun/croma1-earbuds-updated');
+test('buildPreferredDocumentName appends a timestamp suffix to the base name', () => {
+ const name = buildPreferredDocumentName('Croma Earbuds');
+ assert.match(name, /^Croma Earbuds-edit-\d+$/);
});
From 9aba52e73266c281ef9ba70067ddc11e4154893b Mon Sep 17 00:00:00 2001
From: Priyank Modi
Date: Thu, 16 Jul 2026 13:21:55 +0530
Subject: [PATCH 16/20] feat: wire actions.js to the real Express API and drop
mocked edit validation
---
actions.js | 52 +++++++++++++++------
actions.test.js | 122 ++++++++++++++++++++++++++++++++++--------------
2 files changed, 126 insertions(+), 48 deletions(-)
diff --git a/actions.js b/actions.js
index a990e7b913..20144a3bee 100644
--- a/actions.js
+++ b/actions.js
@@ -1,6 +1,5 @@
-const { getTrackedImages, findTrackedImage } = require('./imageStore');
-const { getTemplateInfo, applyEdit } = require('./expressApi');
-const { uploadImageToMeta } = require('./metaUpload');
+const { getTrackedImages, findTrackedImage, recordEdits } = require('./imageStore');
+const expressApi = require('./expressApi');
function formatUnknownImageMessage(phoneNumber) {
const images = getTrackedImages(phoneNumber);
@@ -18,9 +17,15 @@ async function actionCheckAllowedEdits(phoneNumber, imageId) {
if (!image) {
return formatUnknownImageMessage(phoneNumber);
}
- const { unlockedLayers } = getTemplateInfo(image.templateId);
- const layerList = unlockedLayers.map((layer) => `- ${layer}`).join('\n');
- return `Edits allowed on "${image.name}":\n${layerList}`;
+
+ try {
+ const doc = await expressApi.getTaggedDocument(image.docId);
+ const elements = expressApi.collectTaggedElements(doc);
+ return expressApi.formatAllowedEdits(image.name, elements);
+ } catch (err) {
+ console.error('[actionCheckAllowedEdits] Express API error', { docId: image.docId, message: err.message });
+ return `Sorry, I couldn't check the allowed edits for "${image.name}" right now. Please try again in a moment.`;
+ }
}
async function actionEditGraphic(phoneNumber, imageId, edits, { sendImage }) {
@@ -29,20 +34,39 @@ async function actionEditGraphic(phoneNumber, imageId, edits, { sendImage }) {
return formatUnknownImageMessage(phoneNumber);
}
- const { unlockedLayers } = getTemplateInfo(image.templateId);
+ let elements;
+ try {
+ const doc = await expressApi.getTaggedDocument(image.docId);
+ elements = expressApi.collectTaggedElements(doc);
+ } catch (err) {
+ console.error('[actionEditGraphic] Express API error', { docId: image.docId, message: err.message });
+ return `Sorry, I couldn't reach Adobe Express to apply that edit. Please try again in a moment.`;
+ }
+
+ const allowedNames = elements.map((element) => element.name);
const requestedKeys = Object.keys(edits || {});
- const disallowedKeys = requestedKeys.filter((key) => !unlockedLayers.includes(key));
+ const disallowedKeys = requestedKeys.filter((key) => !allowedNames.includes(key));
if (disallowedKeys.length > 0) {
- const allowedList = unlockedLayers.map((layer) => `- ${layer}`).join('\n');
- return `I can't edit ${disallowedKeys.join(', ')} on "${image.name}". Allowed edits:\n${allowedList}`;
+ return `I can't edit ${disallowedKeys.join(', ')} on "${image.name}". ${expressApi.formatAllowedEdits(image.name, elements)}`;
}
- const { mergedEdits, renderedImageUrl } = applyEdit(image.templateId, image.currentEdits, edits);
- image.currentEdits = mergedEdits;
+ const mergedEdits = { ...image.currentEdits, ...edits };
+ const pages = expressApi.pagesForEdits(elements, Object.keys(mergedEdits));
+ const preferredDocumentName = expressApi.buildPreferredDocumentName(image.name);
+
+ let thumbnailUrl;
+ try {
+ const { jobId } = await expressApi.generateVariation(image.docId, mergedEdits, pages, preferredDocumentName);
+ const result = await expressApi.pollJobStatus(jobId);
+ thumbnailUrl = result.document.thumbnailUrl;
+ } catch (err) {
+ console.error('[actionEditGraphic] generate/poll error', { docId: image.docId, message: err.message });
+ return `Sorry, something went wrong generating your updated "${image.name}". Please try again.`;
+ }
- const uploadedUrl = await uploadImageToMeta(renderedImageUrl);
- await sendImage(phoneNumber, uploadedUrl);
+ recordEdits(phoneNumber, imageId, edits);
+ await sendImage(phoneNumber, thumbnailUrl);
const summary = Object.entries(edits).map(([key, value]) => `• ${key}: ${value}`).join('\n');
return `Updated "${image.name}":\n${summary}`;
diff --git a/actions.test.js b/actions.test.js
index fa88280a39..50ca36e902 100644
--- a/actions.test.js
+++ b/actions.test.js
@@ -1,66 +1,120 @@
const test = require('node:test');
const assert = require('node:assert/strict');
+const fs = require('node:fs');
+const path = require('node:path');
+const os = require('node:os');
const { actionCheckAllowedEdits, actionEditGraphic } = require('./actions');
-const { getTrackedImages } = require('./imageStore');
+const expressApi = require('./expressApi');
+const { findTrackedImage } = require('./imageStore');
+
+function writeFixtureCatalog(entries) {
+ const fixturePath = path.join(os.tmpdir(), `express-templates-${Date.now()}-${Math.random().toString(36).slice(2)}.json`);
+ fs.writeFileSync(fixturePath, JSON.stringify(entries));
+ process.env.EXPRESS_TEMPLATES_FILE = fixturePath;
+}
+
+const SAMPLE_ELEMENTS_DOC = {
+ documentPages: [
+ {
+ pageNumber: 1,
+ taggedElements: [
+ { name: 'heading', type: 'text', value: 'The X-Phone Pro is here!' },
+ { name: 'cta', type: 'text', value: 'Available at our store starting 15 Aug 20XX.' },
+ ],
+ },
+ ],
+};
+
+test('actionCheckAllowedEdits lists the tagged elements for a known image', async () => {
+ writeFixtureCatalog([{ id: 'img_1', name: 'Croma Earbuds', docId: 'urn:doc:1' }]);
+ expressApi.getTaggedDocument = async (docId) => {
+ assert.equal(docId, 'urn:doc:1');
+ return SAMPLE_ELEMENTS_DOC;
+ };
-test('actionCheckAllowedEdits lists the unlocked layers for a known image', async () => {
const reply = await actionCheckAllowedEdits('phone-1', 'img_1');
- assert.match(reply, /Diwali Offer Banner/);
- assert.match(reply, /discount_text/);
-});
-test('actionCheckAllowedEdits lists Price, Address, Product Image, Partner Logo for the Croma earbuds image', async () => {
- const reply = await actionCheckAllowedEdits('phone-croma', 'img_3');
assert.match(reply, /Croma Earbuds/);
- assert.match(reply, /- Price/);
- assert.match(reply, /- Address/);
- assert.match(reply, /- Product Image/);
- assert.match(reply, /- Partner Logo/);
+ assert.match(reply, /heading: currently "The X-Phone Pro is here!"/);
+ assert.match(reply, /cta: currently/);
});
test('actionCheckAllowedEdits reports unknown images without throwing', async () => {
+ writeFixtureCatalog([{ id: 'img_1', name: 'Croma Earbuds', docId: 'urn:doc:1' }]);
+
const reply = await actionCheckAllowedEdits('phone-2', 'img_nope');
+
assert.match(reply, /couldn't find that image/);
});
-test('actionEditGraphic rejects edits outside the unlocked layers and sends nothing', async () => {
- let sendImageCalled = false;
- const sendImage = async () => {
- sendImageCalled = true;
+test('actionCheckAllowedEdits returns a friendly message when the Express API call fails', async () => {
+ writeFixtureCatalog([{ id: 'img_1', name: 'Croma Earbuds', docId: 'urn:doc:1' }]);
+ expressApi.getTaggedDocument = async () => {
+ throw new Error('getTaggedDocument failed 500: boom');
};
- const reply = await actionEditGraphic('phone-3', 'img_3', { background_color: 'red' }, { sendImage });
+ const reply = await actionCheckAllowedEdits('phone-3', 'img_1');
+
+ assert.match(reply, /couldn't check the allowed edits/);
+});
+
+test('actionEditGraphic rejects edits outside the tagged elements and makes no generate call', async () => {
+ writeFixtureCatalog([{ id: 'img_1', name: 'Croma Earbuds', docId: 'urn:doc:1' }]);
+ expressApi.getTaggedDocument = async () => SAMPLE_ELEMENTS_DOC;
+ expressApi.generateVariation = async () => {
+ throw new Error('should not be called');
+ };
+ let sendImageCalled = false;
+ const sendImage = async () => { sendImageCalled = true; };
+
+ const reply = await actionEditGraphic('phone-4', 'img_1', { background_color: 'red' }, { sendImage });
assert.match(reply, /can't edit background_color/);
assert.equal(sendImageCalled, false);
});
-test('actionEditGraphic sends the fixed updated Croma earbuds image for any allowed edit', async () => {
- const sentCalls = [];
- const sendImage = async (to, link) => {
- sentCalls.push({ to, link });
+test('actionEditGraphic applies an allowed edit end-to-end: generates, polls, sends the thumbnail, and records the edit', async () => {
+ writeFixtureCatalog([{ id: 'img_1', name: 'Croma Earbuds', docId: 'urn:doc:1' }]);
+ expressApi.getTaggedDocument = async () => SAMPLE_ELEMENTS_DOC;
+ expressApi.generateVariation = async (docId, tagMappings, pages, preferredDocumentName) => {
+ assert.equal(docId, 'urn:doc:1');
+ assert.deepEqual(tagMappings, { cta: '20% off' });
+ assert.equal(pages, '1');
+ assert.match(preferredDocumentName, /^Croma Earbuds-edit-\d+$/);
+ return { jobId: 'job-1', statusUrl: 'https://express-api.adobe.io/status/job-1' };
+ };
+ expressApi.pollJobStatus = async (jobId) => {
+ assert.equal(jobId, 'job-1');
+ return { status: 'succeeded', document: { thumbnailUrl: 'https://example.com/thumb.png' } };
};
- const reply = await actionEditGraphic('phone-5', 'img_3', { Price: '999' }, { sendImage });
+ const sentCalls = [];
+ const sendImage = async (to, link) => { sentCalls.push({ to, link }); };
+
+ const reply = await actionEditGraphic('phone-5', 'img_1', { cta: '20% off' }, { sendImage });
assert.match(reply, /Updated "Croma Earbuds"/);
assert.equal(sentCalls.length, 1);
- assert.equal(sentCalls[0].link, 'https://s7ap1.scene7.com/is/image/varun/croma1-earbuds-updated');
+ assert.equal(sentCalls[0].to, 'phone-5');
+ assert.equal(sentCalls[0].link, 'https://example.com/thumb.png');
+
+ const image = findTrackedImage('phone-5', 'img_1');
+ assert.deepEqual(image.currentEdits, { cta: '20% off' });
});
-test('actionEditGraphic applies an allowed edit, sends the updated image, and remembers the edit', async () => {
- const sentCalls = [];
- const sendImage = async (to, link) => {
- sentCalls.push({ to, link });
- };
+test('actionEditGraphic returns a friendly message and does not record the edit when generation fails', async () => {
+ writeFixtureCatalog([{ id: 'img_1', name: 'Croma Earbuds', docId: 'urn:doc:1' }]);
+ expressApi.getTaggedDocument = async () => SAMPLE_ELEMENTS_DOC;
+ expressApi.generateVariation = async () => { throw new Error('generateVariation failed 500: boom'); };
+
+ let sendImageCalled = false;
+ const sendImage = async () => { sendImageCalled = true; };
- const reply = await actionEditGraphic('phone-4', 'img_2', { headline: 'Flash Sale' }, { sendImage });
+ const reply = await actionEditGraphic('phone-6', 'img_1', { cta: '20% off' }, { sendImage });
- assert.match(reply, /Updated "Summer Sale Flyer"/);
- assert.equal(sentCalls.length, 1);
- assert.equal(sentCalls[0].to, 'phone-4');
- assert.match(sentCalls[0].link, /^https:\/\/mock-meta-cdn\.local\//);
+ assert.match(reply, /something went wrong generating/);
+ assert.equal(sendImageCalled, false);
- const image = getTrackedImages('phone-4').find((img) => img.id === 'img_2');
- assert.equal(image.currentEdits.headline, 'Flash Sale');
+ const image = findTrackedImage('phone-6', 'img_1');
+ assert.deepEqual(image.currentEdits, {});
});
From afc90fadaa7b9f148c6fd650fb4bf95c7dcccbe2 Mon Sep 17 00:00:00 2001
From: Priyank Modi
Date: Thu, 16 Jul 2026 13:26:53 +0530
Subject: [PATCH 17/20] feat: log webhook metadata fields and declare Express
API env vars
---
app.js | 4 ++++
render.yaml | 16 ++++++++++++++++
2 files changed, 20 insertions(+)
diff --git a/app.js b/app.js
index 1d703d50e1..1baa2ef9d1 100644
--- a/app.js
+++ b/app.js
@@ -195,6 +195,10 @@ app.post('/', async (req, res) => {
if (!messages?.length) return;
for (const message of messages) {
+ if (message.context) console.log('[webhook] message.context:', JSON.stringify(message.context));
+ if (message.referral) console.log('[webhook] message.referral:', JSON.stringify(message.referral));
+ if (message.image) console.log('[webhook] message.image:', JSON.stringify(message.image));
+
const userText = message?.text?.body;
if (!userText) continue;
diff --git a/render.yaml b/render.yaml
index 55fe1154ed..fb9ed9678d 100644
--- a/render.yaml
+++ b/render.yaml
@@ -20,3 +20,19 @@ services:
sync: false
- key: OPENAI_MODEL
sync: false
+ - key: EXPRESS_CLIENT_ID
+ sync: false
+ - key: EXPRESS_CLIENT_SECRET
+ sync: false
+ - key: EXPRESS_API_SCOPE
+ sync: false
+ - key: EXPRESS_IMS_TOKEN_URL
+ sync: false
+ - key: EXPRESS_API_BASE_URL
+ sync: false
+ - key: EXPRESS_TEMPLATES_FILE
+ sync: false
+ - key: EXPRESS_STATUS_POLL_INTERVAL_MS
+ sync: false
+ - key: EXPRESS_STATUS_POLL_TIMEOUT_MS
+ sync: false
From b002aee5e6e44b4fe360d6ba125c7f30a01b5998 Mon Sep 17 00:00:00 2001
From: Priyank Modi
Date: Thu, 16 Jul 2026 13:42:54 +0530
Subject: [PATCH 18/20] Fix cross-task issues from final branch review
- Use the real statusUrl returned by generateVariation instead of
reconstructing a status URL from apiBaseUrl()+jobId, which was an
unverified assumption about the API's URL shape.
- Overlay image.currentEdits onto tagged elements before formatting
allowed-edits messages so previously edited fields show their latest
value instead of the stale original document value.
- Narrow the hardcoded campaign graphics list to match the real
single-entry catalog (Croma Earbuds).
- Guard the sendImage call in actionEditGraphic with try/catch so a
delivery failure doesn't swallow an already-successful edit; users
now get a "couldn't send" message instead of no reply at all.
Co-Authored-By: Claude Sonnet 5
---
actions.js | 27 +++++++++++++++++++++------
actions.test.js | 35 ++++++++++++++++++++++++++++++++---
expressApi.js | 12 ++++++------
expressApi.test.js | 38 ++++++++++++++++++++++++++------------
4 files changed, 85 insertions(+), 27 deletions(-)
diff --git a/actions.js b/actions.js
index 20144a3bee..b7d67de23b 100644
--- a/actions.js
+++ b/actions.js
@@ -9,7 +9,13 @@ function formatUnknownImageMessage(phoneNumber) {
async function actionListCampaignGraphics() {
// TODO: fetch from campaign API
- return 'Graphics in your current campaign:\n1. Diwali Offer Banner\n2. Summer Sale Flyer\n3. Croma Earbuds';
+ return 'Graphics in your current campaign:\n1. Croma Earbuds';
+}
+
+function withCurrentEdits(elements, currentEdits) {
+ return elements.map((element) =>
+ element.name in currentEdits ? { ...element, value: currentEdits[element.name] } : element
+ );
}
async function actionCheckAllowedEdits(phoneNumber, imageId) {
@@ -21,7 +27,8 @@ async function actionCheckAllowedEdits(phoneNumber, imageId) {
try {
const doc = await expressApi.getTaggedDocument(image.docId);
const elements = expressApi.collectTaggedElements(doc);
- return expressApi.formatAllowedEdits(image.name, elements);
+ const elementsWithCurrentEdits = withCurrentEdits(elements, image.currentEdits);
+ return expressApi.formatAllowedEdits(image.name, elementsWithCurrentEdits);
} catch (err) {
console.error('[actionCheckAllowedEdits] Express API error', { docId: image.docId, message: err.message });
return `Sorry, I couldn't check the allowed edits for "${image.name}" right now. Please try again in a moment.`;
@@ -48,7 +55,8 @@ async function actionEditGraphic(phoneNumber, imageId, edits, { sendImage }) {
const disallowedKeys = requestedKeys.filter((key) => !allowedNames.includes(key));
if (disallowedKeys.length > 0) {
- return `I can't edit ${disallowedKeys.join(', ')} on "${image.name}". ${expressApi.formatAllowedEdits(image.name, elements)}`;
+ const elementsWithCurrentEdits = withCurrentEdits(elements, image.currentEdits);
+ return `I can't edit ${disallowedKeys.join(', ')} on "${image.name}". ${expressApi.formatAllowedEdits(image.name, elementsWithCurrentEdits)}`;
}
const mergedEdits = { ...image.currentEdits, ...edits };
@@ -57,8 +65,8 @@ async function actionEditGraphic(phoneNumber, imageId, edits, { sendImage }) {
let thumbnailUrl;
try {
- const { jobId } = await expressApi.generateVariation(image.docId, mergedEdits, pages, preferredDocumentName);
- const result = await expressApi.pollJobStatus(jobId);
+ const { statusUrl } = await expressApi.generateVariation(image.docId, mergedEdits, pages, preferredDocumentName);
+ const result = await expressApi.pollJobStatus(statusUrl);
thumbnailUrl = result.document.thumbnailUrl;
} catch (err) {
console.error('[actionEditGraphic] generate/poll error', { docId: image.docId, message: err.message });
@@ -66,9 +74,16 @@ async function actionEditGraphic(phoneNumber, imageId, edits, { sendImage }) {
}
recordEdits(phoneNumber, imageId, edits);
- await sendImage(phoneNumber, thumbnailUrl);
const summary = Object.entries(edits).map(([key, value]) => `• ${key}: ${value}`).join('\n');
+
+ try {
+ await sendImage(phoneNumber, thumbnailUrl);
+ } catch (err) {
+ console.error('[actionEditGraphic] sendImage error', { docId: image.docId, message: err.message });
+ return `Updated "${image.name}", but I couldn't send the image right now — try asking me to resend it.`;
+ }
+
return `Updated "${image.name}":\n${summary}`;
}
diff --git a/actions.test.js b/actions.test.js
index 50ca36e902..f870b2d582 100644
--- a/actions.test.js
+++ b/actions.test.js
@@ -5,7 +5,7 @@ const path = require('node:path');
const os = require('node:os');
const { actionCheckAllowedEdits, actionEditGraphic } = require('./actions');
const expressApi = require('./expressApi');
-const { findTrackedImage } = require('./imageStore');
+const { findTrackedImage, recordEdits } = require('./imageStore');
function writeFixtureCatalog(entries) {
const fixturePath = path.join(os.tmpdir(), `express-templates-${Date.now()}-${Math.random().toString(36).slice(2)}.json`);
@@ -39,6 +39,18 @@ test('actionCheckAllowedEdits lists the tagged elements for a known image', asyn
assert.match(reply, /cta: currently/);
});
+test('actionCheckAllowedEdits shows the latest edited value instead of the stale original document value', async () => {
+ writeFixtureCatalog([{ id: 'img_1', name: 'Croma Earbuds', docId: 'urn:doc:1' }]);
+ expressApi.getTaggedDocument = async () => SAMPLE_ELEMENTS_DOC;
+ recordEdits('phone-1b', 'img_1', { cta: '20% off' });
+
+ const reply = await actionCheckAllowedEdits('phone-1b', 'img_1');
+
+ assert.match(reply, /cta: currently "20% off"/);
+ assert.doesNotMatch(reply, /Available at our store starting 15 Aug 20XX\./);
+ assert.match(reply, /heading: currently "The X-Phone Pro is here!"/);
+});
+
test('actionCheckAllowedEdits reports unknown images without throwing', async () => {
writeFixtureCatalog([{ id: 'img_1', name: 'Croma Earbuds', docId: 'urn:doc:1' }]);
@@ -83,8 +95,8 @@ test('actionEditGraphic applies an allowed edit end-to-end: generates, polls, se
assert.match(preferredDocumentName, /^Croma Earbuds-edit-\d+$/);
return { jobId: 'job-1', statusUrl: 'https://express-api.adobe.io/status/job-1' };
};
- expressApi.pollJobStatus = async (jobId) => {
- assert.equal(jobId, 'job-1');
+ expressApi.pollJobStatus = async (statusUrl) => {
+ assert.equal(statusUrl, 'https://express-api.adobe.io/status/job-1');
return { status: 'succeeded', document: { thumbnailUrl: 'https://example.com/thumb.png' } };
};
@@ -118,3 +130,20 @@ test('actionEditGraphic returns a friendly message and does not record the edit
const image = findTrackedImage('phone-6', 'img_1');
assert.deepEqual(image.currentEdits, {});
});
+
+test('actionEditGraphic tells the user delivery failed but keeps the recorded edit when sendImage throws', async () => {
+ writeFixtureCatalog([{ id: 'img_1', name: 'Croma Earbuds', docId: 'urn:doc:1' }]);
+ expressApi.getTaggedDocument = async () => SAMPLE_ELEMENTS_DOC;
+ expressApi.generateVariation = async () => ({ jobId: 'job-1', statusUrl: 'https://express-api.adobe.io/status/job-1' });
+ expressApi.pollJobStatus = async () => ({ status: 'succeeded', document: { thumbnailUrl: 'https://example.com/thumb.png' } });
+
+ const sendImage = async () => { throw new Error('WhatsApp could not fetch the link'); };
+
+ const reply = await actionEditGraphic('phone-7', 'img_1', { cta: '20% off' }, { sendImage });
+
+ assert.match(reply, /couldn't send the image right now/);
+ assert.doesNotMatch(reply, /something went wrong generating/);
+
+ const image = findTrackedImage('phone-7', 'img_1');
+ assert.deepEqual(image.currentEdits, { cta: '20% off' });
+});
diff --git a/expressApi.js b/expressApi.js
index 1d43d2ef4d..2be744773e 100644
--- a/expressApi.js
+++ b/expressApi.js
@@ -35,9 +35,9 @@ async function generateVariation(docId, tagMappings, pages, preferredDocumentNam
return response.json();
}
-async function getJobStatus(jobId) {
+async function getJobStatus(statusUrl) {
const headers = await buildAuthHeaders();
- const response = await fetch(`${apiBaseUrl()}/status/${encodeURIComponent(jobId)}`, { headers });
+ const response = await fetch(statusUrl, { headers });
if (!response.ok) {
const text = await response.text();
throw new Error(`getJobStatus failed ${response.status}: ${text}`);
@@ -45,16 +45,16 @@ async function getJobStatus(jobId) {
return response.json();
}
-async function pollJobStatus(jobId, { intervalMs, timeoutMs } = {}) {
+async function pollJobStatus(statusUrl, { intervalMs, timeoutMs } = {}) {
const interval = intervalMs ?? Number(process.env.EXPRESS_STATUS_POLL_INTERVAL_MS || 2000);
const timeout = timeoutMs ?? Number(process.env.EXPRESS_STATUS_POLL_TIMEOUT_MS || 60000);
const deadline = Date.now() + timeout;
for (;;) {
- const result = await getJobStatus(jobId);
+ const result = await getJobStatus(statusUrl);
if (result.status === 'succeeded') return result;
- if (result.status === 'failed') throw new Error(`Express job ${jobId} failed`);
- if (Date.now() >= deadline) throw new Error(`Express job ${jobId} timed out after ${timeout}ms`);
+ if (result.status === 'failed') throw new Error(`Express job at ${statusUrl} failed`);
+ if (Date.now() >= deadline) throw new Error(`Express job at ${statusUrl} timed out after ${timeout}ms`);
await sleep(interval);
}
}
diff --git a/expressApi.test.js b/expressApi.test.js
index e3fe77ed75..c0b974d0c7 100644
--- a/expressApi.test.js
+++ b/expressApi.test.js
@@ -79,17 +79,21 @@ test('generateVariation posts the right body and returns jobId/statusUrl', async
global.fetch = originalFetch;
});
-test('getJobStatus returns the parsed status response', async () => {
+test('getJobStatus fetches the exact statusUrl provided, with no reconstruction', async () => {
process.env.EXPRESS_CLIENT_ID = 'client-1';
process.env.EXPRESS_CLIENT_SECRET = 'secret-1';
+ const statusUrl = 'https://express-api.adobe.io/status/job-1';
stubFetch([
- [/\/status\//, async () => ({
- ok: true,
- json: async () => ({ jobId: 'job-1', status: 'succeeded', document: { name: 'GD2.express', id: 'urn:doc:2', thumbnailUrl: 'https://example.com/thumb.png' } }),
- })],
+ [/\/status\/job-1$/, async (url) => {
+ assert.equal(url, statusUrl);
+ return {
+ ok: true,
+ json: async () => ({ jobId: 'job-1', status: 'succeeded', document: { name: 'GD2.express', id: 'urn:doc:2', thumbnailUrl: 'https://example.com/thumb.png' } }),
+ };
+ }],
]);
- const result = await getJobStatus('job-1');
+ const result = await getJobStatus(statusUrl);
assert.equal(result.status, 'succeeded');
assert.equal(result.document.thumbnailUrl, 'https://example.com/thumb.png');
@@ -99,9 +103,11 @@ test('getJobStatus returns the parsed status response', async () => {
test('pollJobStatus resolves once status is succeeded', async () => {
process.env.EXPRESS_CLIENT_ID = 'client-1';
process.env.EXPRESS_CLIENT_SECRET = 'secret-1';
+ const statusUrl = 'https://express-api.adobe.io/status/job-2';
let calls = 0;
stubFetch([
- [/\/status\//, async () => {
+ [/\/status\/job-2$/, async (url) => {
+ assert.equal(url, statusUrl);
calls += 1;
const status = calls < 2 ? 'running' : 'succeeded';
return {
@@ -115,7 +121,7 @@ test('pollJobStatus resolves once status is succeeded', async () => {
}],
]);
- const result = await pollJobStatus('job-2', { intervalMs: 1, timeoutMs: 1000 });
+ const result = await pollJobStatus(statusUrl, { intervalMs: 1, timeoutMs: 1000 });
assert.equal(result.status, 'succeeded');
assert.equal(calls, 2);
@@ -125,22 +131,30 @@ test('pollJobStatus resolves once status is succeeded', async () => {
test('pollJobStatus throws when status is failed', async () => {
process.env.EXPRESS_CLIENT_ID = 'client-1';
process.env.EXPRESS_CLIENT_SECRET = 'secret-1';
+ const statusUrl = 'https://express-api.adobe.io/status/job-3';
stubFetch([
- [/\/status\//, async () => ({ ok: true, json: async () => ({ jobId: 'job-3', status: 'failed' }) })],
+ [/\/status\/job-3$/, async () => ({ ok: true, json: async () => ({ jobId: 'job-3', status: 'failed' }) })],
]);
- await assert.rejects(() => pollJobStatus('job-3', { intervalMs: 1, timeoutMs: 1000 }), /failed/);
+ await assert.rejects(
+ () => pollJobStatus(statusUrl, { intervalMs: 1, timeoutMs: 1000 }),
+ (err) => err.message === `Express job at ${statusUrl} failed`
+ );
global.fetch = originalFetch;
});
test('pollJobStatus throws once the timeout elapses without succeeding', async () => {
process.env.EXPRESS_CLIENT_ID = 'client-1';
process.env.EXPRESS_CLIENT_SECRET = 'secret-1';
+ const statusUrl = 'https://express-api.adobe.io/status/job-4';
stubFetch([
- [/\/status\//, async () => ({ ok: true, json: async () => ({ jobId: 'job-4', status: 'running' }) })],
+ [/\/status\/job-4$/, async () => ({ ok: true, json: async () => ({ jobId: 'job-4', status: 'running' }) })],
]);
- await assert.rejects(() => pollJobStatus('job-4', { intervalMs: 5, timeoutMs: 20 }), /timed out/);
+ await assert.rejects(
+ () => pollJobStatus(statusUrl, { intervalMs: 5, timeoutMs: 20 }),
+ (err) => /timed out/.test(err.message) && err.message.includes(statusUrl)
+ );
global.fetch = originalFetch;
});
From ba05c0fb28e359eefafde2c2ebb77c2944b17b5d Mon Sep 17 00:00:00 2001
From: Priyank Modi
Date: Thu, 16 Jul 2026 13:45:34 +0530
Subject: [PATCH 19/20] Add implementation plan for real Adobe Express API
integration
Co-Authored-By: Claude Sonnet 5
---
...2026-07-16-real-express-api-integration.md | 1039 +++++++++++++++++
1 file changed, 1039 insertions(+)
create mode 100644 docs/superpowers/plans/2026-07-16-real-express-api-integration.md
diff --git a/docs/superpowers/plans/2026-07-16-real-express-api-integration.md b/docs/superpowers/plans/2026-07-16-real-express-api-integration.md
new file mode 100644
index 0000000000..4762840bb3
--- /dev/null
+++ b/docs/superpowers/plans/2026-07-16-real-express-api-integration.md
@@ -0,0 +1,1039 @@
+# Real Adobe Express API Integration Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Replace every mocked/hardcoded piece of the image-edit flow with real Adobe Express API calls (`tagged-documents`, `generate-variation`, `status`), driven by a shared `docId` catalog, ending with the real edited-image `thumbnailUrl` sent back over WhatsApp.
+
+**Architecture:** A new `expressAuth.js` handles IMS OAuth token fetch/cache. `expressApi.js` is rewritten to make real HTTP calls (tagged-document lookup, variation generation, status polling) plus small formatting/helper functions, using `expressAuth.js` for auth headers. `imageStore.js` is rewritten to read a shared JSON catalog (`data/express-templates.json`, `id`/`name`/`docId`) fresh on every call instead of hardcoded seed data, layering in-memory per-conversation edit state on top. `actions.js` is rewired to call the real `expressApi.js` functions (via a namespace import so tests can stub them directly) instead of the old mocks, with all Express API failures caught and turned into friendly WhatsApp replies. `app.js` gets a few extra labeled `console.log` lines for webhook fields that might carry docID metadata — no other behavior change. `metaUpload.js` is left completely untouched and unused.
+
+**Tech Stack:** Node.js 20, Express 5, `openai` SDK, Node's built-in `node:test` + `node:assert/strict` + global `fetch` (no new dependencies).
+
+## Global Constraints
+
+- No new npm dependencies — use Node's built-in `node:test` test runner and global `fetch`, stubbed directly in tests (no mocking library).
+- Follow existing code style: CommonJS `require`, 2-space indentation, semicolons.
+- Real Adobe Express HTTP errors must never reach the WhatsApp user raw — every call site catches and replaces them with a short friendly retry message, logging the technical detail (status/body/docId/jobId) server-side via `console.error`.
+- `data/express-templates.json` is committed to git (not gitignored) and is a flat, phone-number-agnostic catalog: `[{ id, name, docId }]`. No per-phone-number entries.
+- IMS token responses' `expires_in` is treated as **milliseconds** (Adobe IMS v3 convention), added directly to `Date.now()` — flagged as an assumption to verify against the real endpoint; isolated to one line in `expressAuth.js` if it needs to change to `* 1000`.
+- `metaUpload.js` and `metaUpload.test.js` stay in the repo, completely unmodified, and are not imported anywhere after this change.
+- A failed or timed-out edit must not be recorded into the per-conversation edit state — only a successful `generate-variation` + `status: succeeded` commits the edit, so retries don't compound bad state.
+
+---
+
+## Task 1: `expressAuth.js` — IMS token fetch/cache
+
+**Files:**
+- Create: `expressAuth.js`
+- Test: `expressAuth.test.js`
+
+**Interfaces:**
+- Consumes: env vars `EXPRESS_CLIENT_ID`, `EXPRESS_CLIENT_SECRET`, `EXPRESS_API_SCOPE` (optional), `EXPRESS_IMS_TOKEN_URL` (optional).
+- Produces: `getAccessToken() -> Promise` (cached IMS access token, refetches within 60s of expiry). `buildAuthHeaders() -> Promise<{ Authorization: string, 'X-API-KEY': string }>`.
+
+- [ ] **Step 1: Write the failing tests**
+
+Create `expressAuth.test.js`:
+
+```js
+const test = require('node:test');
+const assert = require('node:assert/strict');
+
+const originalFetch = global.fetch;
+
+function freshExpressAuth() {
+ delete require.cache[require.resolve('./expressAuth')];
+ return require('./expressAuth');
+}
+
+test('getAccessToken fetches a token from the IMS endpoint using client credentials', async () => {
+ process.env.EXPRESS_CLIENT_ID = 'client-123';
+ process.env.EXPRESS_CLIENT_SECRET = 'secret-456';
+ global.fetch = async (url, options) => {
+ assert.equal(url, 'https://ims-na1.adobelogin.com/ims/token/v3');
+ assert.equal(options.method, 'POST');
+ assert.equal(options.headers['Content-Type'], 'application/x-www-form-urlencoded');
+ const body = options.body.toString();
+ assert.match(body, /grant_type=client_credentials/);
+ assert.match(body, /client_id=client-123/);
+ assert.match(body, /client_secret=secret-456/);
+ return { ok: true, json: async () => ({ access_token: 'tok-abc', expires_in: 86400000, token_type: 'bearer' }) };
+ };
+
+ const { getAccessToken } = freshExpressAuth();
+ const token = await getAccessToken();
+
+ assert.equal(token, 'tok-abc');
+ global.fetch = originalFetch;
+});
+
+test('getAccessToken caches the token and does not refetch on a second call', async () => {
+ process.env.EXPRESS_CLIENT_ID = 'client-123';
+ process.env.EXPRESS_CLIENT_SECRET = 'secret-456';
+ let fetchCalls = 0;
+ global.fetch = async () => {
+ fetchCalls += 1;
+ return { ok: true, json: async () => ({ access_token: 'tok-cached', expires_in: 86400000 }) };
+ };
+
+ const { getAccessToken } = freshExpressAuth();
+ const first = await getAccessToken();
+ const second = await getAccessToken();
+
+ assert.equal(first, 'tok-cached');
+ assert.equal(second, 'tok-cached');
+ assert.equal(fetchCalls, 1);
+ global.fetch = originalFetch;
+});
+
+test('getAccessToken refetches once the cached token is within 60s of expiring', async () => {
+ process.env.EXPRESS_CLIENT_ID = 'client-123';
+ process.env.EXPRESS_CLIENT_SECRET = 'secret-456';
+ let fetchCalls = 0;
+ global.fetch = async () => {
+ fetchCalls += 1;
+ return { ok: true, json: async () => ({ access_token: `tok-${fetchCalls}`, expires_in: 30000 }) };
+ };
+
+ const { getAccessToken } = freshExpressAuth();
+ const first = await getAccessToken();
+ const second = await getAccessToken();
+
+ assert.equal(first, 'tok-1');
+ assert.equal(second, 'tok-2');
+ assert.equal(fetchCalls, 2);
+ global.fetch = originalFetch;
+});
+
+test('getAccessToken throws with the status and body when the IMS endpoint errors', async () => {
+ process.env.EXPRESS_CLIENT_ID = 'client-123';
+ process.env.EXPRESS_CLIENT_SECRET = 'secret-456';
+ global.fetch = async () => ({ ok: false, status: 401, text: async () => 'invalid client' });
+
+ const { getAccessToken } = freshExpressAuth();
+ await assert.rejects(() => getAccessToken(), /401/);
+ global.fetch = originalFetch;
+});
+
+test('buildAuthHeaders returns Authorization and X-API-KEY headers', async () => {
+ process.env.EXPRESS_CLIENT_ID = 'client-789';
+ process.env.EXPRESS_CLIENT_SECRET = 'secret-000';
+ global.fetch = async () => ({ ok: true, json: async () => ({ access_token: 'tok-xyz', expires_in: 86400000 }) });
+
+ const { buildAuthHeaders } = freshExpressAuth();
+ const headers = await buildAuthHeaders();
+
+ assert.deepEqual(headers, { Authorization: 'Bearer tok-xyz', 'X-API-KEY': 'client-789' });
+ global.fetch = originalFetch;
+});
+```
+
+- [ ] **Step 2: Run tests to verify they fail**
+
+Run: `node --test expressAuth.test.js`
+Expected: FAIL — `Cannot find module './expressAuth'`
+
+- [ ] **Step 3: Write the implementation**
+
+Create `expressAuth.js`:
+
+```js
+const IMS_TOKEN_URL = process.env.EXPRESS_IMS_TOKEN_URL || 'https://ims-na1.adobelogin.com/ims/token/v3';
+const DEFAULT_SCOPE = 'ee.express_api,openid,AdobeID,read_organizations,additional_info.projectedProductContext';
+const REFRESH_MARGIN_MS = 60_000;
+
+let cachedToken = null; // { accessToken, expiresAt }
+
+async function getAccessToken() {
+ if (cachedToken && cachedToken.expiresAt - Date.now() > REFRESH_MARGIN_MS) {
+ return cachedToken.accessToken;
+ }
+
+ const response = await fetch(IMS_TOKEN_URL, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
+ body: new URLSearchParams({
+ grant_type: 'client_credentials',
+ client_id: process.env.EXPRESS_CLIENT_ID,
+ client_secret: process.env.EXPRESS_CLIENT_SECRET,
+ scope: process.env.EXPRESS_API_SCOPE || DEFAULT_SCOPE,
+ }),
+ });
+
+ if (!response.ok) {
+ const text = await response.text();
+ throw new Error(`IMS token request failed ${response.status}: ${text}`);
+ }
+
+ const data = await response.json();
+ cachedToken = {
+ accessToken: data.access_token,
+ expiresAt: Date.now() + Number(data.expires_in),
+ };
+ return cachedToken.accessToken;
+}
+
+async function buildAuthHeaders() {
+ const token = await getAccessToken();
+ return {
+ Authorization: `Bearer ${token}`,
+ 'X-API-KEY': process.env.EXPRESS_CLIENT_ID,
+ };
+}
+
+module.exports = { getAccessToken, buildAuthHeaders };
+```
+
+- [ ] **Step 4: Run tests to verify they pass**
+
+Run: `node --test expressAuth.test.js`
+Expected: PASS — 5 tests passing
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add expressAuth.js expressAuth.test.js
+git commit -m "feat: add Adobe IMS token fetch/cache module"
+```
+
+---
+
+## Task 2: `imageStore.js` — real catalog-backed image store
+
+**Files:**
+- Create: `data/express-templates.json`
+- Modify: `imageStore.js` (full rewrite)
+- Modify: `imageStore.test.js` (full rewrite)
+
+**Interfaces:**
+- Consumes: env var `EXPRESS_TEMPLATES_FILE` (optional, defaults to `data/express-templates.json` relative to this file).
+- Produces: `getTrackedImages(phoneNumber) -> Array<{ id, name, docId, currentEdits }>`, `findTrackedImage(phoneNumber, imageId) -> object | undefined`, `recordEdits(phoneNumber, imageId, newEdits) -> object` (merges and returns the new `currentEdits`).
+
+- [ ] **Step 1: Write the failing tests**
+
+Create `imageStore.test.js`:
+
+```js
+const test = require('node:test');
+const assert = require('node:assert/strict');
+const fs = require('node:fs');
+const path = require('node:path');
+const os = require('node:os');
+const { getTrackedImages, findTrackedImage, recordEdits } = require('./imageStore');
+
+function writeFixtureCatalog(entries) {
+ const fixturePath = path.join(os.tmpdir(), `express-templates-${Date.now()}-${Math.random().toString(36).slice(2)}.json`);
+ fs.writeFileSync(fixturePath, JSON.stringify(entries));
+ process.env.EXPRESS_TEMPLATES_FILE = fixturePath;
+}
+
+test('getTrackedImages reads the catalog from EXPRESS_TEMPLATES_FILE', () => {
+ writeFixtureCatalog([
+ { id: 'img_1', name: 'Diwali Offer Banner', docId: 'urn:doc:1' },
+ { id: 'img_2', name: 'Summer Sale Flyer', docId: 'urn:doc:2' },
+ ]);
+
+ const images = getTrackedImages('phone-1');
+
+ assert.equal(images.length, 2);
+ assert.deepEqual(images[0], { id: 'img_1', name: 'Diwali Offer Banner', docId: 'urn:doc:1', currentEdits: {} });
+});
+
+test('getTrackedImages returns an empty list when the catalog file is missing', () => {
+ process.env.EXPRESS_TEMPLATES_FILE = path.join(os.tmpdir(), 'does-not-exist.json');
+
+ const images = getTrackedImages('phone-2');
+
+ assert.deepEqual(images, []);
+});
+
+test('findTrackedImage returns the matching image by id', () => {
+ writeFixtureCatalog([{ id: 'img_3', name: 'Croma Earbuds', docId: 'urn:doc:3' }]);
+
+ const image = findTrackedImage('phone-3', 'img_3');
+
+ assert.equal(image.name, 'Croma Earbuds');
+});
+
+test('findTrackedImage returns undefined for an unknown id', () => {
+ writeFixtureCatalog([{ id: 'img_3', name: 'Croma Earbuds', docId: 'urn:doc:3' }]);
+
+ const image = findTrackedImage('phone-4', 'img_nope');
+
+ assert.equal(image, undefined);
+});
+
+test('recordEdits merges edits per phone number and image id, visible via findTrackedImage', () => {
+ writeFixtureCatalog([{ id: 'img_1', name: 'Diwali Offer Banner', docId: 'urn:doc:1' }]);
+
+ recordEdits('phone-5', 'img_1', { headline: 'Flash Sale' });
+ recordEdits('phone-5', 'img_1', { discount_text: '70%' });
+
+ const image = findTrackedImage('phone-5', 'img_1');
+ assert.deepEqual(image.currentEdits, { headline: 'Flash Sale', discount_text: '70%' });
+});
+
+test('recordEdits keeps edits independent per phone number', () => {
+ writeFixtureCatalog([{ id: 'img_1', name: 'Diwali Offer Banner', docId: 'urn:doc:1' }]);
+
+ recordEdits('phone-6', 'img_1', { headline: 'Only for phone-6' });
+
+ const otherPhoneImage = findTrackedImage('phone-7', 'img_1');
+ assert.deepEqual(otherPhoneImage.currentEdits, {});
+});
+```
+
+- [ ] **Step 2: Run tests to verify they fail**
+
+Run: `node --test imageStore.test.js`
+Expected: FAIL — assertions fail against the old hardcoded `SEED_IMAGES` behavior (module exists but returns the wrong shape/values)
+
+- [ ] **Step 3: Write the implementation**
+
+Replace the contents of `imageStore.js`:
+
+```js
+const fs = require('node:fs');
+const path = require('node:path');
+
+function catalogPath() {
+ return process.env.EXPRESS_TEMPLATES_FILE || path.join(__dirname, 'data', 'express-templates.json');
+}
+
+function loadCatalog() {
+ try {
+ const raw = fs.readFileSync(catalogPath(), 'utf8');
+ return JSON.parse(raw);
+ } catch (err) {
+ console.error('[imageStore] failed to read catalog', { path: catalogPath(), message: err.message });
+ return [];
+ }
+}
+
+const conversationEdits = new Map();
+
+function getTrackedImages(phoneNumber) {
+ return loadCatalog().map((entry) => ({
+ ...entry,
+ currentEdits: conversationEdits.get(`${phoneNumber}:${entry.id}`) || {},
+ }));
+}
+
+function findTrackedImage(phoneNumber, imageId) {
+ return getTrackedImages(phoneNumber).find((image) => image.id === imageId);
+}
+
+function recordEdits(phoneNumber, imageId, newEdits) {
+ const key = `${phoneNumber}:${imageId}`;
+ const merged = { ...(conversationEdits.get(key) || {}), ...newEdits };
+ conversationEdits.set(key, merged);
+ return merged;
+}
+
+module.exports = { getTrackedImages, findTrackedImage, recordEdits };
+```
+
+- [ ] **Step 4: Create the real catalog file**
+
+Create `data/express-templates.json` with the one confirmed real entry (the Diwali/Summer entries from the old mock had no real Express document behind them, so they're not carried forward as fake data — add more entries here once the UI app provides their real `docId`s):
+
+```json
+[
+ { "id": "img_1", "name": "Croma Earbuds", "docId": "urn:aaid:sc:AP:aaed427c-b4e4-55e4-b924-74d375f91684" }
+]
+```
+
+- [ ] **Step 5: Run tests to verify they pass**
+
+Run: `node --test imageStore.test.js`
+Expected: PASS — 6 tests passing
+
+- [ ] **Step 6: Commit**
+
+```bash
+git add imageStore.js imageStore.test.js data/express-templates.json
+git commit -m "feat: read the shared docID catalog instead of hardcoded seed images"
+```
+
+---
+
+## Task 3: `expressApi.js` — real Adobe Express HTTP calls
+
+**Files:**
+- Modify: `expressApi.js` (full rewrite)
+- Modify: `expressApi.test.js` (full rewrite)
+
+**Interfaces:**
+- Consumes: `buildAuthHeaders` from `./expressAuth` (Task 1); env vars `EXPRESS_API_BASE_URL`, `EXPRESS_STATUS_POLL_INTERVAL_MS`, `EXPRESS_STATUS_POLL_TIMEOUT_MS` (all optional).
+- Produces: `getTaggedDocument(docId) -> Promise