@@ -47252,6 +47252,7 @@ const dotenv = __importStar(__nccwpck_require__(2437));
4725247252const local_action_1 = __nccwpck_require__(7002);
4725347253const issue_repository_1 = __nccwpck_require__(57);
4725447254const constants_1 = __nccwpck_require__(8593);
47255+ const setup_files_1 = __nccwpck_require__(1666);
4725547256const logger_1 = __nccwpck_require__(8836);
4725647257const prompts_1 = __nccwpck_require__(5554);
4725747258const ai_1 = __nccwpck_require__(4470);
@@ -47650,12 +47651,27 @@ program
4765047651 process.exit(1);
4765147652 }
4765247653 (0, logger_1.logInfo)(`📦 Repository: ${gitInfo.owner}/${gitInfo.repo}`);
47654+ const token = (0, setup_files_1.getSetupToken)(cwd, options.token);
47655+ if (!token) {
47656+ (0, logger_1.logError)('🛑 Setup requires PERSONAL_ACCESS_TOKEN with a valid token.');
47657+ (0, logger_1.logInfo)(' You can:');
47658+ (0, logger_1.logInfo)(' • Pass it on the command line: copilot setup --token <your_github_token>');
47659+ (0, logger_1.logInfo)(' • Add it to your environment: export PERSONAL_ACCESS_TOKEN=your_github_token');
47660+ if ((0, setup_files_1.setupEnvFileExists)(cwd)) {
47661+ (0, logger_1.logInfo)(' • Or add PERSONAL_ACCESS_TOKEN=your_github_token to your existing .env file');
47662+ }
47663+ else {
47664+ (0, logger_1.logInfo)(' • Or create a .env file in this repo with: PERSONAL_ACCESS_TOKEN=your_github_token');
47665+ }
47666+ process.exit(1);
47667+ return;
47668+ }
4765347669 (0, logger_1.logInfo)('⚙️ Running initial setup (labels, issue types, access)...');
4765447670 const params = {
4765547671 [constants_1.INPUT_KEYS.DEBUG]: options.debug.toString(),
4765647672 [constants_1.INPUT_KEYS.SINGLE_ACTION]: constants_1.ACTIONS.INITIAL_SETUP,
4765747673 [constants_1.INPUT_KEYS.SINGLE_ACTION_ISSUE]: 1,
47658- [constants_1.INPUT_KEYS.TOKEN]: options. token || process.env.PERSONAL_ACCESS_TOKEN ,
47674+ [constants_1.INPUT_KEYS.TOKEN]: token,
4765947675 repo: {
4766047676 owner: gitInfo.owner,
4766147677 repo: gitInfo.repo,
@@ -48712,6 +48728,21 @@ class ProjectDetail {
4871248728 this.url = data[`url`] ?? '';
4871348729 this.number = data[`number`] ?? -1;
4871448730 }
48731+ /**
48732+ * Returns the full public URL to the project (board).
48733+ * Uses the URL from the API when present and valid; otherwise builds it from owner, type and number.
48734+ * Returns empty string when project number is invalid (e.g. missing from API).
48735+ */
48736+ get publicUrl() {
48737+ if (this.url && typeof this.url === 'string' && this.url.startsWith('https://')) {
48738+ return this.url;
48739+ }
48740+ if (typeof this.number !== 'number' || this.number <= 0) {
48741+ return '';
48742+ }
48743+ const path = this.type === 'organization' ? 'orgs' : 'users';
48744+ return `https://github.com/${path}/${this.owner}/projects/${this.number}`;
48745+ }
4871548746}
4871648747exports.ProjectDetail = ProjectDetail;
4871748748
@@ -51556,14 +51587,22 @@ class ProjectRepository {
5155651587 number: issueOrPullRequestNumber
5155751588 });
5155851589 if (!issueOrPrResult.repository.issueOrPullRequest) {
51559- console.error (`Issue or PR #${issueOrPullRequestNumber} not found.`);
51590+ (0, logger_1.logError) (`Issue or PR #${issueOrPullRequestNumber} not found in repository .`);
5156051591 return undefined;
5156151592 }
5156251593 const contentId = issueOrPrResult.repository.issueOrPullRequest.id;
5156351594 // Search for the item ID in the project with pagination
5156451595 let cursor = null;
5156551596 let projectItemId = undefined;
51597+ let totalItemsChecked = 0;
51598+ const maxPages = 100; // 100 * 100 = 10_000 items max to avoid runaway loops
51599+ let pageCount = 0;
5156651600 do {
51601+ if (pageCount >= maxPages) {
51602+ (0, logger_1.logError)(`Stopped after ${maxPages} pages (${totalItemsChecked} items). Issue or PR #${issueOrPullRequestNumber} not found in project.`);
51603+ break;
51604+ }
51605+ pageCount += 1;
5156751606 const projectQuery = `
5156851607 query($projectId: ID!, $cursor: String) {
5156951608 node(id: $projectId) {
@@ -51592,16 +51631,36 @@ class ProjectRepository {
5159251631 projectId: project.id,
5159351632 cursor
5159451633 });
51595- const items = projectResult.node.items.nodes;
51634+ if (projectResult.node === null) {
51635+ (0, logger_1.logError)(`Project not found for ID "${project.id}". Ensure the project is loaded via getProjectDetail (GraphQL node ID), not the project number.`);
51636+ throw new Error(`Project not found or invalid project ID. The project ID must be the GraphQL node ID from the API (e.g. PVT_...), not the project number.`);
51637+ }
51638+ const items = projectResult.node.items?.nodes ?? [];
51639+ totalItemsChecked += items.length;
51640+ const pageInfo = projectResult.node.items?.pageInfo;
5159651641 const foundItem = items.find((item) => item.content?.id === contentId);
5159751642 if (foundItem) {
5159851643 projectItemId = foundItem.id;
5159951644 break;
5160051645 }
51601- cursor = projectResult.node.items.pageInfo.hasNextPage
51602- ? projectResult.node.items.pageInfo.endCursor
51603- : null;
51646+ // Advance cursor only when there is a next page AND a non-null cursor (avoid missing pages)
51647+ const hasNextPage = pageInfo?.hasNextPage === true;
51648+ const endCursor = pageInfo?.endCursor ?? null;
51649+ if (hasNextPage && endCursor) {
51650+ cursor = endCursor;
51651+ }
51652+ else {
51653+ if (hasNextPage && !endCursor) {
51654+ (0, logger_1.logError)(`Project items pagination: hasNextPage is true but endCursor is null (page ${pageCount}, ${totalItemsChecked} items so far). Cannot fetch more.`);
51655+ }
51656+ cursor = null;
51657+ }
5160451658 } while (cursor);
51659+ if (projectItemId === undefined) {
51660+ (0, logger_1.logError)(`Issue or PR #${issueOrPullRequestNumber} not found in project after checking ${totalItemsChecked} items (${pageCount} page(s)). ` +
51661+ `Link it to the project first, or wait for the board to sync.`);
51662+ throw new Error(`Issue or pull request #${issueOrPullRequestNumber} is not in the project yet (checked ${totalItemsChecked} items). Link it to the project first, or wait for the board to sync.`);
51663+ }
5160551664 return projectItemId;
5160651665 };
5160751666 this.isContentLinked = async (project, contentId, token) => {
@@ -54003,6 +54062,18 @@ class InitialSetupUseCase {
5400354062 (0, setup_files_1.ensureGitHubDirs)(process.cwd());
5400454063 const filesResult = (0, setup_files_1.copySetupFiles)(process.cwd());
5400554064 steps.push(`✅ Setup files: ${filesResult.copied} copied, ${filesResult.skipped} already existed`);
54065+ if (!(0, setup_files_1.hasValidSetupToken)(process.cwd())) {
54066+ (0, logger_1.logInfo)(' 🛑 Setup requires PERSONAL_ACCESS_TOKEN (environment or .env) with a valid token.');
54067+ errors.push('PERSONAL_ACCESS_TOKEN must be set (environment or .env) with a valid token to run setup.');
54068+ results.push(new result_1.Result({
54069+ id: this.taskId,
54070+ success: false,
54071+ executed: true,
54072+ steps: steps,
54073+ errors: errors,
54074+ }));
54075+ return results;
54076+ }
5400654077 // 1. Verificar acceso a GitHub con Personal Access Token
5400754078 (0, logger_1.logInfo)('🔐 Checking GitHub access...');
5400854079 const githubAccessResult = await this.verifyGitHubAccess(param);
@@ -57923,7 +57994,7 @@ class CheckPriorityIssueSizeUseCase {
5792357994 success: true,
5792457995 executed: true,
5792557996 steps: [
57926- `Priority set to \`${priorityLabel}\` in [${project.title}](https://github.com/${param.owner}/${param.repo}/projects/${ project.id }).`,
57997+ `Priority set to \`${priorityLabel}\` in [${project.title}](${ project.publicUrl }).`,
5792757998 ],
5792857999 }));
5792958000 }
@@ -58364,7 +58435,7 @@ class MoveIssueToInProgressUseCase {
5836458435 success: true,
5836558436 executed: true,
5836658437 steps: [
58367- `Moved issue to \`${columnName}\` in [${project.title}](https://github.com/${param.owner}/${param.repo}/projects/${ project.id }).`,
58438+ `Moved issue to \`${columnName}\` in [${project.title}](${ project.publicUrl }).`,
5836858439 ],
5836958440 }));
5837058441 }
@@ -59116,7 +59187,7 @@ class CheckPriorityPullRequestSizeUseCase {
5911659187 success: true,
5911759188 executed: true,
5911859189 steps: [
59119- `Priority set to \`${priorityLabel}\` in [${project.title}](https://github.com/${param.owner}/${param.repo}/projects/${ project.id }).`,
59190+ `Priority set to \`${priorityLabel}\` in [${project.title}](${ project.publicUrl }).`,
5912059191 ],
5912159192 }));
5912259193 }
@@ -60342,6 +60413,10 @@ var __importStar = (this && this.__importStar) || (function () {
6034260413Object.defineProperty(exports, "__esModule", ({ value: true }));
6034360414exports.ensureGitHubDirs = ensureGitHubDirs;
6034460415exports.copySetupFiles = copySetupFiles;
60416+ exports.ensureEnvWithToken = ensureEnvWithToken;
60417+ exports.getSetupToken = getSetupToken;
60418+ exports.hasValidSetupToken = hasValidSetupToken;
60419+ exports.setupEnvFileExists = setupEnvFileExists;
6034560420const fs = __importStar(__nccwpck_require__(7147));
6034660421const path = __importStar(__nccwpck_require__(1017));
6034760422const logger_1 = __nccwpck_require__(8836);
@@ -60370,11 +60445,13 @@ function ensureGitHubDirs(cwd) {
6037060445 * Copy setup files from setup/ to repo (.github/ workflows, ISSUE_TEMPLATE, pull_request_template.md, .env at root).
6037160446 * Skips files that already exist at destination (no overwrite).
6037260447 * Logs each file copied or skipped. No-op if setup/ does not exist.
60373- * @param cwd - Repo root
60448+ * By default setup dir is the copilot package root (not cwd), so it works when running from another repo.
60449+ * @param cwd - Repo root (destination)
60450+ * @param setupDirOverride - Optional path to setup/ folder (for tests). If not set, uses package root.
6037460451 * @returns { copied, skipped }
6037560452 */
60376- function copySetupFiles(cwd) {
60377- const setupDir = path.join(cwd , 'setup');
60453+ function copySetupFiles(cwd, setupDirOverride ) {
60454+ const setupDir = setupDirOverride ?? path.join(__dirname, '..', '..' , 'setup');
6037860455 if (!fs.existsSync(setupDir))
6037960456 return { copied: 0, skipped: 0 };
6038060457 let copied = 0;
@@ -60430,20 +60507,80 @@ function copySetupFiles(cwd) {
6043060507 copied += 1;
6043160508 }
6043260509 }
60433- const envSrc = path.join(setupDir, '.env');
60434- const envDst = path.join(cwd, '.env');
60435- if (fs.existsSync(envSrc) && fs.statSync(envSrc).isFile()) {
60436- if (fs.existsSync(envDst)) {
60437- (0, logger_1.logInfo)(' ⏭️ .env already exists; skipping.');
60438- skipped += 1;
60510+ ensureEnvWithToken(cwd);
60511+ return { copied, skipped };
60512+ }
60513+ const ENV_TOKEN_KEY = 'PERSONAL_ACCESS_TOKEN';
60514+ const ENV_PLACEHOLDER_VALUE = 'github_pat_11..';
60515+ /** Minimum length for a token to be considered "defined" (not placeholder). */
60516+ const MIN_VALID_TOKEN_LENGTH = 20;
60517+ function getTokenFromEnvFile(envPath) {
60518+ if (!fs.existsSync(envPath) || !fs.statSync(envPath).isFile())
60519+ return null;
60520+ const content = fs.readFileSync(envPath, 'utf8');
60521+ const match = content.match(new RegExp(`^${ENV_TOKEN_KEY}=(.+)$`, 'm'));
60522+ if (!match)
60523+ return null;
60524+ const value = match[1].trim().replace(/^["']|["']$/g, '');
60525+ return value.length > 0 ? value : null;
60526+ }
60527+ /**
60528+ * Logs the current state of PERSONAL_ACCESS_TOKEN (environment or .env). Does not create .env.
60529+ */
60530+ function ensureEnvWithToken(cwd) {
60531+ const envPath = path.join(cwd, '.env');
60532+ const tokenInEnv = process.env[ENV_TOKEN_KEY]?.trim();
60533+ if (tokenInEnv) {
60534+ (0, logger_1.logInfo)(' 🔑 PERSONAL_ACCESS_TOKEN is set in environment; .env not needed.');
60535+ return;
60536+ }
60537+ if (fs.existsSync(envPath)) {
60538+ const tokenInFile = getTokenFromEnvFile(envPath);
60539+ if (tokenInFile) {
60540+ (0, logger_1.logInfo)(' ✅ .env exists and contains PERSONAL_ACCESS_TOKEN.');
6043960541 }
6044060542 else {
60441- fs.copyFileSync(envSrc, envDst);
60442- (0, logger_1.logInfo)(' ✅ Copied setup/.env → .env');
60443- copied += 1;
60543+ (0, logger_1.logInfo)(' ⚠️ .env exists but PERSONAL_ACCESS_TOKEN is missing or empty.');
6044460544 }
60545+ return;
6044560546 }
60446- return { copied, skipped };
60547+ (0, logger_1.logInfo)(' 💡 You can create a .env file here with PERSONAL_ACCESS_TOKEN=your_token or set it in your environment.');
60548+ }
60549+ function isTokenValueValid(token) {
60550+ const t = token.trim();
60551+ return t.length >= MIN_VALID_TOKEN_LENGTH && t !== ENV_PLACEHOLDER_VALUE;
60552+ }
60553+ /**
60554+ * Resolves the PERSONAL_ACCESS_TOKEN for setup from a single priority order:
60555+ * 1. override (e.g. CLI --token) if provided and valid,
60556+ * 2. process.env.PERSONAL_ACCESS_TOKEN,
60557+ * 3. .env file in cwd.
60558+ * Returns undefined if no valid token is found.
60559+ */
60560+ function getSetupToken(cwd, override) {
60561+ const overrideTrimmed = override?.trim();
60562+ if (overrideTrimmed && isTokenValueValid(overrideTrimmed))
60563+ return overrideTrimmed;
60564+ const fromEnv = process.env[ENV_TOKEN_KEY]?.trim();
60565+ if (fromEnv && isTokenValueValid(fromEnv))
60566+ return fromEnv;
60567+ const envPath = path.join(cwd, '.env');
60568+ const fromFile = getTokenFromEnvFile(envPath);
60569+ if (fromFile !== null && isTokenValueValid(fromFile))
60570+ return fromFile;
60571+ return undefined;
60572+ }
60573+ /**
60574+ * Returns true if a valid setup token is available (same resolution order as getSetupToken).
60575+ * Pass an optional override (e.g. CLI --token) so validation considers all sources consistently.
60576+ */
60577+ function hasValidSetupToken(cwd, override) {
60578+ return getSetupToken(cwd, override) !== undefined;
60579+ }
60580+ /** Returns true if a .env file exists in the given directory. */
60581+ function setupEnvFileExists(cwd) {
60582+ const envPath = path.join(cwd, '.env');
60583+ return fs.existsSync(envPath) && fs.statSync(envPath).isFile();
6044760584}
6044860585
6044960586
@@ -60650,8 +60787,14 @@ exports.getActionInputsWithDefaults = getActionInputsWithDefaults;
6065060787const fs = __importStar(__nccwpck_require__(7147));
6065160788const path = __importStar(__nccwpck_require__(1017));
6065260789const yaml = __importStar(__nccwpck_require__(1917));
60790+ /**
60791+ * Resolves action.yml from the copilot package root, not cwd.
60792+ * When run as CLI from another repo, cwd is that repo; action.yml lives next to the bundle.
60793+ * - From source: __dirname is src/utils → ../../action.yml = repo root.
60794+ * - From bundle (build/cli): __dirname is bundle dir → ../../action.yml = package root.
60795+ */
6065360796function loadActionYaml() {
60654- const actionYamlPath = path.join(process.cwd() , 'action.yml');
60797+ const actionYamlPath = path.join(__dirname, '..', '..' , 'action.yml');
6065560798 const yamlContent = fs.readFileSync(actionYamlPath, 'utf8');
6065660799 return yaml.load(yamlContent);
6065760800}
0 commit comments