Skip to content

Conversation

@charliecreates
Copy link
Contributor

@charliecreates charliecreates bot commented Dec 9, 2025

Component / Package Name:

jsx-email, preview

This PR contains:

  • bugfix
  • feature
  • refactor
  • documentation
  • other

Are tests included?

  • yes (bugfixes and features will not be merged without tests)
  • no

Breaking Changes?

  • yes (breaking changes will not be merged unless absolutely necessary)
  • no

If yes, please include "BREAKING CHANGES:" in the first commit message body, followed by a description of what is breaking.

List any relevant issue numbers:

resolves #381
resolves #366
#379

Description

Ports the post-merge-base main fixes into next/v3 for preview deploy builds + static assets:

  • Normalize globby() patterns for preview static assets using Vite’s normalizePath().
  • Align email preview --build-path behavior with main:
    • relative build paths resolve against the original CWD
    • help text no longer claims “absolute only”
  • Remove react/jsx-runtime from rollupOptions.external for deployable preview builds.
  • Restore Windows drive-letter guard for Vite root vs temp directory.
  • Fix case-sensitive imports in the preview app (Sidebar/Logo) so deploy builds work on Linux.
  • Add test/cli/preview-build-path.test.ts regression test.

Verification

# Lint (note: this task auto-writes docs; unrelated changes were discarded)
$ pnpm exec moon run repo:lint

# Build (ensures a clean `dist/preview`)
$ pnpm exec moon run jsx-email:build --updateCache

# Prettier (check-only on changed files)
$ pnpm prettier --check apps/preview/app/src/components/sidebar.tsx apps/preview/app/src/layouts/Shell.tsx packages/jsx-email/src/cli/commands/preview.ts packages/jsx-email/src/cli/vite-static.ts test/cli/preview-build-path.test.ts

# OXLint (check-only on changed files)
$ pnpm oxlint apps/preview/app/src/components/sidebar.tsx apps/preview/app/src/layouts/Shell.tsx packages/jsx-email/src/cli/commands/preview.ts packages/jsx-email/src/cli/vite-static.ts test/cli/preview-build-path.test.ts

# Vitest (jsx-email)
$ cd packages/jsx-email && mise exec [email protected] -- env FORCE_COLOR=1 pnpm exec vitest --config ../../shared/vitest.config.ts
# Test Files 41 passed; Tests 184 passed

# Vitest (CLI)
$ cd test/cli && mise exec [email protected] -- env FORCE_COLOR=1 pnpm exec vitest --config ../../shared/vitest.config.ts
# Test Files 4 passed; Tests 4 passed
  • Self review (reviewChanges) returned comments for files not touched in this PR (e.g. pnpm-workspace.yaml, shared/tsconfig.base.json). I confirmed the diff for this PR is only:
    • apps/preview/app/src/components/sidebar.tsx
    • apps/preview/app/src/layouts/Shell.tsx
    • packages/jsx-email/src/cli/commands/preview.ts
    • packages/jsx-email/src/cli/vite-static.ts
    • test/cli/preview-build-path.test.ts

Copy link
Contributor Author

@charliecreates charliecreates bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main concerns are test robustness and long-term maintainability: describe(async () => ...) and console.log in test/cli/preview-build-path.test.ts can create flakiness/noisy CI output, and the test cleanup does not remove the created template directory. In preview.ts, the Windows drive-letter guard logic is good, but variable naming around buildPath is currently ambiguous and could lead to accidental misuse during future refactors. No functional regressions are obvious in the path normalization or import casing fixes.

Summary of changes

Summary

This PR ports preview deploy/static-asset fixes into next/v3 across the preview app and CLI:

  • Preview app (Linux/case-sensitive FS compatibility)

    • Updated imports to match on-disk casing:
      • apps/preview/app/src/components/sidebar.tsx: ./Logo./logo
      • apps/preview/app/src/layouts/Shell.tsx: ../components/Sidebar../components/sidebar
  • CLI preview deploy behavior (packages/jsx-email/src/cli/commands/preview.ts)

    • --build-path help text updated and a relative path example added.
    • --build-path now defaults to ./.deploy and relative paths are resolved against the original CWD via originalCwd.
    • Removed react/jsx-runtime from rollupOptions.external for deployable builds.
    • Restored a Windows drive-letter guard to fail fast when Vite root and temp dir are on different drives.
  • Static asset glob normalization (packages/jsx-email/src/cli/vite-static.ts)

    • Normalized globby() patterns using Vite’s normalizePath() to avoid path separator issues.
  • Tests

    • Added regression coverage for email preview --build-path in test/cli/preview-build-path.test.ts.

