@assembly-js/app-foundation 0.0.38 → 0.0.39

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.
@@ -13,7 +13,9 @@
13
13
  // cronWiringChangedThisTurn). Everything else — a template clone, a Vercel
14
14
  // build, an update turn that never touched the cron — gets the same findings
15
15
  // as warnings: those paths have no agent who can act on a failure, so failing
16
- // them would only strand an app that already worked.
16
+ // them would only strand an app that already worked. A below-floor schedule
17
+ // the app shipped before the turn stays a warning even then (see
18
+ // baselineCronSchedules).
17
19
  import { readFileSync, readdirSync, existsSync } from 'node:fs';
18
20
  import { spawnSync } from 'node:child_process';
19
21
  import { join, relative, sep } from 'node:path';
@@ -65,6 +67,30 @@ function isCronWiringPath(file) {
65
67
  return CRON_WIRING_PATHS.some((p) => (p.endsWith('/') ? file.startsWith(p) : file === p));
66
68
  }
67
69
 
70
+ // The cron entries vercel.json carried at the turn baseline, keyed by path and
71
+ // schedule. A schedule the app already shipped is product behavior the turn has
72
+ // no mandate to change, so its interval finding stays a warning even when the
73
+ // turn rewired the route behind it. An unreadable baseline yields an empty set,
74
+ // which keeps every interval finding fatal.
75
+ function baselineCronSchedules() {
76
+ const baseline = (process.env[TURN_BASELINE_ENV] ?? '').trim();
77
+ const schedules = new Set();
78
+ if (!baseline) return schedules;
79
+ const result = spawnSync('git', ['show', `${baseline}:vercel.json`], { cwd: ROOT, encoding: 'utf8' });
80
+ if (result.status !== 0) return schedules;
81
+ try {
82
+ const crons = JSON.parse(result.stdout).crons;
83
+ if (Array.isArray(crons)) for (const c of crons) schedules.add(cronKey(c));
84
+ } catch {
85
+ // An unparseable baseline file shipped no schedule this turn could preserve.
86
+ }
87
+ return schedules;
88
+ }
89
+
90
+ function cronKey(entry) {
91
+ return `${entry?.path}\n${typeof entry?.schedule === 'string' ? entry.schedule.trim() : ''}`;
92
+ }
93
+
68
94
  // Whether this turn changed vercel.json or a cron route: commits since the
69
95
  // baseline plus whatever is still uncommitted. No baseline means no turn (a
70
96
  // Vercel build, a clone bootstrap), so nothing is the agent's to fix. A git
@@ -226,6 +252,8 @@ if (crons !== undefined && !Array.isArray(crons)) {
226
252
  }
227
253
  const entries = Array.isArray(crons) ? crons : [];
228
254
  const coveredRoutes = new Set();
255
+ const shippedSchedules = baselineCronSchedules();
256
+ const preexistingIntervals = [];
229
257
 
230
258
  entries.forEach((entry, i) => {
231
259
  const label = `vercel.json crons[${i}]`;
@@ -251,10 +279,12 @@ entries.forEach((entry, i) => {
251
279
  if (interval === null) {
252
280
  problems.push(`${label} (${entry.path}): "${entry.schedule}" is not a valid 5-field cron expression.`);
253
281
  } else if (interval < MIN_INTERVAL_MINUTES) {
254
- problems.push(
255
- `${label} (${entry.path}): "${entry.schedule}" fires every ${interval} minutes; the floor is every ` +
256
- `${MIN_INTERVAL_MINUTES}. Use "*/30 * * * *" and keep per-action cadences in the app's schedule table.`,
257
- );
282
+ const finding = `${label} (${entry.path}): "${entry.schedule}" fires every ${interval} minutes; the floor is every ${MIN_INTERVAL_MINUTES}.`;
283
+ if (shippedSchedules.has(cronKey(entry))) {
284
+ preexistingIntervals.push(`${finding} The app already shipped this schedule, so leave it unchanged.`);
285
+ } else {
286
+ problems.push(`${finding} Use "*/30 * * * *" and keep per-action cadences in the app's schedule table.`);
287
+ }
258
288
  }
259
289
  });
260
290
 
@@ -281,4 +311,5 @@ if (entries.length > 0) {
281
311
  }
282
312
  }
283
313
 
314
+ if (preexistingIntervals.length > 0) report(preexistingIntervals, { fatal: false });
284
315
  if (problems.length > 0) report(problems, { fatal: cronWiringChangedThisTurn() });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assembly-js/app-foundation",
3
- "version": "0.0.38",
3
+ "version": "0.0.39",
4
4
  "description": "Versioned foundation helpers and build context for Assembly generated apps",
5
5
  "type": "module",
6
6
  "files": [