@jonit-dev/night-watch-cli 1.7.56 → 1.7.58
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/dist/cli.js +234 -135
- package/dist/commands/install.d.ts +1 -0
- package/dist/commands/install.d.ts.map +1 -1
- package/dist/commands/install.js +13 -4
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/update.d.ts +2 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +4 -1
- package/dist/commands/update.js.map +1 -1
- package/dist/web/assets/index-Vgyivb5u.js +365 -0
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3,15 +3,6 @@ import 'reflect-metadata';
|
|
|
3
3
|
|
|
4
4
|
// dist/cli.js
|
|
5
5
|
import "reflect-metadata";
|
|
6
|
-
import "reflect-metadata";
|
|
7
|
-
import "reflect-metadata";
|
|
8
|
-
import "reflect-metadata";
|
|
9
|
-
import "reflect-metadata";
|
|
10
|
-
import "reflect-metadata";
|
|
11
|
-
import "reflect-metadata";
|
|
12
|
-
import "reflect-metadata";
|
|
13
|
-
import "reflect-metadata";
|
|
14
|
-
import "reflect-metadata";
|
|
15
6
|
import * as fs from "fs";
|
|
16
7
|
import * as path from "path";
|
|
17
8
|
import { fileURLToPath } from "url";
|
|
@@ -139,14 +130,15 @@ import * as fs27 from "fs";
|
|
|
139
130
|
import * as path27 from "path";
|
|
140
131
|
import * as fs28 from "fs";
|
|
141
132
|
import * as path28 from "path";
|
|
142
|
-
import { spawn as spawn5 } from "child_process";
|
|
133
|
+
import { execSync as execSync5, spawn as spawn5 } from "child_process";
|
|
143
134
|
import { Router } from "express";
|
|
144
135
|
import { Router as Router2 } from "express";
|
|
145
136
|
import { Router as Router3 } from "express";
|
|
146
137
|
import { Router as Router4 } from "express";
|
|
138
|
+
import { CronExpressionParser } from "cron-parser";
|
|
147
139
|
import * as fs29 from "fs";
|
|
148
140
|
import * as path29 from "path";
|
|
149
|
-
import { execSync as
|
|
141
|
+
import { execSync as execSync6 } from "child_process";
|
|
150
142
|
import { Router as Router5 } from "express";
|
|
151
143
|
import * as path30 from "path";
|
|
152
144
|
import { Router as Router6 } from "express";
|
|
@@ -154,7 +146,7 @@ import { Router as Router7 } from "express";
|
|
|
154
146
|
import * as path31 from "path";
|
|
155
147
|
import { Router as Router8 } from "express";
|
|
156
148
|
import { Router as Router9 } from "express";
|
|
157
|
-
import { CronExpressionParser } from "cron-parser";
|
|
149
|
+
import { CronExpressionParser as CronExpressionParser2 } from "cron-parser";
|
|
158
150
|
import { spawnSync } from "child_process";
|
|
159
151
|
import * as fs32 from "fs";
|
|
160
152
|
import * as path33 from "path";
|
|
@@ -162,7 +154,7 @@ import * as fs33 from "fs";
|
|
|
162
154
|
import * as path34 from "path";
|
|
163
155
|
import chalk3 from "chalk";
|
|
164
156
|
import chalk4 from "chalk";
|
|
165
|
-
import { execSync as
|
|
157
|
+
import { execSync as execSync7 } from "child_process";
|
|
166
158
|
import * as fs34 from "fs";
|
|
167
159
|
import * as readline3 from "readline";
|
|
168
160
|
import * as fs35 from "fs";
|
|
@@ -263,8 +255,8 @@ var init_constants = __esm({
|
|
|
263
255
|
DEFAULT_PRD_DIR = "docs/prds";
|
|
264
256
|
DEFAULT_MAX_RUNTIME = 7200;
|
|
265
257
|
DEFAULT_REVIEWER_MAX_RUNTIME = 3600;
|
|
266
|
-
DEFAULT_CRON_SCHEDULE = "
|
|
267
|
-
DEFAULT_REVIEWER_SCHEDULE = "
|
|
258
|
+
DEFAULT_CRON_SCHEDULE = "5 */3 * * *";
|
|
259
|
+
DEFAULT_REVIEWER_SCHEDULE = "25 */6 * * *";
|
|
268
260
|
DEFAULT_CRON_SCHEDULE_OFFSET = 0;
|
|
269
261
|
DEFAULT_MAX_RETRIES = 3;
|
|
270
262
|
DEFAULT_REVIEWER_MAX_RETRIES = 2;
|
|
@@ -277,7 +269,7 @@ var init_constants = __esm({
|
|
|
277
269
|
DEFAULT_EXECUTOR_ENABLED = true;
|
|
278
270
|
DEFAULT_REVIEWER_ENABLED = true;
|
|
279
271
|
DEFAULT_PROVIDER_ENV = {};
|
|
280
|
-
DEFAULT_FALLBACK_ON_RATE_LIMIT =
|
|
272
|
+
DEFAULT_FALLBACK_ON_RATE_LIMIT = true;
|
|
281
273
|
DEFAULT_CLAUDE_MODEL = "sonnet";
|
|
282
274
|
VALID_CLAUDE_MODELS = ["sonnet", "opus"];
|
|
283
275
|
CLAUDE_MODEL_IDS = {
|
|
@@ -286,7 +278,7 @@ var init_constants = __esm({
|
|
|
286
278
|
};
|
|
287
279
|
DEFAULT_NOTIFICATIONS = { webhooks: [] };
|
|
288
280
|
DEFAULT_PRD_PRIORITY = [];
|
|
289
|
-
DEFAULT_SLICER_SCHEDULE = "
|
|
281
|
+
DEFAULT_SLICER_SCHEDULE = "35 */12 * * *";
|
|
290
282
|
DEFAULT_SLICER_MAX_RUNTIME = 600;
|
|
291
283
|
DEFAULT_ROADMAP_SCANNER = {
|
|
292
284
|
enabled: true,
|
|
@@ -307,7 +299,7 @@ var init_constants = __esm({
|
|
|
307
299
|
DEFAULT_AUTO_MERGE_METHOD = "squash";
|
|
308
300
|
VALID_MERGE_METHODS = ["squash", "merge", "rebase"];
|
|
309
301
|
DEFAULT_QA_ENABLED = true;
|
|
310
|
-
DEFAULT_QA_SCHEDULE = "
|
|
302
|
+
DEFAULT_QA_SCHEDULE = "45 2,14 * * *";
|
|
311
303
|
DEFAULT_QA_MAX_RUNTIME = 3600;
|
|
312
304
|
DEFAULT_QA_ARTIFACTS = "both";
|
|
313
305
|
DEFAULT_QA_SKIP_LABEL = "skip-qa";
|
|
@@ -323,7 +315,7 @@ var init_constants = __esm({
|
|
|
323
315
|
};
|
|
324
316
|
QA_LOG_NAME = "night-watch-qa";
|
|
325
317
|
DEFAULT_AUDIT_ENABLED = true;
|
|
326
|
-
DEFAULT_AUDIT_SCHEDULE = "
|
|
318
|
+
DEFAULT_AUDIT_SCHEDULE = "50 3 * * 1";
|
|
327
319
|
DEFAULT_AUDIT_MAX_RUNTIME = 1800;
|
|
328
320
|
DEFAULT_AUDIT = {
|
|
329
321
|
enabled: DEFAULT_AUDIT_ENABLED,
|
|
@@ -376,6 +368,7 @@ function getDefaultConfig() {
|
|
|
376
368
|
// Cron scheduling
|
|
377
369
|
cronSchedule: DEFAULT_CRON_SCHEDULE,
|
|
378
370
|
reviewerSchedule: DEFAULT_REVIEWER_SCHEDULE,
|
|
371
|
+
scheduleBundleId: null,
|
|
379
372
|
cronScheduleOffset: DEFAULT_CRON_SCHEDULE_OFFSET,
|
|
380
373
|
maxRetries: DEFAULT_MAX_RETRIES,
|
|
381
374
|
// Reviewer retry configuration
|
|
@@ -443,6 +436,13 @@ function normalizeConfig(rawConfig) {
|
|
|
443
436
|
normalized.maxLogSize = readNumber(rawConfig.maxLogSize) ?? readNumber(logging?.maxLogSize);
|
|
444
437
|
normalized.cronSchedule = readString(rawConfig.cronSchedule) ?? readString(cron?.executorSchedule);
|
|
445
438
|
normalized.reviewerSchedule = readString(rawConfig.reviewerSchedule) ?? readString(cron?.reviewerSchedule);
|
|
439
|
+
const rawScheduleBundleId = rawConfig.scheduleBundleId;
|
|
440
|
+
if (typeof rawScheduleBundleId === "string") {
|
|
441
|
+
const trimmed = rawScheduleBundleId.trim();
|
|
442
|
+
normalized.scheduleBundleId = trimmed.length > 0 ? trimmed : null;
|
|
443
|
+
} else if (rawScheduleBundleId === null) {
|
|
444
|
+
normalized.scheduleBundleId = null;
|
|
445
|
+
}
|
|
446
446
|
normalized.cronScheduleOffset = readNumber(rawConfig.cronScheduleOffset);
|
|
447
447
|
normalized.maxRetries = readNumber(rawConfig.maxRetries);
|
|
448
448
|
normalized.reviewerMaxRetries = readNumber(rawConfig.reviewerMaxRetries);
|
|
@@ -4086,12 +4086,50 @@ var init_prd_states = __esm({
|
|
|
4086
4086
|
init_repositories();
|
|
4087
4087
|
}
|
|
4088
4088
|
});
|
|
4089
|
+
function isNightWatchEntry(line) {
|
|
4090
|
+
if (line.includes(CRONTAB_MARKER_PREFIX)) {
|
|
4091
|
+
return true;
|
|
4092
|
+
}
|
|
4093
|
+
if (line.includes("night-watch")) {
|
|
4094
|
+
return true;
|
|
4095
|
+
}
|
|
4096
|
+
return LEGACY_SCRIPT_NAMES.some((scriptName) => line.includes(scriptName));
|
|
4097
|
+
}
|
|
4098
|
+
function normalizePathValue(value) {
|
|
4099
|
+
const trimmed = value.trim();
|
|
4100
|
+
let unquoted = trimmed;
|
|
4101
|
+
if (trimmed.startsWith("'") && trimmed.endsWith("'") || trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
4102
|
+
unquoted = trimmed.slice(1, -1);
|
|
4103
|
+
}
|
|
4104
|
+
return unquoted.replace(/\\ /g, " ").replace(/\/+$/, "");
|
|
4105
|
+
}
|
|
4106
|
+
function extractCdPath(line) {
|
|
4107
|
+
const match = line.match(/\bcd\s+((?:'[^']*'|"[^"]*"|[^;&])+?)\s*(?:&&|;)/);
|
|
4108
|
+
if (!match) {
|
|
4109
|
+
return null;
|
|
4110
|
+
}
|
|
4111
|
+
return normalizePathValue(match[1]);
|
|
4112
|
+
}
|
|
4089
4113
|
function isEntryForProject(line, projectDir) {
|
|
4090
|
-
if (!line
|
|
4114
|
+
if (!isNightWatchEntry(line)) {
|
|
4091
4115
|
return false;
|
|
4092
4116
|
}
|
|
4093
4117
|
const normalized = projectDir.replace(/\/+$/, "");
|
|
4094
|
-
const
|
|
4118
|
+
const extractedPath = extractCdPath(line);
|
|
4119
|
+
if (extractedPath !== null) {
|
|
4120
|
+
return extractedPath === normalized;
|
|
4121
|
+
}
|
|
4122
|
+
const escaped = normalized.replace(/ /g, "\\ ");
|
|
4123
|
+
const candidates = [
|
|
4124
|
+
`cd ${normalized}`,
|
|
4125
|
+
`cd ${normalized}/`,
|
|
4126
|
+
`cd '${normalized}'`,
|
|
4127
|
+
`cd '${normalized}/'`,
|
|
4128
|
+
`cd "${normalized}"`,
|
|
4129
|
+
`cd "${normalized}/"`,
|
|
4130
|
+
`cd ${escaped}`,
|
|
4131
|
+
`cd ${escaped}/`
|
|
4132
|
+
];
|
|
4095
4133
|
return candidates.some((candidate) => line.includes(candidate));
|
|
4096
4134
|
}
|
|
4097
4135
|
function readCrontab() {
|
|
@@ -4176,10 +4214,19 @@ function removeEntriesForProject(projectDir, marker) {
|
|
|
4176
4214
|
return removedCount;
|
|
4177
4215
|
}
|
|
4178
4216
|
var CRONTAB_MARKER_PREFIX;
|
|
4217
|
+
var LEGACY_SCRIPT_NAMES;
|
|
4179
4218
|
var init_crontab = __esm({
|
|
4180
4219
|
"../core/dist/utils/crontab.js"() {
|
|
4181
4220
|
"use strict";
|
|
4182
4221
|
CRONTAB_MARKER_PREFIX = "# night-watch-cli:";
|
|
4222
|
+
LEGACY_SCRIPT_NAMES = [
|
|
4223
|
+
"night-watch-cron.sh",
|
|
4224
|
+
"night-watch-pr-reviewer-cron.sh",
|
|
4225
|
+
"night-watch-qa-cron.sh",
|
|
4226
|
+
"night-watch-audit-cron.sh",
|
|
4227
|
+
"night-watch-slice-cron.sh",
|
|
4228
|
+
"night-watch-slicer-cron.sh"
|
|
4229
|
+
];
|
|
4183
4230
|
}
|
|
4184
4231
|
});
|
|
4185
4232
|
function getProjectName(projectDir) {
|
|
@@ -5011,6 +5058,9 @@ var init_claim_manager = __esm({
|
|
|
5011
5058
|
init_constants();
|
|
5012
5059
|
}
|
|
5013
5060
|
});
|
|
5061
|
+
function isPlainObject(value) {
|
|
5062
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5063
|
+
}
|
|
5014
5064
|
function saveConfig(projectDir, changes) {
|
|
5015
5065
|
const configPath = path8.join(projectDir, CONFIG_FILE_NAME);
|
|
5016
5066
|
try {
|
|
@@ -5022,8 +5072,8 @@ function saveConfig(projectDir, changes) {
|
|
|
5022
5072
|
const merged = { ...existing };
|
|
5023
5073
|
for (const [key, value] of Object.entries(changes)) {
|
|
5024
5074
|
if (value !== void 0) {
|
|
5025
|
-
if (key
|
|
5026
|
-
merged
|
|
5075
|
+
if (PARTIAL_MERGE_KEYS.has(key) && isPlainObject(existing[key]) && isPlainObject(value)) {
|
|
5076
|
+
merged[key] = { ...existing[key], ...value };
|
|
5027
5077
|
} else {
|
|
5028
5078
|
merged[key] = value;
|
|
5029
5079
|
}
|
|
@@ -5038,10 +5088,12 @@ function saveConfig(projectDir, changes) {
|
|
|
5038
5088
|
};
|
|
5039
5089
|
}
|
|
5040
5090
|
}
|
|
5091
|
+
var PARTIAL_MERGE_KEYS;
|
|
5041
5092
|
var init_config_writer = __esm({
|
|
5042
5093
|
"../core/dist/utils/config-writer.js"() {
|
|
5043
5094
|
"use strict";
|
|
5044
5095
|
init_constants();
|
|
5096
|
+
PARTIAL_MERGE_KEYS = /* @__PURE__ */ new Set(["notifications", "qa", "audit", "roadmapScanner"]);
|
|
5045
5097
|
}
|
|
5046
5098
|
});
|
|
5047
5099
|
function getHistoryPath() {
|
|
@@ -8854,11 +8906,11 @@ function performInstall(projectDir, config, options) {
|
|
|
8854
8906
|
const executorLog = path22.join(logDir, "executor.log");
|
|
8855
8907
|
const reviewerLog = path22.join(logDir, "reviewer.log");
|
|
8856
8908
|
if (!options?.force) {
|
|
8857
|
-
const
|
|
8858
|
-
if (
|
|
8909
|
+
const existingEntries2 = Array.from(/* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)]));
|
|
8910
|
+
if (existingEntries2.length > 0) {
|
|
8859
8911
|
return {
|
|
8860
8912
|
success: false,
|
|
8861
|
-
entries:
|
|
8913
|
+
entries: existingEntries2,
|
|
8862
8914
|
error: "Already installed. Uninstall first or use force."
|
|
8863
8915
|
};
|
|
8864
8916
|
}
|
|
@@ -8905,8 +8957,10 @@ function performInstall(projectDir, config, options) {
|
|
|
8905
8957
|
const auditEntry = `${auditSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} audit >> ${shellQuote(auditLog)} 2>&1 ${marker}`;
|
|
8906
8958
|
entries.push(auditEntry);
|
|
8907
8959
|
}
|
|
8960
|
+
const existingEntries = new Set(Array.from(/* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)])));
|
|
8908
8961
|
const currentCrontab = readCrontab();
|
|
8909
|
-
const
|
|
8962
|
+
const baseCrontab = options?.force ? currentCrontab.filter((line) => !existingEntries.has(line) && !line.includes(marker)) : currentCrontab;
|
|
8963
|
+
const newCrontab = [...baseCrontab, ...entries];
|
|
8910
8964
|
writeCrontab(newCrontab);
|
|
8911
8965
|
return { success: true, entries };
|
|
8912
8966
|
} catch (err) {
|
|
@@ -8918,7 +8972,7 @@ function performInstall(projectDir, config, options) {
|
|
|
8918
8972
|
}
|
|
8919
8973
|
}
|
|
8920
8974
|
function installCommand(program2) {
|
|
8921
|
-
program2.command("install").description("Add crontab entries for automated execution").option("-s, --schedule <cron>", "Cron schedule for PRD executor").option("--reviewer-schedule <cron>", "Cron schedule for reviewer").option("--no-reviewer", "Skip installing reviewer cron").option("--no-slicer", "Skip installing slicer cron").option("--no-qa", "Skip installing QA cron").option("--no-audit", "Skip installing audit cron").action(async (options) => {
|
|
8975
|
+
program2.command("install").description("Add crontab entries for automated execution").option("-s, --schedule <cron>", "Cron schedule for PRD executor").option("--reviewer-schedule <cron>", "Cron schedule for reviewer").option("--no-reviewer", "Skip installing reviewer cron").option("--no-slicer", "Skip installing slicer cron").option("--no-qa", "Skip installing QA cron").option("--no-audit", "Skip installing audit cron").option("-f, --force", "Replace existing cron entries for this project").action(async (options) => {
|
|
8922
8976
|
try {
|
|
8923
8977
|
const projectDir = process.cwd();
|
|
8924
8978
|
const config = loadConfig(projectDir);
|
|
@@ -8935,13 +8989,13 @@ function installCommand(program2) {
|
|
|
8935
8989
|
const executorLog = path22.join(logDir, "executor.log");
|
|
8936
8990
|
const reviewerLog = path22.join(logDir, "reviewer.log");
|
|
8937
8991
|
const existingEntries = Array.from(/* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)]));
|
|
8938
|
-
if (existingEntries.length > 0) {
|
|
8992
|
+
if (existingEntries.length > 0 && !options.force) {
|
|
8939
8993
|
warn(`Night Watch is already installed for ${projectName}.`);
|
|
8940
8994
|
console.log();
|
|
8941
8995
|
dim("Existing crontab entries:");
|
|
8942
8996
|
existingEntries.forEach((entry) => dim(` ${entry}`));
|
|
8943
8997
|
console.log();
|
|
8944
|
-
dim("Run 'night-watch
|
|
8998
|
+
dim("Run 'night-watch install --force' to replace them.");
|
|
8945
8999
|
return;
|
|
8946
9000
|
}
|
|
8947
9001
|
const entries = [];
|
|
@@ -8989,8 +9043,10 @@ function installCommand(program2) {
|
|
|
8989
9043
|
const auditEntry = `${auditSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}cd ${shellQuote(projectDir)} && ${shellQuote(nightWatchBin)} audit >> ${shellQuote(auditLog)} 2>&1 ${marker}`;
|
|
8990
9044
|
entries.push(auditEntry);
|
|
8991
9045
|
}
|
|
9046
|
+
const existingEntrySet = new Set(existingEntries);
|
|
8992
9047
|
const currentCrontab = readCrontab();
|
|
8993
|
-
const
|
|
9048
|
+
const baseCrontab = options.force ? currentCrontab.filter((line) => !existingEntrySet.has(line) && !line.includes(marker)) : currentCrontab;
|
|
9049
|
+
const newCrontab = [...baseCrontab, ...entries];
|
|
8994
9050
|
writeCrontab(newCrontab);
|
|
8995
9051
|
success(`Night Watch installed successfully for ${projectName}!`);
|
|
8996
9052
|
console.log();
|
|
@@ -12143,6 +12199,24 @@ function spawnAction2(projectDir, command, req, res, onSpawned) {
|
|
|
12143
12199
|
});
|
|
12144
12200
|
}
|
|
12145
12201
|
}
|
|
12202
|
+
function formatCommandError(error2) {
|
|
12203
|
+
if (!(error2 instanceof Error)) {
|
|
12204
|
+
return String(error2);
|
|
12205
|
+
}
|
|
12206
|
+
const withStreams = error2;
|
|
12207
|
+
const stderr = typeof withStreams.stderr === "string" ? withStreams.stderr : withStreams.stderr?.toString("utf-8") ?? "";
|
|
12208
|
+
const stdout = typeof withStreams.stdout === "string" ? withStreams.stdout : withStreams.stdout?.toString("utf-8") ?? "";
|
|
12209
|
+
const output = stderr.trim() || stdout.trim();
|
|
12210
|
+
return output || error2.message;
|
|
12211
|
+
}
|
|
12212
|
+
function runCliCommand(projectDir, args) {
|
|
12213
|
+
execSync5(`night-watch ${args.join(" ")}`, {
|
|
12214
|
+
cwd: projectDir,
|
|
12215
|
+
encoding: "utf-8",
|
|
12216
|
+
stdio: "pipe",
|
|
12217
|
+
env: process.env
|
|
12218
|
+
});
|
|
12219
|
+
}
|
|
12146
12220
|
function createActionRouteHandlers(ctx) {
|
|
12147
12221
|
const router = Router({ mergeParams: true });
|
|
12148
12222
|
const p = ctx.pathPrefix;
|
|
@@ -12165,10 +12239,22 @@ function createActionRouteHandlers(ctx) {
|
|
|
12165
12239
|
spawnAction2(ctx.getProjectDir(req), ["planner"], req, res);
|
|
12166
12240
|
});
|
|
12167
12241
|
router.post(`/${p}install-cron`, (req, res) => {
|
|
12168
|
-
|
|
12242
|
+
const projectDir = ctx.getProjectDir(req);
|
|
12243
|
+
try {
|
|
12244
|
+
runCliCommand(projectDir, ["install", "--force"]);
|
|
12245
|
+
res.json({ started: true });
|
|
12246
|
+
} catch (error2) {
|
|
12247
|
+
res.status(500).json({ error: formatCommandError(error2) });
|
|
12248
|
+
}
|
|
12169
12249
|
});
|
|
12170
12250
|
router.post(`/${p}uninstall-cron`, (req, res) => {
|
|
12171
|
-
|
|
12251
|
+
const projectDir = ctx.getProjectDir(req);
|
|
12252
|
+
try {
|
|
12253
|
+
runCliCommand(projectDir, ["uninstall", "--keep-logs"]);
|
|
12254
|
+
res.json({ started: true });
|
|
12255
|
+
} catch (error2) {
|
|
12256
|
+
res.status(500).json({ error: formatCommandError(error2) });
|
|
12257
|
+
}
|
|
12172
12258
|
});
|
|
12173
12259
|
router.post(`/${p}cancel`, async (req, res) => {
|
|
12174
12260
|
try {
|
|
@@ -12558,6 +12644,26 @@ function createProjectBoardRoutes() {
|
|
|
12558
12644
|
});
|
|
12559
12645
|
}
|
|
12560
12646
|
init_dist();
|
|
12647
|
+
function isValidCronExpression(value) {
|
|
12648
|
+
try {
|
|
12649
|
+
CronExpressionParser.parse(value.trim());
|
|
12650
|
+
return true;
|
|
12651
|
+
} catch {
|
|
12652
|
+
return false;
|
|
12653
|
+
}
|
|
12654
|
+
}
|
|
12655
|
+
function validateCronField(fieldName, value) {
|
|
12656
|
+
if (value === void 0) {
|
|
12657
|
+
return null;
|
|
12658
|
+
}
|
|
12659
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
12660
|
+
return `${fieldName} must be a non-empty string`;
|
|
12661
|
+
}
|
|
12662
|
+
if (!isValidCronExpression(value)) {
|
|
12663
|
+
return `${fieldName} must be a valid cron expression`;
|
|
12664
|
+
}
|
|
12665
|
+
return null;
|
|
12666
|
+
}
|
|
12561
12667
|
function validateConfigChanges(changes) {
|
|
12562
12668
|
if (typeof changes !== "object" || changes === null) {
|
|
12563
12669
|
return "Invalid request body";
|
|
@@ -12610,11 +12716,18 @@ function validateConfigChanges(changes) {
|
|
|
12610
12716
|
if (changes.prdPriority !== void 0 && (!Array.isArray(changes.prdPriority) || !changes.prdPriority.every((p) => typeof p === "string"))) {
|
|
12611
12717
|
return "prdPriority must be an array of strings";
|
|
12612
12718
|
}
|
|
12613
|
-
|
|
12614
|
-
|
|
12719
|
+
const rootCronFields = [
|
|
12720
|
+
["cronSchedule", changes.cronSchedule],
|
|
12721
|
+
["reviewerSchedule", changes.reviewerSchedule]
|
|
12722
|
+
];
|
|
12723
|
+
for (const [fieldName, value] of rootCronFields) {
|
|
12724
|
+
const cronError = validateCronField(fieldName, value);
|
|
12725
|
+
if (cronError) {
|
|
12726
|
+
return cronError;
|
|
12727
|
+
}
|
|
12615
12728
|
}
|
|
12616
|
-
if (changes.
|
|
12617
|
-
return "
|
|
12729
|
+
if (changes.scheduleBundleId !== void 0 && changes.scheduleBundleId !== null && (typeof changes.scheduleBundleId !== "string" || changes.scheduleBundleId.trim().length === 0)) {
|
|
12730
|
+
return "scheduleBundleId must be a non-empty string or null";
|
|
12618
12731
|
}
|
|
12619
12732
|
if (changes.notifications?.webhooks !== void 0) {
|
|
12620
12733
|
if (!Array.isArray(changes.notifications.webhooks)) {
|
|
@@ -12641,6 +12754,19 @@ function validateConfigChanges(changes) {
|
|
|
12641
12754
|
if (rs.autoScanInterval !== void 0 && (typeof rs.autoScanInterval !== "number" || rs.autoScanInterval < 30)) {
|
|
12642
12755
|
return "roadmapScanner.autoScanInterval must be a number >= 30";
|
|
12643
12756
|
}
|
|
12757
|
+
const slicerScheduleError = validateCronField("roadmapScanner.slicerSchedule", rs.slicerSchedule);
|
|
12758
|
+
if (slicerScheduleError) {
|
|
12759
|
+
return slicerScheduleError;
|
|
12760
|
+
}
|
|
12761
|
+
if (rs.slicerMaxRuntime !== void 0 && (typeof rs.slicerMaxRuntime !== "number" || rs.slicerMaxRuntime < 60)) {
|
|
12762
|
+
return "roadmapScanner.slicerMaxRuntime must be a number >= 60";
|
|
12763
|
+
}
|
|
12764
|
+
if (rs.priorityMode !== void 0 && rs.priorityMode !== "roadmap-first" && rs.priorityMode !== "audit-first") {
|
|
12765
|
+
return "roadmapScanner.priorityMode must be one of: roadmap-first, audit-first";
|
|
12766
|
+
}
|
|
12767
|
+
if (rs.issueColumn !== void 0 && rs.issueColumn !== "Draft" && rs.issueColumn !== "Ready") {
|
|
12768
|
+
return "roadmapScanner.issueColumn must be one of: Draft, Ready";
|
|
12769
|
+
}
|
|
12644
12770
|
}
|
|
12645
12771
|
if (changes.providerEnv !== void 0) {
|
|
12646
12772
|
if (typeof changes.providerEnv !== "object" || changes.providerEnv === null) {
|
|
@@ -12700,8 +12826,9 @@ function validateConfigChanges(changes) {
|
|
|
12700
12826
|
if (qa.enabled !== void 0 && typeof qa.enabled !== "boolean") {
|
|
12701
12827
|
return "qa.enabled must be a boolean";
|
|
12702
12828
|
}
|
|
12703
|
-
|
|
12704
|
-
|
|
12829
|
+
const qaScheduleError = validateCronField("qa.schedule", qa.schedule);
|
|
12830
|
+
if (qaScheduleError) {
|
|
12831
|
+
return qaScheduleError;
|
|
12705
12832
|
}
|
|
12706
12833
|
if (qa.maxRuntime !== void 0 && (typeof qa.maxRuntime !== "number" || qa.maxRuntime < 60)) {
|
|
12707
12834
|
return "qa.maxRuntime must be a number >= 60";
|
|
@@ -12732,28 +12859,14 @@ function validateConfigChanges(changes) {
|
|
|
12732
12859
|
if (audit.enabled !== void 0 && typeof audit.enabled !== "boolean") {
|
|
12733
12860
|
return "audit.enabled must be a boolean";
|
|
12734
12861
|
}
|
|
12735
|
-
|
|
12736
|
-
|
|
12862
|
+
const auditScheduleError = validateCronField("audit.schedule", audit.schedule);
|
|
12863
|
+
if (auditScheduleError) {
|
|
12864
|
+
return auditScheduleError;
|
|
12737
12865
|
}
|
|
12738
12866
|
if (audit.maxRuntime !== void 0 && (typeof audit.maxRuntime !== "number" || audit.maxRuntime < 60)) {
|
|
12739
12867
|
return "audit.maxRuntime must be a number >= 60";
|
|
12740
12868
|
}
|
|
12741
12869
|
}
|
|
12742
|
-
if (changes.roadmapScanner !== void 0) {
|
|
12743
|
-
const rs = changes.roadmapScanner;
|
|
12744
|
-
if (rs.slicerSchedule !== void 0 && (typeof rs.slicerSchedule !== "string" || rs.slicerSchedule.trim().length === 0)) {
|
|
12745
|
-
return "roadmapScanner.slicerSchedule must be a non-empty string";
|
|
12746
|
-
}
|
|
12747
|
-
if (rs.slicerMaxRuntime !== void 0 && (typeof rs.slicerMaxRuntime !== "number" || rs.slicerMaxRuntime < 60)) {
|
|
12748
|
-
return "roadmapScanner.slicerMaxRuntime must be a number >= 60";
|
|
12749
|
-
}
|
|
12750
|
-
if (rs.priorityMode !== void 0 && rs.priorityMode !== "roadmap-first" && rs.priorityMode !== "audit-first") {
|
|
12751
|
-
return "roadmapScanner.priorityMode must be one of: roadmap-first, audit-first";
|
|
12752
|
-
}
|
|
12753
|
-
if (rs.issueColumn !== void 0 && rs.issueColumn !== "Draft" && rs.issueColumn !== "Ready") {
|
|
12754
|
-
return "roadmapScanner.issueColumn must be one of: Draft, Ready";
|
|
12755
|
-
}
|
|
12756
|
-
}
|
|
12757
12870
|
if (changes.boardProvider !== void 0) {
|
|
12758
12871
|
if (typeof changes.boardProvider !== "object" || changes.boardProvider === null) {
|
|
12759
12872
|
return "boardProvider must be an object";
|
|
@@ -12838,7 +12951,7 @@ init_dist();
|
|
|
12838
12951
|
function runDoctorChecks(projectDir, config) {
|
|
12839
12952
|
const checks = [];
|
|
12840
12953
|
try {
|
|
12841
|
-
|
|
12954
|
+
execSync6("git rev-parse --is-inside-work-tree", {
|
|
12842
12955
|
cwd: projectDir,
|
|
12843
12956
|
stdio: "pipe"
|
|
12844
12957
|
});
|
|
@@ -12847,7 +12960,7 @@ function runDoctorChecks(projectDir, config) {
|
|
|
12847
12960
|
checks.push({ name: "git", status: "fail", detail: "Not a git repository" });
|
|
12848
12961
|
}
|
|
12849
12962
|
try {
|
|
12850
|
-
|
|
12963
|
+
execSync6(`which ${config.provider}`, { stdio: "pipe" });
|
|
12851
12964
|
checks.push({
|
|
12852
12965
|
name: "provider",
|
|
12853
12966
|
status: "pass",
|
|
@@ -13120,9 +13233,20 @@ data: ${JSON.stringify(snapshot)}
|
|
|
13120
13233
|
});
|
|
13121
13234
|
return router;
|
|
13122
13235
|
}
|
|
13236
|
+
function applyScheduleOffset2(schedule, offset) {
|
|
13237
|
+
if (offset === 0) {
|
|
13238
|
+
return schedule;
|
|
13239
|
+
}
|
|
13240
|
+
const parts = schedule.trim().split(/\s+/);
|
|
13241
|
+
if (parts.length < 5 || !/^\d+$/.test(parts[0])) {
|
|
13242
|
+
return schedule.trim();
|
|
13243
|
+
}
|
|
13244
|
+
parts[0] = String(offset);
|
|
13245
|
+
return parts.join(" ");
|
|
13246
|
+
}
|
|
13123
13247
|
function computeNextRun(cronExpr) {
|
|
13124
13248
|
try {
|
|
13125
|
-
const interval =
|
|
13249
|
+
const interval = CronExpressionParser2.parse(cronExpr);
|
|
13126
13250
|
return interval.next().toISOString();
|
|
13127
13251
|
} catch {
|
|
13128
13252
|
return null;
|
|
@@ -13132,6 +13256,48 @@ function hasScheduledCommand(entries, command) {
|
|
|
13132
13256
|
const commandPattern = new RegExp(`\\s${command}\\s+>>`);
|
|
13133
13257
|
return entries.some((entry) => commandPattern.test(entry));
|
|
13134
13258
|
}
|
|
13259
|
+
function buildScheduleInfoResponse(config, entries, installed) {
|
|
13260
|
+
const offset = config.cronScheduleOffset ?? 0;
|
|
13261
|
+
const executorSchedule = applyScheduleOffset2(config.cronSchedule, offset);
|
|
13262
|
+
const reviewerSchedule = applyScheduleOffset2(config.reviewerSchedule, offset);
|
|
13263
|
+
const qaSchedule = applyScheduleOffset2(config.qa.schedule, offset);
|
|
13264
|
+
const auditSchedule = applyScheduleOffset2(config.audit.schedule, offset);
|
|
13265
|
+
const plannerSchedule = applyScheduleOffset2(config.roadmapScanner.slicerSchedule, offset);
|
|
13266
|
+
const executorInstalled = installed && config.executorEnabled !== false && hasScheduledCommand(entries, "run");
|
|
13267
|
+
const reviewerInstalled = installed && config.reviewerEnabled && hasScheduledCommand(entries, "review");
|
|
13268
|
+
const qaInstalled = installed && config.qa.enabled && hasScheduledCommand(entries, "qa");
|
|
13269
|
+
const auditInstalled = installed && config.audit.enabled && hasScheduledCommand(entries, "audit");
|
|
13270
|
+
const plannerInstalled = installed && config.roadmapScanner.enabled && (hasScheduledCommand(entries, "planner") || hasScheduledCommand(entries, "slice"));
|
|
13271
|
+
return {
|
|
13272
|
+
executor: {
|
|
13273
|
+
schedule: executorSchedule,
|
|
13274
|
+
installed: executorInstalled,
|
|
13275
|
+
nextRun: executorInstalled ? computeNextRun(executorSchedule) : null
|
|
13276
|
+
},
|
|
13277
|
+
reviewer: {
|
|
13278
|
+
schedule: reviewerSchedule,
|
|
13279
|
+
installed: reviewerInstalled,
|
|
13280
|
+
nextRun: reviewerInstalled ? computeNextRun(reviewerSchedule) : null
|
|
13281
|
+
},
|
|
13282
|
+
qa: {
|
|
13283
|
+
schedule: qaSchedule,
|
|
13284
|
+
installed: qaInstalled,
|
|
13285
|
+
nextRun: qaInstalled ? computeNextRun(qaSchedule) : null
|
|
13286
|
+
},
|
|
13287
|
+
audit: {
|
|
13288
|
+
schedule: auditSchedule,
|
|
13289
|
+
installed: auditInstalled,
|
|
13290
|
+
nextRun: auditInstalled ? computeNextRun(auditSchedule) : null
|
|
13291
|
+
},
|
|
13292
|
+
planner: {
|
|
13293
|
+
schedule: plannerSchedule,
|
|
13294
|
+
installed: plannerInstalled,
|
|
13295
|
+
nextRun: plannerInstalled ? computeNextRun(plannerSchedule) : null
|
|
13296
|
+
},
|
|
13297
|
+
paused: !installed,
|
|
13298
|
+
entries
|
|
13299
|
+
};
|
|
13300
|
+
}
|
|
13135
13301
|
function createScheduleInfoRoutes(deps) {
|
|
13136
13302
|
const { projectDir, getConfig } = deps;
|
|
13137
13303
|
const router = Router9();
|
|
@@ -13139,42 +13305,7 @@ function createScheduleInfoRoutes(deps) {
|
|
|
13139
13305
|
try {
|
|
13140
13306
|
const config = getConfig();
|
|
13141
13307
|
const snapshot = await fetchStatusSnapshot(projectDir, config);
|
|
13142
|
-
|
|
13143
|
-
const entries = snapshot.crontab.entries;
|
|
13144
|
-
const executorInstalled = installed && config.executorEnabled !== false && hasScheduledCommand(entries, "run");
|
|
13145
|
-
const reviewerInstalled = installed && config.reviewerEnabled && hasScheduledCommand(entries, "review");
|
|
13146
|
-
const qaInstalled = installed && config.qa.enabled && hasScheduledCommand(entries, "qa");
|
|
13147
|
-
const auditInstalled = installed && config.audit.enabled && hasScheduledCommand(entries, "audit");
|
|
13148
|
-
const plannerInstalled = installed && config.roadmapScanner.enabled && (hasScheduledCommand(entries, "planner") || hasScheduledCommand(entries, "slice"));
|
|
13149
|
-
res.json({
|
|
13150
|
-
executor: {
|
|
13151
|
-
schedule: config.cronSchedule,
|
|
13152
|
-
installed: executorInstalled,
|
|
13153
|
-
nextRun: executorInstalled ? computeNextRun(config.cronSchedule) : null
|
|
13154
|
-
},
|
|
13155
|
-
reviewer: {
|
|
13156
|
-
schedule: config.reviewerSchedule,
|
|
13157
|
-
installed: reviewerInstalled,
|
|
13158
|
-
nextRun: reviewerInstalled ? computeNextRun(config.reviewerSchedule) : null
|
|
13159
|
-
},
|
|
13160
|
-
qa: {
|
|
13161
|
-
schedule: config.qa.schedule,
|
|
13162
|
-
installed: qaInstalled,
|
|
13163
|
-
nextRun: qaInstalled ? computeNextRun(config.qa.schedule) : null
|
|
13164
|
-
},
|
|
13165
|
-
audit: {
|
|
13166
|
-
schedule: config.audit.schedule,
|
|
13167
|
-
installed: auditInstalled,
|
|
13168
|
-
nextRun: auditInstalled ? computeNextRun(config.audit.schedule) : null
|
|
13169
|
-
},
|
|
13170
|
-
planner: {
|
|
13171
|
-
schedule: config.roadmapScanner.slicerSchedule,
|
|
13172
|
-
installed: plannerInstalled,
|
|
13173
|
-
nextRun: plannerInstalled ? computeNextRun(config.roadmapScanner.slicerSchedule) : null
|
|
13174
|
-
},
|
|
13175
|
-
paused: !installed,
|
|
13176
|
-
entries
|
|
13177
|
-
});
|
|
13308
|
+
res.json(buildScheduleInfoResponse(config, snapshot.crontab.entries, snapshot.crontab.installed));
|
|
13178
13309
|
} catch (error2) {
|
|
13179
13310
|
res.status(500).json({ error: error2 instanceof Error ? error2.message : String(error2) });
|
|
13180
13311
|
}
|
|
@@ -13230,42 +13361,7 @@ data: ${JSON.stringify(snapshot)}
|
|
|
13230
13361
|
const config = req.projectConfig;
|
|
13231
13362
|
const projectDir = req.projectDir;
|
|
13232
13363
|
const snapshot = await fetchStatusSnapshot(projectDir, config);
|
|
13233
|
-
|
|
13234
|
-
const entries = snapshot.crontab.entries;
|
|
13235
|
-
const executorInstalled = installed && config.executorEnabled !== false && hasScheduledCommand(entries, "run");
|
|
13236
|
-
const reviewerInstalled = installed && config.reviewerEnabled && hasScheduledCommand(entries, "review");
|
|
13237
|
-
const qaInstalled = installed && config.qa.enabled && hasScheduledCommand(entries, "qa");
|
|
13238
|
-
const auditInstalled = installed && config.audit.enabled && hasScheduledCommand(entries, "audit");
|
|
13239
|
-
const plannerInstalled = installed && config.roadmapScanner.enabled && (hasScheduledCommand(entries, "planner") || hasScheduledCommand(entries, "slice"));
|
|
13240
|
-
res.json({
|
|
13241
|
-
executor: {
|
|
13242
|
-
schedule: config.cronSchedule,
|
|
13243
|
-
installed: executorInstalled,
|
|
13244
|
-
nextRun: executorInstalled ? computeNextRun(config.cronSchedule) : null
|
|
13245
|
-
},
|
|
13246
|
-
reviewer: {
|
|
13247
|
-
schedule: config.reviewerSchedule,
|
|
13248
|
-
installed: reviewerInstalled,
|
|
13249
|
-
nextRun: reviewerInstalled ? computeNextRun(config.reviewerSchedule) : null
|
|
13250
|
-
},
|
|
13251
|
-
qa: {
|
|
13252
|
-
schedule: config.qa.schedule,
|
|
13253
|
-
installed: qaInstalled,
|
|
13254
|
-
nextRun: qaInstalled ? computeNextRun(config.qa.schedule) : null
|
|
13255
|
-
},
|
|
13256
|
-
audit: {
|
|
13257
|
-
schedule: config.audit.schedule,
|
|
13258
|
-
installed: auditInstalled,
|
|
13259
|
-
nextRun: auditInstalled ? computeNextRun(config.audit.schedule) : null
|
|
13260
|
-
},
|
|
13261
|
-
planner: {
|
|
13262
|
-
schedule: config.roadmapScanner.slicerSchedule,
|
|
13263
|
-
installed: plannerInstalled,
|
|
13264
|
-
nextRun: plannerInstalled ? computeNextRun(config.roadmapScanner.slicerSchedule) : null
|
|
13265
|
-
},
|
|
13266
|
-
paused: !installed,
|
|
13267
|
-
entries
|
|
13268
|
-
});
|
|
13364
|
+
res.json(buildScheduleInfoResponse(config, snapshot.crontab.entries, snapshot.crontab.installed));
|
|
13269
13365
|
} catch (error2) {
|
|
13270
13366
|
res.status(500).json({ error: error2 instanceof Error ? error2.message : String(error2) });
|
|
13271
13367
|
}
|
|
@@ -13620,6 +13716,9 @@ function parseProjectDirs(projects, cwd) {
|
|
|
13620
13716
|
const dirs = projects.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0).map((entry) => path33.resolve(cwd, entry));
|
|
13621
13717
|
return Array.from(new Set(dirs));
|
|
13622
13718
|
}
|
|
13719
|
+
function shouldInstallGlobal(options) {
|
|
13720
|
+
return options.global !== false;
|
|
13721
|
+
}
|
|
13623
13722
|
function runCommand2(command, args, cwd) {
|
|
13624
13723
|
const result = spawnSync(command, args, {
|
|
13625
13724
|
cwd,
|
|
@@ -13649,7 +13748,7 @@ function updateCommand(program2) {
|
|
|
13649
13748
|
try {
|
|
13650
13749
|
const cwd = process.cwd();
|
|
13651
13750
|
const projectDirs = parseProjectDirs(options.projects, cwd);
|
|
13652
|
-
if (
|
|
13751
|
+
if (shouldInstallGlobal(options)) {
|
|
13653
13752
|
dim(`Updating global install: npm install -g ${options.globalSpec}`);
|
|
13654
13753
|
runCommand2("npm", ["install", "-g", options.globalSpec]);
|
|
13655
13754
|
success("Global CLI update completed.");
|
|
@@ -13821,7 +13920,7 @@ function prsCommand(program2) {
|
|
|
13821
13920
|
init_dist();
|
|
13822
13921
|
function getOpenPrBranches(projectDir) {
|
|
13823
13922
|
try {
|
|
13824
|
-
|
|
13923
|
+
execSync7("git rev-parse --git-dir", {
|
|
13825
13924
|
cwd: projectDir,
|
|
13826
13925
|
encoding: "utf-8",
|
|
13827
13926
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -13830,12 +13929,12 @@ function getOpenPrBranches(projectDir) {
|
|
|
13830
13929
|
return /* @__PURE__ */ new Set();
|
|
13831
13930
|
}
|
|
13832
13931
|
try {
|
|
13833
|
-
|
|
13932
|
+
execSync7("which gh", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
13834
13933
|
} catch {
|
|
13835
13934
|
return /* @__PURE__ */ new Set();
|
|
13836
13935
|
}
|
|
13837
13936
|
try {
|
|
13838
|
-
const output =
|
|
13937
|
+
const output = execSync7("gh pr list --state open --json headRefName", {
|
|
13839
13938
|
cwd: projectDir,
|
|
13840
13939
|
encoding: "utf-8",
|
|
13841
13940
|
stdio: ["pipe", "pipe", "pipe"]
|