Comment on lines +7 to +35
describe('cli: preview --build-path', async () => {
const outRelational = './.test/build-path';
const outAbsolute = resolve(__dirname, outRelational);
const templatePath = './.test/.deploy/emails';
const isWindows = os.platform() === 'win32';

beforeAll(async () => {
await rm(outAbsolute, { force: true, recursive: true });
});

afterAll(async () => {
await rm(outAbsolute, { force: true, recursive: true });
});

if (!isWindows) {
test('relative build path writes to an absolute path derived from the original cwd', async () => {
await execa({ cwd: __dirname, shell: true })`email create BatmanEmail --out ${templatePath} `;
const { stdout } = await execa({
cwd: __dirname,
shell: true
})`email preview ${templatePath} --build-path ${outRelational}`;

console.log(stdout);

await access(join(outAbsolute, 'index.html'));

const html = await readFile(join(outAbsolute, 'index.html'), 'utf8');
expect(html).toContain('<div id="root"></div>');
}, 60e3);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test suite uses describe(..., async () => { ... }). In Vitest/Jest-style runners, describe callbacks are expected to be synchronous; returning a Promise can lead to subtle scheduling issues and hooks not registering as expected.

Additionally, the test has a console.log(stdout) which will add noise to CI output and can hide real failures.

Finally, the email create ... step writes into templatePath, but the cleanup only removes outAbsolute. If the test runs multiple times (or on failure), .test/.deploy may accumulate files and create flakiness.

Suggestion

Make the describe callback synchronous, remove console.log, and clean up the template output directory as well.

describe('cli: preview --build-path', () => {
  const outRelational = './.test/build-path';
  const outAbsolute = resolve(__dirname, outRelational);
  const templatePath = './.test/.deploy/emails';
  const templateAbsolute = resolve(__dirname, templatePath);

  beforeAll(async () => {
    await rm(outAbsolute, { force: true, recursive: true });
    await rm(templateAbsolute, { force: true, recursive: true });
  });

  afterAll(async () => {
    await rm(outAbsolute, { force: true, recursive: true });
    await rm(templateAbsolute, { force: true, recursive: true });
  });

  // ... remove console.log
});

Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion.

Comment on lines +21 to +35
if (!isWindows) {
test('relative build path writes to an absolute path derived from the original cwd', async () => {
await execa({ cwd: __dirname, shell: true })`email create BatmanEmail --out ${templatePath} `;
const { stdout } = await execa({
cwd: __dirname,
shell: true
})`email preview ${templatePath} --build-path ${outRelational}`;

console.log(stdout);

await access(join(outAbsolute, 'index.html'));

const html = await readFile(join(outAbsolute, 'index.html'), 'utf8');
expect(html).toContain('<div id="root"></div>');
}, 60e3);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The non-Windows test invokes email preview ... --build-path ... and expects it to return (so it can access(index.html)). If email preview starts a server even when --build-path is provided (or if behavior changes), this test can hang/flap depending on CLI behavior.

It would be more robust to:

  • Ensure the CLI path you're testing is a pure build-and-exit mode when --build-path is set, or
  • Add an explicit --no-open and any build-only flag (if supported) to guarantee process termination.

This is especially important because you set a long timeout (60s) and use shell: true, which can mask process-lifecycle issues.

Suggestion

Harden the test to avoid hanging on long-running preview processes. If --build-path is intended to build and exit, assert that behavior explicitly by checking the exit code and/or by adding flags that guarantee exit.

Example:

const res = await execa({ cwd: __dirname, shell: true })
  `email preview ${templatePath} --build-path ${outRelational} --no-open`;
expect(res.exitCode).toBe(0);

If there is a dedicated build-only option (e.g. --build / --build-only), prefer that.

Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion.

Comment on lines +38 to +50
if (isWindows) {
test('errors when Vite root and temp build path are on different drives (Windows)', async () => {
await execa({ cwd: __dirname, shell: true })`email create BatmanEmail --out ${templatePath} `;
await expect(
execa({
cwd: __dirname,
shell: true
})`email preview ${templatePath} --build-path ${outRelational}`
).rejects.toThrow(
/Temporary directory drive letter different than root directory drive letter/
);
}, 60e3);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Windows branch expects a drive-letter mismatch error when running email preview ... --build-path <relative>. But --build-path is an output directory, and a relative path will resolve under the test cwd—it won’t naturally create a cross-drive condition.

If the intent is to validate the cross-drive failure mode, the test should either (a) simulate differing drives by controlling the temp directory location, or (b) unit-test the drive-letter guard logic with injected paths. As-is, this can be flaky or always-skip the real mismatch condition depending on environment.

Suggestion

Make the Windows test deterministic by decoupling it from the machine’s actual temp-drive layout.

Options:

  1. Refactor the guard into a small pure function and unit test it:
export const assertSameDriveOnWindows = (root: string, tempDir: string) => {
  // ... throw AssertionError ...
};

Then in CLI tests, validate the integration path uses the temp dir.

  1. If the code honors TMPDIR/TEMP/TMP on Windows, set it for the spawned process to a path on a different drive (if available) and skip when not.

Reply with "@CharlieHelps yes please" if you’d like me to add a commit that refactors the guard for deterministic testing and updates this test accordingly.

@charliecreates
Copy link
Contributor Author

Superseded by #391 (combined PR targeting next/v3).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants