|
| 1 | +import { |
| 2 | + mkdirSync, |
| 3 | + mkdtempSync, |
| 4 | + realpathSync, |
| 5 | + rmSync, |
| 6 | + writeFileSync, |
| 7 | +} from 'node:fs'; |
| 8 | +import { tmpdir } from 'node:os'; |
| 9 | +import { dirname, join } from 'node:path'; |
| 10 | +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; |
| 11 | +import { BaseBuilder } from './base-builder.js'; |
| 12 | +import type { StandaloneConfig } from './types.js'; |
| 13 | + |
| 14 | +/** |
| 15 | + * Minimal subclass to expose the protected `getInputFiles()` for testing. |
| 16 | + */ |
| 17 | +class TestBuilder extends BaseBuilder { |
| 18 | + async build(): Promise<void> { |
| 19 | + // no-op |
| 20 | + } |
| 21 | + |
| 22 | + // Expose for tests |
| 23 | + public getInputFiles(): Promise<string[]> { |
| 24 | + return super.getInputFiles(); |
| 25 | + } |
| 26 | +} |
| 27 | + |
| 28 | +// Resolve symlinks in tmpdir to avoid macOS /var -> /private/var issues |
| 29 | +const realTmpdir = realpathSync(tmpdir()); |
| 30 | + |
| 31 | +/** |
| 32 | + * Normalize a path to forward slashes for cross-platform comparison. |
| 33 | + * tinyglobby always returns forward-slash paths, even on Windows, |
| 34 | + * while Node's `path.join()` uses backslashes on Windows. |
| 35 | + */ |
| 36 | +function normalize(p: string): string { |
| 37 | + return p.replace(/\\/g, '/'); |
| 38 | +} |
| 39 | + |
| 40 | +function writeFile(dir: string, relativePath: string, content = ''): string { |
| 41 | + const fullPath = join(dir, relativePath); |
| 42 | + mkdirSync(dirname(fullPath), { recursive: true }); |
| 43 | + writeFileSync(fullPath, content, 'utf-8'); |
| 44 | + return fullPath; |
| 45 | +} |
| 46 | + |
| 47 | +function createBuilder(workingDir: string, dirs: string[]): TestBuilder { |
| 48 | + const config: StandaloneConfig = { |
| 49 | + buildTarget: 'standalone', |
| 50 | + workingDir, |
| 51 | + dirs, |
| 52 | + stepsBundlePath: join(workingDir, 'steps.js'), |
| 53 | + workflowsBundlePath: join(workingDir, 'workflows.js'), |
| 54 | + webhookBundlePath: join(workingDir, 'webhook.js'), |
| 55 | + }; |
| 56 | + return new TestBuilder(config); |
| 57 | +} |
| 58 | + |
| 59 | +describe('getInputFiles', () => { |
| 60 | + let testRoot: string; |
| 61 | + |
| 62 | + beforeEach(() => { |
| 63 | + testRoot = mkdtempSync(join(realTmpdir, 'get-input-files-')); |
| 64 | + }); |
| 65 | + |
| 66 | + afterEach(() => { |
| 67 | + rmSync(testRoot, { recursive: true, force: true }); |
| 68 | + }); |
| 69 | + |
| 70 | + it('discovers files inside dot-prefixed directories', async () => { |
| 71 | + const srcDir = join(testRoot, 'src'); |
| 72 | + writeFile(srcDir, '.hidden/step.ts', "'use step';"); |
| 73 | + writeFile(srcDir, '.config/workflow.ts', "'use workflow';"); |
| 74 | + writeFile(srcDir, 'regular/step.ts', "'use step';"); |
| 75 | + |
| 76 | + const builder = createBuilder(testRoot, ['src']); |
| 77 | + const files = (await builder.getInputFiles()).map(normalize); |
| 78 | + |
| 79 | + expect(files).toContain(normalize(join(srcDir, '.hidden/step.ts'))); |
| 80 | + expect(files).toContain(normalize(join(srcDir, '.config/workflow.ts'))); |
| 81 | + expect(files).toContain(normalize(join(srcDir, 'regular/step.ts'))); |
| 82 | + }); |
| 83 | + |
| 84 | + it('discovers dot-prefixed files', async () => { |
| 85 | + const srcDir = join(testRoot, 'src'); |
| 86 | + writeFile(srcDir, '.hidden-step.ts', "'use step';"); |
| 87 | + writeFile(srcDir, 'visible-step.ts', "'use step';"); |
| 88 | + |
| 89 | + const builder = createBuilder(testRoot, ['src']); |
| 90 | + const files = (await builder.getInputFiles()).map(normalize); |
| 91 | + |
| 92 | + expect(files).toContain(normalize(join(srcDir, '.hidden-step.ts'))); |
| 93 | + expect(files).toContain(normalize(join(srcDir, 'visible-step.ts'))); |
| 94 | + }); |
| 95 | + |
| 96 | + it('still excludes explicitly ignored dot-directories', async () => { |
| 97 | + const srcDir = join(testRoot, 'src'); |
| 98 | + writeFile(srcDir, '.git/hooks/pre-commit.ts'); |
| 99 | + writeFile(srcDir, '.next/server/page.ts'); |
| 100 | + writeFile(srcDir, '.nuxt/workflow/steps.mjs'); |
| 101 | + writeFile(srcDir, '.vercel/output/step.ts'); |
| 102 | + writeFile(srcDir, '.svelte-kit/output/step.ts'); |
| 103 | + writeFile(srcDir, '.workflow-data/state.ts'); |
| 104 | + writeFile(srcDir, '.well-known/workflow/route.ts'); |
| 105 | + writeFile(srcDir, '.turbo/cache/build.ts'); |
| 106 | + writeFile(srcDir, '.cache/babel/plugin.js'); |
| 107 | + writeFile(srcDir, '.yarn/releases/yarn.cjs'); |
| 108 | + writeFile(srcDir, '.pnpm-store/v3/files.ts'); |
| 109 | + writeFile(srcDir, 'node_modules/pkg/index.ts'); |
| 110 | + // This one should still be found |
| 111 | + writeFile(srcDir, '.custom/step.ts', "'use step';"); |
| 112 | + |
| 113 | + const builder = createBuilder(testRoot, ['src']); |
| 114 | + const files = (await builder.getInputFiles()).map(normalize); |
| 115 | + |
| 116 | + expect(files).not.toContain( |
| 117 | + normalize(join(srcDir, '.git/hooks/pre-commit.ts')) |
| 118 | + ); |
| 119 | + expect(files).not.toContain( |
| 120 | + normalize(join(srcDir, '.next/server/page.ts')) |
| 121 | + ); |
| 122 | + expect(files).not.toContain( |
| 123 | + normalize(join(srcDir, '.nuxt/workflow/steps.mjs')) |
| 124 | + ); |
| 125 | + expect(files).not.toContain( |
| 126 | + normalize(join(srcDir, '.vercel/output/step.ts')) |
| 127 | + ); |
| 128 | + expect(files).not.toContain( |
| 129 | + normalize(join(srcDir, '.svelte-kit/output/step.ts')) |
| 130 | + ); |
| 131 | + expect(files).not.toContain( |
| 132 | + normalize(join(srcDir, '.workflow-data/state.ts')) |
| 133 | + ); |
| 134 | + expect(files).not.toContain( |
| 135 | + normalize(join(srcDir, '.well-known/workflow/route.ts')) |
| 136 | + ); |
| 137 | + expect(files).not.toContain( |
| 138 | + normalize(join(srcDir, '.turbo/cache/build.ts')) |
| 139 | + ); |
| 140 | + expect(files).not.toContain( |
| 141 | + normalize(join(srcDir, '.cache/babel/plugin.js')) |
| 142 | + ); |
| 143 | + expect(files).not.toContain( |
| 144 | + normalize(join(srcDir, '.yarn/releases/yarn.cjs')) |
| 145 | + ); |
| 146 | + expect(files).not.toContain( |
| 147 | + normalize(join(srcDir, '.pnpm-store/v3/files.ts')) |
| 148 | + ); |
| 149 | + expect(files).not.toContain( |
| 150 | + normalize(join(srcDir, 'node_modules/pkg/index.ts')) |
| 151 | + ); |
| 152 | + expect(files).toContain(normalize(join(srcDir, '.custom/step.ts'))); |
| 153 | + }); |
| 154 | + |
| 155 | + it('discovers files with various supported extensions in dot-directories', async () => { |
| 156 | + const srcDir = join(testRoot, 'src'); |
| 157 | + writeFile(srcDir, '.api/route.tsx'); |
| 158 | + writeFile(srcDir, '.api/handler.mts'); |
| 159 | + writeFile(srcDir, '.api/utils.js'); |
| 160 | + writeFile(srcDir, '.api/config.cjs'); |
| 161 | + |
| 162 | + const builder = createBuilder(testRoot, ['src']); |
| 163 | + const files = (await builder.getInputFiles()).map(normalize); |
| 164 | + |
| 165 | + expect(files).toContain(normalize(join(srcDir, '.api/route.tsx'))); |
| 166 | + expect(files).toContain(normalize(join(srcDir, '.api/handler.mts'))); |
| 167 | + expect(files).toContain(normalize(join(srcDir, '.api/utils.js'))); |
| 168 | + expect(files).toContain(normalize(join(srcDir, '.api/config.cjs'))); |
| 169 | + }); |
| 170 | +}); |
0 commit comments