@vaharoni/devops 1.2.12 → 1.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/target-templates/lang-variants-common/python/.cursor/rules/monorepo-python.mdc +56 -0
- package/src/target-templates/lang-variants-common/typescript/.cursor/rules/monorepo-typescript.mdc +51 -0
- package/src/target-templates/lang-variants-prisma/python/.cursor/rules/prisma-python.mdc +55 -0
- package/src/target-templates/lang-variants-prisma/python/.cursor/rules/testing-python.mdc +89 -0
- package/src/target-templates/lang-variants-prisma/typescript/.cursor/rules/prisma-typescript.mdc +54 -0
- package/src/target-templates/lang-variants-prisma/typescript/.cursor/rules/testing-typescript.mdc +103 -0
- package/src/target-templates/lang-variants-prisma/typescript/db/db-client-test.ts +5 -3
package/package.json
CHANGED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
alwaysApply: true
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Python Monorepo
|
|
6
|
+
|
|
7
|
+
This project includes a Python workspace alongside the TypeScript workspace.
|
|
8
|
+
|
|
9
|
+
- **`./devops`** is for TypeScript/Node packages (package.json tree)
|
|
10
|
+
- **`./devopspy`** is for Python packages (pyproject.toml tree)
|
|
11
|
+
|
|
12
|
+
The Python workspace is configured through a root `pyproject.toml` using uv workspaces.
|
|
13
|
+
|
|
14
|
+
## Structure
|
|
15
|
+
|
|
16
|
+
Packages under `applications` depend on packages under `libs` using workspace dependencies. It makes sense to extract code to packages under `libs` when it is used in more than one application. Applications never rely on other applications.
|
|
17
|
+
|
|
18
|
+
All web services found under `applications` should respond to a `GET healthz` with status 200.
|
|
19
|
+
|
|
20
|
+
## Using the devopspy CLI (Python only)
|
|
21
|
+
|
|
22
|
+
We use `uv` as our package manager. The `./devopspy` script is able to run scripts defined in pyproject.toml inside a workspace, run a shell process inside a workspace, and execute the tests in a project:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Most important commands
|
|
26
|
+
./devopspy run <project-name>:<script-name>
|
|
27
|
+
./devopspy exec --in <project-name> <command>
|
|
28
|
+
./devopspy test
|
|
29
|
+
./devopspy test <project-name>
|
|
30
|
+
|
|
31
|
+
# Examples
|
|
32
|
+
./devopspy run my-api:dev
|
|
33
|
+
./devopspy exec --in my-api uv add some-package
|
|
34
|
+
./devopspy test my-service
|
|
35
|
+
|
|
36
|
+
# To sync dependencies on all workspaces in the monorepo, use uv normally
|
|
37
|
+
uv sync
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Scripts are defined in pyproject.toml under `[tool.devops.scripts]`:
|
|
41
|
+
|
|
42
|
+
```toml
|
|
43
|
+
[tool.devops.scripts]
|
|
44
|
+
dev = "python -c 'from src.my_package.scripts import dev; dev()'"
|
|
45
|
+
start = "python -c 'from src.my_package.scripts import start; start()'"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`./devopspy` automatically loads the right env variables, so don't use python-dotenv.
|
|
49
|
+
|
|
50
|
+
There is no need to `cd` into package directories. Prefer to use `./devopspy run` and `./devopspy exec`.
|
|
51
|
+
|
|
52
|
+
## Testing
|
|
53
|
+
|
|
54
|
+
Use pytest.
|
|
55
|
+
|
|
56
|
+
**Always run tests with `./devopspy test`** to ensure the test environment is used.
|
package/src/target-templates/lang-variants-common/typescript/.cursor/rules/monorepo-typescript.mdc
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
alwaysApply: true
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Monorepo Structure
|
|
6
|
+
|
|
7
|
+
We have a monorepo setup configured through a global package.json. There is a `./devops` script to execute commands inside typescript packages.
|
|
8
|
+
|
|
9
|
+
## Structure
|
|
10
|
+
|
|
11
|
+
Packages under `applications` depend on packages under `libs` using `workspace:*` notation from package.json. It makes sense to extract code to packages under `libs` when it is used in more than one application. Applications never rely on other applications.
|
|
12
|
+
|
|
13
|
+
Applications are encouraged to extract private concerns to "local libs" modules. A typical use-case are persistence-related operations (getters and mutations).
|
|
14
|
+
|
|
15
|
+
All web services found under `applications` should respond to a `GET healthz` with status 200.
|
|
16
|
+
|
|
17
|
+
## Using the devops CLI
|
|
18
|
+
|
|
19
|
+
We use `bun` as our package manager. The `./devops` script is able to run package.json scripts inside a workspace, run a shell process inside a workspace, and execute the tests in a project:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Most important commands
|
|
23
|
+
./devops run <project-name>:<script-name>
|
|
24
|
+
./devops exec --in <project-name> <command>
|
|
25
|
+
./devops test
|
|
26
|
+
./devops test <project-name>
|
|
27
|
+
|
|
28
|
+
# Examples
|
|
29
|
+
./devops run my-api:dev
|
|
30
|
+
./devops exec --in my-api bun add some-package
|
|
31
|
+
./devops test my-service
|
|
32
|
+
|
|
33
|
+
# To install dependencies on all workspaces in the monorepo, use bun normally
|
|
34
|
+
bun install
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`./devops` automatically loads the right env variables, so don't use dotenv.
|
|
38
|
+
|
|
39
|
+
There is no need to `cd` into package directories. Prefer to use `./devops run` and `./devops exec`.
|
|
40
|
+
|
|
41
|
+
## Testing
|
|
42
|
+
|
|
43
|
+
Use vitest.
|
|
44
|
+
|
|
45
|
+
**Always run tests with `./devops test`** to ensure the test environment is used.
|
|
46
|
+
|
|
47
|
+
## Inter-service Communication
|
|
48
|
+
|
|
49
|
+
This repo runs in kubernetes and localhost contexts, which means the endpoint URLs will be different depending on the context. To communicate between services, the package `@vaharoni/devops` exports `function getServiceEndpoint(serviceName: string): string`. This function does not return a trailing slash. The `serviceName` can be found in the `deployment.service_name` custom key of the `package.json` of the application you wish to send requests to. If this key does not exist, halt and alert the user.
|
|
50
|
+
|
|
51
|
+
However, in general, importing a shared lib is preferred over crossing the wire.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
globs: *.prisma,*.sql
|
|
3
|
+
alwaysApply: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Prisma (Python)
|
|
7
|
+
|
|
8
|
+
Assume all existing prisma migrations are applied, hence never change existing migrations.
|
|
9
|
+
|
|
10
|
+
NEVER reset the development database without permission.
|
|
11
|
+
|
|
12
|
+
As you know, Prisma is not good at renaming, so after creating migrations automatically from schema changes always go over the migrations and make sure it captured the user intent.
|
|
13
|
+
|
|
14
|
+
## Commands
|
|
15
|
+
|
|
16
|
+
All prisma commands should be executed via the devopspy CLI:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
./devopspy prisma <some command>
|
|
20
|
+
./devopspy prisma migrate dev
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
To generate the Prisma client after schema changes:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
./devopspy run db:generate
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## DB Package
|
|
30
|
+
|
|
31
|
+
The prisma schema resides in `db/prisma/schema.prisma`. To use the Prisma client from any application, add `db` as a workspace dependency in your `pyproject.toml`:
|
|
32
|
+
|
|
33
|
+
```toml
|
|
34
|
+
[tool.uv.sources]
|
|
35
|
+
db = { workspace = true }
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Then import as usual:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from db import prisma
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Schema Conventions
|
|
45
|
+
|
|
46
|
+
When editing Prisma schemas:
|
|
47
|
+
|
|
48
|
+
- Define new models with singular PascalCase names and map them to plural snake_case table names via `@@map`.
|
|
49
|
+
- All table columns should remain in snake_case using `@map` on each field when needed.
|
|
50
|
+
- Default primary keys should be `String @id @default(cuid())` unless there is an explicit reason to use another strategy.
|
|
51
|
+
- Add `@default(now())` + `@map("created_at")` for creation timestamps and `@default(now()) @updatedAt @map("updated_at")` for update timestamps.
|
|
52
|
+
- Always create an index (`@@index`) for every relational field to keep lookups performant.
|
|
53
|
+
- Prefer Prisma's default column type mapping; use `@db.*` annotations only when you truly need a non-default database type.
|
|
54
|
+
- Prefer lowercase values for enums we fully control (e.g., `pending`, `completed` instead of `PENDING`, `COMPLETED`).
|
|
55
|
+
- Prefer strings rather than enums for data-driven closed-lists unless the user requests otherwise.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
alwaysApply: true
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Testing
|
|
6
|
+
|
|
7
|
+
## Running Tests
|
|
8
|
+
|
|
9
|
+
**IMPORTANT**: Always use `./devopspy test` to run tests:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Run all tests
|
|
13
|
+
./devopspy test
|
|
14
|
+
|
|
15
|
+
# Run tests for a specific package
|
|
16
|
+
./devopspy test <project-name>
|
|
17
|
+
./devopspy test my-service
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
This command:
|
|
21
|
+
|
|
22
|
+
- Sets `MONOREPO_ENV=test` to use the test database (separate from development)
|
|
23
|
+
- Loads the correct environment variables from `config/.env.test`
|
|
24
|
+
- Ensures test isolation
|
|
25
|
+
|
|
26
|
+
**Never run tests directly with `pytest`** - this will use the development database and might pollute it with test data.
|
|
27
|
+
|
|
28
|
+
## Prisma Transactional Testing
|
|
29
|
+
|
|
30
|
+
We use a transactional testing pattern where each test runs inside a database transaction that is automatically rolled back after the test completes. This ensures:
|
|
31
|
+
|
|
32
|
+
- Complete test isolation - tests don't affect each other
|
|
33
|
+
- No cleanup code needed - the rollback handles it
|
|
34
|
+
- Fast tests - no need to truncate tables between tests
|
|
35
|
+
|
|
36
|
+
### Setting Up Tests for a New Package
|
|
37
|
+
|
|
38
|
+
1. Create a `tests/conftest.py` in your package that imports the test fixtures:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from db.db_client_test import *
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
2. Ensure `db` is in your workspace dependencies in `pyproject.toml`:
|
|
45
|
+
|
|
46
|
+
```toml
|
|
47
|
+
[project]
|
|
48
|
+
dependencies = ["db"]
|
|
49
|
+
|
|
50
|
+
[tool.uv.sources]
|
|
51
|
+
db = { workspace = true }
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Writing Tests
|
|
55
|
+
|
|
56
|
+
Tests receive a `db_connection` fixture that provides a transactional database connection:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
import pytest
|
|
60
|
+
|
|
61
|
+
@pytest.mark.asyncio
|
|
62
|
+
async def test_creates_record(db_connection):
|
|
63
|
+
# This record will be rolled back after the test
|
|
64
|
+
record = await db_connection.mymodel.create(
|
|
65
|
+
data={"name": "test"}
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
assert record.id is not None
|
|
69
|
+
|
|
70
|
+
@pytest.mark.asyncio
|
|
71
|
+
async def test_can_use_same_data(db_connection):
|
|
72
|
+
# This test gets a clean slate - the previous test's data was rolled back
|
|
73
|
+
record = await db_connection.mymodel.create(
|
|
74
|
+
data={"name": "test"} # same name works!
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
assert record.id is not None
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### How It Works
|
|
81
|
+
|
|
82
|
+
The `db_connection` fixture in `db/db_client_test.py`:
|
|
83
|
+
|
|
84
|
+
- Creates a new Prisma client and connects to the database
|
|
85
|
+
- Starts a transaction and yields a `TransactionProxy` to the test
|
|
86
|
+
- Raises `RollbackTransaction` after the test completes to rollback all changes
|
|
87
|
+
- Disconnects from the database
|
|
88
|
+
|
|
89
|
+
This means each test receives an isolated transaction that is automatically rolled back.
|
package/src/target-templates/lang-variants-prisma/typescript/.cursor/rules/prisma-typescript.mdc
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
globs: *.prisma,*.sql
|
|
3
|
+
alwaysApply: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Prisma
|
|
7
|
+
|
|
8
|
+
Assume all existing prisma migrations are applied, hence never change existing migrations.
|
|
9
|
+
|
|
10
|
+
NEVER reset the development database without permission.
|
|
11
|
+
|
|
12
|
+
As you know, Prisma is not good at renaming, so after creating migrations automatically from schema changes always go over the migrations and make sure it captured the user intent.
|
|
13
|
+
|
|
14
|
+
## Commands
|
|
15
|
+
|
|
16
|
+
All prisma commands should be executed via the devops CLI:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
./devops prisma <some command>
|
|
20
|
+
./devops prisma migrate dev
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
To generate the Prisma client after schema changes:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
./devops run db:generate
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## DB Package
|
|
30
|
+
|
|
31
|
+
The prisma schema resides in `db/prisma/schema.prisma`. To connect to prisma from any application, add `"db": "workspace:*"` to the dependency list of the app's `package.json`. Then import as usual:
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { prisma } from "db";
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
To import Prisma types corresponding to tables, import from the prisma client package:
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { type SomeType } from "@prisma/client";
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Schema Conventions
|
|
44
|
+
|
|
45
|
+
When editing Prisma schemas:
|
|
46
|
+
|
|
47
|
+
- Define new models with singular PascalCase names and map them to plural snake_case table names via `@@map`.
|
|
48
|
+
- All table columns should remain in snake_case using `@map` on each field when needed.
|
|
49
|
+
- Default primary keys should be `String @id @default(cuid())` unless there is an explicit reason to use another strategy.
|
|
50
|
+
- Add `@default(now())` + `@map("created_at")` for creation timestamps and `@default(now()) @updatedAt @map("updated_at")` for update timestamps.
|
|
51
|
+
- Always create an index (`@@index`) for every relational field to keep lookups performant.
|
|
52
|
+
- Prefer Prisma's default column type mapping; use `@db.*` annotations only when you truly need a non-default database type.
|
|
53
|
+
- Prefer lowercase values for enums we fully control (e.g., `pending`, `completed` instead of `PENDING`, `COMPLETED`).
|
|
54
|
+
- Prefer strings rather than enums for data-driven closed-lists unless the user requests otherwise.
|
package/src/target-templates/lang-variants-prisma/typescript/.cursor/rules/testing-typescript.mdc
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
alwaysApply: true
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Testing
|
|
6
|
+
|
|
7
|
+
## Running Tests
|
|
8
|
+
|
|
9
|
+
**IMPORTANT**: Always use `./devops test` to run tests:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Run all tests
|
|
13
|
+
./devops test
|
|
14
|
+
|
|
15
|
+
# Run tests for a specific package
|
|
16
|
+
./devops test <project-name>
|
|
17
|
+
./devops test @local/my-service
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
This command:
|
|
21
|
+
|
|
22
|
+
- Sets `MONOREPO_ENV=test` to use the test database (separate from development)
|
|
23
|
+
- Loads the correct environment variables from `config/.env.test`
|
|
24
|
+
- Ensures test isolation
|
|
25
|
+
|
|
26
|
+
**Never run tests directly with `bun test` or `vitest`** - this will use the development database and might pollute it with test data if the transactional testing (see below) is not properly configured.
|
|
27
|
+
|
|
28
|
+
## Prisma Transactional Testing
|
|
29
|
+
|
|
30
|
+
We use a transactional testing pattern where each test runs inside a database transaction that is automatically rolled back after the test completes. This ensures:
|
|
31
|
+
|
|
32
|
+
- Complete test isolation - tests don't affect each other
|
|
33
|
+
- No cleanup code needed - the rollback handles it
|
|
34
|
+
- Fast tests - no need to truncate tables between tests
|
|
35
|
+
|
|
36
|
+
### Setting Up Tests for a New Package
|
|
37
|
+
|
|
38
|
+
1. Add vitest as a dev dependency and a test script to your `package.json`:
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"vitest": "^3.0.9"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"test": "vitest"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
2. Create a `vitest.config.ts` in your package root:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { defineConfig } from "vitest/config";
|
|
55
|
+
|
|
56
|
+
export default defineConfig({
|
|
57
|
+
test: {
|
|
58
|
+
setupFiles: ["../../db/prisma-setup-vitest.ts"], // adjust path based on package location
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
3. Ensure `"db": "workspace:*"` is in your dependencies if you use Prisma.
|
|
64
|
+
|
|
65
|
+
### Writing Tests
|
|
66
|
+
|
|
67
|
+
Tests can create database records freely - they will be rolled back automatically:
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { describe, it, expect } from "vitest";
|
|
71
|
+
import { prisma } from "db";
|
|
72
|
+
|
|
73
|
+
describe("MyFeature", () => {
|
|
74
|
+
it("creates a record", async () => {
|
|
75
|
+
// This record will be rolled back after the test
|
|
76
|
+
const record = await prisma.myModel.create({
|
|
77
|
+
data: { name: "test" },
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
expect(record.id).toBeDefined();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("can use the same data", async () => {
|
|
84
|
+
// This test gets a clean slate - the previous test's data was rolled back
|
|
85
|
+
const record = await prisma.myModel.create({
|
|
86
|
+
data: { name: "test" }, // same name works!
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
expect(record.id).toBeDefined();
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### How It Works
|
|
95
|
+
|
|
96
|
+
The setup in `db/prisma-setup-vitest.ts` and `db/db-client-test.ts`:
|
|
97
|
+
|
|
98
|
+
- Creates a transactional Prisma client with `$begin()` and `$rollback()` methods
|
|
99
|
+
- Stores it in `globalThis.prismaGlobal` (same singleton used by `db/db-client.ts`)
|
|
100
|
+
- Mocks the `"db"` module so all imports get the transactional client
|
|
101
|
+
- Wraps each test in `beforeEach`/`afterEach` hooks that start and rollback transactions
|
|
102
|
+
|
|
103
|
+
This means all code importing `prisma` from `"db"` - including services being tested - uses the same transactional client.
|
|
@@ -107,9 +107,11 @@ export const prismaClientSingleton = () => {
|
|
|
107
107
|
return client as unknown as ExtendedTransactionClient | FlatTransactionClient;
|
|
108
108
|
};
|
|
109
109
|
|
|
110
|
+
// Use the same global singleton key as db-client.ts (prismaGlobal)
|
|
111
|
+
// This ensures all code importing from "db" uses the same transactional client
|
|
110
112
|
declare global {
|
|
111
113
|
// eslint-disable-next-line no-var
|
|
112
|
-
var
|
|
114
|
+
var prismaGlobal:
|
|
113
115
|
| ExtendedTransactionClient
|
|
114
116
|
| FlatTransactionClient
|
|
115
117
|
| undefined;
|
|
@@ -125,9 +127,9 @@ export type FlatTransactionClient = Prisma.TransactionClient & {
|
|
|
125
127
|
};
|
|
126
128
|
|
|
127
129
|
const prisma: ExtendedTransactionClient | FlatTransactionClient =
|
|
128
|
-
globalThis.
|
|
130
|
+
globalThis.prismaGlobal ?? prismaClientSingleton();
|
|
129
131
|
|
|
130
|
-
globalThis.
|
|
132
|
+
globalThis.prismaGlobal = prisma;
|
|
131
133
|
|
|
132
134
|
export { prisma };
|
|
133
135
|
|