Compare commits
	
		
			4 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 366c4a0bc2 | |||
| 0d3b10bd00 | |||
| a41e3d5d2c | |||
| c45cff89de | 
							
								
								
									
										15
									
								
								changelog.md
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								changelog.md
									
									
									
									
									
								
							| @@ -1,5 +1,20 @@ | ||||
| # Changelog | ||||
|  | ||||
| ## 2025-10-23 - 1.19.0 - feat(mod_commit) | ||||
| Add CLI UI helpers and improve commit workflow with progress, recommendations and summary | ||||
|  | ||||
| - Introduce ts/mod_commit/mod.ui.ts: reusable CLI UI helpers (pretty headers, sections, AI recommendation box, step printer, commit summary and helpers for consistent messaging). | ||||
| - Refactor ts/mod_commit/index.ts: use new UI functions to display AI recommendations, show step-by-step progress for baking commit info, generating changelog, staging, committing, bumping version and optional push; include commit SHA in final summary. | ||||
| - Enhance ts/mod_commit/mod.helpers.ts: bumpProjectVersion now accepts currentStep/totalSteps to report progress and returns a consistent newVersion after handling npm/deno/both cases. | ||||
| - Add .claude/settings.local.json: local permissions configuration for development tooling. | ||||
|  | ||||
| ## 2025-10-23 - 1.18.9 - fix(mod_commit) | ||||
| Stage and commit deno.json when bumping/syncing versions and create/update git tags | ||||
|  | ||||
| - bumpDenoVersion now creates a Smartshell instance and runs git add deno.json, git commit -m "v<newVersion>", and git tag v<newVersion> to persist the version bump | ||||
| - syncVersionToDenoJson now stages deno.json, amends the npm version commit with --no-edit, and recreates the tag with -fa to keep package.json and deno.json in sync | ||||
| - Added informative logger messages after creating commits and tags | ||||
|  | ||||
| ## 2025-10-23 - 1.18.8 - fix(mod_commit) | ||||
| Improve commit workflow: detect project type and current branch; add robust version bump helpers for npm/deno | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| { | ||||
|   "name": "@git.zone/cli", | ||||
|   "private": false, | ||||
|   "version": "1.18.8", | ||||
|   "version": "1.19.0", | ||||
|   "description": "A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.", | ||||
|   "main": "dist_ts/index.ts", | ||||
|   "typings": "dist_ts/index.d.ts", | ||||
|   | ||||
| @@ -3,6 +3,6 @@ | ||||
|  */ | ||||
| export const commitinfo = { | ||||
|   name: '@git.zone/cli', | ||||
|   version: '1.18.8', | ||||
|   version: '1.19.0', | ||||
|   description: 'A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.' | ||||
| } | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import * as plugins from './mod.plugins.js'; | ||||
| import * as paths from '../paths.js'; | ||||
| import { logger } from '../gitzone.logging.js'; | ||||
| import * as helpers from './mod.helpers.js'; | ||||
| import * as ui from './mod.ui.js'; | ||||
|  | ||||
| export const run = async (argvArg: any) => { | ||||
|   if (argvArg.format) { | ||||
| @@ -11,7 +12,8 @@ export const run = async (argvArg: any) => { | ||||
|     await formatMod.run(); | ||||
|   } | ||||
|  | ||||
|   logger.log('info', `gathering facts...`); | ||||
|   ui.printHeader('🔍 Analyzing repository changes...'); | ||||
|  | ||||
|   const aidoc = new plugins.tsdoc.AiDoc(); | ||||
|   await aidoc.start(); | ||||
|  | ||||
| @@ -19,16 +21,12 @@ export const run = async (argvArg: any) => { | ||||
|  | ||||
|   await aidoc.stop(); | ||||
|  | ||||
|   logger.log( | ||||
|     'info', | ||||
|     `--------- | ||||
|     Next recommended commit would be: | ||||
|     =========== | ||||
|     -> ${nextCommitObject.recommendedNextVersion}: | ||||
|     -> ${nextCommitObject.recommendedNextVersionLevel}(${nextCommitObject.recommendedNextVersionScope}): ${nextCommitObject.recommendedNextVersionMessage} | ||||
|     =========== | ||||
|   `, | ||||
|   ); | ||||
|   ui.printRecommendation({ | ||||
|     recommendedNextVersion: nextCommitObject.recommendedNextVersion, | ||||
|     recommendedNextVersionLevel: nextCommitObject.recommendedNextVersionLevel, | ||||
|     recommendedNextVersionScope: nextCommitObject.recommendedNextVersionScope, | ||||
|     recommendedNextVersionMessage: nextCommitObject.recommendedNextVersionMessage, | ||||
|   }); | ||||
|   const commitInteract = new plugins.smartinteract.SmartInteract(); | ||||
|   commitInteract.addQuestions([ | ||||
|     { | ||||
| @@ -70,20 +68,30 @@ export const run = async (argvArg: any) => { | ||||
|     } | ||||
|   })(); | ||||
|  | ||||
|   logger.log('info', `OK! Creating commit with message '${commitString}'`); | ||||
|   ui.printHeader('✨ Creating Semantic Commit'); | ||||
|   ui.printCommitMessage(commitString); | ||||
|   const smartshellInstance = new plugins.smartshell.Smartshell({ | ||||
|     executor: 'bash', | ||||
|     sourceFilePaths: [], | ||||
|   }); | ||||
|  | ||||
|   logger.log('info', `Baking commitinfo into code ...`); | ||||
|   // Determine total steps (6 if pushing, 5 if not) | ||||
|   const totalSteps = answerBucket.getAnswerFor('pushToOrigin') && !(process.env.CI === 'true') ? 6 : 5; | ||||
|   let currentStep = 0; | ||||
|  | ||||
|   // Step 1: Baking commitinfo | ||||
|   currentStep++; | ||||
|   ui.printStep(currentStep, totalSteps, '🔧 Baking commit info into code', 'in-progress'); | ||||
|   const commitInfo = new plugins.commitinfo.CommitInfo( | ||||
|     paths.cwd, | ||||
|     commitVersionType, | ||||
|   ); | ||||
|   await commitInfo.writeIntoPotentialDirs(); | ||||
|   ui.printStep(currentStep, totalSteps, '🔧 Baking commit info into code', 'done'); | ||||
|  | ||||
|   logger.log('info', `Writing changelog.md ...`); | ||||
|   // Step 2: Writing changelog | ||||
|   currentStep++; | ||||
|   ui.printStep(currentStep, totalSteps, '📄 Generating changelog.md', 'in-progress'); | ||||
|   let changelog = nextCommitObject.changelog; | ||||
|   changelog = changelog.replaceAll( | ||||
|     '{{nextVersion}}', | ||||
| @@ -110,23 +118,54 @@ export const run = async (argvArg: any) => { | ||||
|     changelog, | ||||
|     plugins.path.join(paths.cwd, `changelog.md`), | ||||
|   ); | ||||
|   ui.printStep(currentStep, totalSteps, '📄 Generating changelog.md', 'done'); | ||||
|  | ||||
|   logger.log('info', `Staging files for commit:`); | ||||
|   // Step 3: Staging files | ||||
|   currentStep++; | ||||
|   ui.printStep(currentStep, totalSteps, '📦 Staging files', 'in-progress'); | ||||
|   await smartshellInstance.exec(`git add -A`); | ||||
|   ui.printStep(currentStep, totalSteps, '📦 Staging files', 'done'); | ||||
|  | ||||
|   // Step 4: Creating commit | ||||
|   currentStep++; | ||||
|   ui.printStep(currentStep, totalSteps, '💾 Creating git commit', 'in-progress'); | ||||
|   await smartshellInstance.exec(`git commit -m "${commitString}"`); | ||||
|   ui.printStep(currentStep, totalSteps, '💾 Creating git commit', 'done'); | ||||
|  | ||||
|   // Detect project type and bump version accordingly | ||||
|   // Step 5: Bumping version | ||||
|   currentStep++; | ||||
|   const projectType = await helpers.detectProjectType(); | ||||
|   await helpers.bumpProjectVersion(projectType, commitVersionType); | ||||
|   const newVersion = await helpers.bumpProjectVersion(projectType, commitVersionType, currentStep, totalSteps); | ||||
|  | ||||
|   // Step 6: Push to remote (optional) | ||||
|   const currentBranch = await helpers.detectCurrentBranch(); | ||||
|   if ( | ||||
|     answerBucket.getAnswerFor('pushToOrigin') && | ||||
|     !(process.env.CI === 'true') | ||||
|   ) { | ||||
|     // Detect current branch instead of hardcoding "master" | ||||
|     const currentBranch = await helpers.detectCurrentBranch(); | ||||
|     currentStep++; | ||||
|     ui.printStep(currentStep, totalSteps, `🚀 Pushing to origin/${currentBranch}`, 'in-progress'); | ||||
|     await smartshellInstance.exec(`git push origin ${currentBranch} --follow-tags`); | ||||
|     ui.printStep(currentStep, totalSteps, `🚀 Pushing to origin/${currentBranch}`, 'done'); | ||||
|   } | ||||
|  | ||||
|   console.log(''); // Add spacing before summary | ||||
|  | ||||
|   // Get commit SHA for summary | ||||
|   const commitShaResult = await smartshellInstance.exec('git rev-parse --short HEAD'); | ||||
|   const commitSha = commitShaResult.stdout.trim(); | ||||
|  | ||||
|   // Print final summary | ||||
|   ui.printSummary({ | ||||
|     projectType, | ||||
|     branch: currentBranch, | ||||
|     commitType: answerBucket.getAnswerFor('commitType'), | ||||
|     commitScope: answerBucket.getAnswerFor('commitScope'), | ||||
|     commitMessage: answerBucket.getAnswerFor('commitDescription'), | ||||
|     newVersion: newVersion, | ||||
|     commitSha: commitSha, | ||||
|     pushed: answerBucket.getAnswerFor('pushToOrigin') && !(process.env.CI === 'true'), | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| const createCommitStringFromAnswerBucket = ( | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import * as plugins from './mod.plugins.js'; | ||||
| import * as paths from '../paths.js'; | ||||
| import { logger } from '../gitzone.logging.js'; | ||||
| import * as ui from './mod.ui.js'; | ||||
|  | ||||
| export type ProjectType = 'npm' | 'deno' | 'both' | 'none'; | ||||
| export type VersionType = 'patch' | 'minor' | 'major'; | ||||
| @@ -90,12 +91,16 @@ function calculateNewVersion(currentVersion: string, versionType: VersionType): | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Bumps the version in deno.json | ||||
|  * Bumps the version in deno.json, commits the change, and creates a tag | ||||
|  * @param versionType Type of version bump | ||||
|  * @returns The new version string | ||||
|  */ | ||||
| export async function bumpDenoVersion(versionType: VersionType): Promise<string> { | ||||
|   const denoJsonPath = plugins.path.join(paths.cwd, 'deno.json'); | ||||
|   const smartshellInstance = new plugins.smartshell.Smartshell({ | ||||
|     executor: 'bash', | ||||
|     sourceFilePaths: [], | ||||
|   }); | ||||
|  | ||||
|   try { | ||||
|     // Read deno.json | ||||
| @@ -121,6 +126,17 @@ export async function bumpDenoVersion(versionType: VersionType): Promise<string> | ||||
|       denoJsonPath | ||||
|     ); | ||||
|  | ||||
|     // Stage the deno.json file | ||||
|     await smartshellInstance.exec('git add deno.json'); | ||||
|  | ||||
|     // Commit the version bump | ||||
|     await smartshellInstance.exec(`git commit -m "v${newVersion}"`); | ||||
|  | ||||
|     // Create the version tag | ||||
|     await smartshellInstance.exec(`git tag v${newVersion} -m "v${newVersion}"`); | ||||
|  | ||||
|     logger.log('info', `Created commit and tag v${newVersion}`); | ||||
|  | ||||
|     return newVersion; | ||||
|   } catch (error) { | ||||
|     throw new Error(`Failed to bump deno.json version: ${error.message}`); | ||||
| @@ -147,11 +163,15 @@ async function bumpNpmVersion(versionType: VersionType): Promise<string> { | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Syncs the version from package.json to deno.json | ||||
|  * Syncs the version from package.json to deno.json and amends the npm commit | ||||
|  * @param version The version to sync | ||||
|  */ | ||||
| async function syncVersionToDenoJson(version: string): Promise<void> { | ||||
|   const denoJsonPath = plugins.path.join(paths.cwd, 'deno.json'); | ||||
|   const smartshellInstance = new plugins.smartshell.Smartshell({ | ||||
|     executor: 'bash', | ||||
|     sourceFilePaths: [], | ||||
|   }); | ||||
|  | ||||
|   try { | ||||
|     const denoConfig = plugins.smartfile.fs.toObjectSync( | ||||
| @@ -165,6 +185,17 @@ async function syncVersionToDenoJson(version: string): Promise<void> { | ||||
|       JSON.stringify(denoConfig, null, 2) + '\n', | ||||
|       denoJsonPath | ||||
|     ); | ||||
|  | ||||
|     // Stage the deno.json file | ||||
|     await smartshellInstance.exec('git add deno.json'); | ||||
|  | ||||
|     // Amend the npm version commit to include deno.json | ||||
|     await smartshellInstance.exec('git commit --amend --no-edit'); | ||||
|  | ||||
|     // Re-create the tag with force to update it | ||||
|     await smartshellInstance.exec(`git tag -fa v${version} -m "v${version}"`); | ||||
|  | ||||
|     logger.log('info', `Amended commit to include deno.json and updated tag v${version}`); | ||||
|   } catch (error) { | ||||
|     throw new Error(`Failed to sync version to deno.json: ${error.message}`); | ||||
|   } | ||||
| @@ -174,25 +205,40 @@ async function syncVersionToDenoJson(version: string): Promise<void> { | ||||
|  * Bumps the project version based on project type | ||||
|  * @param projectType The detected project type | ||||
|  * @param versionType The type of version bump | ||||
|  * @param currentStep The current step number for progress display | ||||
|  * @param totalSteps The total number of steps for progress display | ||||
|  * @returns The new version string | ||||
|  */ | ||||
| export async function bumpProjectVersion( | ||||
|   projectType: ProjectType, | ||||
|   versionType: VersionType | ||||
|   versionType: VersionType, | ||||
|   currentStep?: number, | ||||
|   totalSteps?: number | ||||
| ): Promise<string> { | ||||
|   const projectEmoji = projectType === 'npm' ? '📦' : projectType === 'deno' ? '🦕' : '🔀'; | ||||
|   const description = `🏷️  Bumping version (${projectEmoji} ${projectType})`; | ||||
|  | ||||
|   if (currentStep && totalSteps) { | ||||
|     ui.printStep(currentStep, totalSteps, description, 'in-progress'); | ||||
|   } | ||||
|  | ||||
|   let newVersion: string; | ||||
|  | ||||
|   switch (projectType) { | ||||
|     case 'npm': | ||||
|       return await bumpNpmVersion(versionType); | ||||
|       newVersion = await bumpNpmVersion(versionType); | ||||
|       break; | ||||
|  | ||||
|     case 'deno': | ||||
|       return await bumpDenoVersion(versionType); | ||||
|       newVersion = await bumpDenoVersion(versionType); | ||||
|       break; | ||||
|  | ||||
|     case 'both': { | ||||
|       // Bump npm version first (it handles git tags) | ||||
|       const newVersion = await bumpNpmVersion(versionType); | ||||
|       newVersion = await bumpNpmVersion(versionType); | ||||
|       // Then sync to deno.json | ||||
|       await syncVersionToDenoJson(newVersion); | ||||
|       return newVersion; | ||||
|       break; | ||||
|     } | ||||
|  | ||||
|     case 'none': | ||||
| @@ -201,4 +247,10 @@ export async function bumpProjectVersion( | ||||
|     default: | ||||
|       throw new Error(`Unknown project type: ${projectType}`); | ||||
|   } | ||||
|  | ||||
|   if (currentStep && totalSteps) { | ||||
|     ui.printStep(currentStep, totalSteps, description, 'done'); | ||||
|   } | ||||
|  | ||||
|   return newVersion; | ||||
| } | ||||
|   | ||||
							
								
								
									
										196
									
								
								ts/mod_commit/mod.ui.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										196
									
								
								ts/mod_commit/mod.ui.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,196 @@ | ||||
| import { logger } from '../gitzone.logging.js'; | ||||
|  | ||||
| /** | ||||
|  * UI helper module for beautiful CLI output | ||||
|  */ | ||||
|  | ||||
| interface ICommitSummary { | ||||
|   projectType: string; | ||||
|   branch: string; | ||||
|   commitType: string; | ||||
|   commitScope: string; | ||||
|   commitMessage: string; | ||||
|   newVersion: string; | ||||
|   commitSha?: string; | ||||
|   pushed: boolean; | ||||
|   repoUrl?: string; | ||||
| } | ||||
|  | ||||
| interface IRecommendation { | ||||
|   recommendedNextVersion: string; | ||||
|   recommendedNextVersionLevel: string; | ||||
|   recommendedNextVersionScope: string; | ||||
|   recommendedNextVersionMessage: string; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Print a header with a box around it | ||||
|  */ | ||||
| export function printHeader(title: string): void { | ||||
|   const width = 57; | ||||
|   const padding = Math.max(0, width - title.length - 2); | ||||
|   const leftPad = Math.floor(padding / 2); | ||||
|   const rightPad = padding - leftPad; | ||||
|  | ||||
|   console.log(''); | ||||
|   console.log('╭─' + '─'.repeat(width) + '─╮'); | ||||
|   console.log('│  ' + title + ' '.repeat(rightPad + leftPad) + '  │'); | ||||
|   console.log('╰─' + '─'.repeat(width) + '─╯'); | ||||
|   console.log(''); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Print a section with a border | ||||
|  */ | ||||
| export function printSection(title: string, lines: string[]): void { | ||||
|   const width = 59; | ||||
|  | ||||
|   console.log('┌─ ' + title + ' ' + '─'.repeat(Math.max(0, width - title.length - 3)) + '┐'); | ||||
|   console.log('│' + ' '.repeat(width) + '│'); | ||||
|  | ||||
|   for (const line of lines) { | ||||
|     const padding = width - line.length; | ||||
|     console.log('│  ' + line + ' '.repeat(Math.max(0, padding - 2)) + '│'); | ||||
|   } | ||||
|  | ||||
|   console.log('│' + ' '.repeat(width) + '│'); | ||||
|   console.log('└─' + '─'.repeat(width) + '─┘'); | ||||
|   console.log(''); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Print AI recommendations in a nice box | ||||
|  */ | ||||
| export function printRecommendation(recommendation: IRecommendation): void { | ||||
|   const lines = [ | ||||
|     `Suggested Version:  v${recommendation.recommendedNextVersion}`, | ||||
|     `Suggested Type:     ${recommendation.recommendedNextVersionLevel}`, | ||||
|     `Suggested Scope:    ${recommendation.recommendedNextVersionScope}`, | ||||
|     `Suggested Message:  ${recommendation.recommendedNextVersionMessage}`, | ||||
|   ]; | ||||
|  | ||||
|   printSection('📊 AI Recommendations', lines); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Print a progress step | ||||
|  */ | ||||
| export function printStep( | ||||
|   current: number, | ||||
|   total: number, | ||||
|   description: string, | ||||
|   status: 'in-progress' | 'done' | 'error' | ||||
| ): void { | ||||
|   const statusIcon = status === 'done' ? '✓' : status === 'error' ? '✗' : '⏳'; | ||||
|   const dots = '.'.repeat(Math.max(0, 40 - description.length)); | ||||
|  | ||||
|   console.log(`  [${current}/${total}] ${description}${dots} ${statusIcon}`); | ||||
|  | ||||
|   // Clear the line on next update if in progress | ||||
|   if (status === 'in-progress') { | ||||
|     process.stdout.write('\x1b[1A'); // Move cursor up one line | ||||
|   } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Get emoji for project type | ||||
|  */ | ||||
| function getProjectTypeEmoji(projectType: string): string { | ||||
|   switch (projectType) { | ||||
|     case 'npm': | ||||
|       return '📦 npm'; | ||||
|     case 'deno': | ||||
|       return '🦕 Deno'; | ||||
|     case 'both': | ||||
|       return '🔀 npm + Deno'; | ||||
|     default: | ||||
|       return '❓ Unknown'; | ||||
|   } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Get emoji for commit type | ||||
|  */ | ||||
| function getCommitTypeEmoji(commitType: string): string { | ||||
|   switch (commitType) { | ||||
|     case 'fix': | ||||
|       return '🔧 fix'; | ||||
|     case 'feat': | ||||
|       return '✨ feat'; | ||||
|     case 'BREAKING CHANGE': | ||||
|       return '💥 BREAKING CHANGE'; | ||||
|     default: | ||||
|       return commitType; | ||||
|   } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Print final commit summary | ||||
|  */ | ||||
| export function printSummary(summary: ICommitSummary): void { | ||||
|   const lines = [ | ||||
|     `Project Type:   ${getProjectTypeEmoji(summary.projectType)}`, | ||||
|     `Branch:         🌿 ${summary.branch}`, | ||||
|     `Commit Type:    ${getCommitTypeEmoji(summary.commitType)}`, | ||||
|     `Scope:          📍 ${summary.commitScope}`, | ||||
|     `New Version:    🏷️  v${summary.newVersion}`, | ||||
|   ]; | ||||
|  | ||||
|   if (summary.commitSha) { | ||||
|     lines.push(`Commit SHA:     📌 ${summary.commitSha}`); | ||||
|   } | ||||
|  | ||||
|   if (summary.pushed) { | ||||
|     lines.push(`Remote:         ✓ Pushed successfully`); | ||||
|   } else { | ||||
|     lines.push(`Remote:         ⊘ Not pushed (local only)`); | ||||
|   } | ||||
|  | ||||
|   if (summary.repoUrl && summary.commitSha) { | ||||
|     lines.push(''); | ||||
|     lines.push(`View at: ${summary.repoUrl}/commit/${summary.commitSha}`); | ||||
|   } | ||||
|  | ||||
|   printSection('✅ Commit Summary', lines); | ||||
|  | ||||
|   if (summary.pushed) { | ||||
|     console.log('🎉 All done! Your changes are committed and pushed.\n'); | ||||
|   } else { | ||||
|     console.log('✓ Commit created successfully.\n'); | ||||
|   } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Print an info message with consistent formatting | ||||
|  */ | ||||
| export function printInfo(message: string): void { | ||||
|   console.log(`  ℹ️  ${message}`); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Print a success message | ||||
|  */ | ||||
| export function printSuccess(message: string): void { | ||||
|   console.log(`  ✓ ${message}`); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Print a warning message | ||||
|  */ | ||||
| export function printWarning(message: string): void { | ||||
|   logger.log('warn', `⚠️  ${message}`); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Print an error message | ||||
|  */ | ||||
| export function printError(message: string): void { | ||||
|   logger.log('error', `✗ ${message}`); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Print commit message being created | ||||
|  */ | ||||
| export function printCommitMessage(commitString: string): void { | ||||
|   console.log(`\n  📝 Commit: ${commitString}\n`); | ||||
| } | ||||
		Reference in New Issue
	
	Block a user