UNPKG

md-linear-sync

Version:

Sync Linear tickets to local markdown files with status-based folder organization

234 lines โ€ข 9.26 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.setupSlackCommand = setupSlackCommand; const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const child_process_1 = require("child_process"); const util_1 = require("util"); const clipboardy_1 = __importDefault(require("clipboardy")); const execAsync = (0, util_1.promisify)(child_process_1.exec); async function setupSlackCommand() { console.log('๐Ÿ”ง Setting up Slack notifications\n'); await setupSlackApp(); } async function setupSlackApp() { console.log('This is the md-linear-sync Slack notification system setup wizard\n'); console.log('Step 1: We need to create a Slack app to process the notifications\n'); console.log('After pressing Enter:'); console.log('โ€ข Manifest will be copied to clipboard'); console.log('โ€ข Slack page will open'); console.log('โ€ข Select "From an app manifest"'); console.log('โ€ข Choose your workspace'); console.log('โ€ข Paste the manifest and create the app'); console.log('โ€ข Install the app to your workspace\n'); console.log('Press Enter to begin...'); await getInput(''); // Read the manifest file const manifestPath = path_1.default.join(__dirname, '../../slack-app-manifest.json'); const manifest = JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf-8')); // Copy manifest to clipboard try { await clipboardy_1.default.write(JSON.stringify(manifest, null, 2)); console.log('๐Ÿ“‹ Manifest copied to clipboard!'); } catch (error) { console.log('โš ๏ธ Could not copy to clipboard, here\'s the manifest:\n'); console.log(JSON.stringify(manifest, null, 2)); console.log('\n๐Ÿ“‹ Copy the above JSON'); } // Open Slack apps page (non-blocking) openURL('https://api.slack.com/apps?new_app=1').catch(() => { }); console.log('๐ŸŒ Opening Slack app creation page...'); console.log('๐Ÿ”— Go here if page didn\'t open: https://api.slack.com/apps?new_app=1'); console.log('\nInstructions:'); console.log('1. Select "From an app manifest"'); console.log('2. Choose your workspace'); console.log('3. Paste the manifest (Cmd+V / Ctrl+V)'); console.log('4. Create the app'); console.log('5. Install the app to your workspace'); console.log('\nPress Enter once Slack app is created...'); await getInput(''); console.log('๐Ÿ”‘ Now we need the Bot User OAuth Token'); console.log(' Go to "OAuth & Permissions" โ†’ Install to Workspace โ†’ Copy the token starting with "xoxb-"'); const botToken = await getInput('Bot User OAuth Token: '); if (!botToken.startsWith('xoxb-')) { console.log('โŒ That doesn\'t look like a valid bot token'); console.log(' It should start with: xoxb-'); return; } // Update .env file await updateEnvFile('SLACK_BOT_TOKEN', botToken); // Auto-create notification channel console.log('\n๐Ÿ“บ Creating notification channel...'); const channelName = 'md-linear-sync-notifications'; try { const channelId = await createSlackChannel(botToken, channelName); console.log(`โœ… Created #${channelName} channel`); // Invite bot to the channel if (channelId) { await inviteBotToChannel(botToken, channelId); console.log(`๐Ÿค– Bot invited to #${channelName}`); } // Get team info for URL const teamInfo = await getTeamInfo(botToken); if (teamInfo && channelId) { const channelUrl = `https://${teamInfo.domain}.slack.com/channels/${channelId}`; console.log(`๐Ÿ”— Join the channel: ${channelUrl}`); } } catch (error) { console.log(`โš ๏ธ Could not create channel: ${error instanceof Error ? error.message : 'Unknown error'}`); console.log(' You can manually invite the bot to any channel you want to use'); } // Test the bot await testSlackBot(botToken, channelName); console.log('\n๐ŸŽ‰ Slack setup complete!'); console.log(`๐Ÿ’ก Notifications will be sent to #${channelName}`); // Ensure process exits cleanly process.exit(0); } async function updateEnvFile(key, value) { const envPath = path_1.default.join(process.cwd(), '.env'); let envContent = ''; // Read existing .env file if it exists if (fs_1.default.existsSync(envPath)) { envContent = fs_1.default.readFileSync(envPath, 'utf-8'); } // Check if key already exists const lines = envContent.split('\n'); const keyIndex = lines.findIndex(line => line.startsWith(`${key}=`)); if (keyIndex >= 0) { // Update existing key lines[keyIndex] = `${key}=${value}`; } else { // Add new key lines.push(`${key}=${value}`); } // Write back to file fs_1.default.writeFileSync(envPath, lines.filter(line => line.trim()).join('\n') + '\n'); console.log(`โœ… Updated .env file with ${key}`); } async function getTeamInfo(botToken) { try { const response = await fetch('https://slack.com/api/team.info', { headers: { 'Authorization': `Bearer ${botToken}` } }); const result = await response.json(); return result.ok ? { domain: result.team.domain } : null; } catch (error) { return null; } } async function inviteBotToChannel(botToken, channelId) { const response = await fetch('https://slack.com/api/conversations.join', { method: 'POST', headers: { 'Authorization': `Bearer ${botToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ channel: channelId }) }); const result = await response.json(); if (!result.ok && result.error !== 'already_in_channel') { throw new Error(result.error); } } async function createSlackChannel(botToken, channelName) { const response = await fetch('https://slack.com/api/conversations.create', { method: 'POST', headers: { 'Authorization': `Bearer ${botToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: channelName, is_private: false }) }); const result = await response.json(); if (!result.ok) { if (result.error === 'name_taken') { console.log(`โœ… Channel #${channelName} already exists`); // Get existing channel info const infoResponse = await fetch(`https://slack.com/api/conversations.list?types=public_channel&limit=200`, { headers: { 'Authorization': `Bearer ${botToken}` } }); const infoResult = await infoResponse.json(); const existingChannel = infoResult.channels?.find((ch) => ch.name === channelName); return existingChannel?.id || ''; } throw new Error(result.error); } return result.channel.id; } async function testSlackBot(botToken, channel) { console.log('\n๐Ÿงช Testing bot token...'); try { const response = await fetch('https://slack.com/api/chat.postMessage', { method: 'POST', headers: { 'Authorization': `Bearer ${botToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ channel: channel.replace('#', ''), text: '๐ŸŽ‰ Linear Markdown Sync bot is now connected!' }) }); const result = await response.json(); if (result.ok) { console.log('โœ… Test message sent successfully!'); console.log('๐Ÿ“ฑ Check your Slack channel for the test message'); } else { console.log('โŒ Test failed:', result.error); if (result.error === 'channel_not_found') { console.log('๐Ÿ’ก Make sure the bot is invited to the channel'); } } } catch (error) { console.log('โŒ Test failed:', error instanceof Error ? error.message : 'Unknown error'); } } // Utility functions async function getUserChoice(maxChoice) { while (true) { const choice = await getInput(`Enter your choice (1-${maxChoice}): `); const num = parseInt(choice.trim()); if (num >= 1 && num <= maxChoice) { return num; } console.log(`Please enter a number between 1 and ${maxChoice}`); } } async function getInput(prompt) { process.stdout.write(prompt); return new Promise((resolve) => { process.stdin.once('data', (data) => { resolve(data.toString().trim()); }); }); } async function openURL(url) { const platform = process.platform; let command = ''; if (platform === 'darwin') { command = `open "${url}"`; } else if (platform === 'win32') { command = `start "${url}"`; } else { command = `xdg-open "${url}"`; } await execAsync(command); } //# sourceMappingURL=slack-setup.js.map