-
Notifications
You must be signed in to change notification settings - Fork 0
TypeScript Rewrite: Info command #36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
d4mation
wants to merge
18
commits into
ENG-219/command-help
Choose a base branch
from
ENG-219/command-info
base: ENG-219/command-help
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 9 commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
7c2931f
ENG-219: Add info command
d4mation fe9f9e5
Merge branch 'ENG-219/app-bootstrap' into ENG-219/command-info
d4mation c90fc5b
Merge branch 'ENG-219/app-bootstrap' into ENG-219/command-info
d4mation e75d08e
Merge branch 'ENG-219/app-bootstrap' into ENG-219/command-info
d4mation ee8976d
Merge branch 'ENG-219/app-bootstrap' into ENG-219/command-info
d4mation ce56ee9
ENG-219: Use createTempProject in info tests for isolation
d4mation 3d30626
Merge branch 'ENG-219/app-bootstrap' into ENG-219/command-info
d4mation 6d6bcc1
Merge branch 'ENG-219/command-help' into ENG-219/command-info
d4mation 0b56e3d
ENG-219: Match info command styling to PHP version and expand tests
d4mation 179dfcd
Merge branch 'ENG-219/command-help' into ENG-219/command-info
d4mation df855ac
Merge branch 'ENG-219/command-help' into ENG-219/command-info
d4mation 134efab
Merge branch 'ENG-219/command-help' into ENG-219/command-info
d4mation 061e9b4
Merge branch 'ENG-219/command-help' into ENG-219/command-info
d4mation 0e7e2a0
Merge branch 'ENG-219/command-help' into ENG-219/command-info
d4mation ffaaa21
Merge branch 'ENG-219/command-help' into ENG-219/command-info
d4mation cc1c37f
ENG-219: Merge command-help and update imports to .ts
d4mation c796866
ENG-219: Remove invalid .puprc tests from info command
d4mation 75264b5
ENG-219: Validate JSON output in info command test
d4mation File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| import type { Command } from 'commander'; | ||
| import chalk from 'chalk'; | ||
| import fs from 'fs-extra'; | ||
| import path from 'node:path'; | ||
| import { getConfig } from '../config.js'; | ||
| import { PUP_VERSION } from '../app.js'; | ||
| import * as output from '../utils/output.js'; | ||
|
|
||
| /** | ||
| * Registers the `info` command with the CLI program. | ||
| * | ||
| * @since TBD | ||
| * | ||
| * @param {Command} program - The Commander.js program instance. | ||
| * | ||
| * @returns {void} | ||
| */ | ||
| export function registerInfoCommand(program: Command): void { | ||
| program | ||
| .command('info') | ||
| .description('Gets pup details for the current project.') | ||
| .action(async () => { | ||
| const config = getConfig(); | ||
| const workingDir = config.getWorkingDir(); | ||
|
|
||
| output.title('CLI Info'); | ||
| output.log(`pup ${PUP_VERSION}`); | ||
|
|
||
| // Node.js version | ||
| output.log(`Using: Node.js ${process.version}`); | ||
|
|
||
| output.section('Working Directory'); | ||
| output.log(workingDir); | ||
|
|
||
| output.section('File info'); | ||
|
|
||
| const files = ['.distignore', '.distinclude', '.gitattributes', '.puprc']; | ||
|
|
||
| const filesError: string[] = []; | ||
| const filesExist: string[] = []; | ||
| const filesAbsent: string[] = []; | ||
|
|
||
| for (const file of files) { | ||
| const filePath = path.join(workingDir, file); | ||
| const exists = await fs.pathExists(filePath); | ||
| const styledFile = chalk.cyan(file); | ||
|
|
||
| if (exists && file === '.puprc') { | ||
| try { | ||
| const contents = await fs.readFile(filePath, 'utf-8'); | ||
| JSON.parse(contents); | ||
| filesExist.push(`β ${styledFile} - ${chalk.green('exists')}`); | ||
| } catch (e) { | ||
| const reason = e instanceof SyntaxError ? e.message : String(e); | ||
| filesError.push(`β ${styledFile} - ${chalk.green('exists')} but could not be parsed: ${reason}`); | ||
| } | ||
| } else if (exists) { | ||
| filesExist.push(`β ${styledFile} - ${chalk.green('exists')}`); | ||
| } else { | ||
| filesAbsent.push(`β« ${styledFile} - does not exist`); | ||
| } | ||
| } | ||
|
|
||
| for (const line of filesError) output.log(line); | ||
| for (const line of filesExist) output.log(line); | ||
| for (const line of filesAbsent) output.log(line); | ||
|
|
||
| output.section('Config'); | ||
| output.log(JSON.stringify(config.toJSON(), null, 2)); | ||
| }); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,202 @@ | ||
| import fs from 'fs-extra'; | ||
| import path from 'node:path'; | ||
| import { | ||
| runPup, | ||
| writePuprc, | ||
| getPuprc, | ||
| createTempProject, | ||
| cleanupTempProjects, | ||
| } from '../helpers/setup.js'; | ||
|
|
||
| describe('info command', () => { | ||
| let projectDir: string; | ||
|
|
||
| beforeEach(() => { | ||
| projectDir = createTempProject(); | ||
| writePuprc(getPuprc(), projectDir); | ||
| }); | ||
|
|
||
| afterEach(() => { | ||
| cleanupTempProjects(); | ||
| }); | ||
|
|
||
| it('should exit with code 0', async () => { | ||
| const result = await runPup('info', { cwd: projectDir }); | ||
| expect(result.exitCode).toBe(0); | ||
| }); | ||
|
|
||
| describe('CLI Info section', () => { | ||
| it('should display the CLI Info title with underline', async () => { | ||
| const result = await runPup('info', { cwd: projectDir }); | ||
| expect(result.stdout).toContain('CLI Info'); | ||
| expect(result.stdout).toContain('='.repeat('CLI Info'.length)); | ||
| }); | ||
|
|
||
| it('should display the pup version', async () => { | ||
| const result = await runPup('info', { cwd: projectDir }); | ||
| expect(result.stdout).toMatch(/pup \d+\.\d+\.\d+/); | ||
| }); | ||
|
|
||
| it('should display the Node.js version', async () => { | ||
| const result = await runPup('info', { cwd: projectDir }); | ||
| expect(result.stdout).toMatch(/Using: Node\.js v\d+/); | ||
| }); | ||
| }); | ||
|
|
||
| describe('Working Directory section', () => { | ||
| it('should display the Working Directory section header', async () => { | ||
| const result = await runPup('info', { cwd: projectDir }); | ||
| expect(result.stdout).toContain('Working Directory'); | ||
| expect(result.stdout).toContain('-'.repeat('Working Directory'.length)); | ||
| }); | ||
|
|
||
| it('should display the working directory path', async () => { | ||
| const result = await runPup('info', { cwd: projectDir }); | ||
| expect(result.stdout).toContain(projectDir); | ||
| }); | ||
| }); | ||
|
|
||
| describe('File info section', () => { | ||
| it('should display the File info section header', async () => { | ||
| const result = await runPup('info', { cwd: projectDir }); | ||
| expect(result.stdout).toContain('File info'); | ||
| expect(result.stdout).toContain('-'.repeat('File info'.length)); | ||
| }); | ||
|
|
||
| it('should list all four tracked files', async () => { | ||
| const result = await runPup('info', { cwd: projectDir }); | ||
| expect(result.stdout).toContain('.distignore'); | ||
| expect(result.stdout).toContain('.distinclude'); | ||
| expect(result.stdout).toContain('.gitattributes'); | ||
| expect(result.stdout).toContain('.puprc'); | ||
| }); | ||
|
|
||
| describe('when no optional files exist', () => { | ||
| beforeEach(() => { | ||
| // Remove .puprc so all files are absent | ||
| fs.removeSync(path.join(projectDir, '.puprc')); | ||
| }); | ||
|
|
||
| it('should show absent icon for all files', async () => { | ||
| const result = await runPup('info', { cwd: projectDir }); | ||
| const lines = result.stdout.split('\n'); | ||
| const fileLines = lines.filter((l) => /\.(distignore|distinclude|gitattributes|puprc)/.test(l)); | ||
|
|
||
| for (const line of fileLines) { | ||
| expect(line).toContain('β«'); | ||
| expect(line).toContain('does not exist'); | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| describe('when .puprc exists and is valid', () => { | ||
| it('should show exists icon for .puprc', async () => { | ||
| const result = await runPup('info', { cwd: projectDir }); | ||
| const puprcLine = result.stdout.split('\n').find((l) => l.includes('.puprc')); | ||
| expect(puprcLine).toContain('β '); | ||
| expect(puprcLine).toContain('exists'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('when .puprc exists but is invalid JSON', () => { | ||
| beforeEach(() => { | ||
| fs.writeFileSync(path.join(projectDir, '.puprc'), 'not valid json {{{'); | ||
| }); | ||
|
|
||
| it('should show error icon for .puprc', async () => { | ||
| const result = await runPup('info', { cwd: projectDir }); | ||
| const puprcLine = result.stdout.split('\n').find((l) => l.includes('.puprc')); | ||
| expect(puprcLine).toContain('β'); | ||
| expect(puprcLine).toContain('could not be parsed'); | ||
| }); | ||
|
|
||
| it('should include the parse error reason', async () => { | ||
| const result = await runPup('info', { cwd: projectDir }); | ||
| const puprcLine = result.stdout.split('\n').find((l) => l.includes('.puprc')); | ||
| expect(puprcLine).toMatch(/could not be parsed:.+/); | ||
| }); | ||
| }); | ||
|
|
||
| describe('when .distignore exists', () => { | ||
| beforeEach(() => { | ||
| fs.writeFileSync(path.join(projectDir, '.distignore'), '*.test.js\n'); | ||
| }); | ||
|
|
||
| it('should show exists icon for .distignore', async () => { | ||
| const result = await runPup('info', { cwd: projectDir }); | ||
| const line = result.stdout.split('\n').find((l) => l.includes('.distignore')); | ||
| expect(line).toContain('β '); | ||
| expect(line).toContain('exists'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('when .distinclude exists', () => { | ||
| beforeEach(() => { | ||
| fs.writeFileSync(path.join(projectDir, '.distinclude'), 'vendor/\n'); | ||
| }); | ||
|
|
||
| it('should show exists icon for .distinclude', async () => { | ||
| const result = await runPup('info', { cwd: projectDir }); | ||
| const line = result.stdout.split('\n').find((l) => l.includes('.distinclude')); | ||
| expect(line).toContain('β '); | ||
| expect(line).toContain('exists'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('when .gitattributes exists', () => { | ||
| beforeEach(() => { | ||
| fs.writeFileSync(path.join(projectDir, '.gitattributes'), '*.php diff=php\n'); | ||
| }); | ||
|
|
||
| it('should show exists icon for .gitattributes', async () => { | ||
| const result = await runPup('info', { cwd: projectDir }); | ||
| const line = result.stdout.split('\n').find((l) => l.includes('.gitattributes')); | ||
| expect(line).toContain('β '); | ||
| expect(line).toContain('exists'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('file ordering', () => { | ||
| it('should show error files before existing files', async () => { | ||
| // Write an invalid .puprc and a valid .distignore | ||
| fs.writeFileSync(path.join(projectDir, '.puprc'), '{invalid'); | ||
| fs.writeFileSync(path.join(projectDir, '.distignore'), '*.test.js\n'); | ||
|
|
||
| const result = await runPup('info', { cwd: projectDir }); | ||
| const lines = result.stdout.split('\n'); | ||
| const errorIdx = lines.findIndex((l) => l.includes('β')); | ||
| const existIdx = lines.findIndex((l) => l.includes('β ')); | ||
|
|
||
| expect(errorIdx).toBeGreaterThan(-1); | ||
| expect(existIdx).toBeGreaterThan(-1); | ||
| expect(errorIdx).toBeLessThan(existIdx); | ||
| }); | ||
|
|
||
| it('should show existing files before absent files', async () => { | ||
| const result = await runPup('info', { cwd: projectDir }); | ||
| const lines = result.stdout.split('\n'); | ||
| const existIdx = lines.findIndex((l) => l.includes('β ')); | ||
| const absentIdx = lines.findIndex((l) => l.includes('β«')); | ||
|
|
||
| expect(existIdx).toBeGreaterThan(-1); | ||
| expect(absentIdx).toBeGreaterThan(-1); | ||
| expect(existIdx).toBeLessThan(absentIdx); | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| describe('Config section', () => { | ||
| it('should display the Config section header', async () => { | ||
| const result = await runPup('info', { cwd: projectDir }); | ||
| expect(result.stdout).toContain('Config'); | ||
| expect(result.stdout).toContain('-'.repeat('Config'.length)); | ||
| }); | ||
|
|
||
| it('should display config as formatted JSON', async () => { | ||
| const result = await runPup('info', { cwd: projectDir }); | ||
| // The config JSON should contain key fields from .puprc | ||
| expect(result.stdout).toContain('"zip_name"'); | ||
| expect(result.stdout).toContain('"build"'); | ||
| }); | ||
| }); | ||
| }); |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Noticed while comparing directly to the PHP version. This ensures we're using the exact same colors and making it feel like a very natural transition from the PHP version.