mycontext-cli 4.2.6 → 4.2.8
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/README.md +531 -144
- package/dist/README.md +531 -144
- package/dist/cli.js +14 -5
- package/dist/cli.js.map +1 -1
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +19 -47
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/init-interactive.d.ts +147 -0
- package/dist/commands/init-interactive.d.ts.map +1 -0
- package/dist/commands/init-interactive.js +917 -0
- package/dist/commands/init-interactive.js.map +1 -0
- package/dist/config/shadcn-catalog.json +93 -0
- package/dist/core/brain/BrainClient.d.ts +1 -1
- package/dist/core/brain/BrainClient.d.ts.map +1 -1
- package/dist/core/brain/BrainClient.js +5 -5
- package/dist/core/brain/BrainClient.js.map +1 -1
- package/dist/doctor/DoctorEngine.d.ts.map +1 -1
- package/dist/doctor/DoctorEngine.js +21 -11
- package/dist/doctor/DoctorEngine.js.map +1 -1
- package/dist/doctor/rules/dead-code-rules.d.ts.map +1 -1
- package/dist/doctor/rules/dead-code-rules.js +33 -0
- package/dist/doctor/rules/dead-code-rules.js.map +1 -1
- package/dist/doctor/rules/index.d.ts +3 -1
- package/dist/doctor/rules/index.d.ts.map +1 -1
- package/dist/doctor/rules/index.js +7 -1
- package/dist/doctor/rules/index.js.map +1 -1
- package/dist/doctor/rules/instantdb-rules.d.ts +3 -0
- package/dist/doctor/rules/instantdb-rules.d.ts.map +1 -0
- package/dist/doctor/rules/instantdb-rules.js +544 -0
- package/dist/doctor/rules/instantdb-rules.js.map +1 -0
- package/dist/doctor/rules/nextjs-rules.d.ts.map +1 -1
- package/dist/doctor/rules/nextjs-rules.js +53 -3
- package/dist/doctor/rules/nextjs-rules.js.map +1 -1
- package/dist/doctor/rules/typescript-rules.d.ts +3 -0
- package/dist/doctor/rules/typescript-rules.d.ts.map +1 -0
- package/dist/doctor/rules/typescript-rules.js +177 -0
- package/dist/doctor/rules/typescript-rules.js.map +1 -0
- package/dist/package.json +4 -2
- package/dist/services/ComponentInferenceEngine.d.ts +66 -0
- package/dist/services/ComponentInferenceEngine.d.ts.map +1 -0
- package/dist/services/ComponentInferenceEngine.js +302 -0
- package/dist/services/ComponentInferenceEngine.js.map +1 -0
- package/dist/services/ComponentRegistry.d.ts +61 -0
- package/dist/services/ComponentRegistry.d.ts.map +1 -0
- package/dist/services/ComponentRegistry.js +128 -0
- package/dist/services/ComponentRegistry.js.map +1 -0
- package/dist/services/InferenceEngine.d.ts +41 -0
- package/dist/services/InferenceEngine.d.ts.map +1 -0
- package/dist/services/InferenceEngine.js +307 -0
- package/dist/services/InferenceEngine.js.map +1 -0
- package/dist/services/Planner.d.ts +77 -0
- package/dist/services/Planner.d.ts.map +1 -0
- package/dist/services/Planner.js +828 -0
- package/dist/services/Planner.js.map +1 -0
- package/dist/services/ProjectScanner.d.ts.map +1 -1
- package/dist/services/ProjectScanner.js +15 -1
- package/dist/services/ProjectScanner.js.map +1 -1
- package/dist/services/ScaffoldEngine.d.ts +87 -0
- package/dist/services/ScaffoldEngine.d.ts.map +1 -0
- package/dist/services/ScaffoldEngine.js +409 -0
- package/dist/services/ScaffoldEngine.js.map +1 -0
- package/dist/services/ScaffoldPreview.d.ts +62 -0
- package/dist/services/ScaffoldPreview.d.ts.map +1 -0
- package/dist/services/ScaffoldPreview.js +292 -0
- package/dist/services/ScaffoldPreview.js.map +1 -0
- package/dist/services/TemplateEngine.d.ts +136 -0
- package/dist/services/TemplateEngine.d.ts.map +1 -0
- package/dist/services/TemplateEngine.js +483 -0
- package/dist/services/TemplateEngine.js.map +1 -0
- package/dist/services/TemplateHelpers.d.ts +9 -0
- package/dist/services/TemplateHelpers.d.ts.map +1 -0
- package/dist/services/TemplateHelpers.js +212 -0
- package/dist/services/TemplateHelpers.js.map +1 -0
- package/dist/templates/actions/auth-actions.ts.hbs +140 -0
- package/dist/templates/actions/crud-actions.ts.hbs +113 -0
- package/dist/templates/components/auth/login-form.tsx.hbs +67 -0
- package/dist/templates/components/auth/login-skeleton.tsx.hbs +24 -0
- package/dist/templates/components/auth/register-form.tsx.hbs +116 -0
- package/dist/templates/components/crud/entity-card.tsx.hbs +71 -0
- package/dist/templates/components/crud/entity-form.tsx.hbs +158 -0
- package/dist/templates/components/crud/entity-skeleton.tsx.hbs +90 -0
- package/dist/templates/components/crud/entity-table.tsx.hbs +129 -0
- package/dist/templates/components/ui/button.tsx.hbs +53 -0
- package/dist/templates/components/ui/card.tsx.hbs +68 -0
- package/dist/templates/components/ui/input.tsx.hbs +33 -0
- package/dist/templates/components/ui/label.tsx.hbs +20 -0
- package/dist/templates/components/ui/skeleton.tsx.hbs +15 -0
- package/dist/templates/components/ui/theme-provider.tsx.hbs +66 -0
- package/dist/templates/components/ui/theme-toggle.tsx.hbs +30 -0
- package/dist/templates/config/app.css.hbs +150 -0
- package/dist/templates/layouts/dashboard-layout.tsx.hbs +69 -0
- package/dist/templates/layouts/error.tsx.hbs +51 -0
- package/dist/templates/layouts/loading.tsx.hbs +22 -0
- package/dist/templates/layouts/not-found.tsx.hbs +24 -0
- package/dist/templates/layouts/root-layout.tsx.hbs +40 -0
- package/dist/templates/lib/instant.ts.hbs +19 -0
- package/dist/templates/lib/utils.ts.hbs +24 -0
- package/dist/templates/pages/auth/login-page.tsx.hbs +30 -0
- package/dist/templates/pages/auth/register-page.tsx.hbs +38 -0
- package/dist/templates/pages/crud/create-page.tsx.hbs +42 -0
- package/dist/templates/pages/crud/detail-page.tsx.hbs +90 -0
- package/dist/templates/pages/crud/list-page.tsx.hbs +60 -0
- package/dist/templates/pages/crud/loading.tsx.hbs +13 -0
- package/dist/templates/pages/landing-page.tsx.hbs +111 -0
- package/dist/types/asl.d.ts +387 -0
- package/dist/types/asl.d.ts.map +1 -0
- package/dist/types/asl.js +139 -0
- package/dist/types/asl.js.map +1 -0
- package/dist/types/living-context.d.ts +1 -1
- package/dist/types/living-context.d.ts.map +1 -1
- package/dist/utils/FileGenerator.js +3 -3
- package/dist/utils/FileGenerator.js.map +1 -1
- package/dist/utils/generateTypesFromSchema.d.ts +47 -0
- package/dist/utils/generateTypesFromSchema.d.ts.map +1 -0
- package/dist/utils/generateTypesFromSchema.js +298 -0
- package/dist/utils/generateTypesFromSchema.js.map +1 -0
- package/package.json +4 -2
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.instantdbRules = void 0;
|
|
37
|
+
/**
|
|
38
|
+
* InstantDB Rules — Schema validation and drift detection for InstantDB projects
|
|
39
|
+
*/
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
// ─── Helper ───────────────────────────────────────────────────────
|
|
42
|
+
function diag(rule, filePath, message, opts = {}) {
|
|
43
|
+
return {
|
|
44
|
+
ruleId: rule.id,
|
|
45
|
+
filePath,
|
|
46
|
+
line: opts.line,
|
|
47
|
+
severity: rule.severity,
|
|
48
|
+
message,
|
|
49
|
+
help: opts.help || rule.help,
|
|
50
|
+
autoFixable: opts.autoFixable ?? false,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* InstantDB type mapping: i.string() → "string", etc.
|
|
55
|
+
*/
|
|
56
|
+
const INSTANTDB_TYPE_MAP = {
|
|
57
|
+
string: "string",
|
|
58
|
+
number: "number",
|
|
59
|
+
boolean: "boolean",
|
|
60
|
+
date: "date",
|
|
61
|
+
json: "json",
|
|
62
|
+
any: "any",
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Parse InstantDB schema file and extract entity definitions.
|
|
66
|
+
*
|
|
67
|
+
* Handles two schema formats:
|
|
68
|
+
* 1. Modern InstantDB DSL: `entityName: i.entity({ field: i.string(), ... })`
|
|
69
|
+
* 2. Legacy JSON-like: `entityName: { fields: { field: { type: "string" } } }`
|
|
70
|
+
*/
|
|
71
|
+
async function parseInstantDBSchema(ctx) {
|
|
72
|
+
const schemaPaths = [
|
|
73
|
+
"instant.schema.ts",
|
|
74
|
+
"src/instant.schema.ts",
|
|
75
|
+
".mycontext/schema.ts",
|
|
76
|
+
"instant.schema.js",
|
|
77
|
+
];
|
|
78
|
+
let schemaContent = null;
|
|
79
|
+
for (const sp of schemaPaths) {
|
|
80
|
+
const content = await ctx.readFile(sp);
|
|
81
|
+
if (content) {
|
|
82
|
+
schemaContent = content;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (!schemaContent) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
// Try modern i.entity() DSL first, fall back to legacy format
|
|
90
|
+
const entities = parseModernDSL(schemaContent) || parseLegacyFormat(schemaContent);
|
|
91
|
+
if (!entities || entities.size === 0)
|
|
92
|
+
return null;
|
|
93
|
+
// Parse links and register them as valid "fields" on entities
|
|
94
|
+
// Links represent relationship traversals (e.g., properties.claims, user_companies.user)
|
|
95
|
+
// Also collect ALL link labels globally for nested query validation
|
|
96
|
+
const allLinkLabels = parseAndRegisterLinks(schemaContent, entities);
|
|
97
|
+
// Store link labels on the returned map for the drift checker to use
|
|
98
|
+
entities.__allLinkLabels = allLinkLabels;
|
|
99
|
+
return entities;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Parse the `links` section from the schema and register link labels
|
|
103
|
+
* as valid fields on the corresponding entities.
|
|
104
|
+
*
|
|
105
|
+
* For example, a link like:
|
|
106
|
+
* propertyClaims: { forward: { on: "properties", has: "many", label: "claims" }, ... }
|
|
107
|
+
* registers "claims" as a valid field on the "properties" entity.
|
|
108
|
+
*/
|
|
109
|
+
function parseAndRegisterLinks(content, entities) {
|
|
110
|
+
const allLabels = new Set();
|
|
111
|
+
const linksMatch = content.match(/links:\s*\{/);
|
|
112
|
+
if (!linksMatch)
|
|
113
|
+
return allLabels;
|
|
114
|
+
const startIdx = (linksMatch.index || 0) + linksMatch[0].length;
|
|
115
|
+
const linksBody = extractBalancedBraces(content, startIdx);
|
|
116
|
+
if (!linksBody)
|
|
117
|
+
return allLabels;
|
|
118
|
+
// Match link definitions with forward/reverse
|
|
119
|
+
const linkPattern = /(\w+)\s*:\s*\{/g;
|
|
120
|
+
let match;
|
|
121
|
+
while ((match = linkPattern.exec(linksBody)) !== null) {
|
|
122
|
+
const linkStartIdx = match.index + match[0].length;
|
|
123
|
+
const linkBody = extractBalancedBraces(linksBody, linkStartIdx);
|
|
124
|
+
if (!linkBody)
|
|
125
|
+
continue;
|
|
126
|
+
// Parse forward: { on: "entityName", has: "many", label: "labelName" }
|
|
127
|
+
const forwardMatch = linkBody.match(/forward:\s*\{[^}]*on:\s*["'](\w+)["'][^}]*label:\s*["'](\w+)["']/);
|
|
128
|
+
if (forwardMatch) {
|
|
129
|
+
const entityName = forwardMatch[1];
|
|
130
|
+
const label = forwardMatch[2];
|
|
131
|
+
if (entityName && label) {
|
|
132
|
+
allLabels.add(label);
|
|
133
|
+
const entity = entities.get(entityName);
|
|
134
|
+
if (entity) {
|
|
135
|
+
entity.fields[label] = { type: "link", required: false };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Parse reverse: { on: "entityName", has: "one", label: "labelName" }
|
|
140
|
+
const reverseMatch = linkBody.match(/reverse:\s*\{[^}]*on:\s*["'](\w+)["'][^}]*label:\s*["'](\w+)["']/);
|
|
141
|
+
if (reverseMatch) {
|
|
142
|
+
const entityName = reverseMatch[1];
|
|
143
|
+
const label = reverseMatch[2];
|
|
144
|
+
if (entityName && label) {
|
|
145
|
+
allLabels.add(label);
|
|
146
|
+
const entity = entities.get(entityName);
|
|
147
|
+
if (entity) {
|
|
148
|
+
entity.fields[label] = { type: "link", required: false };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return allLabels;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Parse modern InstantDB DSL: `profiles: i.entity({ first_name: i.string(), ... })`
|
|
157
|
+
*/
|
|
158
|
+
function parseModernDSL(content) {
|
|
159
|
+
// Check if this file uses i.entity() syntax
|
|
160
|
+
if (!content.includes("i.entity("))
|
|
161
|
+
return null;
|
|
162
|
+
const entities = new Map();
|
|
163
|
+
// Match each entity: `entityName: i.entity({ ... })`
|
|
164
|
+
// We need to handle nested braces, so we find the start and then balance braces
|
|
165
|
+
const entityStartPattern = /(\w+)\s*:\s*i\.entity\(\s*\{/g;
|
|
166
|
+
let match;
|
|
167
|
+
while ((match = entityStartPattern.exec(content)) !== null) {
|
|
168
|
+
const entityName = match[1];
|
|
169
|
+
if (!entityName)
|
|
170
|
+
continue;
|
|
171
|
+
// Find the matching closing brace for the entity body
|
|
172
|
+
const startIdx = match.index + match[0].length;
|
|
173
|
+
const entityBody = extractBalancedBraces(content, startIdx);
|
|
174
|
+
if (!entityBody)
|
|
175
|
+
continue;
|
|
176
|
+
const fields = {};
|
|
177
|
+
// Match fields: `fieldName: i.string()`, `fieldName: i.number().optional()`, etc.
|
|
178
|
+
const fieldPattern = /(\w+)\s*:\s*i\.(string|number|boolean|date|json|any)\(\)/g;
|
|
179
|
+
let fieldMatch;
|
|
180
|
+
while ((fieldMatch = fieldPattern.exec(entityBody)) !== null) {
|
|
181
|
+
const fieldName = fieldMatch[1];
|
|
182
|
+
const idbType = fieldMatch[2];
|
|
183
|
+
if (!fieldName || !idbType)
|
|
184
|
+
continue;
|
|
185
|
+
const mappedType = INSTANTDB_TYPE_MAP[idbType] || "any";
|
|
186
|
+
// Check for .optional() modifier after the i.type() call
|
|
187
|
+
const afterCall = entityBody.substring(fieldMatch.index + fieldMatch[0].length, fieldMatch.index + fieldMatch[0].length + 20);
|
|
188
|
+
const isOptional = afterCall.startsWith(".optional()");
|
|
189
|
+
fields[fieldName] = {
|
|
190
|
+
type: mappedType,
|
|
191
|
+
required: !isOptional,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
// Always include "id" as a built-in field
|
|
195
|
+
fields["id"] = { type: "string", required: true };
|
|
196
|
+
entities.set(entityName, { name: entityName, fields });
|
|
197
|
+
}
|
|
198
|
+
return entities.size > 0 ? entities : null;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Parse legacy JSON-like format: `entityName: { fields: { field: { type: "string" } } }`
|
|
202
|
+
*/
|
|
203
|
+
function parseLegacyFormat(content) {
|
|
204
|
+
const entities = new Map();
|
|
205
|
+
try {
|
|
206
|
+
const entitiesMatch = content.match(/entities:\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}/s);
|
|
207
|
+
if (!entitiesMatch || !entitiesMatch[1])
|
|
208
|
+
return null;
|
|
209
|
+
const entitiesSection = entitiesMatch[1];
|
|
210
|
+
const entityPattern = /(\w+):\s*\{[^}]*fields:\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}/gs;
|
|
211
|
+
let entityMatch;
|
|
212
|
+
while ((entityMatch = entityPattern.exec(entitiesSection)) !== null) {
|
|
213
|
+
const entityName = entityMatch[1];
|
|
214
|
+
const fieldsSection = entityMatch[2];
|
|
215
|
+
if (!entityName || !fieldsSection)
|
|
216
|
+
continue;
|
|
217
|
+
const fields = {};
|
|
218
|
+
const fieldPattern = /(\w+):\s*\{[^}]*type:\s*["'](\w+)["'][^}]*\}/g;
|
|
219
|
+
let fieldMatch;
|
|
220
|
+
while ((fieldMatch = fieldPattern.exec(fieldsSection)) !== null) {
|
|
221
|
+
const fieldName = fieldMatch[1];
|
|
222
|
+
const fieldType = fieldMatch[2];
|
|
223
|
+
if (!fieldName || !fieldType)
|
|
224
|
+
continue;
|
|
225
|
+
fields[fieldName] = {
|
|
226
|
+
type: fieldType,
|
|
227
|
+
required: fieldMatch[0].includes("required") || fieldMatch[0].includes("optional: false"),
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
entities.set(entityName, { name: entityName, fields });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
return entities.size > 0 ? entities : null;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Extract content between balanced braces starting after an opening brace.
|
|
240
|
+
* Returns the content between braces (excluding the outer braces).
|
|
241
|
+
*/
|
|
242
|
+
function extractBalancedBraces(content, startAfterOpenBrace) {
|
|
243
|
+
let depth = 1;
|
|
244
|
+
let i = startAfterOpenBrace;
|
|
245
|
+
while (i < content.length && depth > 0) {
|
|
246
|
+
if (content[i] === "{")
|
|
247
|
+
depth++;
|
|
248
|
+
else if (content[i] === "}")
|
|
249
|
+
depth--;
|
|
250
|
+
i++;
|
|
251
|
+
}
|
|
252
|
+
if (depth !== 0)
|
|
253
|
+
return null;
|
|
254
|
+
return content.substring(startAfterOpenBrace, i - 1);
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Common JavaScript/TypeScript built-in properties that should never be treated
|
|
258
|
+
* as schema field accesses, even if the variable name matches an entity name.
|
|
259
|
+
*/
|
|
260
|
+
const JS_BUILTIN_PROPERTIES = new Set([
|
|
261
|
+
// Array methods & properties
|
|
262
|
+
"length", "map", "filter", "reduce", "forEach", "find", "findIndex", "some",
|
|
263
|
+
"every", "includes", "indexOf", "slice", "splice", "push", "pop", "shift",
|
|
264
|
+
"unshift", "concat", "join", "sort", "reverse", "flat", "flatMap", "fill",
|
|
265
|
+
"at", "entries", "keys", "values", "from", "isArray", "of",
|
|
266
|
+
// Object methods
|
|
267
|
+
"keys", "values", "entries", "assign", "freeze", "create", "hasOwnProperty",
|
|
268
|
+
"toString", "valueOf", "constructor", "prototype",
|
|
269
|
+
// Promise/async
|
|
270
|
+
"then", "catch", "finally", "resolve", "reject", "all", "allSettled", "race",
|
|
271
|
+
// String methods
|
|
272
|
+
"trim", "split", "replace", "match", "search", "startsWith", "endsWith",
|
|
273
|
+
"toUpperCase", "toLowerCase", "substring", "charAt", "charCodeAt", "padStart",
|
|
274
|
+
"padEnd", "repeat", "normalize", "localeCompare",
|
|
275
|
+
// Common DOM/React/Next.js
|
|
276
|
+
"current", "style", "className", "children", "props", "state", "ref",
|
|
277
|
+
"target", "value", "type", "name", "href", "src", "alt", "id",
|
|
278
|
+
"addEventListener", "removeEventListener", "preventDefault", "stopPropagation",
|
|
279
|
+
// Common chaining
|
|
280
|
+
"default", "log", "error", "warn", "info", "debug", "env",
|
|
281
|
+
// InstantDB / InstaQL query operators
|
|
282
|
+
"in", "gt", "lt", "gte", "lte", "ne", "like", "not", "$isNull",
|
|
283
|
+
]);
|
|
284
|
+
/**
|
|
285
|
+
* Extract field accesses from TypeScript code that are specifically
|
|
286
|
+
* within InstantDB query/transaction contexts.
|
|
287
|
+
*
|
|
288
|
+
* Only checks:
|
|
289
|
+
* 1. db.useQuery({ entityName: { fieldName: {} } }) — query objects
|
|
290
|
+
* 2. tx.entityName.update({ fieldName: value }) — transaction mutations
|
|
291
|
+
* 3. Direct entity field reads: entityVar.fieldName where entityVar is
|
|
292
|
+
* destructured from a query result (conservative heuristic)
|
|
293
|
+
*/
|
|
294
|
+
function extractFieldAccesses(content, schemaEntities) {
|
|
295
|
+
const accesses = [];
|
|
296
|
+
const entityNames = Array.from(schemaEntities.keys());
|
|
297
|
+
const entityNamesSet = new Set(entityNames.map(n => n.toLowerCase()));
|
|
298
|
+
// ── Strategy 1: db.useQuery({ entity: { field: {} } }) ──
|
|
299
|
+
// Match query objects passed to useQuery/query
|
|
300
|
+
for (const entityName of entityNames) {
|
|
301
|
+
// Find query blocks like: entityName: { $: { where: { ... } }, fieldName: {} }
|
|
302
|
+
// or shorthand: entityName: { fieldName: {} }
|
|
303
|
+
const queryBlockPattern = new RegExp(`\\b${entityName}\\s*:\\s*\\{([^}]*(?:\\{[^}]*\\}[^}]*)*)\\}`, "gs");
|
|
304
|
+
let qMatch;
|
|
305
|
+
while ((qMatch = queryBlockPattern.exec(content)) !== null) {
|
|
306
|
+
const blockContent = qMatch[1];
|
|
307
|
+
if (!blockContent)
|
|
308
|
+
continue;
|
|
309
|
+
// Extract field names from the query block (keys before a colon)
|
|
310
|
+
const fieldKeyPattern = /(\w+)\s*:/g;
|
|
311
|
+
let fkMatch;
|
|
312
|
+
while ((fkMatch = fieldKeyPattern.exec(blockContent)) !== null) {
|
|
313
|
+
const fieldName = fkMatch[1];
|
|
314
|
+
if (!fieldName)
|
|
315
|
+
continue;
|
|
316
|
+
// Skip $ (query operator), common non-field keys, and builtins
|
|
317
|
+
if (fieldName === "$" || fieldName === "where" || fieldName === "order" ||
|
|
318
|
+
fieldName === "limit" || fieldName === "offset" || fieldName === "first" ||
|
|
319
|
+
fieldName === "last" || fieldName === "before" || fieldName === "after")
|
|
320
|
+
continue;
|
|
321
|
+
if (JS_BUILTIN_PROPERTIES.has(fieldName))
|
|
322
|
+
continue;
|
|
323
|
+
const lineNum = content.substring(0, qMatch.index).split("\n").length;
|
|
324
|
+
accesses.push({
|
|
325
|
+
entity: entityName,
|
|
326
|
+
field: fieldName,
|
|
327
|
+
line: lineNum,
|
|
328
|
+
match: `${entityName}.${fieldName}`,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
// ── Strategy 2: tx.entityName.update/merge/delete({ field: value }) ──
|
|
334
|
+
for (const entityName of entityNames) {
|
|
335
|
+
const txPattern = new RegExp(`tx\\.${entityName}[^.]*\\.(?:update|merge)\\s*\\(\\s*\\{([^}]*)\\}`, "gs");
|
|
336
|
+
let txMatch;
|
|
337
|
+
while ((txMatch = txPattern.exec(content)) !== null) {
|
|
338
|
+
const updateBody = txMatch[1];
|
|
339
|
+
if (!updateBody)
|
|
340
|
+
continue;
|
|
341
|
+
const fieldKeyPattern = /(\w+)\s*:/g;
|
|
342
|
+
let fkMatch;
|
|
343
|
+
while ((fkMatch = fieldKeyPattern.exec(updateBody)) !== null) {
|
|
344
|
+
const fieldName = fkMatch[1];
|
|
345
|
+
if (!fieldName || JS_BUILTIN_PROPERTIES.has(fieldName))
|
|
346
|
+
continue;
|
|
347
|
+
const lineNum = content.substring(0, txMatch.index).split("\n").length;
|
|
348
|
+
accesses.push({
|
|
349
|
+
entity: entityName,
|
|
350
|
+
field: fieldName,
|
|
351
|
+
line: lineNum,
|
|
352
|
+
match: `tx.${entityName}.${fieldName}`,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return accesses;
|
|
358
|
+
}
|
|
359
|
+
// ─── Rules ────────────────────────────────────────────────────────
|
|
360
|
+
const schemaFieldDrift = {
|
|
361
|
+
id: "instantdb/schema-field-drift",
|
|
362
|
+
name: "Schema Field Drift Detection",
|
|
363
|
+
category: "node",
|
|
364
|
+
severity: "error",
|
|
365
|
+
description: "Detects code accessing fields that don't exist in the InstantDB schema",
|
|
366
|
+
help: "Update field names in code to match the schema, or add missing fields to instant.schema.ts",
|
|
367
|
+
appliesTo: ["nextjs", "node"],
|
|
368
|
+
async check(ctx) {
|
|
369
|
+
const results = [];
|
|
370
|
+
// Parse the schema
|
|
371
|
+
const schemaEntities = await parseInstantDBSchema(ctx);
|
|
372
|
+
if (!schemaEntities || schemaEntities.size === 0) {
|
|
373
|
+
return results; // No schema found or no entities, skip check
|
|
374
|
+
}
|
|
375
|
+
// Find all TypeScript/JavaScript files
|
|
376
|
+
const files = await ctx.findFiles(/\.(tsx?|jsx?)$/);
|
|
377
|
+
for (const file of files) {
|
|
378
|
+
// Skip schema files, node_modules, scripts/, .mycontext/, and config files
|
|
379
|
+
if (file.includes("schema.") ||
|
|
380
|
+
file.includes("node_modules") ||
|
|
381
|
+
file.startsWith("scripts/") ||
|
|
382
|
+
file.includes("/scripts/") ||
|
|
383
|
+
file.startsWith(".mycontext/") ||
|
|
384
|
+
file.includes("/.mycontext/") ||
|
|
385
|
+
file.endsWith(".config.ts") ||
|
|
386
|
+
file.endsWith(".config.js")) {
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
const contentRaw = await ctx.readFile(file);
|
|
390
|
+
if (!contentRaw)
|
|
391
|
+
continue;
|
|
392
|
+
// Strip comments (to avoid matching // TODO: as a field)
|
|
393
|
+
const content = contentRaw.replace(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, "$1");
|
|
394
|
+
// Only check files that actually use InstantDB
|
|
395
|
+
if (!content.includes("db.useQuery") &&
|
|
396
|
+
!content.includes("db.transact") &&
|
|
397
|
+
!content.includes("tx.") &&
|
|
398
|
+
!content.includes("useQuery") &&
|
|
399
|
+
!content.includes("adminDb")) {
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
// Extract field accesses from InstantDB contexts only
|
|
403
|
+
const accesses = extractFieldAccesses(content, schemaEntities);
|
|
404
|
+
// Get global link labels (registered on the map metadata in parseInstantDBSchema)
|
|
405
|
+
const allLinkLabels = schemaEntities.__allLinkLabels || new Set();
|
|
406
|
+
// Check each access against the schema
|
|
407
|
+
for (const access of accesses) {
|
|
408
|
+
const entity = schemaEntities.get(access.entity);
|
|
409
|
+
if (!entity)
|
|
410
|
+
continue;
|
|
411
|
+
// Skip if the field is a known link label globally (relationship traversal)
|
|
412
|
+
if (allLinkLabels.has(access.field))
|
|
413
|
+
continue;
|
|
414
|
+
// Check if the field exists in the schema
|
|
415
|
+
if (!entity.fields[access.field]) {
|
|
416
|
+
// Field doesn't exist in schema - this is drift!
|
|
417
|
+
// Try to find a similar field name
|
|
418
|
+
const availableFields = Object.keys(entity.fields);
|
|
419
|
+
const similarField = findSimilarField(access.field, availableFields);
|
|
420
|
+
let helpMessage = `Field '${access.field}' does not exist in ${access.entity} schema. `;
|
|
421
|
+
if (similarField) {
|
|
422
|
+
helpMessage += `Did you mean '${similarField}'? Available fields: ${availableFields.join(", ")}`;
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
helpMessage += `Available fields: ${availableFields.join(", ")}`;
|
|
426
|
+
}
|
|
427
|
+
results.push(diag(this, file, `${access.entity}.${access.field} - field '${access.field}' not in schema`, {
|
|
428
|
+
line: access.line,
|
|
429
|
+
help: helpMessage,
|
|
430
|
+
autoFixable: similarField !== null,
|
|
431
|
+
}));
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return results;
|
|
436
|
+
},
|
|
437
|
+
async fix(ctx, diagnostic) {
|
|
438
|
+
// Auto-fix by replacing the incorrect field name with the suggested one
|
|
439
|
+
const match = diagnostic.message.match(/(\w+)\.(\w+) - field '(\w+)' not in schema/);
|
|
440
|
+
if (!match)
|
|
441
|
+
return false;
|
|
442
|
+
const [, entity, , wrongField] = match;
|
|
443
|
+
// Extract the suggestion from the help message
|
|
444
|
+
const suggestionMatch = diagnostic.help.match(/Did you mean '(\w+)'\?/);
|
|
445
|
+
if (!suggestionMatch)
|
|
446
|
+
return false;
|
|
447
|
+
const correctField = suggestionMatch[1];
|
|
448
|
+
// Read the file
|
|
449
|
+
const content = await ctx.readFile(diagnostic.filePath);
|
|
450
|
+
if (!content)
|
|
451
|
+
return false;
|
|
452
|
+
const lines = content.split("\n");
|
|
453
|
+
if (!diagnostic.line || diagnostic.line > lines.length)
|
|
454
|
+
return false;
|
|
455
|
+
// Replace the field name on the specific line
|
|
456
|
+
const lineIndex = diagnostic.line - 1;
|
|
457
|
+
const originalLine = lines[lineIndex];
|
|
458
|
+
if (!originalLine)
|
|
459
|
+
return false;
|
|
460
|
+
// Replace entity.wrongField with entity.correctField
|
|
461
|
+
const pattern = new RegExp(`(\\w+)\\.${wrongField}\\b`, 'g');
|
|
462
|
+
const newLine = originalLine.replace(pattern, `$1.${correctField}`);
|
|
463
|
+
if (newLine === originalLine)
|
|
464
|
+
return false; // Nothing changed
|
|
465
|
+
lines[lineIndex] = newLine;
|
|
466
|
+
// Write the file back
|
|
467
|
+
const fs = await Promise.resolve().then(() => __importStar(require("fs/promises")));
|
|
468
|
+
const absolutePath = path.join(ctx.root, diagnostic.filePath);
|
|
469
|
+
await fs.writeFile(absolutePath, lines.join("\n"), "utf-8");
|
|
470
|
+
return true;
|
|
471
|
+
},
|
|
472
|
+
};
|
|
473
|
+
/**
|
|
474
|
+
* Find a similar field name using simple heuristics
|
|
475
|
+
* Common patterns:
|
|
476
|
+
* - payment_method -> method (remove entity prefix)
|
|
477
|
+
* - delivery_zone_id -> zone_id (remove prefix)
|
|
478
|
+
* - user_email -> email (remove entity prefix)
|
|
479
|
+
*/
|
|
480
|
+
function findSimilarField(wrongField, availableFields) {
|
|
481
|
+
const wrongLower = wrongField.toLowerCase();
|
|
482
|
+
// 1. Exact match (shouldn't happen, but just in case)
|
|
483
|
+
for (const field of availableFields) {
|
|
484
|
+
if (field.toLowerCase() === wrongLower) {
|
|
485
|
+
return field;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
// 2. Check if wrongField ends with one of the available fields
|
|
489
|
+
// e.g., payment_method ends with method
|
|
490
|
+
for (const field of availableFields) {
|
|
491
|
+
if (wrongLower.endsWith(field.toLowerCase()) || wrongLower.endsWith('_' + field.toLowerCase())) {
|
|
492
|
+
return field;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
// 3. Check if wrongField contains one of the available fields
|
|
496
|
+
// e.g., delivery_zone_id contains zone_id
|
|
497
|
+
for (const field of availableFields) {
|
|
498
|
+
if (wrongLower.includes(field.toLowerCase())) {
|
|
499
|
+
return field;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
// 4. Levenshtein distance for typos (simple version)
|
|
503
|
+
let closest = null;
|
|
504
|
+
let minDistance = Infinity;
|
|
505
|
+
for (const field of availableFields) {
|
|
506
|
+
const distance = levenshteinDistance(wrongLower, field.toLowerCase());
|
|
507
|
+
if (distance < minDistance && distance <= 3) { // Allow up to 3 character differences
|
|
508
|
+
minDistance = distance;
|
|
509
|
+
closest = field;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return closest;
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Calculate Levenshtein distance between two strings
|
|
516
|
+
*/
|
|
517
|
+
function levenshteinDistance(str1, str2) {
|
|
518
|
+
const matrix = Array.from({ length: str2.length + 1 }, () => Array(str1.length + 1).fill(0));
|
|
519
|
+
for (let i = 0; i <= str2.length; i++) {
|
|
520
|
+
matrix[i][0] = i;
|
|
521
|
+
}
|
|
522
|
+
for (let j = 0; j <= str1.length; j++) {
|
|
523
|
+
matrix[0][j] = j;
|
|
524
|
+
}
|
|
525
|
+
for (let i = 1; i <= str2.length; i++) {
|
|
526
|
+
for (let j = 1; j <= str1.length; j++) {
|
|
527
|
+
if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
|
|
528
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
529
|
+
}
|
|
530
|
+
else {
|
|
531
|
+
matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, // substitution
|
|
532
|
+
matrix[i][j - 1] + 1, // insertion
|
|
533
|
+
matrix[i - 1][j] + 1 // deletion
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return matrix[str2.length][str1.length];
|
|
539
|
+
}
|
|
540
|
+
// ─── Export ───────────────────────────────────────────────────────
|
|
541
|
+
exports.instantdbRules = [
|
|
542
|
+
schemaFieldDrift,
|
|
543
|
+
];
|
|
544
|
+
//# sourceMappingURL=instantdb-rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instantdb-rules.js","sourceRoot":"","sources":["../../../src/doctor/rules/instantdb-rules.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nextjs-rules.d.ts","sourceRoot":"","sources":["../../../src/doctor/rules/nextjs-rules.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAA2B,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"nextjs-rules.d.ts","sourceRoot":"","sources":["../../../src/doctor/rules/nextjs-rules.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAA2B,MAAM,UAAU,CAAC;AA2XpE,eAAO,MAAM,WAAW,EAAE,UAAU,EAWnC,CAAC"}
|
|
@@ -208,22 +208,53 @@ const metadataExport = {
|
|
|
208
208
|
name: "Metadata Export",
|
|
209
209
|
category: "nextjs",
|
|
210
210
|
severity: "warning",
|
|
211
|
-
description: "
|
|
211
|
+
description: "Public pages/layouts should export metadata or generateMetadata for SEO",
|
|
212
212
|
help: "Add 'export const metadata = { title: ... }' or 'export async function generateMetadata()'",
|
|
213
213
|
appliesTo: ["nextjs"],
|
|
214
214
|
async check(ctx) {
|
|
215
215
|
const results = [];
|
|
216
216
|
const pages = await ctx.findFiles(/page\.(tsx|jsx|ts|js)$/);
|
|
217
|
+
// Route groups that are typically auth-gated (no SEO needed)
|
|
218
|
+
const authGatedGroups = new Set([
|
|
219
|
+
"(pms)", "(admin)", "(dashboard)", "(agents)", "(app)",
|
|
220
|
+
"(protected)", "(auth)", "(settings)", "(account)", "(manage)",
|
|
221
|
+
]);
|
|
222
|
+
// Detect additional auth-gated groups by checking for auth patterns in layouts
|
|
223
|
+
const layouts = await ctx.findFiles(/layout\.(tsx|jsx|ts|js)$/);
|
|
224
|
+
const authPatterns = /\b(redirect|getServerSession|auth|session|middleware|cookies|signIn|requireAuth|useAuth)\b/;
|
|
225
|
+
const detectedAuthGroups = new Set();
|
|
226
|
+
for (const layout of layouts) {
|
|
227
|
+
const content = await ctx.readFile(layout);
|
|
228
|
+
if (!content)
|
|
229
|
+
continue;
|
|
230
|
+
if (authPatterns.test(content)) {
|
|
231
|
+
// Extract route group from path, e.g. "app/(pms)/[slug]/layout.tsx" → "(pms)"
|
|
232
|
+
const groupMatch = layout.match(/\(([^)]+)\)/);
|
|
233
|
+
if (groupMatch) {
|
|
234
|
+
detectedAuthGroups.add(`(${groupMatch[1]})`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
217
238
|
for (const p of pages) {
|
|
239
|
+
// Skip pages in auth-gated route groups
|
|
240
|
+
const isAuthGated = [...authGatedGroups, ...detectedAuthGroups].some(group => p.includes(`/${group}/`) || p.startsWith(`${group}/`));
|
|
241
|
+
if (isAuthGated)
|
|
242
|
+
continue;
|
|
243
|
+
// Skip API routes
|
|
244
|
+
if (p.includes("/api/"))
|
|
245
|
+
continue;
|
|
218
246
|
const content = await ctx.readFile(p);
|
|
219
247
|
if (!content)
|
|
220
248
|
continue;
|
|
249
|
+
// Skip client components (they can't export metadata)
|
|
250
|
+
if (content.includes('"use client"') || content.includes("'use client'"))
|
|
251
|
+
continue;
|
|
221
252
|
// Check if there's metadata or generateMetadata
|
|
222
253
|
const hasMetadata = content.includes("export const metadata") ||
|
|
223
254
|
content.includes("export async function generateMetadata") ||
|
|
224
255
|
content.includes("export function generateMetadata");
|
|
225
256
|
if (!hasMetadata) {
|
|
226
|
-
results.push(diag(this, p, "
|
|
257
|
+
results.push(diag(this, p, "Public page missing metadata export for SEO"));
|
|
227
258
|
}
|
|
228
259
|
}
|
|
229
260
|
return results;
|
|
@@ -336,11 +367,30 @@ const loadingFiles = {
|
|
|
336
367
|
const hasLoading = await ctx.fileExists(path.join(dir, "loading.tsx")) ||
|
|
337
368
|
await ctx.fileExists(path.join(dir, "loading.jsx"));
|
|
338
369
|
if (!hasLoading) {
|
|
339
|
-
results.push(diag(this, p, "Page with async data has no loading.tsx for Suspense"
|
|
370
|
+
results.push(diag(this, p, "Page with async data has no loading.tsx for Suspense", {
|
|
371
|
+
autoFixable: true,
|
|
372
|
+
}));
|
|
340
373
|
}
|
|
341
374
|
}
|
|
342
375
|
return results;
|
|
343
376
|
},
|
|
377
|
+
async fix(ctx, d) {
|
|
378
|
+
const dir = path.dirname(d.filePath);
|
|
379
|
+
const loadingPath = path.join(dir, "loading.tsx");
|
|
380
|
+
const absPath = path.join(ctx.root, loadingPath);
|
|
381
|
+
const skeleton = `export default function Loading() {
|
|
382
|
+
return (
|
|
383
|
+
<div className="flex items-center justify-center min-h-[400px]">
|
|
384
|
+
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary" />
|
|
385
|
+
</div>
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
`;
|
|
389
|
+
const { writeFile, ensureDir } = await Promise.resolve().then(() => __importStar(require("fs-extra")));
|
|
390
|
+
await ensureDir(path.dirname(absPath));
|
|
391
|
+
await writeFile(absPath, skeleton);
|
|
392
|
+
return true;
|
|
393
|
+
},
|
|
344
394
|
};
|
|
345
395
|
exports.nextjsRules = [
|
|
346
396
|
missingRootLayout,
|