@bongos/core 1.19.806 → 1.19.807
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/.bongos-core.json +12 -12
- package/docs/module-api-changelog.md +2 -0
- package/docs/recipes/upgrading-the-core.md +39 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/routine-schedule.js +57 -5
- package/src/module-api.js +1 -1
- package/tests/routine_schedule.mjs +61 -1
package/.bongos-core.json
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/gds/package-core.js",
|
|
5
|
-
"core_version": "1.19.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.807",
|
|
6
|
+
"core_contract": "1.19.807",
|
|
7
|
+
"source_commit": "6154a1dcba2e8a7f84903bbf6a300e969382af47",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-17T04:
|
|
9
|
+
"built_at": "2026-09-17T04:54:31.340Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 487,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
19
|
"file_count": 2708,
|
|
20
|
-
"tree_sha256": "
|
|
20
|
+
"tree_sha256": "4d685c0339573af46854e0aac2b8a2b3981f2a289478dadf3d99f83b066b9cdc",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -2532,7 +2532,7 @@
|
|
|
2532
2532
|
{
|
|
2533
2533
|
"path": "docs/module-api-changelog.md",
|
|
2534
2534
|
"mode": "0000644",
|
|
2535
|
-
"sha256": "
|
|
2535
|
+
"sha256": "bf7add64727463c6823ec5b4eb2031ba38eca1adbcacbc29cfa0c22799459865"
|
|
2536
2536
|
},
|
|
2537
2537
|
{
|
|
2538
2538
|
"path": "docs/modules-contract.md",
|
|
@@ -2792,7 +2792,7 @@
|
|
|
2792
2792
|
{
|
|
2793
2793
|
"path": "docs/recipes/upgrading-the-core.md",
|
|
2794
2794
|
"mode": "0000644",
|
|
2795
|
-
"sha256": "
|
|
2795
|
+
"sha256": "ffe0add6718e0620144e8b786f237153aca9888a9f9052adb41948fcbc17ddec"
|
|
2796
2796
|
},
|
|
2797
2797
|
{
|
|
2798
2798
|
"path": "docs/recipes/windows-builders.md",
|
|
@@ -7722,12 +7722,12 @@
|
|
|
7722
7722
|
{
|
|
7723
7723
|
"path": "package-lock.json",
|
|
7724
7724
|
"mode": "0000644",
|
|
7725
|
-
"sha256": "
|
|
7725
|
+
"sha256": "784deb39aa5635d1c72a322fc760668827b10bd9be69a6350c3cdfae04d48ed5"
|
|
7726
7726
|
},
|
|
7727
7727
|
{
|
|
7728
7728
|
"path": "package.json",
|
|
7729
7729
|
"mode": "0000644",
|
|
7730
|
-
"sha256": "
|
|
7730
|
+
"sha256": "9c405eb98a8f839686c4b573afd3c9af4efda58095e2b6a6bd6fd9c335f919b4"
|
|
7731
7731
|
},
|
|
7732
7732
|
{
|
|
7733
7733
|
"path": "public-docs/index.html",
|
|
@@ -8742,7 +8742,7 @@
|
|
|
8742
8742
|
{
|
|
8743
8743
|
"path": "scripts/gds/routine-schedule.js",
|
|
8744
8744
|
"mode": "0000644",
|
|
8745
|
-
"sha256": "
|
|
8745
|
+
"sha256": "9ef89c7bc7e2784abcbaecbdddbb03fe089019597a68323faa45bd1c887f199f"
|
|
8746
8746
|
},
|
|
8747
8747
|
{
|
|
8748
8748
|
"path": "scripts/gds/run-routine.js",
|
|
@@ -9617,7 +9617,7 @@
|
|
|
9617
9617
|
{
|
|
9618
9618
|
"path": "src/module-api.js",
|
|
9619
9619
|
"mode": "0000644",
|
|
9620
|
-
"sha256": "
|
|
9620
|
+
"sha256": "305f25717a1dbfdf109ba439f0160a047b374a80d20ce0111aad0f2a8e70ccde"
|
|
9621
9621
|
},
|
|
9622
9622
|
{
|
|
9623
9623
|
"path": "src/module-loader/catalog.js",
|
|
@@ -12697,7 +12697,7 @@
|
|
|
12697
12697
|
{
|
|
12698
12698
|
"path": "tests/routine_schedule.mjs",
|
|
12699
12699
|
"mode": "0000644",
|
|
12700
|
-
"sha256": "
|
|
12700
|
+
"sha256": "ba6bc3abb46cdaf2100efb843d25526212d747feb71f51b13214ef3ffc92d624"
|
|
12701
12701
|
},
|
|
12702
12702
|
{
|
|
12703
12703
|
"path": "tests/routine_timers.mjs",
|
|
@@ -2086,5 +2086,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
2086
2086
|
landed since 1.19.804 with no explicit bump. run 35180781365. (task 1002620)
|
|
2087
2087
|
1.19.806 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2088
2088
|
landed since 1.19.805 with no explicit bump. run 35182765995. (task 1002620)
|
|
2089
|
+
1.19.807 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2090
|
+
landed since 1.19.806 with no explicit bump. run 35183654397. (task 1002620)
|
|
2089
2091
|
---------------------------------------------------------------------------
|
|
2090
2092
|
```
|
|
@@ -114,6 +114,45 @@ Each subscribed instance: read its installed core → ask the registry (`npm vie
|
|
|
114
114
|
|
|
115
115
|
**Triple-gated, so nothing moves by accident:** the `core-update-subscription` routine is `requiresAutonomy: true` + **default OFF** in [`config/scheduled-routines.json`](../../config/scheduled-routines.json), the roster ships empty, and every upgrade is health-gated. Arm it by enabling the routine's timer on the control plane once autonomy is on.
|
|
116
116
|
|
|
117
|
+
#### How often it sweeps — the lag knob (task 1003674)
|
|
118
|
+
|
|
119
|
+
**This timer's period IS the delay between a builder's merge and the live site
|
|
120
|
+
serving it.** Everything upstream is already fast: CI publishes to npm within
|
|
121
|
+
~2 minutes of a merge. The pull-deploy timer is fast too, but it watches the
|
|
122
|
+
*instance repo*, not npm — it logs `already at origin/main — nothing to deploy`
|
|
123
|
+
and only acts once a pin-bump commit exists. **This sweep is what creates that
|
|
124
|
+
commit**, so nothing reaches the site between its runs. Left at `OnCalendar=daily`
|
|
125
|
+
the worst case is ~24 hours, which is what made shipped work look unshipped and
|
|
126
|
+
pushed the owner into manual go-lives.
|
|
127
|
+
|
|
128
|
+
Install it at the declared cadence (`15min` in `config/scheduled-routines.json`):
|
|
129
|
+
|
|
130
|
+
```ini
|
|
131
|
+
# /etc/systemd/system/<instance>-core-update-subscription.timer
|
|
132
|
+
[Timer]
|
|
133
|
+
OnCalendar=*:0/15
|
|
134
|
+
RandomizedDelaySec=120
|
|
135
|
+
Persistent=true
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
sudo systemctl daemon-reload && sudo systemctl restart <instance>-core-update-subscription.timer
|
|
140
|
+
systemctl list-timers '*core-update-subscription*' --no-pager # confirm the next elapse is minutes away
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Do not go below ~5 minutes.** An upgrading run takes ~2 minutes; at a tighter
|
|
144
|
+
spacing runs overlap. A run with nothing to take costs one `npm view` and exits,
|
|
145
|
+
so 15 minutes is cheap — the cost scales with releases, not with checks.
|
|
146
|
+
|
|
147
|
+
This does **not** weaken the pinned-core model ([ADR 0108](../adr/0108-instance-composition-configurable-root.md)):
|
|
148
|
+
the pin still exists, a bad release can still be frozen or rolled back, and every
|
|
149
|
+
upgrade stays health-gated with auto-rollback (task 2149). A faster sweep only
|
|
150
|
+
removes the wait.
|
|
151
|
+
|
|
152
|
+
**Verify:** merge something, wait for the next elapse, and confirm
|
|
153
|
+
`curl -s https://<instance>/version` reports the new `coreVersion` with nobody
|
|
154
|
+
running a go-live.
|
|
155
|
+
|
|
117
156
|
**Not covered:** multi-host / SSH orchestration across a real fleet (the deferred fleet-control-plane epic, task [#1948](https://example.com/builders#/task/1948)); and *publishing* new patches, which stays a gated owner step (ADR 0134) — a follow-up task decides whether merges auto-publish.
|
|
118
157
|
|
|
119
158
|
## Gotchas
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.807",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.807",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.807",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
|
@@ -52,11 +52,49 @@ const WORKFLOWS_DIR = path.join(ROOT, '.github', 'workflows');
|
|
|
52
52
|
|
|
53
53
|
const { parseRoutineFrontmatter } = require('./run-routine.js');
|
|
54
54
|
|
|
55
|
-
// The cadences a routine may declare. The value is the period a cron
|
|
56
|
-
// must resolve to for the two to agree. Add a name here and
|
|
57
|
-
// must be able to produce it, or the pair can never match.
|
|
55
|
+
// The NAMED cadences a routine may declare. The value is the period a cron
|
|
56
|
+
// expression must resolve to for the two to agree. Add a name here and
|
|
57
|
+
// cronCadence() below must be able to produce it, or the pair can never match.
|
|
58
58
|
const CADENCES = new Set(['hourly', 'daily', 'weekly', 'monthly']);
|
|
59
59
|
|
|
60
|
+
// Sub-hourly cadences are open-ended rather than enumerated: '15min', '5min',
|
|
61
|
+
// '30min'. Task 1003674 found the vocabulary topped out at `hourly`, so a
|
|
62
|
+
// routine an operator wants to fire every 15 minutes could not SAY so — the
|
|
63
|
+
// step cron that realizes it classified as unclassifiable and hard-failed the
|
|
64
|
+
// check, which left "declare nothing and stay dormant" as the only green
|
|
65
|
+
// option. That is the exact failure mode this file exists to refuse.
|
|
66
|
+
//
|
|
67
|
+
// N MUST DIVIDE 60. A cron step restarts each hour, so only a divisor of 60
|
|
68
|
+
// yields a genuinely uniform period: `*/15` fires at :00 :15 :30 :45 (every 15
|
|
69
|
+
// minutes, always), but `*/7` fires at :00 …:56 and then waits 4 — a "7min"
|
|
70
|
+
// cadence that is not one. Refusing the non-divisors keeps the declared period
|
|
71
|
+
// and the realized period the same claim.
|
|
72
|
+
const SUBHOURLY_RE = /^([1-9]\d*)min$/;
|
|
73
|
+
|
|
74
|
+
// The one definition of "is N a real sub-hourly period" — both the cadence NAME
|
|
75
|
+
// ('15min') and the cron STEP (*/15) are validated through here, so the two can
|
|
76
|
+
// never drift apart and disagree about the same number.
|
|
77
|
+
function isUniformDivisor(n) {
|
|
78
|
+
return Number.isInteger(n) && n > 0 && n < 60 && 60 % n === 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function subhourlyMinutes(name) {
|
|
82
|
+
const m = SUBHOURLY_RE.exec(String(name == null ? '' : name));
|
|
83
|
+
if (!m) return null;
|
|
84
|
+
const n = Number(m[1]);
|
|
85
|
+
return isUniformDivisor(n) ? n : null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// The validity predicate the check asks. Kept separate from CADENCES so the
|
|
89
|
+
// named set stays a plain enumerable list for the error message.
|
|
90
|
+
function isCadence(name) {
|
|
91
|
+
return CADENCES.has(name) || subhourlyMinutes(name) !== null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// The minute-field step spelling, hoisted so it is compiled once rather than per
|
|
95
|
+
// classified cron: `*/15` and the equivalent `0/15`.
|
|
96
|
+
const STEP_RE = /^(?:\*|0)\/([1-9]\d*)$/;
|
|
97
|
+
|
|
60
98
|
// Pure: classify a 5-field cron expression into the cadence it actually realizes.
|
|
61
99
|
// Returns null for anything this vocabulary cannot describe (step values, lists,
|
|
62
100
|
// ranges) — a cadence can then never "match" it, which is the safe direction: an
|
|
@@ -67,12 +105,25 @@ const CADENCES = new Set(['hourly', 'daily', 'weekly', 'monthly']);
|
|
|
67
105
|
// 17 6 * * * → daily (a specific hour, every day)
|
|
68
106
|
// 17 6 3 * * → monthly (a specific day-of-month)
|
|
69
107
|
// 17 * * * * → hourly (a specific minute, every hour)
|
|
108
|
+
// */15 * * * * → 15min (a uniform sub-hourly step)
|
|
70
109
|
function cronCadence(expr) {
|
|
71
110
|
const f = String(expr == null ? '' : expr).trim().split(/\s+/);
|
|
72
111
|
if (f.length !== 5) return null;
|
|
73
112
|
const [min, hour, dom, mon, dow] = f;
|
|
74
113
|
const fixed = (v) => /^\d+$/.test(v);
|
|
75
114
|
const any = (v) => v === '*';
|
|
115
|
+
|
|
116
|
+
// Sub-hourly: a step in the minute field with every coarser field wide open.
|
|
117
|
+
// (STEP_RE is module-scope — see above.)
|
|
118
|
+
// Anything narrower (a step confined to one hour, or to weekdays) is NOT the
|
|
119
|
+
// period it looks like, so it stays unclassifiable.
|
|
120
|
+
const step = STEP_RE.exec(min);
|
|
121
|
+
if (step) {
|
|
122
|
+
if (!(any(hour) && any(dom) && any(mon) && any(dow))) return null;
|
|
123
|
+
const n = Number(step[1]);
|
|
124
|
+
return isUniformDivisor(n) ? `${n}min` : null;
|
|
125
|
+
}
|
|
126
|
+
|
|
76
127
|
if (!fixed(min)) return null; // every cadence below pins the minute
|
|
77
128
|
if (!any(mon)) return null; // a specific month is a yearly shape
|
|
78
129
|
if (any(hour)) return any(dom) && any(dow) ? 'hourly' : null;
|
|
@@ -145,8 +196,8 @@ function evaluate({ withCadence, withoutCadence }, workflows) {
|
|
|
145
196
|
const wf = workflows || [];
|
|
146
197
|
|
|
147
198
|
for (const r of withCadence) {
|
|
148
|
-
if (!
|
|
149
|
-
violations.push(`routine '${r.name}': unknown cadence "${r.cadence}" (want ${[...CADENCES].join('|')})`);
|
|
199
|
+
if (!isCadence(r.cadence)) {
|
|
200
|
+
violations.push(`routine '${r.name}': unknown cadence "${r.cadence}" (want ${[...CADENCES].join('|')}, or <N>min where N divides 60)`);
|
|
150
201
|
continue;
|
|
151
202
|
}
|
|
152
203
|
const firing = wf.filter((w) => invokesRoutine(w.text, r.name, r.steps));
|
|
@@ -228,4 +279,5 @@ if (require.main === module) main();
|
|
|
228
279
|
module.exports = {
|
|
229
280
|
checkRoutineCadencesScheduled, evaluate, cronCadence, workflowCrons,
|
|
230
281
|
invokesRoutine, readRoutines, readWorkflows, CADENCES,
|
|
282
|
+
isCadence, subhourlyMinutes, isUniformDivisor,
|
|
231
283
|
};
|
package/src/module-api.js
CHANGED
|
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
71
71
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
72
72
|
// the entry to that file. Look for a version's history there, not here.
|
|
73
73
|
// ---------------------------------------------------------------------------
|
|
74
|
-
const CORE_VERSION = '1.19.
|
|
74
|
+
const CORE_VERSION = '1.19.807'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
75
75
|
|
|
76
76
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
77
77
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -50,13 +50,73 @@ test('cronCadence refuses what its vocabulary cannot describe, rather than guess
|
|
|
50
50
|
// Unclassifiable must mean "never matches a declared cadence" — the safe
|
|
51
51
|
// direction. A clever cron that silently counted as `weekly` would reopen
|
|
52
52
|
// exactly the drift this check exists to catch.
|
|
53
|
-
for (const expr of ['
|
|
53
|
+
for (const expr of ['0 6 * * 1-5', '0 6 * * 1,4', '0 0 1 1 *', '17 6 * *', '', 'nonsense']) {
|
|
54
54
|
assert.equal(rs.cronCadence(expr), null, `${expr} should not classify`);
|
|
55
55
|
}
|
|
56
56
|
// A day-of-month AND day-of-week together is an OR in cron — neither period.
|
|
57
57
|
assert.equal(rs.cronCadence('17 6 3 * 1'), null);
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
+
test('cronCadence classifies a uniform sub-hourly step (task 1003674)', () => {
|
|
61
|
+
// The vocabulary used to stop at `hourly`, so a routine meant to fire every
|
|
62
|
+
// 15 minutes had no way to declare it: the cron that realizes it read as
|
|
63
|
+
// unclassifiable and hard-failed, leaving "declare nothing, stay dormant" as
|
|
64
|
+
// the only green option — the state this check exists to refuse.
|
|
65
|
+
assert.equal(rs.cronCadence('*/15 * * * *'), '15min');
|
|
66
|
+
assert.equal(rs.cronCadence('*/5 * * * *'), '5min');
|
|
67
|
+
assert.equal(rs.cronCadence('*/30 * * * *'), '30min');
|
|
68
|
+
assert.equal(rs.cronCadence('0/15 * * * *'), '15min', 'the 0/N spelling is the same schedule');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('cronCadence refuses a step that is not a true period (task 1003674)', () => {
|
|
72
|
+
// A cron step restarts every hour, so only a divisor of 60 is uniform:
|
|
73
|
+
// */7 fires at :00…:56 then waits 4. Calling that "7min" would be the same
|
|
74
|
+
// silent drift between declared and realized period the check is here for.
|
|
75
|
+
for (const expr of ['*/7 * * * *', '*/8 * * * *', '*/45 * * * *', '*/60 * * * *', '*/0 * * * *']) {
|
|
76
|
+
assert.equal(rs.cronCadence(expr), null, `${expr} is not a uniform period`);
|
|
77
|
+
}
|
|
78
|
+
// A step narrowed to one hour or one weekday is not the sub-hourly period it
|
|
79
|
+
// resembles — it only fires inside that window.
|
|
80
|
+
assert.equal(rs.cronCadence('*/15 6 * * *'), null);
|
|
81
|
+
assert.equal(rs.cronCadence('*/15 * * * 1'), null);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('isCadence accepts the named periods and the divisor-of-60 Nmin form (task 1003674)', () => {
|
|
85
|
+
for (const ok of ['hourly', 'daily', 'weekly', 'monthly', '15min', '5min', '30min', '1min']) {
|
|
86
|
+
assert.equal(rs.isCadence(ok), true, `${ok} should be declarable`);
|
|
87
|
+
}
|
|
88
|
+
for (const no of ['7min', '60min', '90min', 'min', '0min', 'fortnightly', '', null, undefined]) {
|
|
89
|
+
assert.equal(rs.isCadence(no), false, `${no} should not be declarable`);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('a routine declaring a sub-hourly cadence matches a step cron that fires it (task 1003674)', () => {
|
|
94
|
+
// End-to-end through evaluate(): declaring 15min is only green when a
|
|
95
|
+
// committed scheduler actually fires it at 15 minutes.
|
|
96
|
+
const wf = (cron) => [{
|
|
97
|
+
file: 'sweep.yml',
|
|
98
|
+
text: `on:\n schedule:\n - cron: '${cron}'\njobs:\n go:\n steps:\n - run: node scripts/gds/run-routine.js sweepy\n`,
|
|
99
|
+
}];
|
|
100
|
+
const declared = routines([{ name: 'sweepy', cadence: '15min', steps: [] }]);
|
|
101
|
+
assert.deepEqual(rs.evaluate(declared, wf('*/15 * * * *')).violations, []);
|
|
102
|
+
|
|
103
|
+
// Wrong period, and the check names both sides rather than shrugging.
|
|
104
|
+
const drifted = rs.evaluate(declared, wf('17 6 * * *')).violations;
|
|
105
|
+
assert.equal(drifted.length, 1);
|
|
106
|
+
assert.match(drifted[0], /declares cadence: 15min/);
|
|
107
|
+
assert.match(drifted[0], /-> daily|→ daily/);
|
|
108
|
+
|
|
109
|
+
// A non-divisor step cannot satisfy it either — it classifies as nothing.
|
|
110
|
+
assert.match(rs.evaluate(declared, wf('*/7 * * * *')).violations[0], /unclassifiable/);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('an undeclarable cadence is refused by name (task 1003674)', () => {
|
|
114
|
+
const v = rs.evaluate(routines([{ name: 'sweepy', cadence: '7min', steps: [] }]), []).violations;
|
|
115
|
+
assert.equal(v.length, 1);
|
|
116
|
+
assert.match(v[0], /unknown cadence "7min"/);
|
|
117
|
+
assert.match(v[0], /N divides 60/);
|
|
118
|
+
});
|
|
119
|
+
|
|
60
120
|
test('workflowCrons reads every cron a workflow declares, quoted or bare, ignoring trailing comments', () => {
|
|
61
121
|
const y = [" schedule:", " - cron: '17 6 * * 1'", ' - cron: "0 3 * * *"', ' - cron: 5 * * * * # bare + comment'].join('\n');
|
|
62
122
|
assert.deepEqual(rs.workflowCrons(y), ['17 6 * * 1', '0 3 * * *', '5 * * * *']);
|