@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.
Files changed (104) hide show
  1. package/dist/adding-database.md +36 -23
  2. package/dist/cli.md +44 -3
  3. package/dist/core.md +19 -1
  4. package/dist/database-operations.md +236 -0
  5. package/dist/derived-objects.md +225 -0
  6. package/dist/gates/detectors/embedded-data-bundle.d.ts +13 -0
  7. package/dist/gates/detectors/embedded-data-bundle.d.ts.map +1 -0
  8. package/dist/gates/detectors/embedded-data-bundle.js +56 -0
  9. package/dist/gates/detectors/hand-written-migration.d.ts +24 -0
  10. package/dist/gates/detectors/hand-written-migration.d.ts.map +1 -0
  11. package/dist/gates/detectors/hand-written-migration.js +54 -0
  12. package/dist/gates/detectors/secret-in-public-env.d.ts +12 -0
  13. package/dist/gates/detectors/secret-in-public-env.d.ts.map +1 -0
  14. package/dist/gates/detectors/secret-in-public-env.js +38 -0
  15. package/dist/gates/engine.d.ts +28 -0
  16. package/dist/gates/engine.d.ts.map +1 -0
  17. package/dist/gates/engine.js +74 -0
  18. package/dist/gates/registry.d.ts +14 -0
  19. package/dist/gates/registry.d.ts.map +1 -0
  20. package/dist/gates/registry.js +21 -0
  21. package/dist/gates/telemetry.d.ts +47 -0
  22. package/dist/gates/telemetry.d.ts.map +1 -0
  23. package/dist/gates/telemetry.js +121 -0
  24. package/dist/gates/types.d.ts +67 -0
  25. package/dist/gates/types.d.ts.map +1 -0
  26. package/dist/gates/types.js +14 -0
  27. package/dist/governance/cli.d.ts +57 -0
  28. package/dist/governance/cli.d.ts.map +1 -0
  29. package/dist/governance/cli.js +169 -0
  30. package/dist/governance/grounding.d.ts +78 -0
  31. package/dist/governance/grounding.d.ts.map +1 -0
  32. package/dist/governance/grounding.js +299 -0
  33. package/dist/index.cjs +119 -107
  34. package/dist/index.d.ts +2 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +94 -0
  37. package/dist/project-claude-md.md +35 -18
  38. package/dist/prompts/add-feature.d.ts +3 -0
  39. package/dist/prompts/add-feature.d.ts.map +1 -0
  40. package/dist/prompts/add-feature.js +154 -0
  41. package/dist/prompts/claude-md.d.ts +12 -0
  42. package/dist/prompts/claude-md.d.ts.map +1 -0
  43. package/dist/prompts/claude-md.js +87 -0
  44. package/dist/prompts/debug.d.ts +3 -0
  45. package/dist/prompts/debug.d.ts.map +1 -0
  46. package/dist/prompts/debug.js +129 -0
  47. package/dist/prompts/deploy.d.ts +3 -0
  48. package/dist/prompts/deploy.d.ts.map +1 -0
  49. package/dist/prompts/deploy.js +118 -0
  50. package/dist/prompts/design-schema.d.ts +3 -0
  51. package/dist/prompts/design-schema.d.ts.map +1 -0
  52. package/dist/prompts/design-schema.js +97 -0
  53. package/dist/prompts/governance-setup.d.ts +9 -0
  54. package/dist/prompts/governance-setup.d.ts.map +1 -0
  55. package/dist/prompts/governance-setup.js +76 -0
  56. package/dist/prompts/index.d.ts +3 -0
  57. package/dist/prompts/index.d.ts.map +1 -0
  58. package/dist/prompts/index.js +20 -0
  59. package/dist/prompts/new-app.d.ts +3 -0
  60. package/dist/prompts/new-app.d.ts.map +1 -0
  61. package/dist/prompts/new-app.js +203 -0
  62. package/dist/prompts/runbook.d.ts +12 -0
  63. package/dist/prompts/runbook.d.ts.map +1 -0
  64. package/dist/prompts/runbook.js +70 -0
  65. package/dist/prompts/secure.d.ts +3 -0
  66. package/dist/prompts/secure.d.ts.map +1 -0
  67. package/dist/prompts/secure.js +219 -0
  68. package/dist/resources/index.d.ts +12 -0
  69. package/dist/resources/index.d.ts.map +1 -0
  70. package/dist/resources/index.js +182 -0
  71. package/dist/schema-patterns.md +92 -106
  72. package/dist/tools/check-environment.d.ts +25 -0
  73. package/dist/tools/check-environment.d.ts.map +1 -0
  74. package/dist/tools/check-environment.js +281 -0
  75. package/dist/tools/index.d.ts +3 -0
  76. package/dist/tools/index.d.ts.map +1 -0
  77. package/dist/tools/index.js +18 -0
  78. package/dist/tools/project-status.d.ts +28 -0
  79. package/dist/tools/project-status.d.ts.map +1 -0
  80. package/dist/tools/project-status.js +138 -0
  81. package/dist/tools/project-validate.d.ts +19 -0
  82. package/dist/tools/project-validate.d.ts.map +1 -0
  83. package/dist/tools/project-validate.js +323 -0
  84. package/dist/tools/schema-analyze.d.ts +46 -0
  85. package/dist/tools/schema-analyze.d.ts.map +1 -0
  86. package/dist/tools/schema-analyze.js +336 -0
  87. package/package.json +3 -3
  88. package/src/gates/detectors/hand-written-migration.ts +29 -13
  89. package/src/index.ts +1 -1
  90. package/src/prompts/add-feature.ts +4 -4
  91. package/src/prompts/claude-md.ts +4 -2
  92. package/src/prompts/debug.ts +3 -4
  93. package/src/prompts/deploy.ts +17 -8
  94. package/src/prompts/design-schema.ts +45 -59
  95. package/src/prompts/new-app.ts +21 -19
  96. package/src/prompts/secure.ts +3 -3
  97. package/src/resources/adding-database.md +36 -23
  98. package/src/resources/cli.md +44 -3
  99. package/src/resources/core.md +19 -1
  100. package/src/resources/database-operations.md +236 -0
  101. package/src/resources/derived-objects.md +225 -0
  102. package/src/resources/index.ts +15 -1
  103. package/src/resources/project-claude-md.md +35 -18
  104. package/src/resources/schema-patterns.md +92 -106
