@straiffi/archon 1.2.4 → 1.2.6
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/client/assets/{TestsDialog-DJ5RYWoc.js → TestsDialog-BLnGb3oA.js} +2 -2
- package/dist/client/assets/badge-DDU3OIK_.js +41 -0
- package/dist/client/assets/index-BsqDx9qJ.js +151 -0
- package/dist/client/assets/index-CWJnnHXy.css +2 -0
- package/dist/client/index.html +3 -3
- package/dist/server/index.js +138 -67
- package/dist/server/index.js.map +1 -1
- package/dist/server/lib/mobileAccess.js +23 -1
- package/dist/server/lib/mobileAccess.js.map +1 -1
- package/dist/server/lib/projectRepos.js +137 -0
- package/dist/server/lib/projectRepos.js.map +1 -0
- package/dist/server/lib/projects.js +9 -27
- package/dist/server/lib/projects.js.map +1 -1
- package/dist/server/workers/build.js +2 -0
- package/dist/server/workers/build.js.map +1 -1
- package/package.json +1 -1
- package/dist/client/assets/badge-Bpry9xkS.js +0 -41
- package/dist/client/assets/index-CRirnlH7.css +0 -2
- package/dist/client/assets/index-DfuQewLh.js +0 -151
package/dist/server/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import { createCorsOriginResolver, parseCorsOrigins } from './lib/cors.js';
|
|
|
16
16
|
import { branchExistsLocally, commitGitChanges, createWorktreeAsync, deleteWorktree, getBundleReviewDiffForCwd, getGitDiffData, getGitDiffFiles, getGitStatus, getGitUnifiedDiff, getProjectBranch, getProjectBranchSuggestions, getSelectedGitUnifiedDiff, listUnpushedCommitsForCwd, pullGitBranch, pushCurrentBranch, resolveExistingWorktreePath, resolveWorktreePath, summarizeTicketDiffFiles, switchProjectBranch, syncWorktreeFilesAsync, undoLatestUnpushedCommitForCwd } from './lib/git.js';
|
|
17
17
|
import config from './lib/config.js';
|
|
18
18
|
import { autoConfigProject, ProjectAutoConfigError } from './lib/projectAutoConfig.js';
|
|
19
|
+
import { analyzeRepoPath, createInitialCommit, ensureInitialCommitForWorktrees, initializeGitRepo, isInitialCommitRequiredError, } from './lib/projectRepos.js';
|
|
19
20
|
import { browseRepoPath } from './lib/directoryPicker.js';
|
|
20
21
|
import { resolveDesktopServerHost } from './lib/desktopServerHost.js';
|
|
21
22
|
import { emitMobileRealtime, getMobileAccessStatus, regenerateMobilePairingChallenge, revokeMobileSession, startMobileAccess, stopMobileAccess, } from './lib/mobileAccess.js';
|
|
@@ -43,7 +44,7 @@ import { createGitHubPullRequest, findOpenGitHubPullRequest, findOpenGitHubPullR
|
|
|
43
44
|
import { importJiraIssue, JiraApiError, validateJiraConnection } from './lib/integrations/jira.js';
|
|
44
45
|
import { JiraPlanningReferenceError, resolveJiraPlanningDescription } from './lib/integrations/planning.js';
|
|
45
46
|
import { getBundlePullRequestRecord, hasOpenBundlePullRequest, serializeBundlePullRequest, shouldSyncBundlePullRequest, upsertBundlePullRequest, } from './lib/bundlePullRequests.js';
|
|
46
|
-
import { countInboundProjectLinks, getEffectiveProject, getProjectById, getProjectTicketStats, hasProjectRunIde, listProjects, replaceProjectLinks, serializeProject, updateProjectActiveTarget, validateProjectPayload,
|
|
47
|
+
import { countInboundProjectLinks, getEffectiveProject, getProjectById, getProjectTicketStats, hasProjectRunIde, listProjects, replaceProjectLinks, serializeProject, updateProjectActiveTarget, validateProjectPayload, } from './lib/projects.js';
|
|
47
48
|
import { getActiveRunStatus, getActiveRunStatusForProject, getPreviewStatusForProject, hasRunConfig, isRunning, openIde, openTicketIdeInWorkspace, resolveProjectRunContextKey, runSetupCommandsInWorkspace, runTicketInWorkspace, stopAllTickets, stopTicket } from './lib/run.js';
|
|
48
49
|
import { clearAllTestSessions, createTestSession, deleteTestSession, discoverTestsForSession, doesTestFileExistForSession, findProjectTestCommand, getTestSession, isTestSelectionSupported, startTestSessionRun, stopTestSessionRun } from './lib/testSessions.js';
|
|
49
50
|
import { getBundleBuildBlockerResponse, shouldStartBundleBuildNow, transitionTicketState, } from './lib/ticketWorkflowOperations.js';
|
|
@@ -122,6 +123,7 @@ const prepareBundleWorkspace = async (bundle, project, options = {}) => {
|
|
|
122
123
|
const workspacePath = resolveWorktreePath(branch, project);
|
|
123
124
|
let setupCompleted = false;
|
|
124
125
|
try {
|
|
126
|
+
ensureInitialCommitForWorktrees(project.repo_path);
|
|
125
127
|
await createWorktreeAsync(branch, project, { baseBranch: options.baseBranch ?? null });
|
|
126
128
|
await syncWorktreeFilesAsync(branch, project);
|
|
127
129
|
await runSetupCommandsInWorkspace(project, workspacePath);
|
|
@@ -1435,6 +1437,88 @@ const reviewBundleTickets = (projectId, bundleId) => {
|
|
|
1435
1437
|
}),
|
|
1436
1438
|
};
|
|
1437
1439
|
};
|
|
1440
|
+
const buildBundleTickets = (projectId, bundleId, broadcaster) => {
|
|
1441
|
+
const project = getProjectById(projectId);
|
|
1442
|
+
if (!project) {
|
|
1443
|
+
return { ok: false, status: 404, error: 'Project not found' };
|
|
1444
|
+
}
|
|
1445
|
+
const bundle = getBundle(bundleId, project.id);
|
|
1446
|
+
if (!bundle) {
|
|
1447
|
+
return { ok: false, status: 404, error: 'Bundle not found' };
|
|
1448
|
+
}
|
|
1449
|
+
const planTickets = listBundleSliceTickets(project.id, bundle.id, 'plan');
|
|
1450
|
+
if (planTickets.length === 0) {
|
|
1451
|
+
return { ok: false, status: 409, error: 'No plan tickets are available to build in this bundle' };
|
|
1452
|
+
}
|
|
1453
|
+
const bundleTicketIds = listBundleTickets(project.id, bundle.id).map(ticket => ticket.id);
|
|
1454
|
+
const orderedPlanTicketIds = getDependencyOrderedBundleTicketIds(project.id, planTickets.map(ticket => ticket.id));
|
|
1455
|
+
const planTicketById = new Map(planTickets.map(ticket => [ticket.id, ticket]));
|
|
1456
|
+
const orderedPlanTickets = orderedPlanTicketIds
|
|
1457
|
+
.map(ticketId => planTicketById.get(ticketId))
|
|
1458
|
+
.filter((ticket) => ticket !== undefined);
|
|
1459
|
+
const bundleBuildBlocker = orderedPlanTickets.find(ticket => getBundleBuildBlockerResponse(ticket) !== null);
|
|
1460
|
+
if (bundleBuildBlocker) {
|
|
1461
|
+
const blockerResponse = getBundleBuildBlockerResponse(bundleBuildBlocker);
|
|
1462
|
+
if (!blockerResponse) {
|
|
1463
|
+
return { ok: false, status: 409, error: 'Unable to build the bundle right now.' };
|
|
1464
|
+
}
|
|
1465
|
+
return { ok: false, status: 409, error: blockerResponse.error, details: blockerResponse };
|
|
1466
|
+
}
|
|
1467
|
+
for (const planTicket of orderedPlanTickets) {
|
|
1468
|
+
const buildValidation = prepareTicketForBuild(planTicket.id, {
|
|
1469
|
+
ignoredBlockerTicketIds: bundleTicketIds,
|
|
1470
|
+
updateState: false,
|
|
1471
|
+
});
|
|
1472
|
+
if (!buildValidation.ok) {
|
|
1473
|
+
return {
|
|
1474
|
+
ok: false,
|
|
1475
|
+
status: buildValidation.status,
|
|
1476
|
+
error: buildValidation.error,
|
|
1477
|
+
...(buildValidation.blockers ? { details: { blockers: buildValidation.blockers } } : {}),
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
const movedTickets = [];
|
|
1482
|
+
for (const planTicket of orderedPlanTickets) {
|
|
1483
|
+
const buildTransition = prepareTicketForBuild(planTicket.id, {
|
|
1484
|
+
ignoredBlockerTicketIds: bundleTicketIds,
|
|
1485
|
+
});
|
|
1486
|
+
if (!buildTransition.ok) {
|
|
1487
|
+
return {
|
|
1488
|
+
ok: false,
|
|
1489
|
+
status: buildTransition.status,
|
|
1490
|
+
error: buildTransition.error,
|
|
1491
|
+
...(buildTransition.blockers ? { details: { blockers: buildTransition.blockers } } : {}),
|
|
1492
|
+
};
|
|
1493
|
+
}
|
|
1494
|
+
movedTickets.push(buildTransition.ticket);
|
|
1495
|
+
}
|
|
1496
|
+
emitUpdatedTickets(movedTickets.map(ticket => ticket.id));
|
|
1497
|
+
const details = serializeBundleDetails(project, bundle, {
|
|
1498
|
+
target_ticket_id: movedTickets[0]?.id ?? null,
|
|
1499
|
+
});
|
|
1500
|
+
return {
|
|
1501
|
+
ok: true,
|
|
1502
|
+
status: 200,
|
|
1503
|
+
value: details,
|
|
1504
|
+
afterResponse: () => {
|
|
1505
|
+
for (const [index, movedTicket] of movedTickets.entries()) {
|
|
1506
|
+
if (!movedTicket.project_id || !movedTicket.worktree_bundle_id) {
|
|
1507
|
+
continue;
|
|
1508
|
+
}
|
|
1509
|
+
const queuedBuild = enqueueBundledBuild({
|
|
1510
|
+
ticketId: movedTicket.id,
|
|
1511
|
+
projectId: movedTicket.project_id,
|
|
1512
|
+
worktreeBundleId: movedTicket.worktree_bundle_id,
|
|
1513
|
+
activate: index === 0 && shouldStartBundleBuildNow(movedTicket),
|
|
1514
|
+
});
|
|
1515
|
+
if (queuedBuild.shouldStartNow) {
|
|
1516
|
+
startBuild(movedTicket, broadcaster);
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
},
|
|
1520
|
+
};
|
|
1521
|
+
};
|
|
1438
1522
|
const getCurrentReviewWorkspaceSignature = (ticket) => {
|
|
1439
1523
|
if (!ticket) {
|
|
1440
1524
|
return null;
|
|
@@ -1951,6 +2035,7 @@ app.post('/mobile-access/enable', async (req, res) => {
|
|
|
1951
2035
|
clientBuild: resolveClientBuildPaths(import.meta.url),
|
|
1952
2036
|
broadcaster: io,
|
|
1953
2037
|
preferredProjectId,
|
|
2038
|
+
buildBundle: buildBundleTickets,
|
|
1954
2039
|
reviewBundleTickets,
|
|
1955
2040
|
getBundleDetails: getProjectBundleDetails,
|
|
1956
2041
|
submitBundleFollowUp: submitBundleFollowUpResponse,
|
|
@@ -2704,7 +2789,7 @@ app.post('/bundles', async (req, res) => {
|
|
|
2704
2789
|
}
|
|
2705
2790
|
catch (error) {
|
|
2706
2791
|
deleteBundle(bundle.id, result.project.id);
|
|
2707
|
-
return res.status(500).json({
|
|
2792
|
+
return res.status(isInitialCommitRequiredError(error) ? 409 : 500).json({
|
|
2708
2793
|
error: `Unable to prepare the bundle worktree for branch "${bundle.branch}": ${getErrorMessage(error)}`,
|
|
2709
2794
|
});
|
|
2710
2795
|
}
|
|
@@ -2754,67 +2839,15 @@ app.post('/bundles/:id/build', (req, res) => {
|
|
|
2754
2839
|
if ('error' in result) {
|
|
2755
2840
|
return res.status(400).json({ error: result.error });
|
|
2756
2841
|
}
|
|
2757
|
-
const
|
|
2758
|
-
if (!
|
|
2759
|
-
return res.status(
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
if (planTickets.length === 0) {
|
|
2763
|
-
return res.status(409).json({ error: 'No plan tickets are available to build in this bundle' });
|
|
2764
|
-
}
|
|
2765
|
-
const bundleTicketIds = listBundleTickets(result.project.id, bundle.id).map(ticket => ticket.id);
|
|
2766
|
-
const orderedPlanTicketIds = getDependencyOrderedBundleTicketIds(result.project.id, planTickets.map(ticket => ticket.id));
|
|
2767
|
-
const planTicketById = new Map(planTickets.map(ticket => [ticket.id, ticket]));
|
|
2768
|
-
const orderedPlanTickets = orderedPlanTicketIds
|
|
2769
|
-
.map(ticketId => planTicketById.get(ticketId))
|
|
2770
|
-
.filter((ticket) => ticket !== undefined);
|
|
2771
|
-
const bundleBuildBlocker = orderedPlanTickets.find(ticket => getBundleBuildBlockerResponse(ticket) !== null);
|
|
2772
|
-
if (bundleBuildBlocker) {
|
|
2773
|
-
return res.status(409).json(getBundleBuildBlockerResponse(bundleBuildBlocker));
|
|
2774
|
-
}
|
|
2775
|
-
for (const planTicket of orderedPlanTickets) {
|
|
2776
|
-
const buildValidation = prepareTicketForBuild(planTicket.id, {
|
|
2777
|
-
ignoredBlockerTicketIds: bundleTicketIds,
|
|
2778
|
-
updateState: false,
|
|
2779
|
-
});
|
|
2780
|
-
if (!buildValidation.ok) {
|
|
2781
|
-
return res.status(buildValidation.status).json({
|
|
2782
|
-
error: buildValidation.error,
|
|
2783
|
-
...(buildValidation.blockers ? { blockers: buildValidation.blockers } : {}),
|
|
2784
|
-
});
|
|
2785
|
-
}
|
|
2786
|
-
}
|
|
2787
|
-
const movedTickets = [];
|
|
2788
|
-
for (const planTicket of orderedPlanTickets) {
|
|
2789
|
-
const buildTransition = prepareTicketForBuild(planTicket.id, {
|
|
2790
|
-
ignoredBlockerTicketIds: bundleTicketIds,
|
|
2791
|
-
});
|
|
2792
|
-
if (!buildTransition.ok) {
|
|
2793
|
-
return res.status(buildTransition.status).json({
|
|
2794
|
-
error: buildTransition.error,
|
|
2795
|
-
...(buildTransition.blockers ? { blockers: buildTransition.blockers } : {}),
|
|
2796
|
-
});
|
|
2797
|
-
}
|
|
2798
|
-
movedTickets.push(buildTransition.ticket);
|
|
2799
|
-
}
|
|
2800
|
-
emitUpdatedTickets(movedTickets.map(ticket => ticket.id));
|
|
2801
|
-
res.json(serializeBundleDetails(result.project, bundle, {
|
|
2802
|
-
target_ticket_id: movedTickets[0]?.id ?? null,
|
|
2803
|
-
}));
|
|
2804
|
-
for (const [index, movedTicket] of movedTickets.entries()) {
|
|
2805
|
-
if (!movedTicket.project_id || !movedTicket.worktree_bundle_id) {
|
|
2806
|
-
continue;
|
|
2807
|
-
}
|
|
2808
|
-
const queuedBuild = enqueueBundledBuild({
|
|
2809
|
-
ticketId: movedTicket.id,
|
|
2810
|
-
projectId: movedTicket.project_id,
|
|
2811
|
-
worktreeBundleId: movedTicket.worktree_bundle_id,
|
|
2812
|
-
activate: index === 0 && shouldStartBundleBuildNow(movedTicket),
|
|
2842
|
+
const buildResult = buildBundleTickets(result.project.id, req.params.id, io);
|
|
2843
|
+
if (!buildResult.ok) {
|
|
2844
|
+
return res.status(buildResult.status).json({
|
|
2845
|
+
error: buildResult.error,
|
|
2846
|
+
...('details' in buildResult ? buildResult.details : {}),
|
|
2813
2847
|
});
|
|
2814
|
-
if (queuedBuild.shouldStartNow) {
|
|
2815
|
-
startBuild(movedTicket, io);
|
|
2816
|
-
}
|
|
2817
2848
|
}
|
|
2849
|
+
res.status(buildResult.status).json(buildResult.value);
|
|
2850
|
+
buildResult.afterResponse?.();
|
|
2818
2851
|
});
|
|
2819
2852
|
app.post('/bundles/:id/review', (req, res) => {
|
|
2820
2853
|
const result = getRequiredProject(req);
|
|
@@ -2959,20 +2992,47 @@ app.post('/projects/validate-repo-path', (req, res) => {
|
|
|
2959
2992
|
if (typeof req.body?.repo_path !== 'string' || req.body.repo_path.trim() === '') {
|
|
2960
2993
|
return res.status(400).json({ error: 'repo_path must be a non-empty string' });
|
|
2961
2994
|
}
|
|
2962
|
-
const
|
|
2963
|
-
if ('
|
|
2995
|
+
const repoAnalysis = analyzeRepoPath(req.body.repo_path, { allowInitialization: true });
|
|
2996
|
+
if (repoAnalysis.kind === 'invalid') {
|
|
2964
2997
|
return res.json({
|
|
2965
2998
|
valid: false,
|
|
2966
2999
|
repo_root: null,
|
|
2967
|
-
error:
|
|
3000
|
+
error: repoAnalysis.error,
|
|
3001
|
+
will_initialize: false,
|
|
3002
|
+
has_initial_commit: null,
|
|
3003
|
+
});
|
|
3004
|
+
}
|
|
3005
|
+
if (repoAnalysis.kind === 'initializable') {
|
|
3006
|
+
return res.json({
|
|
3007
|
+
valid: false,
|
|
3008
|
+
repo_root: null,
|
|
3009
|
+
error: null,
|
|
3010
|
+
will_initialize: true,
|
|
3011
|
+
has_initial_commit: null,
|
|
2968
3012
|
});
|
|
2969
3013
|
}
|
|
2970
3014
|
return res.json({
|
|
2971
3015
|
valid: true,
|
|
2972
|
-
repo_root:
|
|
3016
|
+
repo_root: repoAnalysis.repoRoot,
|
|
2973
3017
|
error: null,
|
|
3018
|
+
will_initialize: false,
|
|
3019
|
+
has_initial_commit: repoAnalysis.hasInitialCommit,
|
|
2974
3020
|
});
|
|
2975
3021
|
});
|
|
3022
|
+
app.post('/projects/:id/create-initial-commit', (req, res) => {
|
|
3023
|
+
const project = getProjectById(req.params.id);
|
|
3024
|
+
if (!project) {
|
|
3025
|
+
return res.status(404).json({ error: 'Not found' });
|
|
3026
|
+
}
|
|
3027
|
+
try {
|
|
3028
|
+
createInitialCommit(project.repo_path);
|
|
3029
|
+
}
|
|
3030
|
+
catch (error) {
|
|
3031
|
+
return res.status(409).json({ error: getErrorMessage(error) || 'Unable to create the initial commit right now.' });
|
|
3032
|
+
}
|
|
3033
|
+
const updatedProject = getProjectById(project.id);
|
|
3034
|
+
return res.json(serializeProject(updatedProject ?? null));
|
|
3035
|
+
});
|
|
2976
3036
|
app.post('/projects/browse-repo-path', (_req, res) => {
|
|
2977
3037
|
const browseResult = browseRepoPath();
|
|
2978
3038
|
if (browseResult.status === 'cancelled') {
|
|
@@ -3322,12 +3382,23 @@ app.get('/projects', (_req, res) => {
|
|
|
3322
3382
|
res.json(projects.map(serializeProject));
|
|
3323
3383
|
});
|
|
3324
3384
|
app.post('/projects', (req, res) => {
|
|
3325
|
-
const validation = validateProjectPayload(asProjectPayload(req.body));
|
|
3385
|
+
const validation = validateProjectPayload(asProjectPayload(req.body), { allowRepoInitialization: true });
|
|
3326
3386
|
if ('error' in validation) {
|
|
3327
3387
|
return res.status(400).json({ error: validation.error });
|
|
3328
3388
|
}
|
|
3329
3389
|
const projectCount = Number(db.prepare('SELECT COUNT(*) AS count FROM projects').get()?.count ?? 0);
|
|
3330
3390
|
const { name, repo_path, linked_project_ids, run_setup, run_services, test_commands, run_ide, preview_service_name, preview_path, preview_capability_mode, helper_model, helper_variant, commit_message_rules, auto_park_stale_tickets, memory_enabled, worktree_sync, } = validation.values;
|
|
3391
|
+
if (!repo_path) {
|
|
3392
|
+
return res.status(400).json({ error: 'repo_path must be a non-empty string' });
|
|
3393
|
+
}
|
|
3394
|
+
let normalizedRepoPath = repo_path;
|
|
3395
|
+
try {
|
|
3396
|
+
const initializedRepo = initializeGitRepo(repo_path);
|
|
3397
|
+
normalizedRepoPath = initializedRepo.repoRoot;
|
|
3398
|
+
}
|
|
3399
|
+
catch (error) {
|
|
3400
|
+
return res.status(400).json({ error: getErrorMessage(error) || 'Unable to initialize the project repository.' });
|
|
3401
|
+
}
|
|
3331
3402
|
const id = randomUUID();
|
|
3332
3403
|
db.prepare(`
|
|
3333
3404
|
INSERT INTO projects (
|
|
@@ -3349,7 +3420,7 @@ app.post('/projects', (req, res) => {
|
|
|
3349
3420
|
worktree_sync
|
|
3350
3421
|
)
|
|
3351
3422
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3352
|
-
`).run(id, name,
|
|
3423
|
+
`).run(id, name, normalizedRepoPath, run_setup, run_services, test_commands, run_ide ?? null, preview_service_name ?? null, preview_path ?? null, preview_capability_mode ?? null, helper_model ?? null, helper_variant ?? null, commit_message_rules ?? null, auto_park_stale_tickets ?? 0, memory_enabled ?? 0, worktree_sync ?? null);
|
|
3353
3424
|
replaceProjectLinks(id, linked_project_ids ?? []);
|
|
3354
3425
|
ensureProjectRootBundle(id);
|
|
3355
3426
|
if (projectCount === 0) {
|