backend-manager 5.9.17 → 5.9.19

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.
@@ -41,6 +41,11 @@
41
41
  * NEWSLETTER_OPEN=1 Auto-open the rendered preview in the default browser (macOS only).
42
42
  *
43
43
  * --- AI-mode-only env vars (require TEST_EXTENDED_MODE=1) ---
44
+ * NEWSLETTER_SOURCE=<type> Override the source type for this run. Bypasses parent-server
45
+ * fetch and lets the generator's built-in resolver run.
46
+ * Values: '$feed:<url>', '$parent'. When set, sources[] in the
47
+ * config is replaced with this single source.
48
+ * Example: NEWSLETTER_SOURCE='$feed:https://feeds.arstechnica.com/arstechnica/index'
44
49
  * NEWSLETTER_PEEK=1 Fetch + list ready sources, do not claim, exit.
45
50
  * NEWSLETTER_SOURCE_ID=<id> Generate from one specific source WITHOUT claiming it.
46
51
  * NEWSLETTER_LIMIT=10 Sources per category for PEEK (default 10).
@@ -325,60 +330,57 @@ module.exports = {
325
330
  // `api.` subdomain (e.g. 'https://itwcreativeworks.com'); the helper
326
331
  // inserts `api.` at call time. PARENT_API_URL env override is honored
327
332
  // verbatim for one-off testing against a different parent.
328
- const parentUrl = env.PARENT_API_URL || Manager.getParentApiUrl();
329
- assert.ok(parentUrl, 'PARENT_API_URL (env) or parent (config) must be set for the AI pipeline. Set TEST_EXTENDED_MODE=1 to run it, or omit TEST_EXTENDED_MODE for the fast fixture preview.');
333
+ // When NEWSLETTER_SOURCE is set, skip parent URL checks and source fetching —
334
+ // the generator's built-in resolver (resolveNewsletterSources) handles everything.
335
+ let sources = [];
336
+
337
+ if (!env.NEWSLETTER_SOURCE) {
338
+ const parentUrl = env.PARENT_API_URL || Manager.getParentApiUrl();
339
+ assert.ok(parentUrl, 'PARENT_API_URL (env) or parent (config) must be set for the AI pipeline. Set TEST_EXTENDED_MODE=1 to run it, or omit TEST_EXTENDED_MODE for the fast fixture preview.');
340
+
341
+ // --- Peek mode (early return) ---
342
+ if (env.NEWSLETTER_PEEK) {
343
+ const peeked = await peekSources({
344
+ parentUrl,
345
+ categories: newsletterConfig.categories || [],
346
+ limit: parseInt(env.NEWSLETTER_LIMIT, 10) || 10,
347
+ key: env.BACKEND_MANAGER_KEY,
348
+ });
349
+
350
+ console.log(`\nPeek mode — ${peeked.length} ready source(s):\n`);
351
+ peeked.forEach((s, i) => {
352
+ const raw = s.source || {};
353
+ const cats = (s.categories || []).join(', ') || s.category || '(none)';
354
+ console.log(`[${i + 1}] ${s.id}`);
355
+ console.log(` Categories: ${cats}`);
356
+ console.log(` From: ${raw.from || s.from || '(unknown)'}`);
357
+ console.log(` Subject: ${raw.subject || s.subject || '(none)'}`);
358
+ console.log(` Headline: ${s.ai?.headline || '(none — not yet AI-processed)'}`);
359
+ console.log('');
360
+ });
361
+
362
+ assert.ok(true, `Peeked ${peeked.length} sources`);
363
+ return;
364
+ }
330
365
 
331
- // --- Peek mode (early return) ---
332
- if (env.NEWSLETTER_PEEK) {
333
- const sources = await peekSources({
366
+ // --- Fetch sources ---
367
+ const claim = !!env.NEWSLETTER_CLAIM;
368
+ sources = await fetchSourcesForRun({
334
369
  parentUrl,
335
- categories: newsletterConfig.categories || [],
336
- limit: parseInt(env.NEWSLETTER_LIMIT, 10) || 10,
370
+ newsletterConfig,
371
+ brandId: config.brand?.id,
372
+ sourceId: env.NEWSLETTER_SOURCE_ID,
337
373
  key: env.BACKEND_MANAGER_KEY,
374
+ claim,
338
375
  });
339
376
 
340
- console.log(`\nPeek mode — ${sources.length} ready source(s):\n`);
341
- sources.forEach((s, i) => {
342
- const raw = s.source || {};
343
- const cats = (s.categories || []).join(', ') || s.category || '(none)';
344
- console.log(`[${i + 1}] ${s.id}`);
345
- console.log(` Categories: ${cats}`);
346
- console.log(` From: ${raw.from || s.from || '(unknown)'}`);
347
- console.log(` Subject: ${raw.subject || s.subject || '(none)'}`);
348
- console.log(` Headline: ${s.ai?.headline || '(none — not yet AI-processed)'}`);
349
- console.log('');
350
- });
351
-
352
- assert.ok(true, `Peeked ${sources.length} sources`);
353
- return;
354
- }
355
-
356
- // --- Fetch sources ---
357
- // By DEFAULT the test does NOT claim sources — it fetches them without
358
- // claimFor, so they stay 'ready' and available for the real newsletter.
359
- // This keeps test runs repeatable and non-destructive (a test should never
360
- // silently consume production resources). Set NEWSLETTER_CLAIM=1 to exercise
361
- // the real claim/consume path (then use NEWSLETTER_RELEASE=1 to put them back).
362
- const claim = !!env.NEWSLETTER_CLAIM;
363
- const sources = await fetchSourcesForRun({
364
- parentUrl,
365
- newsletterConfig,
366
- brandId: config.brand?.id,
367
- sourceId: env.NEWSLETTER_SOURCE_ID,
368
- key: env.BACKEND_MANAGER_KEY,
369
- claim,
370
- });
371
-
372
- // Environmental precondition: the parent server must have ready sources in
373
- // at least one configured category. Skip cleanly when the pool is empty
374
- // (transient state — no point hard-failing CI on an external queue).
375
- if (sources.length === 0) {
376
- return skip('No ready newsletter sources available on parent server (environmental)');
377
- }
377
+ if (sources.length === 0) {
378
+ return skip('No ready newsletter sources available on parent server (environmental)');
379
+ }
378
380
 
379
- // Track claimed IDs for later --release-all (only when we actually claimed)
380
- if (claim && !env.NEWSLETTER_SOURCE_ID) {
381
- appendClaimed(claimedFile, sources.map((s) => s.id));
381
+ if (claim && !env.NEWSLETTER_SOURCE_ID) {
382
+ appendClaimed(claimedFile, sources.map((s) => s.id));
383
+ }
382
384
  }
383
385
 
384
386
  // Force `newsletter.enabled: true` and inject the per-run newsletter config
@@ -432,12 +434,25 @@ module.exports = {
432
434
 
433
435
  console.log(`[extended] uploading to itw-creative-works/newsletter-assets/${config.brand?.id}/${campaignId || '<auto-id>'}/ + Beehiiv draft`);
434
436
 
437
+ // NEWSLETTER_SOURCE overrides the configured sources[] and lets the
438
+ // generator's built-in resolver run (resolveNewsletterSources). Without
439
+ // this, the test always passes pre-fetched parent sources via opts.sources,
440
+ // bypassing feed resolution entirely.
441
+ // NEWSLETTER_SOURCE='$feed:https://feeds.arstechnica.com/arstechnica/index'
442
+ // NEWSLETTER_SOURCE='$parent' (default behavior, explicit)
443
+ const useResolverSources = !!env.NEWSLETTER_SOURCE;
444
+
445
+ if (useResolverSources) {
446
+ newsletterConfig.sources = [env.NEWSLETTER_SOURCE];
447
+ Manager.config.marketing.newsletter.content = newsletterConfig;
448
+ }
449
+
435
450
  const result = await generator.generate(
436
451
  Manager,
437
452
  assistant,
438
- { name: `Somiibo Newsletter — Iteration ${stamp}` },
453
+ { name: `${config.brand?.name || 'Brand'} Newsletter — Iteration ${stamp}` },
439
454
  {
440
- sources,
455
+ sources: useResolverSources ? undefined : sources,
441
456
  skipClaim: true, // We manage the claim/release lifecycle ourselves
442
457
  skipImages: !!env.NEWSLETTER_NO_IMAGES,
443
458
  // The article is GENERATED whenever the brand's config.article.enabled is on