@@ -0,0 +1,138 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ const V1_PACKAGES = ['@everystack/server', '@everystack/cli', '@everystack/ui'];
4
+ const V2_PACKAGES = ['@everystack/api', '@everystack/auth', '@everystack/admin', '@everystack/logging', '@everystack/security', '@everystack/query'];
5
+ const V3_PACKAGES = ['@everystack/jobs', '@everystack/storage', '@everystack/images'];
6
+ function readJson(path) {
7
+ if (!existsSync(path))
8
+ return null;
9
+ try {
10
+ return JSON.parse(readFileSync(path, 'utf-8'));
11
+ }
12
+ catch {
13
+ return null;
14
+ }
15
+ }
16
+ function detectTier(installed) {
17
+ const hasV3 = V3_PACKAGES.some((p) => installed.includes(p));
18
+ const hasV2 = V2_PACKAGES.some((p) => installed.includes(p));
19
+ const hasV1 = V1_PACKAGES.some((p) => installed.includes(p));
20
+ if (hasV3) {
21
+ return { tier: 'V3', explanation: `Full platform — has V3 packages (${V3_PACKAGES.filter((p) => installed.includes(p)).join(', ')})` };
22
+ }
23
+ if (hasV2) {
24
+ return { tier: 'V2', explanation: `Dynamic — has V2 packages (${V2_PACKAGES.filter((p) => installed.includes(p)).join(', ')})` };
25
+ }
26
+ if (hasV1) {
27
+ return { tier: 'V1', explanation: `Static — has V1 packages (${V1_PACKAGES.filter((p) => installed.includes(p)).join(', ')})` };
28
+ }
29
+ return { tier: 'unknown', explanation: 'No everystack packages found in dependencies' };
30
+ }
31
+ export function analyzeProjectStatus(projectPath) {
32
+ const warnings = [];
33
+ // Read package.json
34
+ const pkg = readJson(join(projectPath, 'package.json'));
35
+ if (!pkg) {
36
+ return {
37
+ tier: 'unknown',
38
+ tierExplanation: 'No package.json found at project path',
39
+ packages: { installed: [], missing: [] },
40
+ files: {
41
+ sstConfig: false, schemaDir: false, schemaFiles: [],
42
+ migrationsDir: false, serverDir: false, handlerFile: null,
43
+ workerFile: null, imageFile: null, seedFile: null,
44
+ },
45
+ deployment: { sstOutputs: false, stage: null, secrets: [] },
46
+ warnings: [
47
+ 'No package.json found — is this the correct project path?',
48
+ 'No project here yet? Describe what you want to build and Claude will help create one.',
49
+ ],
50
+ };
51
+ }
52
+ // Collect everystack dependencies
53
+ const allDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
54
+ const installed = Object.keys(allDeps).filter((d) => d.startsWith('@everystack/'));
55
+ const allKnown = [...V1_PACKAGES, ...V2_PACKAGES, ...V3_PACKAGES];
56
+ const missing = allKnown.filter((p) => !installed.includes(p));
57
+ // Detect tier
58
+ const { tier, explanation } = detectTier(installed);
59
+ // Check key files/directories
60
+ const sstConfig = existsSync(join(projectPath, 'sst.config.ts'));
61
+ const schemaDir = existsSync(join(projectPath, 'db'));
62
+ const migrationsDir = existsSync(join(projectPath, 'db', 'migrations')) ||
63
+ existsSync(join(projectPath, 'server', 'drizzle'));
64
+ const serverDir = existsSync(join(projectPath, 'server'));
65
+ // Find schema files
66
+ const schemaFiles = [];
67
+ if (schemaDir) {
68
+ const schemaPath = join(projectPath, 'db', 'schema.ts');
69
+ if (existsSync(schemaPath))
70
+ schemaFiles.push('db/schema.ts');
71
+ const indexPath = join(projectPath, 'db', 'index.ts');
72
+ if (existsSync(indexPath))
73
+ schemaFiles.push('db/index.ts');
74
+ }
75
+ // Check handler files
76
+ const handlerCandidates = ['server/api.ts', 'server/handler.ts', 'server/index.ts'];
77
+ const handlerFile = handlerCandidates.find((f) => existsSync(join(projectPath, f))) ?? null;
78
+ const workerFile = existsSync(join(projectPath, 'server', 'worker.ts')) ? 'server/worker.ts' : null;
79
+ const imageFile = existsSync(join(projectPath, 'server', 'image.ts')) ? 'server/image.ts' : null;
80
+ const seedFile = existsSync(join(projectPath, 'db', 'seed.ts')) ? 'db/seed.ts' : null;
81
+ // Check deployment state
82
+ const sstOutputsPath = join(projectPath, '.sst', 'outputs.json');
83
+ const sstOutputs = existsSync(sstOutputsPath);
84
+ let stage = null;
85
+ const secrets = [];
86
+ if (sstOutputs) {
87
+ const outputs = readJson(sstOutputsPath);
88
+ if (outputs) {
89
+ // SST outputs are keyed by stage
90
+ const keys = Object.keys(outputs);
91
+ if (keys.length > 0)
92
+ stage = keys[0];
93
+ }
94
+ }
95
+ // Check for sst secret files
96
+ const sstDir = join(projectPath, '.sst');
97
+ if (existsSync(sstDir)) {
98
+ const secretCandidates = ['JwtSecret', 'DatabaseUrl', 'GoogleClientId', 'GoogleClientSecret', 'AppleClientId'];
99
+ for (const s of secretCandidates) {
100
+ if (existsSync(join(sstDir, 'secrets', s))) {
101
+ secrets.push(s);
102
+ }
103
+ }
104
+ }
105
+ // Warnings
106
+ if (tier !== 'unknown' && tier !== 'V1' && !schemaDir) {
107
+ warnings.push('V2+ project but no db/ directory found — schema is required for PostgREST');
108
+ }
109
+ if (tier !== 'unknown' && tier !== 'V1' && schemaFiles.length === 0) {
110
+ warnings.push('No schema.ts found in db/ — create your Drizzle schema');
111
+ }
112
+ if (tier !== 'unknown' && !sstConfig) {
113
+ warnings.push('No sst.config.ts found — required for deployment');
114
+ }
115
+ if (installed.includes('@everystack/api') && !handlerFile) {
116
+ warnings.push('@everystack/api installed but no handler file found in server/');
117
+ }
118
+ if (installed.includes('@everystack/jobs') && !workerFile) {
119
+ warnings.push('@everystack/jobs installed but no server/worker.ts found');
120
+ }
121
+ if (installed.includes('@everystack/images') && !imageFile) {
122
+ warnings.push('@everystack/images installed but no server/image.ts found');
123
+ }
124
+ if (tier !== 'unknown' && tier !== 'V1' && !seedFile) {
125
+ warnings.push('No db/seed.ts found — recommended for development');
126
+ }
127
+ return {
128
+ tier,
129
+ tierExplanation: explanation,
130
+ packages: { installed, missing },
131
+ files: {
132
+ sstConfig, schemaDir, schemaFiles, migrationsDir,
133
+ serverDir, handlerFile, workerFile, imageFile, seedFile,
134
+ },
135
+ deployment: { sstOutputs, stage, secrets },
136
+ warnings,
137
+ };
138
+ }
@@ -0,0 +1,19 @@
1
+ type Severity = 'error' | 'warning' | 'info';
2
+ interface ValidationResult {
3
+ severity: Severity;
4
+ category: string;
5
+ message: string;
6
+ file?: string;
7
+ fix?: string;
8
+ }
9
+ interface ValidationReport {
10
+ results: ValidationResult[];
11
+ summary: {
12
+ errors: number;
13
+ warnings: number;
14
+ info: number;
15
+ };
16
+ }
17
+ export declare function validateProject(projectPath: string): ValidationReport;
18
+ export {};
19
+ //# sourceMappingURL=project-validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-validate.d.ts","sourceRoot":"","sources":["../../src/tools/project-validate.ts"],"names":[],"mappings":"AAGA,KAAK,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAE7C,UAAU,gBAAgB;IACxB,QAAQ,EAAE,QAAQ,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,UAAU,gBAAgB;IACxB,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,OAAO,EAAE;QACP,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AAqUD,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,gBAAgB,CAuBrE"}
@@ -0,0 +1,323 @@
1
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ function readFileSafe(path) {
4
+ if (!existsSync(path))
5
+ return null;
6
+ try {
7
+ return readFileSync(path, 'utf-8');
8
+ }
9
+ catch {
10
+ return null;
11
+ }
12
+ }
13
+ function readJsonSafe(path) {
14
+ const content = readFileSafe(path);
15
+ if (!content)
16
+ return null;
17
+ try {
18
+ return JSON.parse(content);
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ }
24
+ function checkPackageJson(projectPath, results) {
25
+ const pkg = readJsonSafe(join(projectPath, 'package.json'));
26
+ if (!pkg) {
27
+ results.push({
28
+ severity: 'error',
29
+ category: 'project',
30
+ message: 'No package.json found',
31
+ fix: 'Run `npm init` or create package.json manually',
32
+ });
33
+ return;
34
+ }
35
+ const deps = {
36
+ ...(pkg.dependencies || {}),
37
+ ...(pkg.devDependencies || {}),
38
+ };
39
+ // Check for required peer dependencies
40
+ if (deps['@everystack/api'] && !deps['drizzle-orm']) {
41
+ results.push({
42
+ severity: 'error',
43
+ category: 'dependencies',
44
+ message: '@everystack/api requires drizzle-orm as a peer dependency',
45
+ fix: 'Run `pnpm add drizzle-orm drizzle-kit`',
46
+ });
47
+ }
48
+ if (deps['@everystack/server'] && !deps['sst']) {
49
+ results.push({
50
+ severity: 'warning',
51
+ category: 'dependencies',
52
+ message: '@everystack/server expects sst as a peer dependency for deployment',
53
+ fix: 'Run `pnpm add -D sst`',
54
+ });
55
+ }
56
+ if (deps['@everystack/query'] && !deps['@tanstack/react-query']) {
57
+ results.push({
58
+ severity: 'error',
59
+ category: 'dependencies',
60
+ message: '@everystack/query requires @tanstack/react-query as a peer dependency',
61
+ fix: 'Run `pnpm add @tanstack/react-query`',
62
+ });
63
+ }
64
+ }
65
+ function checkSecurityConfig(projectPath, results) {
66
+ // Check for exposed secrets in common locations
67
+ const dangerousFiles = ['.env', '.env.local', '.env.production'];
68
+ for (const f of dangerousFiles) {
69
+ const content = readFileSafe(join(projectPath, f));
70
+ if (content) {
71
+ if (content.includes('JWT_SECRET') || content.includes('DATABASE_URL')) {
72
+ results.push({
73
+ severity: 'warning',
74
+ category: 'security',
75
+ message: `${f} contains sensitive values — use SST secrets instead`,
76
+ file: f,
77
+ fix: 'Run `pnpm sst secret set JwtSecret "value" --stage dev` and remove from .env',
78
+ });
79
+ }
80
+ // Check gitignore
81
+ const gitignore = readFileSafe(join(projectPath, '.gitignore'));
82
+ if (gitignore && !gitignore.includes(f)) {
83
+ results.push({
84
+ severity: 'error',
85
+ category: 'security',
86
+ message: `${f} is not in .gitignore — secrets may be committed`,
87
+ file: '.gitignore',
88
+ fix: `Add '${f}' to .gitignore`,
89
+ });
90
+ }
91
+ }
92
+ }
93
+ // Check for hardcoded secrets in handler files
94
+ const serverFiles = ['server/api.ts', 'server/handler.ts', 'server/plugins/api.ts'];
95
+ for (const f of serverFiles) {
96
+ const content = readFileSafe(join(projectPath, f));
97
+ if (content) {
98
+ if (/['"](?:sk_|pk_|secret_)[a-zA-Z0-9]+['"]/.test(content)) {
99
+ results.push({
100
+ severity: 'error',
101
+ category: 'security',
102
+ message: `Possible hardcoded API key in ${f}`,
103
+ file: f,
104
+ fix: 'Move secrets to SST secret management',
105
+ });
106
+ }
107
+ }
108
+ }
109
+ }
110
+ function checkHandlerConfig(projectPath, results) {
111
+ const handlerCandidates = [
112
+ 'server/plugins/api.ts',
113
+ 'server/api.ts',
114
+ 'server/handler.ts',
115
+ ];
116
+ let handlerSource = null;
117
+ let handlerFile = null;
118
+ for (const f of handlerCandidates) {
119
+ const content = readFileSafe(join(projectPath, f));
120
+ if (content && content.includes('createHandler')) {
121
+ handlerSource = content;
122
+ handlerFile = f;
123
+ break;
124
+ }
125
+ }
126
+ if (!handlerSource || !handlerFile)
127
+ return;
128
+ // pgSettings is required for RLS
129
+ if (!handlerSource.includes('pgSettings')) {
130
+ results.push({
131
+ severity: 'warning',
132
+ category: 'security',
133
+ message: 'No pgSettings configured — RLS policies will not receive JWT claims',
134
+ file: handlerFile,
135
+ fix: 'Add pgSettings to your createHandler() config. Read everystack://security for the pattern.',
136
+ });
137
+ }
138
+ // Check for auth configuration
139
+ if (!handlerSource.includes('verifyToken')) {
140
+ results.push({
141
+ severity: 'warning',
142
+ category: 'security',
143
+ message: 'No auth.verifyToken configured — all routes are unauthenticated',
144
+ file: handlerFile,
145
+ fix: 'Add auth.verifyToken to createHandler() config',
146
+ });
147
+ }
148
+ // exposedTables should be set
149
+ if (!handlerSource.includes('exposedTables')) {
150
+ results.push({
151
+ severity: 'warning',
152
+ category: 'security',
153
+ message: 'No exposedTables set — all schema tables are accessible via API',
154
+ file: handlerFile,
155
+ fix: 'Add exposedTables to restrict which tables are accessible',
156
+ });
157
+ }
158
+ // maxEmbedDepth should be set
159
+ if (!handlerSource.includes('maxEmbedDepth')) {
160
+ results.push({
161
+ severity: 'info',
162
+ category: 'security',
163
+ message: 'maxEmbedDepth not set — using default (3). Set explicitly to prevent deep query DoS.',
164
+ file: handlerFile,
165
+ });
166
+ }
167
+ // rowOwnership for user-scoped tables
168
+ if (!handlerSource.includes('rowOwnership')) {
169
+ results.push({
170
+ severity: 'info',
171
+ category: 'security',
172
+ message: 'No rowOwnership configured — consider it for user-scoped mutation control',
173
+ file: handlerFile,
174
+ fix: 'Add rowOwnership for tables where users should only modify their own rows',
175
+ });
176
+ }
177
+ }
178
+ function checkSchemaConventions(projectPath, results) {
179
+ const schemaPath = join(projectPath, 'db', 'schema.ts');
180
+ const source = readFileSafe(schemaPath);
181
+ if (!source)
182
+ return;
183
+ // Check for UUID primary keys (recommended)
184
+ const serialPkRegex = /serial\(['"]id['"]\)\.primaryKey/;
185
+ if (serialPkRegex.test(source)) {
186
+ results.push({
187
+ severity: 'info',
188
+ category: 'conventions',
189
+ message: 'Using serial IDs — UUID primary keys are recommended for distributed systems',
190
+ file: 'db/schema.ts',
191
+ });
192
+ }
193
+ // Check for timestamps
194
+ const tableNames = [...source.matchAll(/export\s+const\s+(\w+)\s*=\s*pgTable/g)].map((m) => m[1]);
195
+ for (const name of tableNames) {
196
+ // Find the table definition block
197
+ const tableRegex = new RegExp(`export\\s+const\\s+${name}\\s*=\\s*pgTable\\([^,]+,\\s*\\{([^}]+)\\}`);
198
+ const match = source.match(tableRegex);
199
+ if (match) {
200
+ const block = match[1];
201
+ if (!block.includes('created_at') && !block.includes('createdAt')) {
202
+ results.push({
203
+ severity: 'info',
204
+ category: 'conventions',
205
+ message: `Table '${name}' has no createdAt timestamp`,
206
+ file: 'db/schema.ts',
207
+ });
208
+ }
209
+ }
210
+ }
211
+ }
212
+ function checkMigrations(projectPath, results) {
213
+ const migrationsDir = join(projectPath, 'db', 'migrations');
214
+ const serverDrizzle = join(projectPath, 'server', 'drizzle');
215
+ const hasDbMigrations = existsSync(migrationsDir);
216
+ const hasServerDrizzle = existsSync(serverDrizzle);
217
+ if (!hasDbMigrations && !hasServerDrizzle) {
218
+ const hasSchema = existsSync(join(projectPath, 'db', 'schema.ts'));
219
+ if (hasSchema) {
220
+ results.push({
221
+ severity: 'warning',
222
+ category: 'database',
223
+ message: 'Schema exists but no migrations directory found',
224
+ fix: 'Run `npx drizzle-kit generate` to create migrations',
225
+ });
226
+ }
227
+ return;
228
+ }
229
+ // Check for RLS in migrations
230
+ const dir = hasServerDrizzle ? serverDrizzle : migrationsDir;
231
+ try {
232
+ const files = readdirSync(dir).filter((f) => f.endsWith('.sql'));
233
+ let hasRls = false;
234
+ for (const f of files) {
235
+ const content = readFileSafe(join(dir, f));
236
+ if (content && content.includes('ENABLE ROW LEVEL SECURITY')) {
237
+ hasRls = true;
238
+ break;
239
+ }
240
+ }
241
+ if (!hasRls && files.length > 0) {
242
+ results.push({
243
+ severity: 'warning',
244
+ category: 'security',
245
+ message: 'No RLS policies found in migrations — tables are not protected at database level',
246
+ fix: 'Add RLS policies via custom SQL migrations. Read everystack://security for templates.',
247
+ });
248
+ }
249
+ }
250
+ catch {
251
+ // ignore
252
+ }
253
+ }
254
+ function checkDeploymentReadiness(projectPath, results) {
255
+ // SST config
256
+ if (!existsSync(join(projectPath, 'sst.config.ts'))) {
257
+ results.push({
258
+ severity: 'info',
259
+ category: 'deployment',
260
+ message: 'No sst.config.ts — project is not configured for AWS deployment',
261
+ fix: 'Create sst.config.ts following everystack://deployment',
262
+ });
263
+ return;
264
+ }
265
+ const sstConfig = readFileSafe(join(projectPath, 'sst.config.ts'));
266
+ if (sstConfig) {
267
+ // Check for VPC configuration
268
+ if (!sstConfig.includes('Vpc') && !sstConfig.includes('vpc')) {
269
+ results.push({
270
+ severity: 'info',
271
+ category: 'deployment',
272
+ message: 'No VPC configured in sst.config.ts — RDS requires a VPC',
273
+ });
274
+ }
275
+ }
276
+ }
277
+ function checkFileConventions(projectPath, results) {
278
+ // Check for default exports (everystack uses named exports)
279
+ const srcDirs = ['server', 'db', 'lib'];
280
+ for (const dir of srcDirs) {
281
+ const dirPath = join(projectPath, dir);
282
+ if (!existsSync(dirPath))
283
+ continue;
284
+ try {
285
+ const files = readdirSync(dirPath).filter((f) => f.endsWith('.ts') && !f.endsWith('.test.ts'));
286
+ for (const f of files) {
287
+ const content = readFileSafe(join(dirPath, f));
288
+ if (content && /^export\s+default\s/m.test(content)) {
289
+ results.push({
290
+ severity: 'info',
291
+ category: 'conventions',
292
+ message: `${dir}/${f} uses default export — everystack convention prefers named exports`,
293
+ file: `${dir}/${f}`,
294
+ });
295
+ }
296
+ }
297
+ }
298
+ catch {
299
+ // ignore
300
+ }
301
+ }
302
+ }
303
+ export function validateProject(projectPath) {
304
+ const results = [];
305
+ checkPackageJson(projectPath, results);
306
+ checkSecurityConfig(projectPath, results);
307
+ checkHandlerConfig(projectPath, results);
308
+ checkSchemaConventions(projectPath, results);
309
+ checkMigrations(projectPath, results);
310
+ checkDeploymentReadiness(projectPath, results);
311
+ checkFileConventions(projectPath, results);
312
+ // Sort: errors first, then warnings, then info
313
+ const order = { error: 0, warning: 1, info: 2 };
314
+ results.sort((a, b) => order[a.severity] - order[b.severity]);
315
+ return {
316
+ results,
317
+ summary: {
318
+ errors: results.filter((r) => r.severity === 'error').length,
319
+ warnings: results.filter((r) => r.severity === 'warning').length,
320
+ info: results.filter((r) => r.severity === 'info').length,
321
+ },
322
+ };
323
+ }
@@ -0,0 +1,46 @@
1
+ interface ColumnInfo {
2
+ name: string;
3
+ dbName: string;
4
+ type: string;
5
+ modifiers: string[];
6
+ }
7
+ interface TableInfo {
8
+ name: string;
9
+ exportName: string;
10
+ columns: ColumnInfo[];
11
+ file: string;
12
+ }
13
+ interface RelationInfo {
14
+ name: string;
15
+ table: string;
16
+ type: 'one' | 'many';
17
+ fields: string[];
18
+ references: string[];
19
+ relationName?: string;
20
+ }
21
+ interface HandlerConfig {
22
+ exposedTables: string[];
23
+ hiddenColumns: Record<string, string[]>;
24
+ protectedFields: Record<string, string[]>;
25
+ rowOwnership: Record<string, {
26
+ column: string;
27
+ userField: string;
28
+ }>;
29
+ softDelete: {
30
+ tables: string[];
31
+ };
32
+ pgSettings: boolean;
33
+ relations: Record<string, Record<string, unknown>>;
34
+ publicRoutes: string[];
35
+ publicRpc: string[];
36
+ }
37
+ interface SchemaAnalysis {
38
+ tables: TableInfo[];
39
+ relations: RelationInfo[];
40
+ handlerConfig: Partial<HandlerConfig> | null;
41
+ issues: string[];
42
+ suggestions: string[];
43
+ }
44
+ export declare function analyzeSchema(projectPath: string): SchemaAnalysis;
45
+ export {};
46
+ //# sourceMappingURL=schema-analyze.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-analyze.d.ts","sourceRoot":"","sources":["../../src/tools/schema-analyze.ts"],"names":[],"mappings":"AAGA,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,UAAU,SAAS;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,KAAK,GAAG,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,UAAU,aAAa;IACrB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACxC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC1C,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpE,UAAU,EAAE;QAAE,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IACjC,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACnD,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,UAAU,cAAc;IACtB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,aAAa,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC;IAC7C,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AA2ND,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,CAkJjE"}