@infuro/cms-core 1.0.34 → 1.0.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/dist/OrderTrackPage-W3J6WIG2.js +426 -0
- package/dist/OrderTrackPage-Z6FCJQYY.cjs +428 -0
- package/dist/admin.cjs +34329 -20923
- package/dist/admin.d.cts +186 -9
- package/dist/admin.d.ts +186 -9
- package/dist/admin.js +34140 -20829
- package/dist/api.cjs +120 -17515
- package/dist/api.d.cts +633 -3
- package/dist/api.d.ts +633 -3
- package/dist/api.js +14 -17528
- package/dist/auth.cjs +192 -1034
- package/dist/auth.d.cts +194 -51
- package/dist/auth.d.ts +194 -51
- package/dist/auth.js +4 -966
- package/dist/chunk-2XE4X74C.js +29602 -0
- package/dist/chunk-3DZRIZOD.cjs +52 -0
- package/dist/chunk-3KPNTQBV.cjs +620 -0
- package/dist/chunk-46GKADYE.cjs +84 -0
- package/dist/chunk-4TRPKBKO.js +589 -0
- package/dist/chunk-5ELSW2UP.js +150 -0
- package/dist/chunk-6KPQWRFW.js +171 -0
- package/dist/chunk-7NMT3GBR.cjs +29 -0
- package/dist/chunk-7YIBMQDR.cjs +86 -0
- package/dist/chunk-BGJAV67S.cjs +29710 -0
- package/dist/chunk-CNTSLED2.cjs +559 -0
- package/dist/chunk-CRFV5WJK.js +16 -0
- package/dist/chunk-D5FBNKQM.cjs +104 -0
- package/dist/chunk-EF2JBPDM.js +114 -0
- package/dist/chunk-EQPITOP6.js +543 -0
- package/dist/chunk-GO7PPYNU.js +76 -0
- package/dist/chunk-HF3L2GIT.cjs +194 -0
- package/dist/chunk-J7XHLOLO.js +830 -0
- package/dist/chunk-JBRR5UYL.js +87 -0
- package/dist/chunk-JC6DLWTE.js +21 -0
- package/dist/chunk-JHF4YYRC.cjs +188 -0
- package/dist/chunk-JXF23MPG.js +25 -0
- package/dist/chunk-MQBT33IV.js +48 -0
- package/dist/chunk-N44BJZ4C.cjs +123 -0
- package/dist/chunk-OOTLNH63.cjs +18 -0
- package/dist/chunk-PAMVINEX.cjs +577 -0
- package/dist/chunk-PNOKNSG2.cjs +222 -0
- package/dist/chunk-QM733NUK.js +186 -0
- package/dist/chunk-R73BKDRO.cjs +90 -0
- package/dist/chunk-SHUYVCID.js +4 -0
- package/dist/chunk-SYBOCAWB.js +97 -0
- package/dist/chunk-TDV6EJHV.js +192 -0
- package/dist/chunk-TFUOHI37.cjs +839 -0
- package/dist/chunk-TIAEXJLI.cjs +182 -0
- package/dist/chunk-UPLVMVRX.cjs +23 -0
- package/dist/chunk-USNT2KNT.cjs +6 -0
- package/dist/chunk-WEMDMVHQ.js +216 -0
- package/dist/chunk-XDSNZUW7.js +564 -0
- package/dist/chunk-XQ3QUHWR.cjs +83 -0
- package/dist/chunk-XW5ATPRW.cjs +155 -0
- package/dist/chunk-ZQ4X6OAU.js +82 -0
- package/dist/chunk-ZSVCP6N3.js +83 -0
- package/dist/cli.cjs +103 -84
- package/dist/cli.js +62 -27
- package/dist/email-FKR4FXFI.js +5 -0
- package/dist/email-KVKVPNCL.cjs +58 -0
- package/dist/email-queue-CGZBVVAS.js +2 -0
- package/dist/email-queue-TPZWLTIV.cjs +23 -0
- package/dist/erp-config-enabled-CUMQ6N2V.cjs +11 -0
- package/dist/erp-config-enabled-T37X7T34.js +2 -0
- package/dist/erp-order-invoice-3YISSOWW.js +3 -0
- package/dist/erp-order-invoice-ZR5F2KTA.cjs +12 -0
- package/dist/event-order-defaults-6E543227.js +2 -0
- package/dist/event-order-defaults-DUGDVGJI.cjs +39 -0
- package/dist/event-order-notifications-5GSAOYVS.cjs +27 -0
- package/dist/event-order-notifications-IIIF23D5.js +10 -0
- package/dist/hooks.cjs +259 -132
- package/dist/hooks.d.cts +42 -1
- package/dist/hooks.d.ts +42 -1
- package/dist/hooks.js +234 -91
- package/dist/index.cjs +3884 -22307
- package/dist/index.d.cts +2203 -772
- package/dist/index.d.ts +2203 -772
- package/dist/index.js +2871 -22064
- package/dist/interpolate-WAKPN56R.cjs +11 -0
- package/dist/interpolate-WDUWDF3I.js +2 -0
- package/dist/layout-36UFTHC5.cjs +11 -0
- package/dist/layout-AOBN26XG.js +2 -0
- package/dist/migrations/1779400000000-ConvertStatusToEnum.ts +142 -0
- package/dist/migrations/1779400000000-SeedDefaultCurrencies.ts +33 -0
- package/dist/migrations/1779500000000-VendorRolesAndPermissions.ts +102 -0
- package/dist/migrations/1779600000000-VendorRegistrationProfile.ts +52 -0
- package/dist/migrations/1779700000000-CollectionIsCatalog.ts +22 -0
- package/dist/migrations/1779710000000-CollectionPlatformCatalog.ts +60 -0
- package/dist/migrations/1779720000000-ProductCategoryPlatformCatalog.ts +157 -0
- package/dist/migrations/1779800000000-EventsAndEventProducts.ts +173 -0
- package/dist/migrations/1779810000000-BackfillVendorRoleEventsPermissions.ts +38 -0
- package/dist/migrations/1779820000000-FixProductsCategoryForeignKey.ts +70 -0
- package/dist/migrations/1779830000000-ProductTitleColumn.ts +32 -0
- package/dist/migrations/1779840000000-EventAboutAndVisibilityFields.ts +34 -0
- package/dist/migrations/1779850000000-EventCityCountrySplit.ts +43 -0
- package/dist/migrations/1779860000000-CreateCombos.ts +140 -0
- package/dist/migrations/1780100000000-OrderAttendeesEventId.ts +55 -0
- package/dist/migrations/1780110000000-EventContactFormId.ts +49 -0
- package/dist/migrations/1780130000000-ProductVariants.ts +54 -0
- package/dist/migrations/1780140000000-EventManagementEnhancements.ts +44 -0
- package/dist/migrations/1780160000000-EventRegistrationGstProductContactForm.ts +79 -0
- package/dist/migrations/1780170000000-ComboCurrencyPrices.ts +20 -0
- package/dist/migrations/1781110000000-VendorOwnerCombosPermission.ts +38 -0
- package/dist/migrations/1781120000000-RefundPoliciesAndRequests.ts +225 -0
- package/dist/migrations/1781130000000-CreateAttendees.ts +44 -0
- package/dist/migrations/1781140000000-EventOrderNotifications.ts +29 -0
- package/dist/migrations/1781150000000-EventNamedLists.ts +28 -0
- package/dist/migrations/1781160000000-RefundPolicyEventTiers.ts +27 -0
- package/dist/migrations/1781170000000-EventRefundPolicy.ts +42 -0
- package/dist/migrations/1781180000000-RemoveCartItemsUniqueConstraint.ts +15 -0
- package/dist/migrations/1781190000000-RenameProductTaxesToProductConfig.ts +84 -0
- package/dist/migrations/1781200000000-VendorOwnerProductConfigPermissions.ts +51 -0
- package/dist/migrations/1781210000000-VendorRoleProductConfigPermissions.ts +46 -0
- package/dist/migrations/helpers/vendor-role-seed.ts +243 -0
- package/dist/paid-order-erp-3USEG6WL.js +3 -0
- package/dist/paid-order-erp-FSQ3GSXE.cjs +12 -0
- package/dist/pg-boss-service-4XF2TQ2E.js +2 -0
- package/dist/pg-boss-service-L6MTF4EQ.cjs +15 -0
- package/dist/razorpay-KLO2KNCB.js +49 -0
- package/dist/razorpay-U23LGY72.cjs +56 -0
- package/dist/rbac-debug-db-D7ME7QUY.cjs +35 -0
- package/dist/rbac-debug-db-MTCTVNNM.js +33 -0
- package/dist/sms-defaults-2NL24H57.js +2 -0
- package/dist/sms-defaults-KCOPKQBA.cjs +19 -0
- package/dist/stripe-4FD27QVJ.js +51 -0
- package/dist/stripe-VU6OEG2D.cjs +57 -0
- package/dist/theme.cjs +31 -59
- package/dist/theme.d.cts +78 -3
- package/dist/theme.d.ts +78 -3
- package/dist/theme.js +23 -20
- package/dist/vendor-invite-status-77O7ZQ3U.cjs +19 -0
- package/dist/vendor-invite-status-PWJ3DE76.js +2 -0
- package/dist/whatsapp-queue-3DXA4MXU.js +2 -0
- package/dist/whatsapp-queue-KG65MXD5.cjs +15 -0
- package/package.json +147 -142
- package/dist/admin.cjs.map +0 -1
- package/dist/admin.js.map +0 -1
- package/dist/api.cjs.map +0 -1
- package/dist/api.js.map +0 -1
- package/dist/auth.cjs.map +0 -1
- package/dist/auth.js.map +0 -1
- package/dist/cli.cjs.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/config-DJ5CmQvS.d.cts +0 -8
- package/dist/config-DJ5CmQvS.d.ts +0 -8
- package/dist/hooks.cjs.map +0 -1
- package/dist/hooks.js.map +0 -1
- package/dist/index-Bf5GO8fu.d.cts +0 -583
- package/dist/index-DOiJuMQA.d.ts +0 -583
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/theme.cjs.map +0 -1
- package/dist/theme.js.map +0 -1
- package/dist/types-DCVaXlPV.d.cts +0 -78
- package/dist/types-DCVaXlPV.d.ts +0 -78
- package/dist/vendor-scope-DTYhJk_I.d.cts +0 -129
- package/dist/vendor-scope-DTYhJk_I.d.ts +0 -129
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkUSNT2KNT_cjs = require('./chunk-USNT2KNT.cjs');
|
|
4
|
+
|
|
5
|
+
// src/plugins/email/email-queue.ts
|
|
6
|
+
var EMAIL_QUEUE_NAME = "email";
|
|
7
|
+
function registerEmailQueueProcessor(cms) {
|
|
8
|
+
const queue = cms.getPlugin("queue");
|
|
9
|
+
const email = cms.getPlugin("email");
|
|
10
|
+
if (!queue || !email) return;
|
|
11
|
+
queue.registerProcessor(EMAIL_QUEUE_NAME, async (data) => {
|
|
12
|
+
const payload = data;
|
|
13
|
+
const { to, templateName, ctx, subject, html, text } = payload;
|
|
14
|
+
if (!to) return;
|
|
15
|
+
if (templateName && ctx) {
|
|
16
|
+
const rendered = email.renderTemplate(templateName, ctx);
|
|
17
|
+
await email.send({
|
|
18
|
+
to,
|
|
19
|
+
subject: rendered.subject,
|
|
20
|
+
html: rendered.html,
|
|
21
|
+
text: rendered.text
|
|
22
|
+
});
|
|
23
|
+
} else if (subject != null && html != null) {
|
|
24
|
+
await email.send({
|
|
25
|
+
to,
|
|
26
|
+
subject,
|
|
27
|
+
html,
|
|
28
|
+
text
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
chunkUSNT2KNT_cjs.__name(registerEmailQueueProcessor, "registerEmailQueueProcessor");
|
|
34
|
+
async function queueEmail(cms, payload) {
|
|
35
|
+
const queue = cms.getPlugin("queue");
|
|
36
|
+
if (queue) {
|
|
37
|
+
await queue.add(EMAIL_QUEUE_NAME, payload);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const email = cms.getPlugin("email");
|
|
41
|
+
if (email && payload.templateName && payload.ctx) {
|
|
42
|
+
const rendered = email.renderTemplate(payload.templateName, payload.ctx);
|
|
43
|
+
await email.send({
|
|
44
|
+
to: payload.to,
|
|
45
|
+
subject: rendered.subject,
|
|
46
|
+
html: rendered.html,
|
|
47
|
+
text: rendered.text
|
|
48
|
+
});
|
|
49
|
+
} else if (email && payload.subject != null && payload.html != null) {
|
|
50
|
+
await email.send({
|
|
51
|
+
to: payload.to,
|
|
52
|
+
subject: payload.subject,
|
|
53
|
+
html: payload.html,
|
|
54
|
+
text: payload.text
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
chunkUSNT2KNT_cjs.__name(queueEmail, "queueEmail");
|
|
59
|
+
async function queueOrderPlacedEmails(cms, payload) {
|
|
60
|
+
const { orderNumber, total, subtotal, tax, currency, customerName, customerEmail, salesTeamEmails, companyDetails, lineItems, billingAddress, shippingAddress } = payload;
|
|
61
|
+
const base = {
|
|
62
|
+
orderNumber,
|
|
63
|
+
total: total != null ? String(total) : void 0,
|
|
64
|
+
subtotal: subtotal != null ? String(subtotal) : void 0,
|
|
65
|
+
tax: tax != null ? String(tax) : void 0,
|
|
66
|
+
currency,
|
|
67
|
+
customerName,
|
|
68
|
+
companyDetails: companyDetails ?? {},
|
|
69
|
+
lineItems: lineItems ?? [],
|
|
70
|
+
billingAddress,
|
|
71
|
+
shippingAddress
|
|
72
|
+
};
|
|
73
|
+
const customerLower = customerEmail?.trim().toLowerCase() ?? "";
|
|
74
|
+
const jobs = [];
|
|
75
|
+
if (customerEmail?.trim()) {
|
|
76
|
+
jobs.push(queueEmail(cms, {
|
|
77
|
+
to: customerEmail.trim(),
|
|
78
|
+
templateName: "orderPlaced",
|
|
79
|
+
ctx: {
|
|
80
|
+
...base,
|
|
81
|
+
audience: "customer"
|
|
82
|
+
}
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
const seen = /* @__PURE__ */ new Set();
|
|
86
|
+
for (const raw of salesTeamEmails) {
|
|
87
|
+
const to = raw.trim();
|
|
88
|
+
if (!to) continue;
|
|
89
|
+
const key = to.toLowerCase();
|
|
90
|
+
if (seen.has(key)) continue;
|
|
91
|
+
seen.add(key);
|
|
92
|
+
if (customerLower && key === customerLower) continue;
|
|
93
|
+
jobs.push(queueEmail(cms, {
|
|
94
|
+
to,
|
|
95
|
+
templateName: "orderPlaced",
|
|
96
|
+
ctx: {
|
|
97
|
+
...base,
|
|
98
|
+
audience: "sales",
|
|
99
|
+
internalCustomerEmail: customerEmail?.trim() || void 0
|
|
100
|
+
}
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
await Promise.all(jobs);
|
|
104
|
+
}
|
|
105
|
+
chunkUSNT2KNT_cjs.__name(queueOrderPlacedEmails, "queueOrderPlacedEmails");
|
|
106
|
+
async function queueVendorOnboardEmails(cms, payload) {
|
|
107
|
+
const { vendorName, vendorSlug, ownerName, ownerEmail, activation, inviteLink, signInLink, kind = "vendor_onboard", notifyEmails, companyDetails, sendToOwner = true } = payload;
|
|
108
|
+
const base = {
|
|
109
|
+
vendorName,
|
|
110
|
+
vendorSlug,
|
|
111
|
+
ownerName,
|
|
112
|
+
ownerEmail,
|
|
113
|
+
activation,
|
|
114
|
+
inviteLink,
|
|
115
|
+
signInLink,
|
|
116
|
+
kind,
|
|
117
|
+
companyDetails: companyDetails ?? {}
|
|
118
|
+
};
|
|
119
|
+
const ownerLower = ownerEmail?.trim().toLowerCase() ?? "";
|
|
120
|
+
const jobs = [];
|
|
121
|
+
if (sendToOwner && ownerEmail?.trim()) {
|
|
122
|
+
jobs.push(queueEmail(cms, {
|
|
123
|
+
to: ownerEmail.trim(),
|
|
124
|
+
templateName: "vendorOnboarded",
|
|
125
|
+
ctx: {
|
|
126
|
+
...base,
|
|
127
|
+
audience: "owner"
|
|
128
|
+
}
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
const seen = /* @__PURE__ */ new Set();
|
|
132
|
+
for (const raw of notifyEmails) {
|
|
133
|
+
const to = raw.trim();
|
|
134
|
+
if (!to) continue;
|
|
135
|
+
const key = to.toLowerCase();
|
|
136
|
+
if (seen.has(key)) continue;
|
|
137
|
+
seen.add(key);
|
|
138
|
+
if (ownerLower && key === ownerLower) continue;
|
|
139
|
+
jobs.push(queueEmail(cms, {
|
|
140
|
+
to,
|
|
141
|
+
templateName: "vendorOnboarded",
|
|
142
|
+
ctx: {
|
|
143
|
+
...base,
|
|
144
|
+
audience: "internal"
|
|
145
|
+
}
|
|
146
|
+
}));
|
|
147
|
+
}
|
|
148
|
+
await Promise.all(jobs);
|
|
149
|
+
}
|
|
150
|
+
chunkUSNT2KNT_cjs.__name(queueVendorOnboardEmails, "queueVendorOnboardEmails");
|
|
151
|
+
|
|
152
|
+
exports.queueEmail = queueEmail;
|
|
153
|
+
exports.queueOrderPlacedEmails = queueOrderPlacedEmails;
|
|
154
|
+
exports.queueVendorOnboardEmails = queueVendorOnboardEmails;
|
|
155
|
+
exports.registerEmailQueueProcessor = registerEmailQueueProcessor;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { __name } from './chunk-SHUYVCID.js';
|
|
2
|
+
|
|
3
|
+
// src/plugins/email/templates/layout.ts
|
|
4
|
+
function escapeHtml(s) {
|
|
5
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
6
|
+
}
|
|
7
|
+
__name(escapeHtml, "escapeHtml");
|
|
8
|
+
function socialLinkInnerHtml(s) {
|
|
9
|
+
if (s.iconUrl?.trim()) {
|
|
10
|
+
return `<img src="${escapeHtml(s.iconUrl.trim())}" alt="" width="20" height="20" style="display:inline-block;vertical-align:middle;border:0;" />`;
|
|
11
|
+
}
|
|
12
|
+
const label = s.icon && s.icon.trim() || "\u{1F517}";
|
|
13
|
+
return escapeHtml(label);
|
|
14
|
+
}
|
|
15
|
+
__name(socialLinkInnerHtml, "socialLinkInnerHtml");
|
|
16
|
+
function supportLinesHtml(supportEmail, supportPhone, opts) {
|
|
17
|
+
const parts = [];
|
|
18
|
+
const top = opts?.tightTop ? "margin-top:0;" : "margin-top:4px;";
|
|
19
|
+
if (supportEmail) {
|
|
20
|
+
parts.push(`<div style="font-size:13px;color:#444;${top}">📧 ${escapeHtml(supportEmail)}</div>`);
|
|
21
|
+
}
|
|
22
|
+
if (supportPhone) {
|
|
23
|
+
parts.push(`<div style="font-size:13px;color:#444;margin-top:2px;">📞 ${escapeHtml(supportPhone)}</div>`);
|
|
24
|
+
}
|
|
25
|
+
return parts.join("");
|
|
26
|
+
}
|
|
27
|
+
__name(supportLinesHtml, "supportLinesHtml");
|
|
28
|
+
function renderLayout(options) {
|
|
29
|
+
const { bodyHtml, companyDetails } = options;
|
|
30
|
+
const { logoUrl, companyName, supportEmail, supportPhone, socialLinks, footerDisclaimer, followUsTitle = "Follow Us" } = companyDetails;
|
|
31
|
+
const supportFooterHtml = supportLinesHtml(supportEmail, supportPhone, {
|
|
32
|
+
tightTop: true
|
|
33
|
+
});
|
|
34
|
+
const hasSocial = Boolean(socialLinks?.length);
|
|
35
|
+
const brandFooterInner = [];
|
|
36
|
+
if (logoUrl) {
|
|
37
|
+
brandFooterInner.push(`<img src="${escapeHtml(logoUrl)}" alt="" style="max-height:28px;max-width:160px;height:auto;width:auto;display:inline-block;vertical-align:middle;margin-right:10px;border:0;" />`);
|
|
38
|
+
}
|
|
39
|
+
if (companyName) {
|
|
40
|
+
brandFooterInner.push(`<span style="font-size:14px;font-weight:600;color:#111;vertical-align:middle;">${escapeHtml(companyName)}</span>`);
|
|
41
|
+
}
|
|
42
|
+
const brandFooterHtml = brandFooterInner.length ? `<div style="line-height:1.4;">${brandFooterInner.join("")}</div>` : "";
|
|
43
|
+
const followTitleHtml = hasSocial && followUsTitle ? `<div class="footer-follow-title" style="font-size:13px;font-weight:600;color:#555;margin:0;">${escapeHtml(followUsTitle)}</div>` : "";
|
|
44
|
+
const socialRowHtml = hasSocial && socialLinks ? `<div class="footer-social-icons" style="margin-top:6px;text-align:left;font-size:0;line-height:0;">${socialLinks.map((s) => `<a href="${escapeHtml(s.url)}" style="display:inline-block;margin-right:12px;text-decoration:none;color:#333;font-size:18px;line-height:1;vertical-align:middle;">${socialLinkInnerHtml(s)}</a>`).join("")}</div>` : "";
|
|
45
|
+
const rowSupport = supportFooterHtml ? `<tr><td class="footer-support-cell" colspan="2" valign="top" style="padding:0 0 10px 0;">${supportFooterHtml}</td></tr>` : "";
|
|
46
|
+
let footerBlock = "";
|
|
47
|
+
if (brandFooterHtml || followTitleHtml || socialRowHtml || footerDisclaimer || rowSupport) {
|
|
48
|
+
const rowBrand = brandFooterHtml ? `<tr><td class="footer-brand-cell" colspan="2" valign="top" style="padding:0 0 10px 0;">${brandFooterHtml}</td></tr>` : "";
|
|
49
|
+
const rowFollowTitle = followTitleHtml ? `<tr><td class="footer-follow-cell" colspan="2" valign="top" style="padding:14px 0 0 0;text-align:left;">${followTitleHtml}</td></tr>` : "";
|
|
50
|
+
const rowSocial = socialRowHtml ? `<tr><td class="footer-social-cell" colspan="2" style="padding:0;text-align:left;">${socialRowHtml}</td></tr>` : "";
|
|
51
|
+
footerBlock = `<table role="presentation" class="footer-main" width="100%" cellpadding="0" cellspacing="0" style="margin-top:28px;padding-top:16px;">${rowBrand}${rowSupport}${rowFollowTitle}${rowSocial}</table>`;
|
|
52
|
+
}
|
|
53
|
+
const disclaimerBlock = footerDisclaimer ? `<div style="margin-top:20px;padding-top:12px;font-size:11px;line-height:1.5;color:#888;">${escapeHtml(footerDisclaimer).replace(/\n/g, "<br/>")}</div>` : "";
|
|
54
|
+
const responsiveCss = `
|
|
55
|
+
@media only screen and (max-width: 600px) {
|
|
56
|
+
.email-wrap { padding-left: 12px !important; padding-right: 12px !important; }
|
|
57
|
+
.footer-follow-cell { padding-top: 12px !important; }
|
|
58
|
+
}
|
|
59
|
+
`;
|
|
60
|
+
return `<!DOCTYPE html>
|
|
61
|
+
<html>
|
|
62
|
+
<head>
|
|
63
|
+
<meta charset="utf-8">
|
|
64
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
65
|
+
<style type="text/css">${responsiveCss}</style>
|
|
66
|
+
</head>
|
|
67
|
+
<body style="margin:0;padding:0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;font-size:14px;line-height:1.5;color:#333;background:#fff;">
|
|
68
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#fff;">
|
|
69
|
+
<tr><td class="email-wrap" style="padding:16px;">
|
|
70
|
+
<div>
|
|
71
|
+
${bodyHtml}
|
|
72
|
+
</div>
|
|
73
|
+
${footerBlock}
|
|
74
|
+
${disclaimerBlock}
|
|
75
|
+
</td></tr>
|
|
76
|
+
</table>
|
|
77
|
+
</body>
|
|
78
|
+
</html>`;
|
|
79
|
+
}
|
|
80
|
+
__name(renderLayout, "renderLayout");
|
|
81
|
+
|
|
82
|
+
export { renderLayout };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { __name } from './chunk-SHUYVCID.js';
|
|
2
|
+
|
|
3
|
+
// src/plugins/whatsapp/whatsapp-queue.ts
|
|
4
|
+
var WHATSAPP_QUEUE_NAME = "whatsapp";
|
|
5
|
+
function maskPhone(raw) {
|
|
6
|
+
const digits = raw.replace(/\D/g, "");
|
|
7
|
+
if (digits.length <= 4) return "****";
|
|
8
|
+
return `${"*".repeat(Math.max(0, digits.length - 4))}${digits.slice(-4)}`;
|
|
9
|
+
}
|
|
10
|
+
__name(maskPhone, "maskPhone");
|
|
11
|
+
function registerWhatsAppQueueProcessor(cms) {
|
|
12
|
+
const queue = cms.getPlugin("queue");
|
|
13
|
+
const whatsapp = cms.getPlugin("whatsapp");
|
|
14
|
+
if (!queue) {
|
|
15
|
+
console.warn("[whatsapp] queue processor not registered: queue plugin missing");
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (!whatsapp || typeof whatsapp.send !== "function") {
|
|
19
|
+
console.warn("[whatsapp] queue processor not registered: whatsapp plugin missing or not configured");
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
queue.registerProcessor(WHATSAPP_QUEUE_NAME, async (data) => {
|
|
23
|
+
const payload = data;
|
|
24
|
+
if (!payload.to) {
|
|
25
|
+
console.warn("[whatsapp] queue job skipped: missing recipient");
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
console.log("[whatsapp] queue job processing", {
|
|
29
|
+
to: maskPhone(payload.to),
|
|
30
|
+
templateKey: payload.templateKey ?? null,
|
|
31
|
+
externalTemplateRef: payload.externalTemplateRef ?? null
|
|
32
|
+
});
|
|
33
|
+
try {
|
|
34
|
+
const ok = await whatsapp.send(payload);
|
|
35
|
+
if (ok) {
|
|
36
|
+
console.log("[whatsapp] queue job completed", {
|
|
37
|
+
to: maskPhone(payload.to),
|
|
38
|
+
sent: true
|
|
39
|
+
});
|
|
40
|
+
} else {
|
|
41
|
+
console.warn("[whatsapp] queue job completed without delivery", {
|
|
42
|
+
to: maskPhone(payload.to),
|
|
43
|
+
sent: false
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
} catch (err) {
|
|
47
|
+
console.error("[whatsapp] queue job failed", {
|
|
48
|
+
to: maskPhone(payload.to),
|
|
49
|
+
error: err instanceof Error ? err.message : String(err)
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
console.log("[whatsapp] queue processor registered", {
|
|
54
|
+
queue: WHATSAPP_QUEUE_NAME
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
__name(registerWhatsAppQueueProcessor, "registerWhatsAppQueueProcessor");
|
|
58
|
+
async function queueWhatsApp(cms, payload) {
|
|
59
|
+
const queue = cms.getPlugin("queue");
|
|
60
|
+
if (queue) {
|
|
61
|
+
console.log("[whatsapp] enqueue", {
|
|
62
|
+
to: maskPhone(payload.to),
|
|
63
|
+
templateKey: payload.templateKey ?? null,
|
|
64
|
+
externalTemplateRef: payload.externalTemplateRef ?? null
|
|
65
|
+
});
|
|
66
|
+
await queue.add(WHATSAPP_QUEUE_NAME, payload);
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
const whatsapp = cms.getPlugin("whatsapp");
|
|
70
|
+
if (whatsapp && typeof whatsapp.send === "function") {
|
|
71
|
+
console.log("[whatsapp] no queue plugin \u2014 sending immediately", {
|
|
72
|
+
to: maskPhone(payload.to)
|
|
73
|
+
});
|
|
74
|
+
return whatsapp.send(payload);
|
|
75
|
+
}
|
|
76
|
+
console.warn("[whatsapp] send skipped: neither queue nor whatsapp plugin available", {
|
|
77
|
+
to: maskPhone(payload.to)
|
|
78
|
+
});
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
__name(queueWhatsApp, "queueWhatsApp");
|
|
82
|
+
|
|
83
|
+
export { queueWhatsApp, registerWhatsAppQueueProcessor };
|
package/dist/cli.cjs
CHANGED
|
@@ -1,31 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
var
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
-
}
|
|
15
|
-
return to;
|
|
16
|
-
};
|
|
17
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
-
mod
|
|
24
|
-
));
|
|
25
|
-
|
|
26
|
-
// src/cli.ts
|
|
27
|
-
var import_fs = __toESM(require("fs"), 1);
|
|
28
|
-
var import_path = __toESM(require("path"), 1);
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var chunkUSNT2KNT_cjs = require('./chunk-USNT2KNT.cjs');
|
|
5
|
+
var fs = require('fs');
|
|
6
|
+
var path = require('path');
|
|
7
|
+
|
|
8
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
|
+
|
|
10
|
+
var fs__default = /*#__PURE__*/_interopDefault(fs);
|
|
11
|
+
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
12
|
+
|
|
29
13
|
var TEMPLATES = {
|
|
30
14
|
"src/lib/data-source.ts": `import 'reflect-metadata';
|
|
31
15
|
import path from 'path';
|
|
@@ -690,25 +674,26 @@ export function getThemeById(id: string | undefined): ThemeConfig {
|
|
|
690
674
|
`
|
|
691
675
|
};
|
|
692
676
|
function findRoot(cwd) {
|
|
693
|
-
let dir =
|
|
677
|
+
let dir = path__default.default.resolve(cwd);
|
|
694
678
|
for (let i = 0; i < 20; i++) {
|
|
695
|
-
const pkgPath =
|
|
696
|
-
if (
|
|
679
|
+
const pkgPath = path__default.default.join(dir, "package.json");
|
|
680
|
+
if (fs__default.default.existsSync(pkgPath)) {
|
|
697
681
|
try {
|
|
698
|
-
const pkg = JSON.parse(
|
|
682
|
+
const pkg = JSON.parse(fs__default.default.readFileSync(pkgPath, "utf8"));
|
|
699
683
|
if (pkg.dependencies?.next || pkg.devDependencies?.next) return dir;
|
|
700
684
|
} catch {
|
|
701
685
|
}
|
|
702
686
|
}
|
|
703
|
-
const parent =
|
|
687
|
+
const parent = path__default.default.dirname(dir);
|
|
704
688
|
if (parent === dir) break;
|
|
705
689
|
dir = parent;
|
|
706
690
|
}
|
|
707
691
|
return null;
|
|
708
692
|
}
|
|
693
|
+
chunkUSNT2KNT_cjs.__name(findRoot, "findRoot");
|
|
709
694
|
function writeFile(root, filePath, content, force2, dryRun2, log) {
|
|
710
|
-
const full =
|
|
711
|
-
if (
|
|
695
|
+
const full = path__default.default.join(root, filePath);
|
|
696
|
+
if (fs__default.default.existsSync(full) && !force2) {
|
|
712
697
|
log(` skip (exists): ${filePath}`);
|
|
713
698
|
return false;
|
|
714
699
|
}
|
|
@@ -716,18 +701,25 @@ function writeFile(root, filePath, content, force2, dryRun2, log) {
|
|
|
716
701
|
log(` would create: ${filePath}`);
|
|
717
702
|
return true;
|
|
718
703
|
}
|
|
719
|
-
const dir =
|
|
720
|
-
|
|
721
|
-
|
|
704
|
+
const dir = path__default.default.dirname(full);
|
|
705
|
+
fs__default.default.mkdirSync(dir, {
|
|
706
|
+
recursive: true
|
|
707
|
+
});
|
|
708
|
+
fs__default.default.writeFileSync(full, content, "utf8");
|
|
722
709
|
log(` created: ${filePath}`);
|
|
723
710
|
return true;
|
|
724
711
|
}
|
|
712
|
+
chunkUSNT2KNT_cjs.__name(writeFile, "writeFile");
|
|
725
713
|
function patchNextConfig(root, dryRun2, log) {
|
|
726
|
-
const candidates = [
|
|
714
|
+
const candidates = [
|
|
715
|
+
"next.config.js",
|
|
716
|
+
"next.config.mjs",
|
|
717
|
+
"next.config.cjs"
|
|
718
|
+
];
|
|
727
719
|
let configPath = null;
|
|
728
720
|
for (const name of candidates) {
|
|
729
|
-
const p =
|
|
730
|
-
if (
|
|
721
|
+
const p = path__default.default.join(root, name);
|
|
722
|
+
if (fs__default.default.existsSync(p)) {
|
|
731
723
|
configPath = p;
|
|
732
724
|
break;
|
|
733
725
|
}
|
|
@@ -736,36 +728,35 @@ function patchNextConfig(root, dryRun2, log) {
|
|
|
736
728
|
log(" skip next.config: not found");
|
|
737
729
|
return false;
|
|
738
730
|
}
|
|
739
|
-
let content =
|
|
731
|
+
let content = fs__default.default.readFileSync(configPath, "utf8");
|
|
740
732
|
if (content.includes("'@infuro/cms-core'") || content.includes('"@infuro/cms-core"')) {
|
|
741
|
-
log(` skip (already has core): ${
|
|
733
|
+
log(` skip (already has core): ${path__default.default.basename(configPath)}`);
|
|
742
734
|
return false;
|
|
743
735
|
}
|
|
744
736
|
if (content.includes("serverExternalPackages")) {
|
|
745
|
-
content = content.replace(
|
|
746
|
-
/(serverExternalPackages:\s*\[)/,
|
|
747
|
-
"$1'@infuro/cms-core', 'typeorm', 'rss-parser', "
|
|
748
|
-
);
|
|
737
|
+
content = content.replace(/(serverExternalPackages:\s*\[)/, "$1'@infuro/cms-core', 'typeorm', 'rss-parser', ");
|
|
749
738
|
} else {
|
|
750
|
-
content = content.replace(
|
|
751
|
-
/(const nextConfig\s*=\s*\{|module\.exports\s*=\s*\{)/,
|
|
752
|
-
"$1\n serverExternalPackages: ['@infuro/cms-core', 'typeorm', 'rss-parser'],"
|
|
753
|
-
);
|
|
739
|
+
content = content.replace(/(const nextConfig\s*=\s*\{|module\.exports\s*=\s*\{)/, "$1\n serverExternalPackages: ['@infuro/cms-core', 'typeorm', 'rss-parser'],");
|
|
754
740
|
}
|
|
755
741
|
if (dryRun2) {
|
|
756
|
-
log(` would patch: ${
|
|
742
|
+
log(` would patch: ${path__default.default.basename(configPath)}`);
|
|
757
743
|
return true;
|
|
758
744
|
}
|
|
759
|
-
|
|
760
|
-
log(` patched: ${
|
|
745
|
+
fs__default.default.writeFileSync(configPath, content, "utf8");
|
|
746
|
+
log(` patched: ${path__default.default.basename(configPath)}`);
|
|
761
747
|
return true;
|
|
762
748
|
}
|
|
749
|
+
chunkUSNT2KNT_cjs.__name(patchNextConfig, "patchNextConfig");
|
|
763
750
|
function patchTailwind(root, dryRun2, log) {
|
|
764
|
-
const candidates = [
|
|
751
|
+
const candidates = [
|
|
752
|
+
"tailwind.config.js",
|
|
753
|
+
"tailwind.config.mjs",
|
|
754
|
+
"tailwind.config.ts"
|
|
755
|
+
];
|
|
765
756
|
let configPath = null;
|
|
766
757
|
for (const name of candidates) {
|
|
767
|
-
const p =
|
|
768
|
-
if (
|
|
758
|
+
const p = path__default.default.join(root, name);
|
|
759
|
+
if (fs__default.default.existsSync(p)) {
|
|
769
760
|
configPath = p;
|
|
770
761
|
break;
|
|
771
762
|
}
|
|
@@ -774,34 +765,32 @@ function patchTailwind(root, dryRun2, log) {
|
|
|
774
765
|
log(" skip tailwind: config not found");
|
|
775
766
|
return false;
|
|
776
767
|
}
|
|
777
|
-
let content =
|
|
768
|
+
let content = fs__default.default.readFileSync(configPath, "utf8");
|
|
778
769
|
const coreContent = "./node_modules/@infuro/cms-core/dist/**/*.{js,cjs}";
|
|
779
770
|
if (content.includes("@infuro/cms-core")) {
|
|
780
|
-
log(` skip (already has core): ${
|
|
771
|
+
log(` skip (already has core): ${path__default.default.basename(configPath)}`);
|
|
781
772
|
return false;
|
|
782
773
|
}
|
|
783
774
|
if (content.includes("content:")) {
|
|
784
|
-
content = content.replace(
|
|
785
|
-
|
|
786
|
-
`$1
|
|
787
|
-
"${coreContent}",`
|
|
788
|
-
);
|
|
775
|
+
content = content.replace(/(content:\s*\[)/, `$1
|
|
776
|
+
"${coreContent}",`);
|
|
789
777
|
}
|
|
790
778
|
if (dryRun2) {
|
|
791
|
-
log(` would patch: ${
|
|
779
|
+
log(` would patch: ${path__default.default.basename(configPath)}`);
|
|
792
780
|
return true;
|
|
793
781
|
}
|
|
794
|
-
|
|
795
|
-
log(` patched: ${
|
|
782
|
+
fs__default.default.writeFileSync(configPath, content, "utf8");
|
|
783
|
+
log(` patched: ${path__default.default.basename(configPath)}`);
|
|
796
784
|
return true;
|
|
797
785
|
}
|
|
786
|
+
chunkUSNT2KNT_cjs.__name(patchTailwind, "patchTailwind");
|
|
798
787
|
function patchLayout(root, dryRun2, log) {
|
|
799
|
-
const layoutPath =
|
|
800
|
-
if (!
|
|
788
|
+
const layoutPath = path__default.default.join(root, "src/app/layout.tsx");
|
|
789
|
+
if (!fs__default.default.existsSync(layoutPath)) {
|
|
801
790
|
log(" skip layout: src/app/layout.tsx not found");
|
|
802
791
|
return false;
|
|
803
792
|
}
|
|
804
|
-
let content =
|
|
793
|
+
let content = fs__default.default.readFileSync(layoutPath, "utf8");
|
|
805
794
|
if (content.includes("<Providers>")) {
|
|
806
795
|
log(" skip layout: already uses Providers");
|
|
807
796
|
return false;
|
|
@@ -827,14 +816,15 @@ function patchLayout(root, dryRun2, log) {
|
|
|
827
816
|
if (content.includes("<html") && !content.includes("suppressHydrationWarning")) {
|
|
828
817
|
content = content.replace(/<html(\s)/, "<html suppressHydrationWarning$1");
|
|
829
818
|
}
|
|
830
|
-
|
|
819
|
+
fs__default.default.writeFileSync(layoutPath, content, "utf8");
|
|
831
820
|
log(" patched: src/app/layout.tsx");
|
|
832
821
|
return true;
|
|
833
822
|
}
|
|
823
|
+
chunkUSNT2KNT_cjs.__name(patchLayout, "patchLayout");
|
|
834
824
|
function patchPackageJson(root, dryRun2, log) {
|
|
835
|
-
const pkgPath =
|
|
836
|
-
if (!
|
|
837
|
-
const pkg = JSON.parse(
|
|
825
|
+
const pkgPath = path__default.default.join(root, "package.json");
|
|
826
|
+
if (!fs__default.default.existsSync(pkgPath)) return false;
|
|
827
|
+
const pkg = JSON.parse(fs__default.default.readFileSync(pkgPath, "utf8"));
|
|
838
828
|
const scripts = pkg.scripts || {};
|
|
839
829
|
let changed = false;
|
|
840
830
|
if (!scripts.seed) {
|
|
@@ -868,41 +858,65 @@ function patchPackageJson(root, dryRun2, log) {
|
|
|
868
858
|
return false;
|
|
869
859
|
}
|
|
870
860
|
pkg.scripts = scripts;
|
|
871
|
-
pkg.dependencies = {
|
|
872
|
-
|
|
861
|
+
pkg.dependencies = {
|
|
862
|
+
...pkg.dependencies,
|
|
863
|
+
...deps
|
|
864
|
+
};
|
|
865
|
+
pkg.devDependencies = {
|
|
866
|
+
...pkg.devDependencies,
|
|
867
|
+
...dev
|
|
868
|
+
};
|
|
873
869
|
if (dryRun2) {
|
|
874
870
|
log(" would patch: package.json (scripts + devDependencies)");
|
|
875
871
|
return true;
|
|
876
872
|
}
|
|
877
|
-
|
|
873
|
+
fs__default.default.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2), "utf8");
|
|
878
874
|
log(" patched: package.json");
|
|
879
875
|
return true;
|
|
880
876
|
}
|
|
877
|
+
chunkUSNT2KNT_cjs.__name(patchPackageJson, "patchPackageJson");
|
|
881
878
|
async function runNpmInstall(root, log) {
|
|
882
|
-
const { spawnSync } = await import(
|
|
879
|
+
const { spawnSync } = await import('child_process');
|
|
883
880
|
log(" running: npm install (deps)...");
|
|
884
|
-
spawnSync("npm", [
|
|
881
|
+
spawnSync("npm", [
|
|
882
|
+
"install",
|
|
883
|
+
"@infuro/cms-core",
|
|
884
|
+
"typeorm",
|
|
885
|
+
"reflect-metadata",
|
|
886
|
+
"bcryptjs",
|
|
887
|
+
"next-auth",
|
|
888
|
+
"next-themes",
|
|
889
|
+
"sonner",
|
|
890
|
+
"pg-boss"
|
|
891
|
+
], {
|
|
885
892
|
cwd: root,
|
|
886
893
|
stdio: "inherit",
|
|
887
894
|
shell: true
|
|
888
895
|
});
|
|
889
896
|
log(" running: npm install -D tsx dotenv @types/node...");
|
|
890
|
-
spawnSync("npm", [
|
|
897
|
+
spawnSync("npm", [
|
|
898
|
+
"install",
|
|
899
|
+
"-D",
|
|
900
|
+
"tsx",
|
|
901
|
+
"dotenv",
|
|
902
|
+
"@types/node"
|
|
903
|
+
], {
|
|
891
904
|
cwd: root,
|
|
892
905
|
stdio: "inherit",
|
|
893
906
|
shell: true
|
|
894
907
|
});
|
|
895
908
|
}
|
|
909
|
+
chunkUSNT2KNT_cjs.__name(runNpmInstall, "runNpmInstall");
|
|
896
910
|
async function runInit(opts) {
|
|
897
|
-
const log = (msg) => console.log(msg);
|
|
911
|
+
const log = /* @__PURE__ */ chunkUSNT2KNT_cjs.__name((msg) => console.log(msg), "log");
|
|
898
912
|
const cwd = process.cwd();
|
|
899
913
|
const root = findRoot(cwd);
|
|
900
914
|
if (!root) {
|
|
901
915
|
console.error("Not a Next.js project (no package.json with next dependency found from " + cwd + ")");
|
|
902
916
|
process.exit(1);
|
|
903
917
|
}
|
|
904
|
-
const appDir =
|
|
905
|
-
if (!
|
|
918
|
+
const appDir = path__default.default.join(root, "src/app");
|
|
919
|
+
if (!fs__default.default.existsSync(appDir)) {
|
|
906
920
|
console.error("Expected src/app directory not found. Use a Next.js app with src directory (e.g. create-next-app --src-dir).");
|
|
907
921
|
process.exit(1);
|
|
908
922
|
}
|
|
@@ -931,13 +945,19 @@ async function runInit(opts) {
|
|
|
931
945
|
log(" 2. Run npm run migration:run then npm run seed (creates admin from ADMIN_EMAIL/ADMIN_PASSWORD)");
|
|
932
946
|
log(" 3. npm run dev");
|
|
933
947
|
}
|
|
948
|
+
chunkUSNT2KNT_cjs.__name(runInit, "runInit");
|
|
934
949
|
var args = process.argv.slice(2);
|
|
935
950
|
var force = args.includes("--force");
|
|
936
951
|
var dryRun = args.includes("--dry-run");
|
|
937
952
|
var noDeps = args.includes("--no-deps");
|
|
938
953
|
var noPatchConfig = args.includes("--no-patch-config");
|
|
939
954
|
if (args[0] === "init" || args.includes("--init") || args.length === 0 && !args.some((a) => a.startsWith("--"))) {
|
|
940
|
-
runInit({
|
|
955
|
+
runInit({
|
|
956
|
+
force,
|
|
957
|
+
dryRun,
|
|
958
|
+
noDeps,
|
|
959
|
+
noPatchConfig
|
|
960
|
+
}).catch((e) => {
|
|
941
961
|
console.error(e);
|
|
942
962
|
process.exit(1);
|
|
943
963
|
});
|
|
@@ -945,4 +965,3 @@ if (args[0] === "init" || args.includes("--init") || args.length === 0 && !args.
|
|
|
945
965
|
console.log("Usage: npx @infuro/cms-core init [--force] [--dry-run] [--no-deps] [--no-patch-config]");
|
|
946
966
|
process.exit(args[0] === "--help" || args[0] === "-h" ? 0 : 1);
|
|
947
967
|
}
|
|
948
|
-
//# sourceMappingURL=cli.cjs.map
|