opencode-sdlc-plugin 1.1.0 → 1.1.2
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/index.js +438 -88
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +203 -7
- package/dist/index.js.map +1 -1
- package/dist/plugin/index.js +203 -7
- package/dist/plugin/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -291,7 +291,7 @@ var FileManager = class {
|
|
|
291
291
|
return false;
|
|
292
292
|
}
|
|
293
293
|
const sdlcPlugins = [
|
|
294
|
-
"opencode-sdlc",
|
|
294
|
+
"opencode-sdlc-plugin",
|
|
295
295
|
"oh-my-opencode",
|
|
296
296
|
"opencode-antigravity-auth",
|
|
297
297
|
"opencode-openai-codex-auth"
|
|
@@ -1590,7 +1590,7 @@ async function generateOpencodeConfig(answers, configDir) {
|
|
|
1590
1590
|
} catch {
|
|
1591
1591
|
}
|
|
1592
1592
|
}
|
|
1593
|
-
const plugins = ["opencode-sdlc/plugin", "oh-my-opencode"];
|
|
1593
|
+
const plugins = ["opencode-sdlc-plugin/plugin", "oh-my-opencode"];
|
|
1594
1594
|
if (answers.subscriptions.hasGoogle && answers.subscriptions.googleAuth === "antigravity") {
|
|
1595
1595
|
plugins.push("opencode-antigravity-auth");
|
|
1596
1596
|
}
|
|
@@ -1756,7 +1756,7 @@ async function generateOpencodeConfig(answers, configDir) {
|
|
|
1756
1756
|
return config;
|
|
1757
1757
|
}
|
|
1758
1758
|
function getRequiredPlugins(answers) {
|
|
1759
|
-
const plugins = ["opencode-sdlc", "oh-my-opencode"];
|
|
1759
|
+
const plugins = ["opencode-sdlc-plugin", "oh-my-opencode"];
|
|
1760
1760
|
if (answers.subscriptions.hasGoogle && answers.subscriptions.googleAuth === "antigravity") {
|
|
1761
1761
|
plugins.push("opencode-antigravity-auth");
|
|
1762
1762
|
}
|
|
@@ -2304,15 +2304,28 @@ var GitHubClient = class {
|
|
|
2304
2304
|
// Repositories
|
|
2305
2305
|
// ==========================================================================
|
|
2306
2306
|
/**
|
|
2307
|
-
* Create a new repository
|
|
2307
|
+
* Create a new repository (personal or in an organization)
|
|
2308
2308
|
*/
|
|
2309
2309
|
async createRepository(options) {
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2310
|
+
let data;
|
|
2311
|
+
if (options.org) {
|
|
2312
|
+
const response = await this.octokit.repos.createInOrg({
|
|
2313
|
+
org: options.org,
|
|
2314
|
+
name: options.name,
|
|
2315
|
+
description: options.description,
|
|
2316
|
+
private: options.private ?? false,
|
|
2317
|
+
auto_init: options.autoInit ?? false
|
|
2318
|
+
});
|
|
2319
|
+
data = response.data;
|
|
2320
|
+
} else {
|
|
2321
|
+
const response = await this.octokit.repos.createForAuthenticatedUser({
|
|
2322
|
+
name: options.name,
|
|
2323
|
+
description: options.description,
|
|
2324
|
+
private: options.private ?? false,
|
|
2325
|
+
auto_init: options.autoInit ?? false
|
|
2326
|
+
});
|
|
2327
|
+
data = response.data;
|
|
2328
|
+
}
|
|
2316
2329
|
return {
|
|
2317
2330
|
id: data.id,
|
|
2318
2331
|
name: data.name,
|
|
@@ -2322,6 +2335,32 @@ var GitHubClient = class {
|
|
|
2322
2335
|
defaultBranch: data.default_branch || "main"
|
|
2323
2336
|
};
|
|
2324
2337
|
}
|
|
2338
|
+
/**
|
|
2339
|
+
* List organizations the authenticated user belongs to
|
|
2340
|
+
*/
|
|
2341
|
+
async listOrganizations() {
|
|
2342
|
+
const query = `
|
|
2343
|
+
query {
|
|
2344
|
+
viewer {
|
|
2345
|
+
organizations(first: 100) {
|
|
2346
|
+
nodes {
|
|
2347
|
+
id
|
|
2348
|
+
databaseId
|
|
2349
|
+
login
|
|
2350
|
+
name
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
`;
|
|
2356
|
+
const result = await this.octokit.graphql(query);
|
|
2357
|
+
return result.viewer.organizations.nodes.map((org) => ({
|
|
2358
|
+
id: String(org.databaseId),
|
|
2359
|
+
nodeId: org.id,
|
|
2360
|
+
login: org.login,
|
|
2361
|
+
name: org.name
|
|
2362
|
+
}));
|
|
2363
|
+
}
|
|
2325
2364
|
/**
|
|
2326
2365
|
* Get repository details
|
|
2327
2366
|
*/
|
|
@@ -2403,6 +2442,163 @@ var GitHubClient = class {
|
|
|
2403
2442
|
return null;
|
|
2404
2443
|
}
|
|
2405
2444
|
}
|
|
2445
|
+
/**
|
|
2446
|
+
* Get project by number for an organization
|
|
2447
|
+
*/
|
|
2448
|
+
async getOrgProject(orgLogin, projectNumber) {
|
|
2449
|
+
const query = `
|
|
2450
|
+
query($login: String!, $number: Int!) {
|
|
2451
|
+
organization(login: $login) {
|
|
2452
|
+
projectV2(number: $number) {
|
|
2453
|
+
id
|
|
2454
|
+
number
|
|
2455
|
+
title
|
|
2456
|
+
url
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
`;
|
|
2461
|
+
try {
|
|
2462
|
+
const result = await this.octokit.graphql(query, { login: orgLogin, number: projectNumber });
|
|
2463
|
+
return result.organization.projectV2;
|
|
2464
|
+
} catch {
|
|
2465
|
+
return null;
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
/**
|
|
2469
|
+
* List projects for an organization
|
|
2470
|
+
*/
|
|
2471
|
+
async listOrgProjects(orgLogin, limit = 20) {
|
|
2472
|
+
const query = `
|
|
2473
|
+
query($login: String!, $first: Int!) {
|
|
2474
|
+
organization(login: $login) {
|
|
2475
|
+
projectsV2(first: $first) {
|
|
2476
|
+
nodes {
|
|
2477
|
+
id
|
|
2478
|
+
number
|
|
2479
|
+
title
|
|
2480
|
+
url
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
`;
|
|
2486
|
+
try {
|
|
2487
|
+
const result = await this.octokit.graphql(query, { login: orgLogin, first: limit });
|
|
2488
|
+
return result.organization.projectsV2.nodes;
|
|
2489
|
+
} catch {
|
|
2490
|
+
return [];
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
/**
|
|
2494
|
+
* Create a new project
|
|
2495
|
+
*/
|
|
2496
|
+
async createProject(options) {
|
|
2497
|
+
let ownerId;
|
|
2498
|
+
if (options.isOrg) {
|
|
2499
|
+
const query = `
|
|
2500
|
+
query($login: String!) {
|
|
2501
|
+
organization(login: $login) {
|
|
2502
|
+
id
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
`;
|
|
2506
|
+
const result2 = await this.octokit.graphql(query, { login: options.ownerLogin });
|
|
2507
|
+
ownerId = result2.organization.id;
|
|
2508
|
+
} else {
|
|
2509
|
+
const query = `
|
|
2510
|
+
query($login: String!) {
|
|
2511
|
+
user(login: $login) {
|
|
2512
|
+
id
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
`;
|
|
2516
|
+
const result2 = await this.octokit.graphql(query, { login: options.ownerLogin });
|
|
2517
|
+
ownerId = result2.user.id;
|
|
2518
|
+
}
|
|
2519
|
+
const mutation = `
|
|
2520
|
+
mutation($ownerId: ID!, $title: String!) {
|
|
2521
|
+
createProjectV2(input: { ownerId: $ownerId, title: $title }) {
|
|
2522
|
+
projectV2 {
|
|
2523
|
+
id
|
|
2524
|
+
number
|
|
2525
|
+
title
|
|
2526
|
+
url
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
`;
|
|
2531
|
+
const result = await this.octokit.graphql(mutation, { ownerId, title: options.title });
|
|
2532
|
+
return result.createProjectV2.projectV2;
|
|
2533
|
+
}
|
|
2534
|
+
/**
|
|
2535
|
+
* Copy an existing project
|
|
2536
|
+
*/
|
|
2537
|
+
async copyProject(options) {
|
|
2538
|
+
let sourceProject;
|
|
2539
|
+
if (options.sourceIsOrg) {
|
|
2540
|
+
sourceProject = await this.getOrgProject(
|
|
2541
|
+
options.sourceOwnerLogin,
|
|
2542
|
+
options.sourceProjectNumber
|
|
2543
|
+
);
|
|
2544
|
+
} else {
|
|
2545
|
+
sourceProject = await this.getUserProject(
|
|
2546
|
+
options.sourceOwnerLogin,
|
|
2547
|
+
options.sourceProjectNumber
|
|
2548
|
+
);
|
|
2549
|
+
}
|
|
2550
|
+
if (!sourceProject) {
|
|
2551
|
+
throw new Error(
|
|
2552
|
+
`Source project #${options.sourceProjectNumber} not found for ${options.sourceOwnerLogin}`
|
|
2553
|
+
);
|
|
2554
|
+
}
|
|
2555
|
+
let targetOwnerId;
|
|
2556
|
+
if (options.targetIsOrg) {
|
|
2557
|
+
const query = `
|
|
2558
|
+
query($login: String!) {
|
|
2559
|
+
organization(login: $login) {
|
|
2560
|
+
id
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
`;
|
|
2564
|
+
const result2 = await this.octokit.graphql(query, { login: options.targetOwnerLogin });
|
|
2565
|
+
targetOwnerId = result2.organization.id;
|
|
2566
|
+
} else {
|
|
2567
|
+
const query = `
|
|
2568
|
+
query($login: String!) {
|
|
2569
|
+
user(login: $login) {
|
|
2570
|
+
id
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
`;
|
|
2574
|
+
const result2 = await this.octokit.graphql(query, { login: options.targetOwnerLogin });
|
|
2575
|
+
targetOwnerId = result2.user.id;
|
|
2576
|
+
}
|
|
2577
|
+
const mutation = `
|
|
2578
|
+
mutation($projectId: ID!, $ownerId: ID!, $title: String!, $includeDraftIssues: Boolean) {
|
|
2579
|
+
copyProjectV2(input: {
|
|
2580
|
+
projectId: $projectId,
|
|
2581
|
+
ownerId: $ownerId,
|
|
2582
|
+
title: $title,
|
|
2583
|
+
includeDraftIssues: $includeDraftIssues
|
|
2584
|
+
}) {
|
|
2585
|
+
projectV2 {
|
|
2586
|
+
id
|
|
2587
|
+
number
|
|
2588
|
+
title
|
|
2589
|
+
url
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
`;
|
|
2594
|
+
const result = await this.octokit.graphql(mutation, {
|
|
2595
|
+
projectId: sourceProject.id,
|
|
2596
|
+
ownerId: targetOwnerId,
|
|
2597
|
+
title: options.title,
|
|
2598
|
+
includeDraftIssues: options.includeDraftIssues ?? false
|
|
2599
|
+
});
|
|
2600
|
+
return result.copyProjectV2.projectV2;
|
|
2601
|
+
}
|
|
2406
2602
|
/**
|
|
2407
2603
|
* Link a repository to a project
|
|
2408
2604
|
* Note: This creates a linked repository in the project
|
|
@@ -2625,17 +2821,22 @@ async function setupSmartGitHub(cwd) {
|
|
|
2625
2821
|
return manualGitHubConfig();
|
|
2626
2822
|
}
|
|
2627
2823
|
let userLogin;
|
|
2824
|
+
let orgs = [];
|
|
2628
2825
|
try {
|
|
2629
2826
|
const user = await client.getAuthenticatedUser();
|
|
2630
2827
|
userLogin = user.login;
|
|
2631
2828
|
console.log(chalk5.green(`
|
|
2632
|
-
\u2713 Authenticated as ${userLogin}
|
|
2633
|
-
|
|
2829
|
+
\u2713 Authenticated as ${userLogin}`));
|
|
2830
|
+
orgs = await client.listOrganizations();
|
|
2831
|
+
if (orgs.length > 0) {
|
|
2832
|
+
console.log(chalk5.dim(` Organizations: ${orgs.map((o) => o.login).join(", ")}`));
|
|
2833
|
+
}
|
|
2834
|
+
console.log();
|
|
2634
2835
|
} catch (error) {
|
|
2635
2836
|
console.log(chalk5.yellow("\n\u26A0 Could not verify GitHub authentication.\n"));
|
|
2636
2837
|
return manualGitHubConfig();
|
|
2637
2838
|
}
|
|
2638
|
-
const repoResult = await setupRepository(client, userLogin, cwd);
|
|
2839
|
+
const repoResult = await setupRepository(client, userLogin, orgs, cwd);
|
|
2639
2840
|
if (repoResult.action === "skip") {
|
|
2640
2841
|
return { enabled: false };
|
|
2641
2842
|
}
|
|
@@ -2644,7 +2845,8 @@ async function setupSmartGitHub(cwd) {
|
|
|
2644
2845
|
return { enabled: false };
|
|
2645
2846
|
}
|
|
2646
2847
|
client.setRepo(owner, repo);
|
|
2647
|
-
const
|
|
2848
|
+
const ownerIsOrg = orgs.some((o) => o.login === owner);
|
|
2849
|
+
const projectResult = await setupProjectBoard(client, userLogin, owner, ownerIsOrg, orgs);
|
|
2648
2850
|
let rulesetsCreated = false;
|
|
2649
2851
|
const wantRulesets = await confirm({
|
|
2650
2852
|
message: "Configure branch protection rules for main branch?",
|
|
@@ -2662,11 +2864,32 @@ async function setupSmartGitHub(cwd) {
|
|
|
2662
2864
|
statuses: DEFAULT_STATUSES
|
|
2663
2865
|
},
|
|
2664
2866
|
repoCreated: repoResult.action === "create-public" || repoResult.action === "create-private",
|
|
2665
|
-
projectCreated: projectResult.action === "create-blank",
|
|
2867
|
+
projectCreated: projectResult.action === "create-blank" || projectResult.action === "copy",
|
|
2666
2868
|
rulesetsCreated
|
|
2667
2869
|
};
|
|
2668
2870
|
}
|
|
2669
|
-
async function
|
|
2871
|
+
async function selectOwner(userLogin, orgs, prompt) {
|
|
2872
|
+
const choices = [
|
|
2873
|
+
{
|
|
2874
|
+
value: { login: userLogin, isOrg: false, name: userLogin },
|
|
2875
|
+
name: `${userLogin} (personal)`,
|
|
2876
|
+
description: "Your personal account"
|
|
2877
|
+
},
|
|
2878
|
+
...orgs.map((org) => ({
|
|
2879
|
+
value: { login: org.login, isOrg: true, name: org.name || org.login },
|
|
2880
|
+
name: org.login,
|
|
2881
|
+
description: org.name || "Organization"
|
|
2882
|
+
}))
|
|
2883
|
+
];
|
|
2884
|
+
if (choices.length === 1) {
|
|
2885
|
+
return choices[0].value;
|
|
2886
|
+
}
|
|
2887
|
+
return await select({
|
|
2888
|
+
message: prompt,
|
|
2889
|
+
choices
|
|
2890
|
+
});
|
|
2891
|
+
}
|
|
2892
|
+
async function setupRepository(client, userLogin, orgs, cwd) {
|
|
2670
2893
|
const isGitRepo = isGitRepository(cwd);
|
|
2671
2894
|
const existingRemote = isGitRepo ? detectGitHubRepo(cwd) : null;
|
|
2672
2895
|
if (existingRemote) {
|
|
@@ -2714,17 +2937,34 @@ async function setupRepository(client, userLogin, cwd) {
|
|
|
2714
2937
|
if (repoAction === "existing") {
|
|
2715
2938
|
return await connectExistingRepo(client);
|
|
2716
2939
|
}
|
|
2717
|
-
return await createNewRepo(
|
|
2940
|
+
return await createNewRepo(
|
|
2941
|
+
client,
|
|
2942
|
+
userLogin,
|
|
2943
|
+
orgs,
|
|
2944
|
+
repoAction === "create-private",
|
|
2945
|
+
cwd,
|
|
2946
|
+
isGitRepo
|
|
2947
|
+
);
|
|
2718
2948
|
}
|
|
2719
2949
|
async function connectExistingRepo(client) {
|
|
2720
|
-
const
|
|
2721
|
-
message: "Repository owner
|
|
2722
|
-
validate: (v) => v.trim() ? true : "
|
|
2723
|
-
});
|
|
2724
|
-
const repo = await input({
|
|
2725
|
-
message: "Repository name:",
|
|
2726
|
-
validate: (v) => v.trim() ? true : "Repository name is required"
|
|
2950
|
+
const ownerRepo = await input({
|
|
2951
|
+
message: "Repository (owner/repo or just repo for personal):",
|
|
2952
|
+
validate: (v) => v.trim() ? true : "Repository is required"
|
|
2727
2953
|
});
|
|
2954
|
+
let owner;
|
|
2955
|
+
let repo;
|
|
2956
|
+
if (ownerRepo.includes("/")) {
|
|
2957
|
+
[owner, repo] = ownerRepo.split("/", 2);
|
|
2958
|
+
} else {
|
|
2959
|
+
try {
|
|
2960
|
+
const user = await client.getAuthenticatedUser();
|
|
2961
|
+
owner = user.login;
|
|
2962
|
+
repo = ownerRepo;
|
|
2963
|
+
} catch {
|
|
2964
|
+
console.log(chalk5.yellow("\n\u26A0 Could not determine owner. Please use owner/repo format.\n"));
|
|
2965
|
+
return { action: "skip" };
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2728
2968
|
try {
|
|
2729
2969
|
const exists = await client.repoExists(owner, repo);
|
|
2730
2970
|
if (!exists) {
|
|
@@ -2739,7 +2979,8 @@ async function connectExistingRepo(client) {
|
|
|
2739
2979
|
}
|
|
2740
2980
|
return { action: "existing", owner, repo };
|
|
2741
2981
|
}
|
|
2742
|
-
async function createNewRepo(client, userLogin, isPrivate, cwd, isGitRepo) {
|
|
2982
|
+
async function createNewRepo(client, userLogin, orgs, isPrivate, cwd, isGitRepo) {
|
|
2983
|
+
const ownerChoice = await selectOwner(userLogin, orgs, "Create repository under:");
|
|
2743
2984
|
const repoName = await input({
|
|
2744
2985
|
message: "New repository name:",
|
|
2745
2986
|
validate: (v) => {
|
|
@@ -2753,35 +2994,39 @@ async function createNewRepo(client, userLogin, isPrivate, cwd, isGitRepo) {
|
|
|
2753
2994
|
default: ""
|
|
2754
2995
|
});
|
|
2755
2996
|
try {
|
|
2756
|
-
console.log(
|
|
2757
|
-
|
|
2997
|
+
console.log(
|
|
2998
|
+
chalk5.dim(
|
|
2999
|
+
`
|
|
3000
|
+
Creating ${isPrivate ? "private" : "public"} repository in ${ownerChoice.login}...`
|
|
3001
|
+
)
|
|
3002
|
+
);
|
|
2758
3003
|
const newRepo = await client.createRepository({
|
|
2759
3004
|
name: repoName,
|
|
2760
3005
|
description: description || void 0,
|
|
2761
3006
|
private: isPrivate,
|
|
2762
|
-
autoInit: !isGitRepo
|
|
3007
|
+
autoInit: !isGitRepo,
|
|
2763
3008
|
// Only auto-init if no local git repo
|
|
3009
|
+
org: ownerChoice.isOrg ? ownerChoice.login : void 0
|
|
2764
3010
|
});
|
|
2765
3011
|
console.log(chalk5.green(`\u2713 Created repository: ${newRepo.url}
|
|
2766
3012
|
`));
|
|
3013
|
+
const remoteUrl = `git@github.com:${ownerChoice.login}/${repoName}.git`;
|
|
2767
3014
|
if (isGitRepo) {
|
|
2768
|
-
const remoteUrl = `git@github.com:${userLogin}/${repoName}.git`;
|
|
2769
3015
|
const added = addGitRemote("origin", remoteUrl, cwd);
|
|
2770
3016
|
if (added) {
|
|
2771
|
-
console.log(chalk5.green(
|
|
3017
|
+
console.log(chalk5.green("\u2713 Added git remote 'origin'"));
|
|
2772
3018
|
}
|
|
2773
3019
|
} else {
|
|
2774
3020
|
const initialized = initGitRepo(cwd);
|
|
2775
3021
|
if (initialized) {
|
|
2776
3022
|
console.log(chalk5.green("\u2713 Initialized git repository"));
|
|
2777
|
-
const remoteUrl = `git@github.com:${userLogin}/${repoName}.git`;
|
|
2778
3023
|
addGitRemote("origin", remoteUrl, cwd);
|
|
2779
|
-
console.log(chalk5.green(
|
|
3024
|
+
console.log(chalk5.green("\u2713 Added git remote 'origin'"));
|
|
2780
3025
|
}
|
|
2781
3026
|
}
|
|
2782
3027
|
return {
|
|
2783
3028
|
action: isPrivate ? "create-private" : "create-public",
|
|
2784
|
-
owner:
|
|
3029
|
+
owner: ownerChoice.login,
|
|
2785
3030
|
repo: repoName
|
|
2786
3031
|
};
|
|
2787
3032
|
} catch (error) {
|
|
@@ -2792,7 +3037,7 @@ async function createNewRepo(client, userLogin, isPrivate, cwd, isGitRepo) {
|
|
|
2792
3037
|
return { action: "skip" };
|
|
2793
3038
|
}
|
|
2794
3039
|
}
|
|
2795
|
-
async function setupProjectBoard(client, userLogin) {
|
|
3040
|
+
async function setupProjectBoard(client, userLogin, repoOwner, repoOwnerIsOrg, orgs) {
|
|
2796
3041
|
const wantProject = await confirm({
|
|
2797
3042
|
message: "Use a GitHub Project board for issue status tracking?",
|
|
2798
3043
|
default: true
|
|
@@ -2806,12 +3051,17 @@ async function setupProjectBoard(client, userLogin) {
|
|
|
2806
3051
|
{
|
|
2807
3052
|
value: "existing",
|
|
2808
3053
|
name: "Link existing project",
|
|
2809
|
-
description: "
|
|
3054
|
+
description: "Select from your projects or enter project number"
|
|
2810
3055
|
},
|
|
2811
3056
|
{
|
|
2812
3057
|
value: "create-blank",
|
|
2813
3058
|
name: "Create new blank project",
|
|
2814
|
-
description: "Create a new project
|
|
3059
|
+
description: "Create a new empty project"
|
|
3060
|
+
},
|
|
3061
|
+
{
|
|
3062
|
+
value: "copy",
|
|
3063
|
+
name: "Copy from existing project",
|
|
3064
|
+
description: "Copy structure from another project (yours or a template)"
|
|
2815
3065
|
},
|
|
2816
3066
|
{
|
|
2817
3067
|
value: "skip",
|
|
@@ -2824,22 +3074,19 @@ async function setupProjectBoard(client, userLogin) {
|
|
|
2824
3074
|
return { action: "skip" };
|
|
2825
3075
|
}
|
|
2826
3076
|
if (projectAction === "existing") {
|
|
2827
|
-
return await linkExistingProject(client, userLogin);
|
|
3077
|
+
return await linkExistingProject(client, userLogin, repoOwner, repoOwnerIsOrg, orgs);
|
|
2828
3078
|
}
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
async function linkExistingProject(client, userLogin) {
|
|
2832
|
-
try {
|
|
2833
|
-
const projects = await client.listUserProjects(10);
|
|
2834
|
-
if (projects.length > 0) {
|
|
2835
|
-
console.log(chalk5.dim("\n Your recent projects:"));
|
|
2836
|
-
for (const p of projects) {
|
|
2837
|
-
console.log(chalk5.dim(` #${p.number}: ${p.title}`));
|
|
2838
|
-
}
|
|
2839
|
-
console.log();
|
|
2840
|
-
}
|
|
2841
|
-
} catch {
|
|
3079
|
+
if (projectAction === "copy") {
|
|
3080
|
+
return await copyExistingProject(client, userLogin, repoOwner, repoOwnerIsOrg, orgs);
|
|
2842
3081
|
}
|
|
3082
|
+
return await createNewProject(client, userLogin, repoOwner, repoOwnerIsOrg, orgs);
|
|
3083
|
+
}
|
|
3084
|
+
async function linkExistingProject(client, userLogin, repoOwner, repoOwnerIsOrg, orgs) {
|
|
3085
|
+
await showAvailableProjects(client, userLogin, orgs);
|
|
3086
|
+
const projectOwner = await input({
|
|
3087
|
+
message: "Project owner (user or org, leave empty for repo owner):",
|
|
3088
|
+
default: repoOwner
|
|
3089
|
+
});
|
|
2843
3090
|
const projectNum = await number({
|
|
2844
3091
|
message: "Project number (from project URL):",
|
|
2845
3092
|
min: 1,
|
|
@@ -2848,35 +3095,137 @@ async function linkExistingProject(client, userLogin) {
|
|
|
2848
3095
|
if (!projectNum) {
|
|
2849
3096
|
return { action: "skip" };
|
|
2850
3097
|
}
|
|
3098
|
+
const ownerIsOrg = projectOwner === repoOwner ? repoOwnerIsOrg : orgs.some((o) => o.login === projectOwner);
|
|
2851
3099
|
try {
|
|
2852
|
-
const project = await client.getUserProject(
|
|
3100
|
+
const project = ownerIsOrg ? await client.getOrgProject(projectOwner, projectNum) : await client.getUserProject(projectOwner, projectNum);
|
|
2853
3101
|
if (project) {
|
|
2854
3102
|
console.log(chalk5.green(`\u2713 Found project: ${project.title}`));
|
|
2855
3103
|
return { action: "existing", projectNumber: projectNum };
|
|
2856
3104
|
}
|
|
2857
|
-
|
|
3105
|
+
console.log(chalk5.yellow(`
|
|
3106
|
+
\u26A0 Project #${projectNum} not found for ${projectOwner}
|
|
3107
|
+
`));
|
|
3108
|
+
} catch (error) {
|
|
3109
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
3110
|
+
console.log(chalk5.yellow(`
|
|
3111
|
+
\u26A0 Could not verify project: ${message}
|
|
3112
|
+
`));
|
|
2858
3113
|
}
|
|
2859
3114
|
return { action: "existing", projectNumber: projectNum };
|
|
2860
3115
|
}
|
|
2861
|
-
async function createNewProject(client, userLogin) {
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
3116
|
+
async function createNewProject(client, userLogin, repoOwner, repoOwnerIsOrg, orgs) {
|
|
3117
|
+
const ownerChoice = await selectOwner(userLogin, orgs, "Create project under:");
|
|
3118
|
+
const projectTitle = await input({
|
|
3119
|
+
message: "Project title:",
|
|
3120
|
+
default: "Project Board",
|
|
3121
|
+
validate: (v) => v.trim() ? true : "Title is required"
|
|
3122
|
+
});
|
|
3123
|
+
try {
|
|
3124
|
+
console.log(chalk5.dim(`
|
|
3125
|
+
Creating project "${projectTitle}" in ${ownerChoice.login}...`));
|
|
3126
|
+
const project = await client.createProject({
|
|
3127
|
+
title: projectTitle,
|
|
3128
|
+
ownerLogin: ownerChoice.login,
|
|
3129
|
+
isOrg: ownerChoice.isOrg
|
|
3130
|
+
});
|
|
3131
|
+
console.log(chalk5.green(`\u2713 Created project: ${project.title} (#${project.number})`));
|
|
3132
|
+
console.log(chalk5.dim(` URL: ${project.url}
|
|
3133
|
+
`));
|
|
3134
|
+
return {
|
|
3135
|
+
action: "create-blank",
|
|
3136
|
+
projectNumber: project.number,
|
|
3137
|
+
projectTitle: project.title
|
|
3138
|
+
};
|
|
3139
|
+
} catch (error) {
|
|
3140
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
3141
|
+
console.log(chalk5.red(`
|
|
3142
|
+
\u2717 Failed to create project: ${message}
|
|
3143
|
+
`));
|
|
3144
|
+
return { action: "skip" };
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3147
|
+
async function copyExistingProject(client, userLogin, repoOwner, repoOwnerIsOrg, orgs) {
|
|
3148
|
+
console.log(chalk5.dim("\n Available projects to copy from:\n"));
|
|
3149
|
+
await showAvailableProjects(client, userLogin, orgs);
|
|
3150
|
+
const sourceOwner = await input({
|
|
3151
|
+
message: "Source project owner (user or org):",
|
|
3152
|
+
default: userLogin
|
|
3153
|
+
});
|
|
3154
|
+
const sourceProjectNum = await number({
|
|
3155
|
+
message: "Source project number to copy:",
|
|
3156
|
+
min: 1,
|
|
3157
|
+
validate: (v) => v && v >= 1 ? true : "Project number required"
|
|
2868
3158
|
});
|
|
2869
|
-
if (!
|
|
3159
|
+
if (!sourceProjectNum) {
|
|
2870
3160
|
return { action: "skip" };
|
|
2871
3161
|
}
|
|
2872
|
-
const
|
|
2873
|
-
|
|
2874
|
-
|
|
3162
|
+
const targetOwnerChoice = await selectOwner(userLogin, orgs, "Create copied project under:");
|
|
3163
|
+
const projectTitle = await input({
|
|
3164
|
+
message: "Title for new project:",
|
|
3165
|
+
default: "Project Board (copy)",
|
|
3166
|
+
validate: (v) => v.trim() ? true : "Title is required"
|
|
2875
3167
|
});
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
};
|
|
3168
|
+
const includeDrafts = await confirm({
|
|
3169
|
+
message: "Include draft issues from source project?",
|
|
3170
|
+
default: false
|
|
3171
|
+
});
|
|
3172
|
+
const sourceIsOrg = sourceOwner !== userLogin && orgs.some((o) => o.login === sourceOwner);
|
|
3173
|
+
try {
|
|
3174
|
+
console.log(
|
|
3175
|
+
chalk5.dim(
|
|
3176
|
+
`
|
|
3177
|
+
Copying project #${sourceProjectNum} from ${sourceOwner} to ${targetOwnerChoice.login}...`
|
|
3178
|
+
)
|
|
3179
|
+
);
|
|
3180
|
+
const project = await client.copyProject({
|
|
3181
|
+
sourceProjectNumber: sourceProjectNum,
|
|
3182
|
+
sourceOwnerLogin: sourceOwner,
|
|
3183
|
+
sourceIsOrg,
|
|
3184
|
+
targetOwnerLogin: targetOwnerChoice.login,
|
|
3185
|
+
targetIsOrg: targetOwnerChoice.isOrg,
|
|
3186
|
+
title: projectTitle,
|
|
3187
|
+
includeDraftIssues: includeDrafts
|
|
3188
|
+
});
|
|
3189
|
+
console.log(chalk5.green(`\u2713 Copied project: ${project.title} (#${project.number})`));
|
|
3190
|
+
console.log(chalk5.dim(` URL: ${project.url}
|
|
3191
|
+
`));
|
|
3192
|
+
return {
|
|
3193
|
+
action: "copy",
|
|
3194
|
+
projectNumber: project.number,
|
|
3195
|
+
projectTitle: project.title
|
|
3196
|
+
};
|
|
3197
|
+
} catch (error) {
|
|
3198
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
3199
|
+
console.log(chalk5.red(`
|
|
3200
|
+
\u2717 Failed to copy project: ${message}
|
|
3201
|
+
`));
|
|
3202
|
+
return { action: "skip" };
|
|
3203
|
+
}
|
|
3204
|
+
}
|
|
3205
|
+
async function showAvailableProjects(client, userLogin, orgs) {
|
|
3206
|
+
try {
|
|
3207
|
+
const userProjects = await client.listUserProjects(10);
|
|
3208
|
+
if (userProjects.length > 0) {
|
|
3209
|
+
console.log(chalk5.dim(` ${userLogin} (personal):`));
|
|
3210
|
+
for (const p of userProjects) {
|
|
3211
|
+
console.log(chalk5.dim(` #${p.number}: ${p.title}`));
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
} catch {
|
|
3215
|
+
}
|
|
3216
|
+
for (const org of orgs.slice(0, 3)) {
|
|
3217
|
+
try {
|
|
3218
|
+
const orgProjects = await client.listOrgProjects(org.login, 5);
|
|
3219
|
+
if (orgProjects.length > 0) {
|
|
3220
|
+
console.log(chalk5.dim(` ${org.login}:`));
|
|
3221
|
+
for (const p of orgProjects) {
|
|
3222
|
+
console.log(chalk5.dim(` #${p.number}: ${p.title}`));
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
} catch {
|
|
3226
|
+
}
|
|
3227
|
+
}
|
|
3228
|
+
console.log();
|
|
2880
3229
|
}
|
|
2881
3230
|
async function setupBranchRulesets(client, owner, repo) {
|
|
2882
3231
|
console.log(chalk5.dim("\n Configuring branch protection for 'main'...\n"));
|
|
@@ -2927,14 +3276,15 @@ async function setupBranchRulesets(client, owner, repo) {
|
|
|
2927
3276
|
}
|
|
2928
3277
|
}
|
|
2929
3278
|
async function manualGitHubConfig() {
|
|
2930
|
-
const
|
|
2931
|
-
message: "GitHub repository owner:",
|
|
2932
|
-
validate: (v) =>
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
3279
|
+
const ownerRepo = await input({
|
|
3280
|
+
message: "GitHub repository (owner/repo):",
|
|
3281
|
+
validate: (v) => {
|
|
3282
|
+
if (!v.trim()) return "Repository is required";
|
|
3283
|
+
if (!v.includes("/")) return "Please use owner/repo format";
|
|
3284
|
+
return true;
|
|
3285
|
+
}
|
|
2937
3286
|
});
|
|
3287
|
+
const [owner, repo] = ownerRepo.split("/", 2);
|
|
2938
3288
|
const useProject = await confirm({
|
|
2939
3289
|
message: "Use a GitHub Project board?",
|
|
2940
3290
|
default: false
|
|
@@ -4242,11 +4592,11 @@ async function upgrade(options) {
|
|
|
4242
4592
|
];
|
|
4243
4593
|
const updates = [];
|
|
4244
4594
|
const sdlcChannel = detectReleaseChannel(VERSION);
|
|
4245
|
-
const sdlcLatest = await getLatestVersion("opencode-sdlc", sdlcChannel);
|
|
4595
|
+
const sdlcLatest = await getLatestVersion("opencode-sdlc-plugin", sdlcChannel);
|
|
4246
4596
|
if (sdlcLatest) {
|
|
4247
4597
|
const sdlcHasUpdate = semver__default.valid(sdlcLatest) && semver__default.valid(VERSION) ? semver__default.gt(sdlcLatest, VERSION) : sdlcLatest !== VERSION;
|
|
4248
4598
|
updates.push({
|
|
4249
|
-
name: "opencode-sdlc",
|
|
4599
|
+
name: "opencode-sdlc-plugin",
|
|
4250
4600
|
current: VERSION,
|
|
4251
4601
|
latest: sdlcLatest,
|
|
4252
4602
|
updateAvailable: sdlcHasUpdate
|
|
@@ -4424,34 +4774,34 @@ Current version: ${existingVersion}`));
|
|
|
4424
4774
|
writeSpinner.succeed("Configuration files updated");
|
|
4425
4775
|
logger.section("Updating Packages");
|
|
4426
4776
|
const fileManager = new FileManager();
|
|
4427
|
-
const sdlcUpdate = updatesAvailable.find((u) => u.name === "opencode-sdlc");
|
|
4777
|
+
const sdlcUpdate = updatesAvailable.find((u) => u.name === "opencode-sdlc-plugin");
|
|
4428
4778
|
if (sdlcUpdate) {
|
|
4429
|
-
const sdlcSpinner = ora5("Updating opencode-sdlc...").start();
|
|
4779
|
+
const sdlcSpinner = ora5("Updating opencode-sdlc-plugin...").start();
|
|
4430
4780
|
try {
|
|
4431
4781
|
const channel = detectReleaseChannel(VERSION);
|
|
4432
|
-
await fileManager.installDependencies([`opencode-sdlc@${channel}`]);
|
|
4433
|
-
sdlcSpinner.succeed(`opencode-sdlc updated to ${sdlcUpdate.latest}`);
|
|
4782
|
+
await fileManager.installDependencies([`opencode-sdlc-plugin@${channel}`]);
|
|
4783
|
+
sdlcSpinner.succeed(`opencode-sdlc-plugin updated to ${sdlcUpdate.latest}`);
|
|
4434
4784
|
} catch (err) {
|
|
4435
|
-
sdlcSpinner.fail("Failed to update opencode-sdlc");
|
|
4785
|
+
sdlcSpinner.fail("Failed to update opencode-sdlc-plugin");
|
|
4436
4786
|
logger.error(err instanceof Error ? err.message : String(err));
|
|
4437
4787
|
}
|
|
4438
4788
|
} else {
|
|
4439
4789
|
const generator = new ConfigGenerator(fullAnswers);
|
|
4440
4790
|
const packages = generator.getRequiredPackages();
|
|
4441
|
-
const sdlcPackage = packages.find((p) => p.startsWith("opencode-sdlc"));
|
|
4791
|
+
const sdlcPackage = packages.find((p) => p.startsWith("opencode-sdlc-plugin"));
|
|
4442
4792
|
if (sdlcPackage) {
|
|
4443
|
-
const sdlcSpinner = ora5("Installing opencode-sdlc...").start();
|
|
4793
|
+
const sdlcSpinner = ora5("Installing opencode-sdlc-plugin...").start();
|
|
4444
4794
|
try {
|
|
4445
4795
|
const channel = detectReleaseChannel(VERSION);
|
|
4446
|
-
await fileManager.installDependencies([`opencode-sdlc@${channel}`]);
|
|
4447
|
-
sdlcSpinner.succeed("opencode-sdlc installed");
|
|
4796
|
+
await fileManager.installDependencies([`opencode-sdlc-plugin@${channel}`]);
|
|
4797
|
+
sdlcSpinner.succeed("opencode-sdlc-plugin installed");
|
|
4448
4798
|
} catch (err) {
|
|
4449
|
-
sdlcSpinner.fail("Failed to install opencode-sdlc");
|
|
4799
|
+
sdlcSpinner.fail("Failed to install opencode-sdlc-plugin");
|
|
4450
4800
|
logger.error(err instanceof Error ? err.message : String(err));
|
|
4451
4801
|
}
|
|
4452
4802
|
}
|
|
4453
4803
|
}
|
|
4454
|
-
const pluginUpdates = updatesAvailable.filter((u) => u.name !== "opencode-sdlc");
|
|
4804
|
+
const pluginUpdates = updatesAvailable.filter((u) => u.name !== "opencode-sdlc-plugin");
|
|
4455
4805
|
if (pluginUpdates.length > 0) {
|
|
4456
4806
|
const pluginSpinner = ora5("Updating plugins...").start();
|
|
4457
4807
|
try {
|
|
@@ -4465,7 +4815,7 @@ Current version: ${existingVersion}`));
|
|
|
4465
4815
|
} else {
|
|
4466
4816
|
const generator = new ConfigGenerator(fullAnswers);
|
|
4467
4817
|
const allPackages = generator.getRequiredPackages();
|
|
4468
|
-
const pluginPackages = allPackages.filter((p) => !p.startsWith("opencode-sdlc"));
|
|
4818
|
+
const pluginPackages = allPackages.filter((p) => !p.startsWith("opencode-sdlc-plugin"));
|
|
4469
4819
|
if (pluginPackages.length > 0) {
|
|
4470
4820
|
const pluginSpinner = ora5(`Installing plugins: ${pluginPackages.join(", ")}...`).start();
|
|
4471
4821
|
try {
|