@usequeek/theme-check 0.4.0 → 0.4.1

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.
@@ -1135,6 +1135,20 @@ export const templateCopyRule = {
1135
1135
  const findings = [];
1136
1136
  for (const store of context.demos) {
1137
1137
  const name = store.data?.profile?.name;
1138
+ // The header announcement is copied onto real stores like section copy,
1139
+ // whether the bar is enabled or not — so it reads by the same rules.
1140
+ const announcement = store.data?.config?.header?.announcement?.text;
1141
+ if (typeof announcement === 'string' && announcement !== '') {
1142
+ const why = copyViolations(announcement, typeof name === 'string' ? name : null);
1143
+ if (why.length > 0) {
1144
+ findings.push(finding(context, 'theme/template-copy', 'reject', {
1145
+ where: `${context.env.root}${store.file} → config.header.announcement.text`,
1146
+ found: `text: ${why.join('; ')} — "${announcement.length > 80 ? `${announcement.slice(0, 77)}…` : announcement}"`,
1147
+ fix: 'The setup wizard publishes this copy onto real stores unchanged. Write it for any store in the business: the store\'s name becomes a role ("our kitchen", "the studio"), a place becomes generic ("across the city") or goes, and prices, delivery windows, guarantees and founding dates go — they are the vendor’s to state. Testimonials and reviews are exempt.',
1148
+ docs: `${context.env.docs}#templates`,
1149
+ }));
1150
+ }
1151
+ }
1138
1152
  for (const [pageKey, page] of Object.entries(pagesOf(store))) {
1139
1153
  (page?.content ?? []).forEach((section, index) => {
1140
1154
  if (typeof section?.type !== 'string')
@@ -93,10 +93,19 @@ const NAIRA_AMOUNT = [
93
93
  /(?<![\p{L}\p{N}])N\d{1,3}(,\d{3})+(?![\p{N}])|(?<![\p{L}\p{N}])N\d{3,}(?![\p{L}\p{N}])/u,
94
94
  /\d[\d,.]*\s?k?\s?naira\b/i,
95
95
  ];
96
- /** A discount or a coupon code: "Save 20% with code RAINS20", "Up to 25% off". */
96
+ /**
97
+ * A discount or a coupon code: "Save 20% with code RAINS20", "Up to 25% off".
98
+ * "Half-price" and a bare "sale" are offers too ("the clearance sale is on",
99
+ * "shop the sale", "sale ends Sunday") — but only as their own word, so
100
+ * "wholesale" and "salesperson" are not, and never the trade phrase "for sale
101
+ * by the kilo". "Discount" and "coupon" alone name an offer no store can keep.
102
+ */
97
103
  const OFFER = [
98
104
  /\b(?:code|coupon|promo(?: code)?)\s*:?\s*[A-Z][A-Z0-9]{3,}\b/,
99
105
  /\b\d{1,3}\s?%\s?(?:off|discount)\b|\bsave\s+(?:up to\s+)?\d{1,3}\s?%|\bup to\s+\d{1,3}\s?%/i,
106
+ /\bhalf[\s-]price\b/i,
107
+ /\b(?<!for )sale\b(?! by the kilo)/i,
108
+ /\b(?:coupon|discount)s?\b/i,
100
109
  ];
101
110
  /**
102
111
  * Opening hours: "Open daily", "11am–10pm", "Doors open 11am", "from 6 AM",
@@ -109,18 +118,23 @@ const HOURS = new RegExp([
109
118
  String.raw `\b\d{1,2}(?::\d{2})?\s?(?:am|pm)?\s?(?:–|-|to)\s?${TIME}`,
110
119
  String.raw `\b(?:opens?|opening|doors|from|till|until|last\s+orders|closes?|closing)\s+(?:at\s+)?${TIME}`,
111
120
  String.raw `\b(?:[01]?\d|2[0-3]):[0-5]\d\s?(?:–|-|to)\s?(?:[01]?\d|2[0-3]):[0-5]\d\b`,
121
+ // a weekday at a clock time is the store's schedule: "New drop every Friday, 7pm"
122
+ String.raw `\b(?:mon|tues|wednes|thurs|fri|satur|sun)days?\b,?\s+(?:at\s+)?${TIME}`,
112
123
  ].join('|'), 'i');
113
124
  /**
114
125
  * Commitments only the vendor can make. A time window is a promise only next
115
126
  * to a service — delivered, ships, answered, fitted, returned — so a recipe's
116
127
  * or a process's time ("marinated 24–48 hours", "ready in 3 minutes") is not.
117
128
  */
118
- const SERVICE = String.raw `(?:deliver\w*|ship(?:s|ped|ping)?|dispatch\w*|arriv\w*|collect\w*|pick(?:ed|s)?[- ]?up|turnaround|answer\w*|repl(?:y|ies|ied)|respond\w*|install\w*|fit(?:ted|ting)?|resiz\w*|repair\w*|replace\w*|exchang\w*|return\w*|refund\w*)`;
119
- const WINDOW = String.raw `(?:(?:within|in|under)\s+(?:about\s+)?(?:\d+|an?|one|two|three|four)[\s-]?(?:min(?:ute)?s?|hours?|hrs?|days?|weeks?)|\d+\s*(?:–|-|to)\s*\d+[\s-]*(?:working\s+|business\s+)?(?:hours?|days?|weeks?)|(?:same|next)[- ](?:day|evening|morning))`;
129
+ const SERVICE = String.raw `(?:deliver\w*|ship(?:s|ped|ping)?|dispatch\w*|arriv\w*|collect\w*|pick(?:ed|s)?[- ]?up|turnaround|answer\w*|repl(?:y|ies|ied)|respond\w*|install\w*|fit(?:ted|ting)?|resiz\w*|repair\w*|replace\w*|exchang\w*|return\w*|refund\w*|measur\w*|tailor\w*|alter(?:s|ed|ing|ations?)?)`;
130
+ const WINDOW = String.raw `(?:(?:within|in|under)\s+(?:about\s+)?(?:\d+|an?|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|fourteen|fifteen|twenty)[\s-]?(?:min(?:ute)?s?|hours?|hrs?|days?|weeks?)|\d+\s*(?:–|-|to)\s*\d+[\s-]*(?:working\s+|business\s+)?(?:hours?|days?|weeks?)|(?:same|next)[- ](?:day|evening|morning))`;
120
131
  const PROMISE = [
121
132
  new RegExp(String.raw `\b${SERVICE}\b[^.!?\n]{0,40}?${WINDOW}\b|${WINDOW}\b[^.!?\n]{0,40}?\b${SERVICE}\b`, 'i'),
122
133
  /\b\d+[\s-](?:minute|min|hour|hr|day|week)s?\s+(?:delivery|dispatch|shipping|turnaround|returns?|exchanges?|refunds?|adjustments?|service)\b/i,
123
- /\bfree\s+(?:\w+\s+)?(?:delivery|shipping|returns?|pick[- ]?up|collection|alterations?|installation|install|resizing|exchanges?)\b/i,
134
+ /\b(?:free|complimentary)\s+(?:\w+\s+)?(?:delivery|shipping|returns?|pick[- ]?up|collection|alterations?|installation|install|resizing|exchanges?|samples?|styling|gifts?|consultations?|fittings?|refills?)\b/i,
135
+ // a service promised without end, or a reply promised fast
136
+ /\b(?:returns?|exchanges?|refunds?|delivery|shipping)\b[^.!?\n|]{0,20}\balways\b/i,
137
+ /\b(?:answer\w*|repl(?:y|ies|ied)|respond\w*)\s+(?:fast|quickly|promptly|right away)\b|\b(?:fast|quick|prompt)\s+(?:repl(?:y|ies)|answers?|responses?)\b/i,
124
138
  /\b(?:guarantee[ds]?|money[- ]back|warrant(?:y|ies)|no questions asked)\b/i,
125
139
  ];
126
140
  /** The store's history, not the business's: "since 2014", "started in 2019", "six years, two stores". */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usequeek/theme-check",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "The rules a Queek storefront theme is checked against, as a library.",
5
5
  "license": "MIT",
6
6
  "type": "module",