@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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vaharoni/devops",
3
3
  "type": "module",
4
- "version": "1.2.12",
4
+ "version": "1.2.13",
5
5
  "description": "Devops utility",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -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.
@@ -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.
@@ -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.
@@ -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 prismaTestGlobal:
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.prismaTestGlobal ?? prismaClientSingleton();
130
+ globalThis.prismaGlobal ?? prismaClientSingleton();
129
131
 
130
- globalThis.prismaTestGlobal = prisma;
132
+ globalThis.prismaGlobal = prisma;
131
133
 
132
134
  export { prisma };
133
135