Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
"benchmark": "pnpm --filter @manningworks/projex benchmark"
},
"devDependencies": {
"tsx": "^4.21.0",
"typescript": "^5.7.3"
"tsx": "^4.23.13",
"typescript": "^5.9.3"
},
"engines": {
"node": ">=18.0.0",
Expand All @@ -22,5 +22,10 @@
"workspaces": [
"packages/*",
"apps/*"
]
],
"pnpm": {
"overrides": {
"vite": "^7.3.6"
}
}
}
50 changes: 33 additions & 17 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,34 +35,50 @@
"url": "git+https://github.com/ManningWorks/Projex.git"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0",
"@types/node": "^22.10.2",
"@types/react": "^19.0.6",
"@types/react-dom": "^19.0.6",
"@typescript-eslint/eslint-plugin": "^8.19.0",
"@typescript-eslint/parser": "^8.19.0",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.3",
"@types/node": "^22.20.4",
"@types/react": "^19.3.0",
"@types/react-dom": "^19.3.0",
"@typescript-eslint/eslint-plugin": "^8.70.0",
"@typescript-eslint/parser": "^8.70.0",
"@vitest/coverage-v8": "^4.1.11",
"eslint": "^9.17.0",
"jsdom": "^26.0.0",
"typescript": "^5.7.3",
"vite": "^7",
"eslint": "^9.39.5",
"jsdom": "^26.1.0",
"typescript": "^5.9.3",
"vite": "^7.3.6",
"vitest": "^4.1.11"
},
"dependencies": {
"fuse.js": "^7.1.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"zod": "^3.24.1"
"fuse.js": "^7.5.0",
"react": "^19.3.0",
"react-dom": "^19.3.0",
"zod": "^3.25.76"
},
"peerDependencies": {
"@inquirer/prompts": "^6.0.1",
"@inquirer/confirm": "^6.3.2",
"@inquirer/input": "^5.1.6",
"@inquirer/number": "^4.2.3",
"@inquirer/search": "^4.3.3",
"@inquirer/select": "^5.2.5",
"chalk": "^5.3.0",
"commander": "^12.1.0",
"ts-morph": "^28.0.0"
},
"peerDependenciesMeta": {
"@inquirer/prompts": {
"@inquirer/confirm": {
"optional": true
},
"@inquirer/input": {
"optional": true
},
"@inquirer/number": {
"optional": true
},
"@inquirer/search": {
"optional": true
},
"@inquirer/select": {
"optional": true
},
"chalk": {
Expand Down
29 changes: 15 additions & 14 deletions packages/core/src/commands/__tests__/add-learning.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { addLearningCommand } from '../add-learning.js'
import * as configEditor from '../../lib/config-editor.js'
import input from '@inquirer/input'
import select from '@inquirer/select'

vi.mock('@inquirer/prompts', () => ({
input: vi.fn(),
select: vi.fn(),
vi.mock('@inquirer/input', () => ({
default: vi.fn(),
}))
vi.mock('@inquirer/select', () => ({
default: vi.fn(),
}))

vi.mock('node:fs', async (importOriginal) => ({
Expand Down Expand Up @@ -79,20 +83,19 @@ describe('addLearningCommand', () => {
describe('interactive mode', () => {
it('should prompt for type and text when no flags provided', async () => {
const { existsSync } = await import('node:fs')
const prompts = await import('@inquirer/prompts')

vi.mocked(existsSync).mockReturnValue(true)
vi.mocked(prompts.select).mockResolvedValue('challenge')
vi.mocked(prompts.input).mockResolvedValue('Overcame a tough bug')
vi.mocked(select).mockResolvedValue('challenge')
vi.mocked(input).mockResolvedValue('Overcame a tough bug')

const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})

await addLearningCommand('my-project')

expect(prompts.select).toHaveBeenCalledWith(
expect(select).toHaveBeenCalledWith(
expect.objectContaining({ message: 'Entry type:' }),
)
expect(prompts.input).toHaveBeenCalledWith(
expect(input).toHaveBeenCalledWith(
expect.objectContaining({ message: 'Challenge:' }),
)
expect(configEditor.addLearning).toHaveBeenCalled()
Expand All @@ -105,17 +108,16 @@ describe('addLearningCommand', () => {

it('should prompt with "Learning:" label when type is learning', async () => {
const { existsSync } = await import('node:fs')
const prompts = await import('@inquirer/prompts')

vi.mocked(existsSync).mockReturnValue(true)
vi.mocked(prompts.select).mockResolvedValue('learning')
vi.mocked(prompts.input).mockResolvedValue('Something learned')
vi.mocked(select).mockResolvedValue('learning')
vi.mocked(input).mockResolvedValue('Something learned')

vi.spyOn(console, 'log').mockImplementation(() => {})

await addLearningCommand('my-project')

expect(prompts.input).toHaveBeenCalledWith(
expect(input).toHaveBeenCalledWith(
expect.objectContaining({ message: 'Learning:' }),
)
})
Expand Down Expand Up @@ -157,12 +159,11 @@ describe('addLearningCommand', () => {

it('should handle prompt cancellation', async () => {
const { existsSync } = await import('node:fs')
const prompts = await import('@inquirer/prompts')

vi.mocked(existsSync).mockReturnValue(true)
const cancelError = new Error('User cancelled')
cancelError.name = 'ExitPromptError'
vi.mocked(prompts.select).mockRejectedValue(cancelError)
vi.mocked(select).mockRejectedValue(cancelError)

const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})

Expand Down
20 changes: 9 additions & 11 deletions packages/core/src/commands/__tests__/add-post.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { addPostCommand } from '../add-post.js'
import * as configEditor from '../../lib/config-editor.js'
import input from '@inquirer/input'

vi.mock('@inquirer/prompts', () => ({
input: vi.fn(),
vi.mock('@inquirer/input', () => ({
default: vi.fn(),
}))

vi.mock('node:fs', async (importOriginal) => ({
Expand Down Expand Up @@ -71,10 +72,9 @@ describe('addPostCommand', () => {
describe('interactive mode', () => {
it('should prompt for title, date, and url when no flags provided', async () => {
const { existsSync } = await import('node:fs')
const prompts = await import('@inquirer/prompts')

vi.mocked(existsSync).mockReturnValue(true)
vi.mocked(prompts.input)
vi.mocked(input)
.mockResolvedValueOnce('My Blog Post')
.mockResolvedValueOnce('2025-06-01')
.mockResolvedValueOnce('https://example.com')
Expand All @@ -83,13 +83,13 @@ describe('addPostCommand', () => {

await addPostCommand('my-project')

expect(prompts.input).toHaveBeenCalledWith(
expect(input).toHaveBeenCalledWith(
expect.objectContaining({ message: 'Post title:' }),
)
expect(prompts.input).toHaveBeenCalledWith(
expect(input).toHaveBeenCalledWith(
expect.objectContaining({ message: 'Date (YYYY-MM-DD):' }),
)
expect(prompts.input).toHaveBeenCalledWith(
expect(input).toHaveBeenCalledWith(
expect.objectContaining({ message: 'URL (optional - press Enter to skip):' }),
)
expect(configEditor.addPost).toHaveBeenCalled()
Expand All @@ -102,10 +102,9 @@ describe('addPostCommand', () => {

it('should pass undefined url when user skips in interactive mode', async () => {
const { existsSync } = await import('node:fs')
const prompts = await import('@inquirer/prompts')

vi.mocked(existsSync).mockReturnValue(true)
vi.mocked(prompts.input)
vi.mocked(input)
.mockResolvedValueOnce('A Post')
.mockResolvedValueOnce('2025-06-01')
.mockResolvedValueOnce('')
Expand Down Expand Up @@ -157,12 +156,11 @@ describe('addPostCommand', () => {

it('should handle prompt cancellation', async () => {
const { existsSync } = await import('node:fs')
const prompts = await import('@inquirer/prompts')

vi.mocked(existsSync).mockReturnValue(true)
const cancelError = new Error('User cancelled')
cancelError.name = 'ExitPromptError'
vi.mocked(prompts.input).mockRejectedValue(cancelError)
vi.mocked(input).mockRejectedValue(cancelError)

const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})

Expand Down
42 changes: 23 additions & 19 deletions packages/core/src/commands/__tests__/add-project.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,18 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { addProjectCommand } from '../add-project.js'
import * as configEditor from '../../lib/config-editor.js'
import confirm from '@inquirer/confirm'
import input from '@inquirer/input'
import select from '@inquirer/select'

vi.mock('@inquirer/prompts', () => ({
input: vi.fn(),
select: vi.fn(),
confirm: vi.fn(),
vi.mock('@inquirer/confirm', () => ({
default: vi.fn(),
}))
vi.mock('@inquirer/input', () => ({
default: vi.fn(),
}))
vi.mock('@inquirer/select', () => ({
default: vi.fn(),
}))

vi.mock('node:fs', async (importOriginal) => ({
Expand Down Expand Up @@ -187,27 +194,26 @@ describe('addProjectCommand', () => {
describe('interactive mode', () => {
it('should prompt for all fields when no flags provided', async () => {
const { existsSync } = await import('node:fs')
const prompts = await import('@inquirer/prompts')

vi.mocked(existsSync).mockReturnValue(true)
vi.mocked(prompts.select).mockResolvedValue('github')
vi.mocked(prompts.input)
vi.mocked(select).mockResolvedValue('github')
vi.mocked(input)
.mockResolvedValueOnce('My Project')
.mockResolvedValueOnce('user/repo')
.mockResolvedValueOnce('React,TypeScript')
vi.mocked(prompts.confirm).mockResolvedValue(true)
vi.mocked(confirm).mockResolvedValue(true)

const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})

await addProjectCommand()

expect(prompts.select).toHaveBeenCalledWith(
expect(select).toHaveBeenCalledWith(
expect.objectContaining({ message: 'Project type:' }),
)
expect(prompts.input).toHaveBeenCalledWith(
expect(input).toHaveBeenCalledWith(
expect.objectContaining({ message: 'Project name:' }),
)
expect(prompts.confirm).toHaveBeenCalledWith(
expect(confirm).toHaveBeenCalledWith(
expect.objectContaining({ message: 'Featured project?' }),
)
expect(configEditor.addProject).toHaveBeenCalled()
Expand All @@ -220,11 +226,10 @@ describe('addProjectCommand', () => {

it('should prompt only for missing fields with partial flags', async () => {
const { existsSync } = await import('node:fs')
const prompts = await import('@inquirer/prompts')

vi.mocked(existsSync).mockReturnValue(true)
vi.mocked(prompts.input).mockResolvedValue('user/repo')
vi.mocked(prompts.confirm).mockResolvedValue(false)
vi.mocked(input).mockResolvedValue('user/repo')
vi.mocked(confirm).mockResolvedValue(false)

await addProjectCommand({
type: 'github',
Expand All @@ -233,9 +238,9 @@ describe('addProjectCommand', () => {
stack: 'React',
})

expect(prompts.select).not.toHaveBeenCalled()
expect(prompts.input).toHaveBeenCalledTimes(1)
expect(prompts.confirm).toHaveBeenCalledTimes(1)
expect(select).not.toHaveBeenCalled()
expect(input).toHaveBeenCalledTimes(1)
expect(confirm).toHaveBeenCalledTimes(1)
})
})

Expand All @@ -258,12 +263,11 @@ describe('addProjectCommand', () => {

it('should handle prompt cancellation', async () => {
const { existsSync } = await import('node:fs')
const prompts = await import('@inquirer/prompts')

vi.mocked(existsSync).mockReturnValue(true)
const cancelError = new Error('User cancelled')
cancelError.name = 'ExitPromptError'
vi.mocked(prompts.select).mockRejectedValue(cancelError)
vi.mocked(select).mockRejectedValue(cancelError)

const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})

Expand Down
18 changes: 8 additions & 10 deletions packages/core/src/commands/__tests__/add-timeline.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { addTimelineCommand } from '../add-timeline.js'
import * as configEditor from '../../lib/config-editor.js'
import input from '@inquirer/input'

vi.mock('@inquirer/prompts', () => ({
input: vi.fn(),
vi.mock('@inquirer/input', () => ({
default: vi.fn(),
}))

vi.mock('node:fs', async (importOriginal) => ({
Expand Down Expand Up @@ -57,8 +58,7 @@ describe('addTimelineCommand', () => {
const today = new Date()
const expectedDate = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`

const prompts = await import('@inquirer/prompts')
vi.mocked(prompts.input).mockResolvedValue(expectedDate)
vi.mocked(input).mockResolvedValue(expectedDate)

vi.spyOn(console, 'log').mockImplementation(() => {})

Expand All @@ -75,21 +75,20 @@ describe('addTimelineCommand', () => {
describe('interactive mode', () => {
it('should prompt for date and note when no flags provided', async () => {
const { existsSync } = await import('node:fs')
const prompts = await import('@inquirer/prompts')

vi.mocked(existsSync).mockReturnValue(true)
vi.mocked(prompts.input)
vi.mocked(input)
.mockResolvedValueOnce('2025-06-01')
.mockResolvedValueOnce('Milestone reached')

const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})

await addTimelineCommand('my-project')

expect(prompts.input).toHaveBeenCalledWith(
expect(input).toHaveBeenCalledWith(
expect.objectContaining({ message: 'Date (YYYY-MM-DD):' }),
)
expect(prompts.input).toHaveBeenCalledWith(
expect(input).toHaveBeenCalledWith(
expect.objectContaining({ message: 'Milestone note:' }),
)
expect(configEditor.addTimelineEntry).toHaveBeenCalled()
Expand Down Expand Up @@ -136,12 +135,11 @@ describe('addTimelineCommand', () => {

it('should handle prompt cancellation', async () => {
const { existsSync } = await import('node:fs')
const prompts = await import('@inquirer/prompts')

vi.mocked(existsSync).mockReturnValue(true)
const cancelError = new Error('User cancelled')
cancelError.name = 'ExitPromptError'
vi.mocked(prompts.input).mockRejectedValue(cancelError)
vi.mocked(input).mockRejectedValue(cancelError)

const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})

Expand Down
Loading
Loading