Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7e3a53a
chore(cli): Add tests for building workspace packages
Tobbe Jan 3, 2026
bd232da
tests
Tobbe Jan 3, 2026
923513f
try fixing tests on Windows
Tobbe Jan 3, 2026
64340de
Add workspace packages to test-fixture project
Tobbe Jan 3, 2026
ca1bd98
update fixture
Tobbe Jan 3, 2026
a3f2537
concurrentlyOptions
Tobbe Jan 3, 2026
f07c1f8
pkgJson deps
Tobbe Jan 3, 2026
593cf0d
try to fix paths for unit test
Tobbe Jan 3, 2026
3dde21e
add dep to test project fixture package json
Tobbe Jan 3, 2026
473e7ca
Add packages/* as a workspace in test-fixture
Tobbe Jan 3, 2026
7274119
test ingore pattern
Tobbe Jan 3, 2026
ea5b670
update root packagejson in rebuild script
Tobbe Jan 3, 2026
3ee23b6
debug cli smoketest
Tobbe Jan 3, 2026
c60f232
debug cli smoketest
Tobbe Jan 3, 2026
79a83cb
cat lockfile
Tobbe Jan 3, 2026
b0fcf06
exports
Tobbe Jan 3, 2026
5437fe0
build before test
Tobbe Jan 3, 2026
6f89528
ls -la
Tobbe Jan 3, 2026
f99141a
only ls
Tobbe Jan 3, 2026
fd9971f
Merge branch 'main' into tobbe-chore-cli-test-package-building
Tobbe Jan 4, 2026
c52c653
Merge branch 'main' into tobbe-chore-cli-test-package-building
Tobbe Jan 4, 2026
2a35e77
revert cli-smoke-tests.yml
Tobbe Jan 4, 2026
e61e699
Merge branch 'main' into tobbe-chore-cli-test-package-building
Tobbe Jan 4, 2026
a1382b2
revert cli-smoke-tests.yml
Tobbe Jan 4, 2026
e52ec55
revert cli-smoke-tests.yml
Tobbe Jan 4, 2026
0e6221a
Merge branch 'main' into tobbe-chore-cli-test-package-building
Tobbe Jan 4, 2026
d457c97
Merge branch 'main' into tobbe-chore-cli-test-package-building
Tobbe Jan 4, 2026
c4a2ab5
Merge branch 'main' into tobbe-chore-cli-test-package-building
Tobbe Jan 6, 2026
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
3 changes: 2 additions & 1 deletion __fixtures__/test-project/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"private": true,
"workspaces": [
"api",
"web"
"web",
"packages/*"
],
"devDependencies": {
"@cedarjs/core": "2.3.0",
Expand Down
15 changes: 15 additions & 0 deletions __fixtures__/test-project/packages/validators/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Shared Package '@my-org/validators'

Use code in this package by adding it to the dependencies on the side you want
to use it, with the special `workspace:*` version. After that you can import it
into your code:

```json
"dependencies": {
"@my-org/validators": "workspace:*"
}
```

```javascript
import { validators } from '@my-org/validators'
```
22 changes: 22 additions & 0 deletions __fixtures__/test-project/packages/validators/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "@my-org/validators",
"version": "0.0.0",
"private": true,
"type": "module",
"main": "dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"scripts": {
"build": "tsc",
"watch": "tsc --watch"
},
"devDependencies": {
"@cedarjs/testing": "2.2.1",
"typescript": "5.9.3"
}
}
5 changes: 5 additions & 0 deletions __fixtures__/test-project/packages/validators/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function validateEmail(email: string) {
return email.includes('@') &&
email.includes('.') &&
email.lastIndexOf('.') > email.indexOf('@') + 1
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { validateEmail } from './index.js'

describe('validators', () => {
it('should not throw any errors', async () => {
expect(validateEmail('valid@email.com')).not.toThrow()
})
})
17 changes: 17 additions & 0 deletions __fixtures__/test-project/packages/validators/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"compilerOptions": {
"composite": true,
"target": "ES2023",
"module": "Node20",
"esModuleInterop": true,
"skipLibCheck": true,
"baseUrl": ".",
"rootDir": "src",
"outDir": "dist",
"sourceMap": true,
"declaration": true,
"declarationMap": true,
},
"include": ["src"],
"exclude": ["**/*.test.ts"],
}
3 changes: 3 additions & 0 deletions __fixtures__/test-project/redwood.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@
open = true
[notifications]
versionUpdates = ["latest"]

