Skip to content
Closed
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
Original file line number Diff line number Diff line change
@@ -1,165 +1,197 @@
import {copyByPattern} from './copy-by-pattern.js'
import {describe, expect, test, vi, beforeEach} from 'vitest'
import * as fs from '@shopify/cli-kit/node/fs'

vi.mock('@shopify/cli-kit/node/fs')
import {describe, expect, test, vi} from 'vitest'
import {inTemporaryDirectory, mkdir, writeFile, fileExists} from '@shopify/cli-kit/node/fs'
import {joinPath} from '@shopify/cli-kit/node/path'

describe('copyByPattern', () => {
let mockStdout: any

beforeEach(() => {
mockStdout = {write: vi.fn()}
vi.mocked(fs.fileExists).mockResolvedValue(true)
})

test('copies matched files preserving relative paths', async () => {
// Given
vi.mocked(fs.glob).mockResolvedValue(['/src/components/Button.tsx', '/src/utils/helpers.ts'])
vi.mocked(fs.mkdir).mockResolvedValue()
vi.mocked(fs.copyFile).mockResolvedValue()

// When
const result = await copyByPattern(
{
sourceDir: '/src',
outputDir: '/out',
patterns: ['**/*.ts', '**/*.tsx'],
ignore: [],
appDirectory: '/src',
sourceDirConfigValue: '.',
},
{stdout: mockStdout},
)

// Then
expect(fs.copyFile).toHaveBeenCalledWith('/src/components/Button.tsx', '/out/components/Button.tsx')
expect(fs.copyFile).toHaveBeenCalledWith('/src/utils/helpers.ts', '/out/utils/helpers.ts')
expect(result.filesCopied).toBe(2)
expect(result.outputPaths).toEqual(expect.arrayContaining(['components/Button.tsx', 'utils/helpers.ts']))
expect(mockStdout.write).toHaveBeenCalledWith(expect.stringContaining('Included 2 file(s)'))
await inTemporaryDirectory(async (tmpDir) => {
// Given
const sourceDir = joinPath(tmpDir, 'src')
const outputDir = joinPath(tmpDir, 'out')
const appDirectory = tmpDir
await mkdir(sourceDir)
await writeFile(joinPath(sourceDir, 'Button.tsx'), 'button')
await mkdir(joinPath(sourceDir, 'utils'))
await writeFile(joinPath(sourceDir, 'utils', 'helpers.ts'), 'helpers')

const mockStdout = {write: vi.fn()}

// When
const result = await copyByPattern(
{
sourceDir,
outputDir,
patterns: ['**/*.ts', '**/*.tsx'],
ignore: [],
appDirectory,
sourceDirConfigValue: 'src',
},
{stdout: mockStdout as any},
)

// Then
await expect(fileExists(joinPath(outputDir, 'Button.tsx'))).resolves.toBe(true)
await expect(fileExists(joinPath(outputDir, 'utils/helpers.ts'))).resolves.toBe(true)
expect(result.filesCopied).toBe(2)
expect(result.outputPaths).toEqual(expect.arrayContaining(['Button.tsx', 'utils/helpers.ts']))
expect(mockStdout.write).toHaveBeenCalledWith(expect.stringContaining('Included 2 file(s)'))
})
})

test('returns 0 when no files match patterns', async () => {
// Given
vi.mocked(fs.glob).mockResolvedValue([])

// When
const result = await copyByPattern(
{
sourceDir: '/src',
outputDir: '/out',
patterns: ['**/*.png'],
ignore: [],
appDirectory: '/src',
sourceDirConfigValue: '.',
},
{stdout: mockStdout},
)

// Then
expect(result.filesCopied).toBe(0)
expect(result.outputPaths).toEqual([])
expect(fs.mkdir).not.toHaveBeenCalled()
expect(fs.copyFile).not.toHaveBeenCalled()
await inTemporaryDirectory(async (tmpDir) => {
// Given
const sourceDir = joinPath(tmpDir, 'src')
const outputDir = joinPath(tmpDir, 'out')
const appDirectory = tmpDir
await mkdir(sourceDir)

const mockStdout = {write: vi.fn()}

// When
const result = await copyByPattern(
{
sourceDir,
outputDir,
patterns: ['**/*.png'],
ignore: [],
appDirectory,
sourceDirConfigValue: 'src',
},
{stdout: mockStdout as any},
)

// Then
expect(result.filesCopied).toBe(0)
expect(result.outputPaths).toEqual([])
await expect(fileExists(outputDir)).resolves.toBe(false)
})
})

test('skips file and warns when resolved destination escapes the output directory', async () => {
// Given — sourceDir is /out/sub, so a file from /out/sub/../../evil resolves outside /out/sub
// Simulate by providing a glob result whose relative path traverses upward
vi.mocked(fs.glob).mockResolvedValue(['/out/sub/../../evil.js'])
vi.mocked(fs.mkdir).mockResolvedValue()
vi.mocked(fs.copyFile).mockResolvedValue()

// When
const result = await copyByPattern(
{
sourceDir: '/out/sub',
outputDir: '/out/sub',
patterns: ['**/*'],
ignore: [],
appDirectory: '/out',
sourceDirConfigValue: 'sub',
},
{stdout: mockStdout},
)

// Then
expect(mockStdout.write).toHaveBeenCalledWith(expect.stringContaining('skipping'))
expect(fs.copyFile).not.toHaveBeenCalled()
expect(result.filesCopied).toBe(0)
expect(result.outputPaths).toEqual([])
await inTemporaryDirectory(async (tmpDir) => {
// Given
const appDirectory = tmpDir
const sourceDir = joinPath(tmpDir, 'src/sub')
const outputDir = joinPath(tmpDir, 'out')
await mkdir(joinPath(tmpDir, 'src'))
await mkdir(sourceDir)
// File at tmpDir/src/evil.js, which is ../evil.js relative to sourceDir
await writeFile(joinPath(tmpDir, 'src/evil.js'), 'evil')

const mockStdout = {write: vi.fn()}

// When
const result = await copyByPattern(
{
sourceDir,
outputDir,
patterns: ['../evil.js'],
ignore: [],
appDirectory,
sourceDirConfigValue: 'src/sub',
},
{stdout: mockStdout as any},
)

// Then
expect(mockStdout.write).toHaveBeenCalledWith(expect.stringContaining('skipping'))
expect(result.filesCopied).toBe(0)
expect(result.outputPaths).toEqual([])
})
})

test('returns 0 without copying when filepath equals computed destPath', async () => {
// Given — file already lives at the exact destination path
vi.mocked(fs.glob).mockResolvedValue(['/out/logo.png'])
vi.mocked(fs.mkdir).mockResolvedValue()
vi.mocked(fs.copyFile).mockResolvedValue()

// When — sourceDir is /out so relPath=relativePath('/out','/out/logo.png')='logo.png', destPath='/out/logo.png'==filepath
const result = await copyByPattern(
{
sourceDir: '/out',
outputDir: '/out',
patterns: ['*.png'],
ignore: [],
appDirectory: '/out',
sourceDirConfigValue: '.',
},
{stdout: mockStdout},
)

// Then
expect(fs.copyFile).not.toHaveBeenCalled()
expect(result.filesCopied).toBe(0)
expect(result.outputPaths).toEqual([])
expect(mockStdout.write).toHaveBeenCalledWith(expect.stringContaining('Included 0 file(s)'))
await inTemporaryDirectory(async (tmpDir) => {
// Given
const sourceDir = tmpDir
const outputDir = tmpDir
const appDirectory = tmpDir
await writeFile(joinPath(tmpDir, 'logo.png'), 'logo')

const mockStdout = {write: vi.fn()}

// When
const result = await copyByPattern(
{
sourceDir,
outputDir,
patterns: ['*.png'],
ignore: [],
appDirectory,
sourceDirConfigValue: '.',
},
{stdout: mockStdout as any},
)

// Then
expect(result.filesCopied).toBe(0)
expect(result.outputPaths).toEqual([])
expect(mockStdout.write).toHaveBeenCalledWith(expect.stringContaining('Included 0 file(s)'))
})
})

test('calls mkdir(outputDir) before copying when files are matched', async () => {
// Given
vi.mocked(fs.glob).mockResolvedValue(['/src/app.js'])
vi.mocked(fs.mkdir).mockResolvedValue()
vi.mocked(fs.copyFile).mockResolvedValue()

// When
await copyByPattern(
{
sourceDir: '/src',
outputDir: '/out/dist',
patterns: ['*.js'],
ignore: [],
appDirectory: '/src',
sourceDirConfigValue: '.',
},
{stdout: mockStdout},
)

// Then — outputDir created before copying
expect(fs.mkdir).toHaveBeenCalledWith('/out/dist')
await inTemporaryDirectory(async (tmpDir) => {
// Given
const sourceDir = joinPath(tmpDir, 'src')
const outputDir = joinPath(tmpDir, 'out/dist')
const appDirectory = tmpDir
await mkdir(sourceDir)
await writeFile(joinPath(sourceDir, 'app.js'), 'app')

const mockStdout = {write: vi.fn()}

// When
await copyByPattern(
{
sourceDir,
outputDir,
patterns: ['*.js'],
ignore: [],
appDirectory,
sourceDirConfigValue: 'src',
},
{stdout: mockStdout as any},
)

// Then
await expect(fileExists(outputDir)).resolves.toBe(true)
await expect(fileExists(joinPath(outputDir, 'app.js'))).resolves.toBe(true)
})
})

test('passes ignore patterns to glob', async () => {
// Given
vi.mocked(fs.glob).mockResolvedValue([])

// When
await copyByPattern(
{
sourceDir: '/src',
outputDir: '/out',
patterns: ['**/*'],
ignore: ['**/*.test.ts', 'node_modules/**'],
appDirectory: '/src',
sourceDirConfigValue: '.',
},
{stdout: mockStdout},
)

// Then
expect(fs.glob).toHaveBeenCalledWith(
['**/*'],
expect.objectContaining({ignore: ['**/*.test.ts', 'node_modules/**']}),
)
await inTemporaryDirectory(async (tmpDir) => {
// Given
const sourceDir = joinPath(tmpDir, 'src')
const outputDir = joinPath(tmpDir, 'out')
const appDirectory = tmpDir
await mkdir(sourceDir)
await writeFile(joinPath(sourceDir, 'app.js'), 'app')
await writeFile(joinPath(sourceDir, 'app.test.ts'), 'test')

const mockStdout = {write: vi.fn()}

// When
const result = await copyByPattern(
{
sourceDir,
outputDir,
patterns: ['**/*'],
ignore: ['**/*.test.ts'],
appDirectory,
sourceDirConfigValue: 'src',
},
{stdout: mockStdout as any},
)

// Then
expect(result.filesCopied).toBe(1)
expect(result.outputPaths).toEqual(['app.js'])
await expect(fileExists(joinPath(outputDir, 'app.js'))).resolves.toBe(true)
await expect(fileExists(joinPath(outputDir, 'app.test.ts'))).resolves.toBe(false)
})
})
})
Loading