@everystack/mcp 0.3.2 → 0.4.0
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/dist/adding-database.md +36 -23
- package/dist/cli.md +44 -3
- package/dist/core.md +19 -1
- package/dist/database-operations.md +236 -0
- package/dist/derived-objects.md +225 -0
- package/dist/gates/detectors/embedded-data-bundle.d.ts +13 -0
- package/dist/gates/detectors/embedded-data-bundle.d.ts.map +1 -0
- package/dist/gates/detectors/embedded-data-bundle.js +56 -0
- package/dist/gates/detectors/hand-written-migration.d.ts +24 -0
- package/dist/gates/detectors/hand-written-migration.d.ts.map +1 -0
- package/dist/gates/detectors/hand-written-migration.js +54 -0
- package/dist/gates/detectors/secret-in-public-env.d.ts +12 -0
- package/dist/gates/detectors/secret-in-public-env.d.ts.map +1 -0
- package/dist/gates/detectors/secret-in-public-env.js +38 -0
- package/dist/gates/engine.d.ts +28 -0
- package/dist/gates/engine.d.ts.map +1 -0
- package/dist/gates/engine.js +74 -0
- package/dist/gates/registry.d.ts +14 -0
- package/dist/gates/registry.d.ts.map +1 -0
- package/dist/gates/registry.js +21 -0
- package/dist/gates/telemetry.d.ts +47 -0
- package/dist/gates/telemetry.d.ts.map +1 -0
- package/dist/gates/telemetry.js +121 -0
- package/dist/gates/types.d.ts +67 -0
- package/dist/gates/types.d.ts.map +1 -0
- package/dist/gates/types.js +14 -0
- package/dist/governance/cli.d.ts +57 -0
- package/dist/governance/cli.d.ts.map +1 -0
- package/dist/governance/cli.js +169 -0
- package/dist/governance/grounding.d.ts +78 -0
- package/dist/governance/grounding.d.ts.map +1 -0
- package/dist/governance/grounding.js +299 -0
- package/dist/index.cjs +119 -107
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +94 -0
- package/dist/project-claude-md.md +35 -18
- package/dist/prompts/add-feature.d.ts +3 -0
- package/dist/prompts/add-feature.d.ts.map +1 -0
- package/dist/prompts/add-feature.js +154 -0
- package/dist/prompts/claude-md.d.ts +12 -0
- package/dist/prompts/claude-md.d.ts.map +1 -0
- package/dist/prompts/claude-md.js +87 -0
- package/dist/prompts/debug.d.ts +3 -0
- package/dist/prompts/debug.d.ts.map +1 -0
- package/dist/prompts/debug.js +129 -0
- package/dist/prompts/deploy.d.ts +3 -0
- package/dist/prompts/deploy.d.ts.map +1 -0
- package/dist/prompts/deploy.js +118 -0
- package/dist/prompts/design-schema.d.ts +3 -0
- package/dist/prompts/design-schema.d.ts.map +1 -0
- package/dist/prompts/design-schema.js +97 -0
- package/dist/prompts/governance-setup.d.ts +9 -0
- package/dist/prompts/governance-setup.d.ts.map +1 -0
- package/dist/prompts/governance-setup.js +76 -0
- package/dist/prompts/index.d.ts +3 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +20 -0
- package/dist/prompts/new-app.d.ts +3 -0
- package/dist/prompts/new-app.d.ts.map +1 -0
- package/dist/prompts/new-app.js +203 -0
- package/dist/prompts/runbook.d.ts +12 -0
- package/dist/prompts/runbook.d.ts.map +1 -0
- package/dist/prompts/runbook.js +70 -0
- package/dist/prompts/secure.d.ts +3 -0
- package/dist/prompts/secure.d.ts.map +1 -0
- package/dist/prompts/secure.js +219 -0
- package/dist/resources/index.d.ts +12 -0
- package/dist/resources/index.d.ts.map +1 -0
- package/dist/resources/index.js +182 -0
- package/dist/schema-patterns.md +92 -106
- package/dist/tools/check-environment.d.ts +25 -0
- package/dist/tools/check-environment.d.ts.map +1 -0
- package/dist/tools/check-environment.js +281 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +18 -0
- package/dist/tools/project-status.d.ts +28 -0
- package/dist/tools/project-status.d.ts.map +1 -0
- package/dist/tools/project-status.js +138 -0
- package/dist/tools/project-validate.d.ts +19 -0
- package/dist/tools/project-validate.d.ts.map +1 -0
- package/dist/tools/project-validate.js +323 -0
- package/dist/tools/schema-analyze.d.ts +46 -0
- package/dist/tools/schema-analyze.d.ts.map +1 -0
- package/dist/tools/schema-analyze.js +336 -0
- package/package.json +3 -3
- package/src/gates/detectors/hand-written-migration.ts +29 -13
- package/src/index.ts +1 -1
- package/src/prompts/add-feature.ts +4 -4
- package/src/prompts/claude-md.ts +4 -2
- package/src/prompts/debug.ts +3 -4
- package/src/prompts/deploy.ts +17 -8
- package/src/prompts/design-schema.ts +45 -59
- package/src/prompts/new-app.ts +21 -19
- package/src/prompts/secure.ts +3 -3
- package/src/resources/adding-database.md +36 -23
- package/src/resources/cli.md +44 -3
- package/src/resources/core.md +19 -1
- package/src/resources/database-operations.md +236 -0
- package/src/resources/derived-objects.md +225 -0
- package/src/resources/index.ts +15 -1
- package/src/resources/project-claude-md.md +35 -18
- package/src/resources/schema-patterns.md +92 -106
package/dist/schema-patterns.md
CHANGED
|
@@ -1,167 +1,153 @@
|
|
|
1
1
|
# Schema Design Patterns
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Declaring TABLES with `defineModel` in `db/models/` — fields, authz, relations, indexes.
|
|
4
4
|
|
|
5
5
|
## When to Use
|
|
6
|
-
Read this when designing your database schema or adding tables to an existing app.
|
|
6
|
+
Read this when designing your database schema or adding tables to an existing app. This
|
|
7
|
+
resource covers **tables** (`defineModel`). For the derived layer —
|
|
8
|
+
views, materialized views, functions, triggers, standalone sequences — read
|
|
9
|
+
`everystack://derived-objects` (they deploy via `db:reconcile`, not migrations). For the
|
|
10
|
+
operational verbs that move a database (`db:sync`, `db:generate`, `db:plan`/`db:apply`,
|
|
11
|
+
`db:check`), read `everystack://database-operations`.
|
|
7
12
|
|
|
8
13
|
## Schema Location
|
|
9
14
|
|
|
10
|
-
|
|
15
|
+
The whole declared database lives in `db/models/` — one `defineModel` per table, composed
|
|
16
|
+
onto a `defineModule`. This is the single source of truth for the handler, SSR, RLS, and
|
|
17
|
+
grants. You do **not** hand-write `db/schema.ts`, and you do **not** run
|
|
18
|
+
`drizzle-kit generate`/`drizzle-kit migrate` — the Model is authored, and everystack
|
|
19
|
+
generates the migration (`db:generate`) and the typed artifact (`db/schema.generated.ts`,
|
|
20
|
+
never edited). Never hand-write a SQL migration.
|
|
11
21
|
|
|
12
22
|
## Basic Table
|
|
13
23
|
|
|
14
24
|
```typescript
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
export const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
import { defineModel, field, can } from '@everystack/model';
|
|
26
|
+
|
|
27
|
+
export const Post = defineModel('posts', {
|
|
28
|
+
fields: {
|
|
29
|
+
id: field.uuid().primaryKey().defaultRandom(),
|
|
30
|
+
body: field.text().notNull(),
|
|
31
|
+
authorId: field.uuid().notNull().references('users', 'id'),
|
|
32
|
+
status: field.text().notNull().default('draft'),
|
|
33
|
+
createdAt: field.timestamptz().notNull().defaultNow(),
|
|
34
|
+
updatedAt: field.timestamptz().notNull().defaultNow(),
|
|
35
|
+
deletedAt: field.timestamptz(),
|
|
36
|
+
},
|
|
37
|
+
abilities: [can('read'), can('create'), can('update'), can('delete')],
|
|
25
38
|
});
|
|
26
39
|
```
|
|
27
40
|
|
|
41
|
+
Column names are `snake_case` in the database; the field key is the app-side name — the
|
|
42
|
+
generator maps `authorId` → `author_id` for you.
|
|
43
|
+
|
|
28
44
|
## Common Patterns
|
|
29
45
|
|
|
30
46
|
### UUID vs Serial Primary Keys
|
|
31
47
|
Prefer UUID for user-facing IDs (prevents enumeration attacks). Use serial for internal-only tables.
|
|
32
48
|
|
|
33
49
|
```typescript
|
|
34
|
-
id: uuid(
|
|
35
|
-
id: serial(
|
|
50
|
+
id: field.uuid().primaryKey().defaultRandom(), // Preferred for API-exposed tables
|
|
51
|
+
id: field.serial().primaryKey(), // OK for internal tables
|
|
36
52
|
```
|
|
37
53
|
|
|
38
54
|
### Timestamps
|
|
39
55
|
Always include `createdAt` and `updatedAt`:
|
|
40
56
|
```typescript
|
|
41
|
-
createdAt:
|
|
42
|
-
updatedAt:
|
|
57
|
+
createdAt: field.timestamptz().notNull().defaultNow(),
|
|
58
|
+
updatedAt: field.timestamptz().notNull().defaultNow(),
|
|
43
59
|
```
|
|
44
60
|
|
|
45
61
|
### Soft Delete
|
|
46
62
|
Add `deletedAt` for soft-deletable tables:
|
|
47
63
|
```typescript
|
|
48
|
-
deletedAt:
|
|
64
|
+
deletedAt: field.timestamptz(),
|
|
49
65
|
```
|
|
50
66
|
Configure in handler: `softDelete: { column: 'deletedAt', tables: ['posts'] }`.
|
|
51
67
|
|
|
52
68
|
### Foreign Keys
|
|
53
69
|
```typescript
|
|
54
|
-
authorId: uuid(
|
|
70
|
+
authorId: field.uuid().notNull().references('users', 'id'),
|
|
55
71
|
```
|
|
56
72
|
|
|
57
73
|
### Enums
|
|
58
74
|
```typescript
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
export const roleEnum = pgEnum('role', ['user', 'admin']);
|
|
62
|
-
|
|
63
|
-
export const users = pgTable('users', {
|
|
64
|
-
role: roleEnum('role').default('user').notNull(),
|
|
65
|
-
});
|
|
75
|
+
role: field.enum('role', ['user', 'admin']).notNull().default('user'),
|
|
66
76
|
```
|
|
67
77
|
|
|
68
|
-
## Relations
|
|
78
|
+
## Relations
|
|
69
79
|
|
|
70
|
-
|
|
71
|
-
|
|
80
|
+
Declare relations on the Model — the generator emits both the Drizzle relations (used by
|
|
81
|
+
the relational query API, `db.query.posts.findMany({ with: { author: true } })`) and the
|
|
82
|
+
handler's embedding config, so they can't drift apart.
|
|
72
83
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
84
|
+
```typescript
|
|
85
|
+
export const Post = defineModel('posts', {
|
|
86
|
+
fields: { /* … */ },
|
|
87
|
+
relations: {
|
|
88
|
+
author: { model: 'users', from: 'authorId', to: 'id' },
|
|
89
|
+
comments: { model: 'comments', from: 'id', to: 'postId', many: true },
|
|
90
|
+
},
|
|
91
|
+
abilities: [can('read')],
|
|
92
|
+
});
|
|
77
93
|
```
|
|
78
94
|
|
|
79
|
-
Drizzle relations are used by the relational query API (`db.query.posts.findMany({ with: { author: true } })`). The handler also needs its own `relations` config for embedding.
|
|
80
|
-
|
|
81
95
|
## Indexes
|
|
82
96
|
|
|
83
97
|
```typescript
|
|
84
|
-
import {
|
|
85
|
-
|
|
86
|
-
export const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
]
|
|
98
|
+
import { defineModel, field, can, index } from '@everystack/model';
|
|
99
|
+
|
|
100
|
+
export const Post = defineModel('posts', {
|
|
101
|
+
fields: { /* … */ },
|
|
102
|
+
indexes: [
|
|
103
|
+
index(['authorId']),
|
|
104
|
+
index(['slug']).unique(),
|
|
105
|
+
],
|
|
106
|
+
abilities: [can('read')],
|
|
107
|
+
});
|
|
92
108
|
```
|
|
93
109
|
|
|
94
|
-
##
|
|
95
|
-
|
|
96
|
-
```bash
|
|
97
|
-
# Generate migration from schema changes
|
|
98
|
-
npx drizzle-kit generate
|
|
99
|
-
|
|
100
|
-
# Apply migrations locally
|
|
101
|
-
npx drizzle-kit migrate
|
|
110
|
+
## Authorization is declared, not migrated
|
|
102
111
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
112
|
+
You never hand-write `CREATE ROLE`, `GRANT`, or `CREATE POLICY`. Abilities on the Model
|
|
113
|
+
compile to grants + RLS, and `db:sync`/`db:apply` deploy them. `can('read')` grants `anon`
|
|
114
|
+
+ `authenticated`; `can('read', { role: 'admin' })` narrows to a role. RLS is required —
|
|
115
|
+
the CI gate (`db:check`) refuses a table without it. See `everystack://security` for the
|
|
116
|
+
policy patterns the abilities compile to.
|
|
106
117
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
CREATE ROLE authenticator LOGIN NOINHERIT;
|
|
118
|
-
END IF;
|
|
119
|
-
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'anon') THEN
|
|
120
|
-
CREATE ROLE anon NOLOGIN;
|
|
121
|
-
END IF;
|
|
122
|
-
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'authenticated') THEN
|
|
123
|
-
CREATE ROLE authenticated NOLOGIN;
|
|
124
|
-
END IF;
|
|
125
|
-
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'admin') THEN
|
|
126
|
-
CREATE ROLE admin NOLOGIN;
|
|
127
|
-
END IF;
|
|
128
|
-
END $$;
|
|
129
|
-
|
|
130
|
-
GRANT anon TO authenticator;
|
|
131
|
-
GRANT authenticated TO authenticator;
|
|
132
|
-
GRANT admin TO authenticator;
|
|
133
|
-
|
|
134
|
-
-- Table grants
|
|
135
|
-
GRANT USAGE ON SCHEMA public TO anon, authenticated, admin;
|
|
136
|
-
GRANT SELECT ON posts TO anon;
|
|
137
|
-
GRANT SELECT, INSERT, UPDATE, DELETE ON posts TO authenticated;
|
|
138
|
-
GRANT SELECT, INSERT, UPDATE, DELETE ON posts TO admin;
|
|
139
|
-
|
|
140
|
-
-- Enable RLS
|
|
141
|
-
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
|
|
142
|
-
ALTER TABLE posts FORCE ROW LEVEL SECURITY;
|
|
143
|
-
|
|
144
|
-
-- Policies (see everystack://security for templates)
|
|
118
|
+
```typescript
|
|
119
|
+
export const Post = defineModel('posts', {
|
|
120
|
+
fields: { /* … */ },
|
|
121
|
+
abilities: [
|
|
122
|
+
can('read'), // anon + authenticated
|
|
123
|
+
can('create', { role: 'authenticated' }),
|
|
124
|
+
can('update', { role: 'authenticated', own: 'authorId' }), // row ownership
|
|
125
|
+
can('delete', { role: 'admin' }),
|
|
126
|
+
],
|
|
127
|
+
});
|
|
145
128
|
```
|
|
146
129
|
|
|
147
130
|
## Handler Config for Schema
|
|
148
131
|
|
|
132
|
+
The handler config is **derived** from the Models (`deriveHandlerConfig(models)`), so
|
|
133
|
+
`exposedTables`, `hiddenColumns`, `protectedFields`, and `relations` come from the
|
|
134
|
+
declarations rather than a hand-kept parallel list:
|
|
135
|
+
|
|
149
136
|
```typescript
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
relations: {
|
|
155
|
-
posts: { author: { table: 'users', from: 'authorId', to: 'id' } },
|
|
156
|
-
},
|
|
157
|
-
});
|
|
137
|
+
import { deriveHandlerConfig } from '@everystack/model';
|
|
138
|
+
import { models } from '../db/models';
|
|
139
|
+
|
|
140
|
+
createHandler(db, schema, deriveHandlerConfig(models));
|
|
158
141
|
```
|
|
159
142
|
|
|
160
143
|
## Gotchas
|
|
161
144
|
|
|
162
|
-
-
|
|
163
|
-
|
|
164
|
-
-
|
|
165
|
-
|
|
166
|
-
- RLS
|
|
167
|
-
|
|
145
|
+
- Field keys are app-side names (camelCase); the database column is `snake_case` — the
|
|
146
|
+
generator maps `authorId` → `author_id`.
|
|
147
|
+
- Edit the Model, then move the database with `db:sync` (dev) or `db:generate` +
|
|
148
|
+
`db:plan`/`db:apply` (protected stages). Never run `drizzle-kit generate`.
|
|
149
|
+
- RLS and grants are declared with `can()`, never hand-written; `db:check` fails a table
|
|
150
|
+
without RLS.
|
|
151
|
+
- `db/schema.generated.ts` is a generated artifact — never edit it; `db:check` refuses
|
|
152
|
+
drift.
|
|
153
|
+
- Connect as `authenticator` in production (never RDS master/superuser).
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type EnvironmentPhase = 'local' | 'deploy';
|
|
2
|
+
export interface PrerequisiteCheck {
|
|
3
|
+
name: string;
|
|
4
|
+
required: boolean;
|
|
5
|
+
status: 'found' | 'not_found';
|
|
6
|
+
version: string | null;
|
|
7
|
+
installHint: string;
|
|
8
|
+
tier: 'V1' | 'V2';
|
|
9
|
+
phase: 'local' | 'deploy';
|
|
10
|
+
description: string;
|
|
11
|
+
whyNeeded: string;
|
|
12
|
+
installCommands: string[];
|
|
13
|
+
serviceRunning?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface EnvironmentReport {
|
|
16
|
+
phase: EnvironmentPhase;
|
|
17
|
+
prerequisites: PrerequisiteCheck[];
|
|
18
|
+
ready: boolean;
|
|
19
|
+
readyForTier: 'V1' | 'V2' | 'V3' | 'none';
|
|
20
|
+
suggestions: string[];
|
|
21
|
+
platform: 'macOS' | 'linux' | 'windows' | 'unknown';
|
|
22
|
+
nextSteps: string[];
|
|
23
|
+
}
|
|
24
|
+
export declare function checkEnvironment(phase?: EnvironmentPhase): EnvironmentReport;
|
|
25
|
+
//# sourceMappingURL=check-environment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-environment.d.ts","sourceRoot":"","sources":["../../src/tools/check-environment.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,QAAQ,CAAC;AAElD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,OAAO,GAAG,WAAW,CAAC;IAC9B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,KAAK,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,gBAAgB,CAAC;IACxB,aAAa,EAAE,iBAAiB,EAAE,CAAC;IACnC,KAAK,EAAE,OAAO,CAAC;IACf,YAAY,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;IAC1C,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;IACpD,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAiCD,wBAAgB,gBAAgB,CAAC,KAAK,GAAE,gBAA0B,GAAG,iBAAiB,CAgQrF"}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
function detectPlatform() {
|
|
6
|
+
const p = process.platform;
|
|
7
|
+
if (p === 'darwin')
|
|
8
|
+
return 'macOS';
|
|
9
|
+
if (p === 'linux')
|
|
10
|
+
return 'linux';
|
|
11
|
+
if (p === 'win32')
|
|
12
|
+
return 'windows';
|
|
13
|
+
return 'unknown';
|
|
14
|
+
}
|
|
15
|
+
function tryExec(cmd, timeout = 10000) {
|
|
16
|
+
try {
|
|
17
|
+
return execSync(cmd, { timeout, stdio: 'pipe', encoding: 'utf-8' }).trim();
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function parseVersion(output, pattern) {
|
|
24
|
+
if (!output)
|
|
25
|
+
return null;
|
|
26
|
+
if (pattern) {
|
|
27
|
+
const match = output.match(pattern);
|
|
28
|
+
return match?.[1] ?? null;
|
|
29
|
+
}
|
|
30
|
+
return output.replace(/^v/, '');
|
|
31
|
+
}
|
|
32
|
+
function meetsMinVersion(version, major) {
|
|
33
|
+
if (!version)
|
|
34
|
+
return false;
|
|
35
|
+
const parsed = parseInt(version.split('.')[0], 10);
|
|
36
|
+
return !isNaN(parsed) && parsed >= major;
|
|
37
|
+
}
|
|
38
|
+
export function checkEnvironment(phase = 'local') {
|
|
39
|
+
const prerequisites = [];
|
|
40
|
+
const suggestions = [];
|
|
41
|
+
const platform = detectPlatform();
|
|
42
|
+
const isLocal = phase === 'local';
|
|
43
|
+
// Node.js
|
|
44
|
+
const nodeOutput = tryExec('node --version');
|
|
45
|
+
const nodeVersion = parseVersion(nodeOutput);
|
|
46
|
+
const nodeOk = meetsMinVersion(nodeVersion, 20);
|
|
47
|
+
prerequisites.push({
|
|
48
|
+
name: 'node',
|
|
49
|
+
required: true,
|
|
50
|
+
status: nodeVersion ? 'found' : 'not_found',
|
|
51
|
+
version: nodeVersion,
|
|
52
|
+
installHint: 'Install Node.js 20+ from https://nodejs.org',
|
|
53
|
+
tier: 'V1',
|
|
54
|
+
phase: 'local',
|
|
55
|
+
description: 'Node.js runs your code. Your app is written in TypeScript, and Node.js is the engine that executes it.',
|
|
56
|
+
whyNeeded: 'Without Node.js, none of the code can run.',
|
|
57
|
+
installCommands: platform === 'macOS'
|
|
58
|
+
? ['brew install node', 'Or download from https://nodejs.org']
|
|
59
|
+
: platform === 'linux'
|
|
60
|
+
? ['sudo apt install nodejs npm', 'Or download from https://nodejs.org']
|
|
61
|
+
: ['Download and install from https://nodejs.org'],
|
|
62
|
+
});
|
|
63
|
+
if (!nodeVersion) {
|
|
64
|
+
suggestions.push('Node.js is not installed. Install Node.js 20+ from https://nodejs.org');
|
|
65
|
+
}
|
|
66
|
+
else if (!nodeOk) {
|
|
67
|
+
suggestions.push(`Node.js ${nodeVersion} is installed but version 20+ is required. Upgrade at https://nodejs.org`);
|
|
68
|
+
}
|
|
69
|
+
// pnpm (recommended, not required)
|
|
70
|
+
const pnpmOutput = tryExec('pnpm --version');
|
|
71
|
+
const pnpmVersion = parseVersion(pnpmOutput);
|
|
72
|
+
prerequisites.push({
|
|
73
|
+
name: 'pnpm',
|
|
74
|
+
required: false,
|
|
75
|
+
status: pnpmVersion ? 'found' : 'not_found',
|
|
76
|
+
version: pnpmVersion,
|
|
77
|
+
installHint: 'Install pnpm: npm install -g pnpm (recommended for monorepos)',
|
|
78
|
+
tier: 'V1',
|
|
79
|
+
phase: 'local',
|
|
80
|
+
description: 'A faster way to install code libraries your app depends on. Recommended but not required.',
|
|
81
|
+
whyNeeded: 'Makes installing dependencies faster and more reliable.',
|
|
82
|
+
installCommands: ['npm install -g pnpm'],
|
|
83
|
+
});
|
|
84
|
+
if (!pnpmVersion) {
|
|
85
|
+
suggestions.push('pnpm is not installed. Recommended for monorepos: npm install -g pnpm');
|
|
86
|
+
}
|
|
87
|
+
// git
|
|
88
|
+
const gitOutput = tryExec('git --version');
|
|
89
|
+
const gitVersion = parseVersion(gitOutput, /git version (\d+\.\d+\.\d+)/);
|
|
90
|
+
prerequisites.push({
|
|
91
|
+
name: 'git',
|
|
92
|
+
required: true,
|
|
93
|
+
status: gitVersion ? 'found' : 'not_found',
|
|
94
|
+
version: gitVersion,
|
|
95
|
+
installHint: 'Install git from https://git-scm.com',
|
|
96
|
+
tier: 'V1',
|
|
97
|
+
phase: 'local',
|
|
98
|
+
description: 'Tracks every change to your code. Like an unlimited undo button for your entire project.',
|
|
99
|
+
whyNeeded: 'Without git, you cannot track changes, undo mistakes, or collaborate.',
|
|
100
|
+
installCommands: platform === 'macOS'
|
|
101
|
+
? ['xcode-select --install']
|
|
102
|
+
: platform === 'linux'
|
|
103
|
+
? ['sudo apt install git']
|
|
104
|
+
: ['Download from https://git-scm.com'],
|
|
105
|
+
});
|
|
106
|
+
if (!gitVersion) {
|
|
107
|
+
suggestions.push('git is not installed. Install from https://git-scm.com');
|
|
108
|
+
}
|
|
109
|
+
// Expo CLI
|
|
110
|
+
const expoOutput = tryExec('npx expo --version');
|
|
111
|
+
const expoVersion = parseVersion(expoOutput);
|
|
112
|
+
prerequisites.push({
|
|
113
|
+
name: 'expo',
|
|
114
|
+
required: true,
|
|
115
|
+
status: expoVersion ? 'found' : 'not_found',
|
|
116
|
+
version: expoVersion,
|
|
117
|
+
installHint: 'Expo is available via npx. Ensure Node.js is installed, then run: npx create-expo-app',
|
|
118
|
+
tier: 'V1',
|
|
119
|
+
phase: 'local',
|
|
120
|
+
description: 'The framework that makes your app work on phones, tablets, and the web from the same code.',
|
|
121
|
+
whyNeeded: 'Without Expo, you cannot create or run the app.',
|
|
122
|
+
installCommands: ['Runs via npx (no separate install needed). Requires Node.js.'],
|
|
123
|
+
});
|
|
124
|
+
if (!expoVersion) {
|
|
125
|
+
suggestions.push('Expo CLI not available. Ensure Node.js is installed — Expo runs via npx.');
|
|
126
|
+
}
|
|
127
|
+
// SST (deploy phase only)
|
|
128
|
+
const sstOutput = tryExec('npx sst version');
|
|
129
|
+
const sstVersion = parseVersion(sstOutput, /(\d+\.\d+\.\d+)/);
|
|
130
|
+
prerequisites.push({
|
|
131
|
+
name: 'sst',
|
|
132
|
+
required: !isLocal,
|
|
133
|
+
status: sstVersion ? 'found' : 'not_found',
|
|
134
|
+
version: sstVersion,
|
|
135
|
+
installHint: 'SST is installed as a dev dependency: pnpm add -D sst (or npx sst)',
|
|
136
|
+
tier: 'V1',
|
|
137
|
+
phase: 'deploy',
|
|
138
|
+
description: isLocal
|
|
139
|
+
? 'Manages your cloud infrastructure. Not needed yet — you can build and run your app locally first.'
|
|
140
|
+
: 'Manages your cloud infrastructure. Tells AWS what servers, databases, and storage your app needs.',
|
|
141
|
+
whyNeeded: isLocal
|
|
142
|
+
? 'Only needed when you are ready to deploy your app to the internet.'
|
|
143
|
+
: 'Without SST, you cannot deploy your app to the internet.',
|
|
144
|
+
installCommands: ['Installed as a project dependency when you create your app.'],
|
|
145
|
+
});
|
|
146
|
+
if (!sstVersion && !isLocal) {
|
|
147
|
+
suggestions.push('SST not found. It installs as a dev dependency: pnpm add -D sst');
|
|
148
|
+
}
|
|
149
|
+
// AWS CLI (deploy phase only)
|
|
150
|
+
const awsOutput = tryExec('aws --version');
|
|
151
|
+
const awsVersion = parseVersion(awsOutput, /aws-cli\/(\d+\.\d+\.\d+)/);
|
|
152
|
+
prerequisites.push({
|
|
153
|
+
name: 'aws-cli',
|
|
154
|
+
required: !isLocal,
|
|
155
|
+
status: awsVersion ? 'found' : 'not_found',
|
|
156
|
+
version: awsVersion,
|
|
157
|
+
installHint: 'Install AWS CLI from https://aws.amazon.com/cli/',
|
|
158
|
+
tier: 'V1',
|
|
159
|
+
phase: 'deploy',
|
|
160
|
+
description: isLocal
|
|
161
|
+
? 'A command-line tool for Amazon Web Services. Not needed yet — you can build and run your app locally first.'
|
|
162
|
+
: 'A command-line tool that lets your computer talk to Amazon Web Services, where your app runs.',
|
|
163
|
+
whyNeeded: isLocal
|
|
164
|
+
? 'Only needed when you are ready to deploy your app to the internet.'
|
|
165
|
+
: 'Without it, you cannot deploy or manage your app on AWS.',
|
|
166
|
+
installCommands: platform === 'macOS'
|
|
167
|
+
? ['brew install awscli']
|
|
168
|
+
: platform === 'linux'
|
|
169
|
+
? ['curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && unzip awscliv2.zip && sudo ./aws/install']
|
|
170
|
+
: ['Download from https://aws.amazon.com/cli/'],
|
|
171
|
+
});
|
|
172
|
+
if (!awsVersion && !isLocal) {
|
|
173
|
+
suggestions.push('AWS CLI is not installed. Install from https://aws.amazon.com/cli/');
|
|
174
|
+
}
|
|
175
|
+
// AWS credentials (deploy phase only)
|
|
176
|
+
const home = homedir();
|
|
177
|
+
const hasAwsCreds = existsSync(join(home, '.aws', 'credentials')) || existsSync(join(home, '.aws', 'config'));
|
|
178
|
+
prerequisites.push({
|
|
179
|
+
name: 'aws-credentials',
|
|
180
|
+
required: !isLocal,
|
|
181
|
+
status: hasAwsCreds ? 'found' : 'not_found',
|
|
182
|
+
version: null,
|
|
183
|
+
installHint: 'Read everystack://aws-setup for step-by-step AWS account and credential setup.',
|
|
184
|
+
tier: 'V1',
|
|
185
|
+
phase: 'deploy',
|
|
186
|
+
description: isLocal
|
|
187
|
+
? 'A password file for your AWS account. Not needed yet — you can build and run your app locally first.'
|
|
188
|
+
: 'A password file that proves your computer is allowed to deploy to your AWS account.',
|
|
189
|
+
whyNeeded: isLocal
|
|
190
|
+
? 'Only needed when you are ready to deploy your app to the internet.'
|
|
191
|
+
: 'Without credentials, AWS will reject all deploy commands.',
|
|
192
|
+
installCommands: ['Read everystack://aws-setup for full setup guide', 'aws configure'],
|
|
193
|
+
});
|
|
194
|
+
if (!hasAwsCreds && !isLocal) {
|
|
195
|
+
suggestions.push("AWS credentials not configured. Read everystack://aws-setup for step-by-step setup, or run 'aws configure' if you already have access keys.");
|
|
196
|
+
}
|
|
197
|
+
// PostgreSQL
|
|
198
|
+
const psqlOutput = tryExec('psql --version');
|
|
199
|
+
const psqlVersion = parseVersion(psqlOutput, /psql \(PostgreSQL\) (\d+\.\d+)/);
|
|
200
|
+
const pgReadyOutput = psqlVersion ? tryExec('pg_isready') : null;
|
|
201
|
+
const pgServiceUp = pgReadyOutput !== null && pgReadyOutput.includes('accepting connections');
|
|
202
|
+
prerequisites.push({
|
|
203
|
+
name: 'postgresql',
|
|
204
|
+
required: true,
|
|
205
|
+
status: psqlVersion ? 'found' : 'not_found',
|
|
206
|
+
version: psqlVersion,
|
|
207
|
+
serviceRunning: psqlVersion ? pgServiceUp : undefined,
|
|
208
|
+
installHint: 'Install PostgreSQL: brew install postgresql (macOS) or https://www.postgresql.org/download/',
|
|
209
|
+
tier: 'V2',
|
|
210
|
+
phase: 'local',
|
|
211
|
+
description: 'A database that stores your app data: users, posts, recipes, or whatever your app needs.',
|
|
212
|
+
whyNeeded: 'Without PostgreSQL, your app cannot have user accounts or saved data (needed for V2+).',
|
|
213
|
+
installCommands: platform === 'macOS'
|
|
214
|
+
? ['brew install postgresql@16', 'brew services start postgresql@16']
|
|
215
|
+
: platform === 'linux'
|
|
216
|
+
? ['sudo apt install postgresql', 'sudo systemctl start postgresql']
|
|
217
|
+
: ['Download from https://www.postgresql.org/download/'],
|
|
218
|
+
});
|
|
219
|
+
if (!psqlVersion) {
|
|
220
|
+
suggestions.push('PostgreSQL is not installed. Needed for V2+ (database tier). Install: brew install postgresql');
|
|
221
|
+
}
|
|
222
|
+
else if (!pgServiceUp) {
|
|
223
|
+
suggestions.push(platform === 'macOS'
|
|
224
|
+
? 'PostgreSQL is installed but the service is not running. Start it: brew services start postgresql@16'
|
|
225
|
+
: platform === 'linux'
|
|
226
|
+
? 'PostgreSQL is installed but the service is not running. Start it: sudo systemctl start postgresql'
|
|
227
|
+
: 'PostgreSQL is installed but the service is not running. Start the PostgreSQL service.');
|
|
228
|
+
}
|
|
229
|
+
// Determine readiness — in local phase, only local-phase prerequisites count
|
|
230
|
+
const relevant = isLocal
|
|
231
|
+
? prerequisites.filter(c => c.phase === 'local')
|
|
232
|
+
: prerequisites;
|
|
233
|
+
const v1Required = relevant.filter(c => c.tier === 'V1' && c.required);
|
|
234
|
+
const v2Required = relevant.filter(c => c.tier === 'V2' && c.required);
|
|
235
|
+
const v1Ready = nodeOk && v1Required.every(c => c.status === 'found');
|
|
236
|
+
const v2Ready = v1Ready && v2Required.every(c => c.status === 'found');
|
|
237
|
+
// V3 uses same prerequisites as V2 (adds SQS/S3 which are AWS services, no local tooling)
|
|
238
|
+
const v3Ready = v2Ready;
|
|
239
|
+
const readyForTier = v3Ready ? 'V3' : v2Ready ? 'V2' : v1Ready ? 'V1' : 'none';
|
|
240
|
+
// Build nextSteps
|
|
241
|
+
const nextSteps = [];
|
|
242
|
+
const missing = relevant.filter(p => p.required && p.status === 'not_found');
|
|
243
|
+
if (missing.length === 0) {
|
|
244
|
+
if (isLocal) {
|
|
245
|
+
nextSteps.push('All local development prerequisites installed. Ready to create and run your app.');
|
|
246
|
+
// Hint about deploy-phase tools if not yet installed
|
|
247
|
+
const deployMissing = prerequisites.filter(p => p.phase === 'deploy' && p.status === 'not_found');
|
|
248
|
+
if (deployMissing.length > 0) {
|
|
249
|
+
nextSteps.push('When you are ready to put your app on the internet, run check_environment with phase "deploy" to see what else is needed.');
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
nextSteps.push('All prerequisites installed. Ready to create a new app.');
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
const installOrder = isLocal
|
|
258
|
+
? ['node', 'git', 'pnpm', 'expo', 'postgresql']
|
|
259
|
+
: ['node', 'git', 'pnpm', 'aws-cli', 'aws-credentials', 'expo', 'sst', 'postgresql'];
|
|
260
|
+
const ordered = installOrder
|
|
261
|
+
.map(name => missing.find(p => p.name === name))
|
|
262
|
+
.filter((p) => p !== undefined);
|
|
263
|
+
for (const p of ordered) {
|
|
264
|
+
if (p.tier === 'V2') {
|
|
265
|
+
nextSteps.push(`Optional for now: Install ${p.name} — ${p.description.split('.')[0]}. Only needed for apps with user accounts or a database (V2+).`);
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
nextSteps.push(`Install ${p.name} — ${p.description.split('.')[0]}. ${p.installCommands[0]}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
phase,
|
|
274
|
+
prerequisites,
|
|
275
|
+
ready: readyForTier !== 'none',
|
|
276
|
+
readyForTier,
|
|
277
|
+
suggestions,
|
|
278
|
+
platform,
|
|
279
|
+
nextSteps,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AASzE,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAcrD"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { checkEnvironment } from './check-environment.js';
|
|
3
|
+
// NOTE (Brick 0): the legacy regex tools — project_status, schema_analyze,
|
|
4
|
+
// project_validate — are intentionally NOT registered. They predate the v3
|
|
5
|
+
// Model/Module surface and return wrong answers on Model-based apps (e.g. they
|
|
6
|
+
// report a securely-derived handler config as "all tables accessible"). They are
|
|
7
|
+
// rebuilt on @everystack/model in Brick 1 and re-registered there. Their source
|
|
8
|
+
// files remain for that rewrite to reference.
|
|
9
|
+
export function registerTools(server) {
|
|
10
|
+
server.tool('check_environment', 'Verify development prerequisites are installed (Node.js, pnpm, git, Expo, SST, AWS CLI, PostgreSQL). Run this first when a user wants to start a new project or is having setup issues. Use phase "local" (default) to check only what is needed to build and run locally. Use phase "deploy" when ready to deploy to AWS.', {
|
|
11
|
+
phase: z.enum(['local', 'deploy']).optional().describe('Check prerequisites for local development (default) or AWS deployment'),
|
|
12
|
+
}, async ({ phase }) => {
|
|
13
|
+
const report = checkEnvironment(phase ?? 'local');
|
|
14
|
+
return {
|
|
15
|
+
content: [{ type: 'text', text: JSON.stringify(report, null, 2) }],
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
interface ProjectStatus {
|
|
2
|
+
tier: 'V1' | 'V2' | 'V3' | 'unknown';
|
|
3
|
+
tierExplanation: string;
|
|
4
|
+
packages: {
|
|
5
|
+
installed: string[];
|
|
6
|
+
missing: string[];
|
|
7
|
+
};
|
|
8
|
+
files: {
|
|
9
|
+
sstConfig: boolean;
|
|
10
|
+
schemaDir: boolean;
|
|
11
|
+
schemaFiles: string[];
|
|
12
|
+
migrationsDir: boolean;
|
|
13
|
+
serverDir: boolean;
|
|
14
|
+
handlerFile: string | null;
|
|
15
|
+
workerFile: string | null;
|
|
16
|
+
imageFile: string | null;
|
|
17
|
+
seedFile: string | null;
|
|
18
|
+
};
|
|
19
|
+
deployment: {
|
|
20
|
+
sstOutputs: boolean;
|
|
21
|
+
stage: string | null;
|
|
22
|
+
secrets: string[];
|
|
23
|
+
};
|
|
24
|
+
warnings: string[];
|
|
25
|
+
}
|
|
26
|
+
export declare function analyzeProjectStatus(projectPath: string): ProjectStatus;
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=project-status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-status.d.ts","sourceRoot":"","sources":["../../src/tools/project-status.ts"],"names":[],"mappings":"AAUA,UAAU,aAAa;IACrB,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,SAAS,CAAC;IACrC,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE;QACR,SAAS,EAAE,MAAM,EAAE,CAAC;QACpB,OAAO,EAAE,MAAM,EAAE,CAAC;KACnB,CAAC;IACF,KAAK,EAAE;QACL,SAAS,EAAE,OAAO,CAAC;QACnB,SAAS,EAAE,OAAO,CAAC;QACnB,WAAW,EAAE,MAAM,EAAE,CAAC;QACtB,aAAa,EAAE,OAAO,CAAC;QACvB,SAAS,EAAE,OAAO,CAAC;QACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;KACzB,CAAC;IACF,UAAU,EAAE;QACV,UAAU,EAAE,OAAO,CAAC;QACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QACrB,OAAO,EAAE,MAAM,EAAE,CAAC;KACnB,CAAC;IACF,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAgCD,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,aAAa,CAoHvE"}
|