[experimental.packagesWorkspace]
enabled = true
1 change: 1 addition & 0 deletions __fixtures__/test-project/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@cedarjs/forms": "2.3.0",
"@cedarjs/router": "2.3.0",
"@cedarjs/web": "2.3.0",
"@my-org/validators": "workspace:*",
"humanize-string": "2.1.0",
"react": "19.2.3",
"react-dom": "19.2.3"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useState } from 'react'

import { validateEmail } from '@my-org/validators'
import { useForm } from 'react-hook-form'

import {
Expand Down Expand Up @@ -94,9 +95,16 @@ const ContactUsPage = () => {
name="email"
validation={{
required: true,
pattern: {
value: /[^@]+@[^.]+..+/,
message: 'Please enter a valid email address',
validate: (value) => {
if (!value) {
return 'Email is required'
}

if (!validateEmail(value)) {
return 'Please enter a valid email address'
}

return true
},
}}
className="rounded-sm border px-2 py-1"
Expand Down
22 changes: 19 additions & 3 deletions tasks/test-project/codemods/contactUsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,16 @@ const body = `
name="email"
validation={{
required: true,
pattern: {
value: /[^@]+@[^.]+\..+/,
message: 'Please enter a valid email address',
validate: (value) => {
if (!value) {
return 'Email is required'
}

if (!validateEmail(value)) {
return 'Please enter a valid email address'
}

return true
},
}}
className="border rounded-sm px-2 py-1"
Expand Down Expand Up @@ -157,6 +164,15 @@ export default (file, api) => {
],
j.stringLiteral('@cedarjs/router'),
),
j.importDeclaration(
[
j.importSpecifier(
j.identifier('validateEmail'),
j.identifier('validateEmail'),
),
],
j.stringLiteral('@my-org/validators'),
),
]

// Remove the `{ Link, routes }` imports that are generated and unused
Expand Down
133 changes: 128 additions & 5 deletions tasks/test-project/rebuild-test-project-fixture.mts
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,125 @@ async function runCommand() {

await tuiTask({
step: 10,
title: 'Add workspace packages',
task: async () => {
const tomlPath = path.join(OUTPUT_PROJECT_PATH, 'redwood.toml')
const redwoodToml = fs.readFileSync(tomlPath, 'utf-8')
const newRedwoodToml =
redwoodToml + '\n[experimental.packagesWorkspace]\n enabled = true\n'

fs.writeFileSync(tomlPath, newRedwoodToml)

await exec(
'yarn cedar g package @my-org/validators',
[],
getExecaOptions(OUTPUT_PROJECT_PATH),
)

const packagePath = path.join(
OUTPUT_PROJECT_PATH,
'packages',
'validators',
)

fs.writeFileSync(
path.join(packagePath, 'src', 'index.ts'),
'export function validateEmail(email: string) {\n' +
" return email.includes('@') &&\n" +
" email.includes('.') &&\n" +
" email.lastIndexOf('.') > email.indexOf('@') + 1\n" +
'}\n',
)

fs.writeFileSync(
path.join(packagePath, 'src', 'validators.test.ts'),
"import { validateEmail } from './index.js'\n" +
'\n' +
"describe('validators', () => {\n" +
" it('should not throw any errors', async () => {\n" +
" expect(validateEmail('valid@email.com')).not.toThrow()\n" +
' })\n' +
'})\n',
)

const webPackageJson = JSON.parse(
fs.readFileSync(
path.join(OUTPUT_PROJECT_PATH, 'web', 'package.json'),
'utf8',
),
)

webPackageJson.dependencies['@my-org/validators'] = 'workspace:*'

fs.writeFileSync(
path.join(OUTPUT_PROJECT_PATH, 'web', 'package.json'),
JSON.stringify(webPackageJson, null, 2),
)

await exec('yarn install', [], getExecaOptions(OUTPUT_PROJECT_PATH))

const build = await exec(
'yarn cedar build',
[],
getExecaOptions(OUTPUT_PROJECT_PATH),
)

const distFiles = fs.readdirSync(
path.join(OUTPUT_PROJECT_PATH, 'packages', 'validators', 'dist'),
)

if (distFiles.some((file) => file.includes('test'))) {
console.error('distFiles', distFiles)
throw new Error(
'Unexpected test file in validators package dist directory',
)
}

// TODO: Update this when we refine the build process
if (!build.stdout.includes('yarn build exited with code 0')) {
console.error('yarn cedar build output', build.stdout, build.stderr)
throw new Error('Unexpected output from `yarn cedar build`')
}

// Verify that `yarn cedar <cmd>` works inside package directories
// Starting with `yarn cedar info`
// TODO: Enable code below
// const info = await exec(
// 'yarn cedar info',
// [],
// getExecaOptions(OUTPUT_PROJECT_PATH),
// )

// if (
// !info.stdout.includes('Binaries:') ||
// !info.stdout.includes('Node:') ||
// !info.stdout.includes('npmPackages:') ||
// !info.stdout.includes('@cedarjs/core')
// ) {
// console.error('yarn cedar info output', info.stdout, info.stderr)

// throw new Error('Unexpected output from `yarn cedar info`')
// }

// Continue testing `yarn cedar <cmd>` by running `yarn cedar test`
// const test = await exec(
// 'yarn cedar test @my-org/validators',
// [],
// getExecaOptions(OUTPUT_PROJECT_PATH),
// )

// Validate that only the tests for this package ran
// Verify that all tests passed
// TODO: Implement functionality according to the comment above

// The package we've generated (@my-org/validators) is used in the test
// project on both the web and the api side and is further tested by our
// playwright tests that trigger the files that import the package.
},
})

await tuiTask({
step: 11,
title: 'Running prisma migrate reset',
task: () => {
return exec(
Expand All @@ -509,7 +628,7 @@ async function runCommand() {
})

await tuiTask({
step: 11,
step: 12,
title: 'Lint --fix all the things',
task: async () => {
try {
Expand Down Expand Up @@ -540,7 +659,7 @@ async function runCommand() {
})

await tuiTask({
step: 12,
step: 13,
title: 'Replace and Cleanup Fixture',
task: async () => {
// @TODO: This only works on UNIX, we should use path.join everywhere
Expand All @@ -567,10 +686,13 @@ async function runCommand() {
await rimraf(`${OUTPUT_PROJECT_PATH}/.nx`)
await rimraf(`${OUTPUT_PROJECT_PATH}/tarballs`)

// Copy over package.json from template, so we remove the extra dev dependencies, and cfw postinstall script
// that we added in "Adding framework dependencies to project"
// Copy over package.json from template, so we remove the extra dev
// dependencies, and cfw postinstall script that we added in "Adding
// framework dependencies to project"
// There's one devDep we actually do want in there though, and that's the
// prettier plugin for Tailwind CSS
// We also want the `packages/*` workspace config that was added when
// adding the validators package
const rootPackageJson = JSON.parse(
fs.readFileSync(path.join(OUTPUT_PROJECT_PATH, 'package.json'), 'utf8'),
)
Expand All @@ -583,6 +705,7 @@ async function runCommand() {
)
newRootPackageJson.devDependencies['prettier-plugin-tailwindcss'] =
rootPackageJson.devDependencies['prettier-plugin-tailwindcss']
newRootPackageJson.workspaces.push('packages/*')
fs.writeFileSync(
path.join(OUTPUT_PROJECT_PATH, 'package.json'),
JSON.stringify(newRootPackageJson, null, 2) + '\n',
Expand All @@ -595,7 +718,7 @@ async function runCommand() {
})

await tuiTask({
step: 13,
step: 14,
title: 'All done!',
task: () => {
console.log('-'.repeat(30))
Expand Down
Loading