@open-mercato/cli 0.6.3-develop.3901.1.ddad60693a → 0.6.3
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/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +1 -1
- package/dist/agentic/shared/AGENTS.md.template +1 -0
- package/dist/lib/dev-env-reload.js +40 -1
- package/dist/lib/dev-env-reload.js.map +2 -2
- package/dist/lib/generate-watch-structure.js +153 -0
- package/dist/lib/generate-watch-structure.js.map +7 -0
- package/dist/lib/testing/integration.js +14 -0
- package/dist/lib/testing/integration.js.map +2 -2
- package/dist/mercato.js +244 -98
- package/dist/mercato.js.map +2 -2
- package/package.json +7 -8
- package/src/__tests__/mercato.test.ts +13 -3
- package/src/lib/__tests__/dev-env-reload.test.ts +21 -1
- package/src/lib/__tests__/generate-watch-structure.test.ts +80 -0
- package/src/lib/dev-env-reload.ts +47 -0
- package/src/lib/generate-watch-structure.ts +170 -0
- package/src/lib/testing/integration.ts +14 -0
- package/src/mercato.ts +241 -67
package/.turbo/turbo-build.log
CHANGED
package/AGENTS.md
CHANGED
|
@@ -72,7 +72,7 @@ The structural cache purge does two things:
|
|
|
72
72
|
1. Deletes Redis cache keys matching `nav:*` so navigation/sidebar caches are rebuilt next render.
|
|
73
73
|
2. Touches every `*.generated.ts` and `*.generated.checksum` file in the current app's `.mercato/generated/` directory by rewriting them with identical bytes. This advances mtime without changing content, which forces Turbopack's filesystem watcher to invalidate the import graph and recompile leaf files that imported a barrel. Without this, Turbopack can keep serving a cached compile error against a since-fixed module file until the dev server is restarted.
|
|
74
74
|
|
|
75
|
-
The dev escape hatch is `yarn dev:reset`, which clears `.next/
|
|
75
|
+
The dev escape hatch is `yarn dev:reset`, which clears the configured Next dev cache (`.mercato/next/dev`) plus legacy `.next` cache directories for the rare case where Turbopack's internal cache stays stuck after a structural purge.
|
|
76
76
|
|
|
77
77
|
## Database Migrations
|
|
78
78
|
|
|
@@ -201,6 +201,7 @@ When the user asks to **create a new application** or a **new module**, do not i
|
|
|
201
201
|
- Entity classes belong in `src/modules/<module>/data/entities.ts` and use decorators from `@mikro-orm/decorators/legacy`
|
|
202
202
|
- Standalone apps expose `yarn mercato configs cache ...` because the template enables the `configs` module from `@open-mercato/core`
|
|
203
203
|
- `yarn generate` automatically runs a best-effort structural cache purge (`yarn mercato configs cache structural --all-tenants`) after successful generation; if the cache command is unavailable, generation still succeeds
|
|
204
|
+
- Use `yarn dev:reset` only as the stale-Turbopack escape hatch; it clears `.mercato/next/dev` plus legacy `.next` caches.
|
|
204
205
|
- Detail/read-model APIs that expose `customFields` MUST return bare field keys via `normalizeCustomFieldResponse()` (for example `{ priority: 3 }`). Keep `cf_` / `cf:` prefixes for request payloads, filters, and form field IDs only.
|
|
205
206
|
- Sidebar icons MUST use `lucide-react` components — never inline SVG via `React.createElement`
|
|
206
207
|
- `page.meta.ts` MUST include `pageGroup`, `pageGroupKey`, and `pageOrder` for sidebar grouping
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import dotenv from "dotenv";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
const RUNTIME_GRAPH_FILE_PATTERN = /\.generated(?:\.[a-z0-9]+)?(?:\.tsx?|\.checksum)$/i;
|
|
4
5
|
const DEV_ENV_PRIORITY = [
|
|
5
6
|
".env",
|
|
6
7
|
".env.development",
|
|
@@ -81,9 +82,47 @@ function watchDevEnvFiles(appDir, onChange, options = {}) {
|
|
|
81
82
|
}
|
|
82
83
|
};
|
|
83
84
|
}
|
|
85
|
+
function watchDevRuntimeFiles(appDir, onChange, options = {}) {
|
|
86
|
+
const debounceMs = options.debounceMs ?? 250;
|
|
87
|
+
const generatedDir = path.join(appDir, ".mercato", "generated");
|
|
88
|
+
const timers = /* @__PURE__ */ new Map();
|
|
89
|
+
if (!fs.existsSync(generatedDir)) {
|
|
90
|
+
return () => {
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
let watcher = null;
|
|
94
|
+
try {
|
|
95
|
+
watcher = fs.watch(generatedDir, (eventType, fileName) => {
|
|
96
|
+
if (eventType !== "change" && eventType !== "rename") return;
|
|
97
|
+
const name = String(fileName ?? "");
|
|
98
|
+
if (!name) return;
|
|
99
|
+
if (!RUNTIME_GRAPH_FILE_PATTERN.test(name)) return;
|
|
100
|
+
const filePath = path.join(generatedDir, name);
|
|
101
|
+
const existingTimer = timers.get(filePath);
|
|
102
|
+
if (existingTimer) {
|
|
103
|
+
clearTimeout(existingTimer);
|
|
104
|
+
}
|
|
105
|
+
timers.set(filePath, setTimeout(() => {
|
|
106
|
+
timers.delete(filePath);
|
|
107
|
+
onChange(filePath);
|
|
108
|
+
}, debounceMs));
|
|
109
|
+
});
|
|
110
|
+
} catch {
|
|
111
|
+
return () => {
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return () => {
|
|
115
|
+
for (const timer of timers.values()) {
|
|
116
|
+
clearTimeout(timer);
|
|
117
|
+
}
|
|
118
|
+
timers.clear();
|
|
119
|
+
watcher?.close();
|
|
120
|
+
};
|
|
121
|
+
}
|
|
84
122
|
export {
|
|
85
123
|
createDevEnvReloader,
|
|
86
124
|
resolveDevEnvFilePaths,
|
|
87
|
-
watchDevEnvFiles
|
|
125
|
+
watchDevEnvFiles,
|
|
126
|
+
watchDevRuntimeFiles
|
|
88
127
|
};
|
|
89
128
|
//# sourceMappingURL=dev-env-reload.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/dev-env-reload.ts"],
|
|
4
|
-
"sourcesContent": ["import dotenv from 'dotenv'\nimport fs from 'node:fs'\nimport path from 'node:path'\n\nconst DEV_ENV_PRIORITY = [\n '.env',\n '.env.development',\n '.env.local',\n '.env.development.local',\n] as const\n\nexport type DevEnvReloader = {\n reload: () => void\n getWatchedFiles: () => string[]\n}\n\nexport function resolveDevEnvFilePaths(appDir: string): string[] {\n return DEV_ENV_PRIORITY.map((fileName) => path.join(appDir, fileName))\n}\n\nexport function createDevEnvReloader(\n appDir: string,\n environment: NodeJS.ProcessEnv = process.env,\n baseEnvironmentEntries: Iterable<[string, string | undefined]> = Object.entries(environment),\n): DevEnvReloader {\n const baseEnvironment = new Map<string, string | undefined>(\n Array.from(baseEnvironmentEntries, ([key, value]) => [key, value]),\n )\n const managedKeys = new Set<string>()\n const envFilePaths = resolveDevEnvFilePaths(appDir)\n\n const resetManagedKeys = () => {\n for (const key of managedKeys) {\n const baseValue = baseEnvironment.get(key)\n if (baseValue === undefined) {\n delete environment[key]\n } else {\n environment[key] = baseValue\n }\n }\n managedKeys.clear()\n }\n\n const reload = () => {\n resetManagedKeys()\n\n for (const envFilePath of envFilePaths) {\n if (!fs.existsSync(envFilePath)) continue\n\n const parsed = dotenv.parse(fs.readFileSync(envFilePath))\n for (const [key, value] of Object.entries(parsed)) {\n if (baseEnvironment.has(key) && baseEnvironment.get(key) !== undefined) {\n continue\n }\n environment[key] = value\n managedKeys.add(key)\n }\n }\n }\n\n return {\n reload,\n getWatchedFiles: () => [...envFilePaths],\n }\n}\n\nexport function watchDevEnvFiles(\n appDir: string,\n onChange: (filePath: string) => void,\n options: { debounceMs?: number } = {},\n): () => void {\n const debounceMs = options.debounceMs ?? 250\n const envFilePaths = resolveDevEnvFilePaths(appDir)\n const timers = new Map<string, NodeJS.Timeout>()\n const watchers = envFilePaths.map((envFilePath) => {\n const watchDir = path.dirname(envFilePath)\n const watchFileName = path.basename(envFilePath)\n\n if (!fs.existsSync(watchDir)) return null\n\n try {\n return fs.watch(watchDir, (eventType, fileName) => {\n if (eventType !== 'change' && eventType !== 'rename') return\n if (String(fileName ?? '') !== watchFileName) return\n\n const existingTimer = timers.get(envFilePath)\n if (existingTimer) {\n clearTimeout(existingTimer)\n }\n\n timers.set(envFilePath, setTimeout(() => {\n timers.delete(envFilePath)\n onChange(envFilePath)\n }, debounceMs))\n })\n } catch {\n return null\n }\n }).filter((watcher): watcher is fs.FSWatcher => watcher !== null)\n\n return () => {\n for (const timer of timers.values()) {\n clearTimeout(timer)\n }\n timers.clear()\n for (const watcher of watchers) {\n watcher.close()\n }\n }\n}\n"],
|
|
5
|
-
"mappings": "AAAA,OAAO,YAAY;AACnB,OAAO,QAAQ;AACf,OAAO,UAAU;AAEjB,MAAM,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAOO,SAAS,uBAAuB,QAA0B;AAC/D,SAAO,iBAAiB,IAAI,CAAC,aAAa,KAAK,KAAK,QAAQ,QAAQ,CAAC;AACvE;AAEO,SAAS,qBACd,QACA,cAAiC,QAAQ,KACzC,yBAAiE,OAAO,QAAQ,WAAW,GAC3E;AAChB,QAAM,kBAAkB,IAAI;AAAA,IAC1B,MAAM,KAAK,wBAAwB,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,KAAK,KAAK,CAAC;AAAA,EACnE;AACA,QAAM,cAAc,oBAAI,IAAY;AACpC,QAAM,eAAe,uBAAuB,MAAM;AAElD,QAAM,mBAAmB,MAAM;AAC7B,eAAW,OAAO,aAAa;AAC7B,YAAM,YAAY,gBAAgB,IAAI,GAAG;AACzC,UAAI,cAAc,QAAW;AAC3B,eAAO,YAAY,GAAG;AAAA,MACxB,OAAO;AACL,oBAAY,GAAG,IAAI;AAAA,MACrB;AAAA,IACF;AACA,gBAAY,MAAM;AAAA,EACpB;AAEA,QAAM,SAAS,MAAM;AACnB,qBAAiB;AAEjB,eAAW,eAAe,cAAc;AACtC,UAAI,CAAC,GAAG,WAAW,WAAW,EAAG;AAEjC,YAAM,SAAS,OAAO,MAAM,GAAG,aAAa,WAAW,CAAC;AACxD,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,YAAI,gBAAgB,IAAI,GAAG,KAAK,gBAAgB,IAAI,GAAG,MAAM,QAAW;AACtE;AAAA,QACF;AACA,oBAAY,GAAG,IAAI;AACnB,oBAAY,IAAI,GAAG;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,iBAAiB,MAAM,CAAC,GAAG,YAAY;AAAA,EACzC;AACF;AAEO,SAAS,iBACd,QACA,UACA,UAAmC,CAAC,GACxB;AACZ,QAAM,aAAa,QAAQ,cAAc;AACzC,QAAM,eAAe,uBAAuB,MAAM;AAClD,QAAM,SAAS,oBAAI,IAA4B;AAC/C,QAAM,WAAW,aAAa,IAAI,CAAC,gBAAgB;AACjD,UAAM,WAAW,KAAK,QAAQ,WAAW;AACzC,UAAM,gBAAgB,KAAK,SAAS,WAAW;AAE/C,QAAI,CAAC,GAAG,WAAW,QAAQ,EAAG,QAAO;AAErC,QAAI;AACF,aAAO,GAAG,MAAM,UAAU,CAAC,WAAW,aAAa;AACjD,YAAI,cAAc,YAAY,cAAc,SAAU;AACtD,YAAI,OAAO,YAAY,EAAE,MAAM,cAAe;AAE9C,cAAM,gBAAgB,OAAO,IAAI,WAAW;AAC5C,YAAI,eAAe;AACjB,uBAAa,aAAa;AAAA,QAC5B;AAEA,eAAO,IAAI,aAAa,WAAW,MAAM;AACvC,iBAAO,OAAO,WAAW;AACzB,mBAAS,WAAW;AAAA,QACtB,GAAG,UAAU,CAAC;AAAA,MAChB,CAAC;AAAA,IACH,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF,CAAC,EAAE,OAAO,CAAC,YAAqC,YAAY,IAAI;AAEhE,SAAO,MAAM;AACX,eAAW,SAAS,OAAO,OAAO,GAAG;AACnC,mBAAa,KAAK;AAAA,IACpB;AACA,WAAO,MAAM;AACb,eAAW,WAAW,UAAU;AAC9B,cAAQ,MAAM;AAAA,IAChB;AAAA,EACF;AACF;",
|
|
4
|
+
"sourcesContent": ["import dotenv from 'dotenv'\nimport fs from 'node:fs'\nimport path from 'node:path'\n\nconst RUNTIME_GRAPH_FILE_PATTERN = /\\.generated(?:\\.[a-z0-9]+)?(?:\\.tsx?|\\.checksum)$/i\n\nconst DEV_ENV_PRIORITY = [\n '.env',\n '.env.development',\n '.env.local',\n '.env.development.local',\n] as const\n\nexport type DevEnvReloader = {\n reload: () => void\n getWatchedFiles: () => string[]\n}\n\nexport function resolveDevEnvFilePaths(appDir: string): string[] {\n return DEV_ENV_PRIORITY.map((fileName) => path.join(appDir, fileName))\n}\n\nexport function createDevEnvReloader(\n appDir: string,\n environment: NodeJS.ProcessEnv = process.env,\n baseEnvironmentEntries: Iterable<[string, string | undefined]> = Object.entries(environment),\n): DevEnvReloader {\n const baseEnvironment = new Map<string, string | undefined>(\n Array.from(baseEnvironmentEntries, ([key, value]) => [key, value]),\n )\n const managedKeys = new Set<string>()\n const envFilePaths = resolveDevEnvFilePaths(appDir)\n\n const resetManagedKeys = () => {\n for (const key of managedKeys) {\n const baseValue = baseEnvironment.get(key)\n if (baseValue === undefined) {\n delete environment[key]\n } else {\n environment[key] = baseValue\n }\n }\n managedKeys.clear()\n }\n\n const reload = () => {\n resetManagedKeys()\n\n for (const envFilePath of envFilePaths) {\n if (!fs.existsSync(envFilePath)) continue\n\n const parsed = dotenv.parse(fs.readFileSync(envFilePath))\n for (const [key, value] of Object.entries(parsed)) {\n if (baseEnvironment.has(key) && baseEnvironment.get(key) !== undefined) {\n continue\n }\n environment[key] = value\n managedKeys.add(key)\n }\n }\n }\n\n return {\n reload,\n getWatchedFiles: () => [...envFilePaths],\n }\n}\n\nexport function watchDevEnvFiles(\n appDir: string,\n onChange: (filePath: string) => void,\n options: { debounceMs?: number } = {},\n): () => void {\n const debounceMs = options.debounceMs ?? 250\n const envFilePaths = resolveDevEnvFilePaths(appDir)\n const timers = new Map<string, NodeJS.Timeout>()\n const watchers = envFilePaths.map((envFilePath) => {\n const watchDir = path.dirname(envFilePath)\n const watchFileName = path.basename(envFilePath)\n\n if (!fs.existsSync(watchDir)) return null\n\n try {\n return fs.watch(watchDir, (eventType, fileName) => {\n if (eventType !== 'change' && eventType !== 'rename') return\n if (String(fileName ?? '') !== watchFileName) return\n\n const existingTimer = timers.get(envFilePath)\n if (existingTimer) {\n clearTimeout(existingTimer)\n }\n\n timers.set(envFilePath, setTimeout(() => {\n timers.delete(envFilePath)\n onChange(envFilePath)\n }, debounceMs))\n })\n } catch {\n return null\n }\n }).filter((watcher): watcher is fs.FSWatcher => watcher !== null)\n\n return () => {\n for (const timer of timers.values()) {\n clearTimeout(timer)\n }\n timers.clear()\n for (const watcher of watchers) {\n watcher.close()\n }\n }\n}\n\nexport function watchDevRuntimeFiles(\n appDir: string,\n onChange: (filePath: string) => void,\n options: { debounceMs?: number } = {},\n): () => void {\n const debounceMs = options.debounceMs ?? 250\n const generatedDir = path.join(appDir, '.mercato', 'generated')\n const timers = new Map<string, NodeJS.Timeout>()\n\n if (!fs.existsSync(generatedDir)) {\n return () => {}\n }\n\n let watcher: fs.FSWatcher | null = null\n try {\n watcher = fs.watch(generatedDir, (eventType, fileName) => {\n if (eventType !== 'change' && eventType !== 'rename') return\n const name = String(fileName ?? '')\n if (!name) return\n if (!RUNTIME_GRAPH_FILE_PATTERN.test(name)) return\n\n const filePath = path.join(generatedDir, name)\n const existingTimer = timers.get(filePath)\n if (existingTimer) {\n clearTimeout(existingTimer)\n }\n\n timers.set(filePath, setTimeout(() => {\n timers.delete(filePath)\n onChange(filePath)\n }, debounceMs))\n })\n } catch {\n return () => {}\n }\n\n return () => {\n for (const timer of timers.values()) {\n clearTimeout(timer)\n }\n timers.clear()\n watcher?.close()\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
MODULE_CODE_EXTENSIONS,
|
|
6
|
+
SCAN_CONFIGS,
|
|
7
|
+
scanModuleDir,
|
|
8
|
+
stripModuleCodeExtension
|
|
9
|
+
} from "./generators/scanner.js";
|
|
10
|
+
const STRUCTURAL_CONVENTION_FILES = [
|
|
11
|
+
"index.ts",
|
|
12
|
+
"cli.ts",
|
|
13
|
+
"di.ts",
|
|
14
|
+
"acl.ts",
|
|
15
|
+
"setup.ts",
|
|
16
|
+
"encryption.ts",
|
|
17
|
+
"ce.ts",
|
|
18
|
+
"search.ts",
|
|
19
|
+
"events.ts",
|
|
20
|
+
"notifications.ts",
|
|
21
|
+
"notifications.client.ts",
|
|
22
|
+
"notifications.handlers.ts",
|
|
23
|
+
"translations.ts",
|
|
24
|
+
"generators.ts",
|
|
25
|
+
"ai-tools.ts",
|
|
26
|
+
"ai-agents.ts",
|
|
27
|
+
"analytics.ts",
|
|
28
|
+
"workflows.ts",
|
|
29
|
+
"inbox-actions.ts",
|
|
30
|
+
"message-types.ts",
|
|
31
|
+
"message-objects.ts",
|
|
32
|
+
"integration.ts",
|
|
33
|
+
"security.mfa-providers.ts",
|
|
34
|
+
"security.sudo.ts",
|
|
35
|
+
"data/entities.ts",
|
|
36
|
+
"data/extensions.ts",
|
|
37
|
+
"data/fields.ts",
|
|
38
|
+
"data/enrichers.ts",
|
|
39
|
+
"data/guards.ts",
|
|
40
|
+
"api/interceptors.ts",
|
|
41
|
+
"commands/interceptors.ts",
|
|
42
|
+
"widgets/components.ts",
|
|
43
|
+
"widgets/injection-table.ts",
|
|
44
|
+
"frontend/middleware.ts",
|
|
45
|
+
"backend/middleware.ts"
|
|
46
|
+
];
|
|
47
|
+
const CONTENT_SENSITIVE_SCAN_CONFIGS = [
|
|
48
|
+
SCAN_CONFIGS.apiRoutes,
|
|
49
|
+
SCAN_CONFIGS.apiPlainFiles,
|
|
50
|
+
SCAN_CONFIGS.subscribers,
|
|
51
|
+
SCAN_CONFIGS.workers,
|
|
52
|
+
SCAN_CONFIGS.dashboardWidgets,
|
|
53
|
+
SCAN_CONFIGS.injectionWidgets
|
|
54
|
+
];
|
|
55
|
+
const ROUTE_SHAPE_SCAN_CONFIGS = [
|
|
56
|
+
SCAN_CONFIGS.frontendPages,
|
|
57
|
+
SCAN_CONFIGS.backendPages
|
|
58
|
+
];
|
|
59
|
+
function checksum(value) {
|
|
60
|
+
return crypto.createHash("md5").update(value).digest("hex");
|
|
61
|
+
}
|
|
62
|
+
function fileRecord(filePath, base, mode) {
|
|
63
|
+
if (!fs.existsSync(filePath)) return null;
|
|
64
|
+
let stat;
|
|
65
|
+
try {
|
|
66
|
+
stat = fs.statSync(filePath);
|
|
67
|
+
} catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
if (!stat.isFile()) return null;
|
|
71
|
+
const rel = path.relative(base, filePath).replace(/\\/g, "/");
|
|
72
|
+
if (mode === "shape") {
|
|
73
|
+
return `file:${rel}`;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
return `file:${rel}:${stat.size}:${checksum(fs.readFileSync(filePath, "utf8"))}`;
|
|
77
|
+
} catch {
|
|
78
|
+
return `file:${rel}:unreadable`;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function resolveCodeFile(base, relativePath) {
|
|
82
|
+
const stripped = stripModuleCodeExtension(relativePath);
|
|
83
|
+
const candidates = MODULE_CODE_EXTENSIONS.map((extension) => `${stripped}${extension}`);
|
|
84
|
+
for (const candidate of candidates) {
|
|
85
|
+
const filePath = path.join(base, ...candidate.split("/"));
|
|
86
|
+
if (fs.existsSync(filePath)) return filePath;
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
function hasInlinePageMetadata(filePath) {
|
|
91
|
+
try {
|
|
92
|
+
const source = fs.readFileSync(filePath, "utf8");
|
|
93
|
+
return /\bexport\s+(?:const|let|var|function|class)\s+metadata\b/.test(source) || /\bexport\s+\{[^}]*\bmetadata\b[^}]*\}/.test(source);
|
|
94
|
+
} catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function addConventionRecords(records, roots) {
|
|
99
|
+
for (const base of [roots.pkgBase, roots.appBase]) {
|
|
100
|
+
records.push(`module-root:${base}:${fs.existsSync(base) ? "present" : "missing"}`);
|
|
101
|
+
for (const relativePath of STRUCTURAL_CONVENTION_FILES) {
|
|
102
|
+
const filePath = resolveCodeFile(base, relativePath);
|
|
103
|
+
if (!filePath) {
|
|
104
|
+
records.push(`missing:${base}:${relativePath}`);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const record = fileRecord(filePath, base, "content");
|
|
108
|
+
if (record) records.push(record);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function addScannedRecords(records, roots) {
|
|
113
|
+
for (const config of CONTENT_SENSITIVE_SCAN_CONFIGS) {
|
|
114
|
+
for (const scanned of scanModuleDir(roots, config)) {
|
|
115
|
+
const base = scanned.fromApp ? roots.appBase : roots.pkgBase;
|
|
116
|
+
const filePath = path.join(base, ...config.folder.split("/"), ...scanned.relPath.split("/"));
|
|
117
|
+
const record = fileRecord(filePath, base, "content");
|
|
118
|
+
if (record) records.push(`${config.folder}:${record}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
for (const config of ROUTE_SHAPE_SCAN_CONFIGS) {
|
|
122
|
+
for (const scanned of scanModuleDir(roots, config)) {
|
|
123
|
+
const base = scanned.fromApp ? roots.appBase : roots.pkgBase;
|
|
124
|
+
const folderPath = path.join(base, ...config.folder.split("/"));
|
|
125
|
+
const filePath = path.join(folderPath, ...scanned.relPath.split("/"));
|
|
126
|
+
const pageRecord = fileRecord(filePath, base, hasInlinePageMetadata(filePath) ? "content" : "shape");
|
|
127
|
+
if (pageRecord) records.push(`${config.folder}:${pageRecord}`);
|
|
128
|
+
const dir = path.dirname(filePath);
|
|
129
|
+
const stem = stripModuleCodeExtension(path.basename(filePath));
|
|
130
|
+
const metaCandidates = stem === "page" ? ["page.meta", "meta"] : [`${stem}.meta`, "meta"];
|
|
131
|
+
for (const candidate of metaCandidates) {
|
|
132
|
+
const metaPath = resolveCodeFile(dir, candidate);
|
|
133
|
+
if (!metaPath) continue;
|
|
134
|
+
const record = fileRecord(metaPath, base, "content");
|
|
135
|
+
if (record) records.push(`${config.folder}:meta:${record}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function calculateGenerateWatchStructureChecksum(options) {
|
|
141
|
+
const records = [];
|
|
142
|
+
const modulesRecord = fileRecord(options.modulesFile, path.dirname(options.modulesFile), "content");
|
|
143
|
+
records.push(modulesRecord ?? `missing:${options.modulesFile}`);
|
|
144
|
+
for (const roots of options.moduleRoots) {
|
|
145
|
+
addConventionRecords(records, roots);
|
|
146
|
+
addScannedRecords(records, roots);
|
|
147
|
+
}
|
|
148
|
+
return checksum(records.sort((a, b) => a.localeCompare(b)).join("\n"));
|
|
149
|
+
}
|
|
150
|
+
export {
|
|
151
|
+
calculateGenerateWatchStructureChecksum
|
|
152
|
+
};
|
|
153
|
+
//# sourceMappingURL=generate-watch-structure.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/lib/generate-watch-structure.ts"],
|
|
4
|
+
"sourcesContent": ["import crypto from 'node:crypto'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport {\n MODULE_CODE_EXTENSIONS,\n SCAN_CONFIGS,\n scanModuleDir,\n stripModuleCodeExtension,\n type ModuleRoots,\n} from './generators/scanner'\n\nconst STRUCTURAL_CONVENTION_FILES = [\n 'index.ts',\n 'cli.ts',\n 'di.ts',\n 'acl.ts',\n 'setup.ts',\n 'encryption.ts',\n 'ce.ts',\n 'search.ts',\n 'events.ts',\n 'notifications.ts',\n 'notifications.client.ts',\n 'notifications.handlers.ts',\n 'translations.ts',\n 'generators.ts',\n 'ai-tools.ts',\n 'ai-agents.ts',\n 'analytics.ts',\n 'workflows.ts',\n 'inbox-actions.ts',\n 'message-types.ts',\n 'message-objects.ts',\n 'integration.ts',\n 'security.mfa-providers.ts',\n 'security.sudo.ts',\n 'data/entities.ts',\n 'data/extensions.ts',\n 'data/fields.ts',\n 'data/enrichers.ts',\n 'data/guards.ts',\n 'api/interceptors.ts',\n 'commands/interceptors.ts',\n 'widgets/components.ts',\n 'widgets/injection-table.ts',\n 'frontend/middleware.ts',\n 'backend/middleware.ts',\n] as const\n\nconst CONTENT_SENSITIVE_SCAN_CONFIGS = [\n SCAN_CONFIGS.apiRoutes,\n SCAN_CONFIGS.apiPlainFiles,\n SCAN_CONFIGS.subscribers,\n SCAN_CONFIGS.workers,\n SCAN_CONFIGS.dashboardWidgets,\n SCAN_CONFIGS.injectionWidgets,\n] as const\n\nconst ROUTE_SHAPE_SCAN_CONFIGS = [\n SCAN_CONFIGS.frontendPages,\n SCAN_CONFIGS.backendPages,\n] as const\n\nfunction checksum(value: string): string {\n return crypto.createHash('md5').update(value).digest('hex')\n}\n\nfunction fileRecord(filePath: string, base: string, mode: 'content' | 'shape'): string | null {\n if (!fs.existsSync(filePath)) return null\n let stat: fs.Stats\n try {\n stat = fs.statSync(filePath)\n } catch {\n return null\n }\n if (!stat.isFile()) return null\n const rel = path.relative(base, filePath).replace(/\\\\/g, '/')\n if (mode === 'shape') {\n return `file:${rel}`\n }\n try {\n return `file:${rel}:${stat.size}:${checksum(fs.readFileSync(filePath, 'utf8'))}`\n } catch {\n return `file:${rel}:unreadable`\n }\n}\n\nfunction resolveCodeFile(base: string, relativePath: string): string | null {\n const stripped = stripModuleCodeExtension(relativePath)\n const candidates = MODULE_CODE_EXTENSIONS.map((extension) => `${stripped}${extension}`)\n for (const candidate of candidates) {\n const filePath = path.join(base, ...candidate.split('/'))\n if (fs.existsSync(filePath)) return filePath\n }\n return null\n}\n\nfunction hasInlinePageMetadata(filePath: string): boolean {\n try {\n const source = fs.readFileSync(filePath, 'utf8')\n return /\\bexport\\s+(?:const|let|var|function|class)\\s+metadata\\b/.test(source)\n || /\\bexport\\s+\\{[^}]*\\bmetadata\\b[^}]*\\}/.test(source)\n } catch {\n return false\n }\n}\n\nfunction addConventionRecords(records: string[], roots: ModuleRoots): void {\n for (const base of [roots.pkgBase, roots.appBase]) {\n records.push(`module-root:${base}:${fs.existsSync(base) ? 'present' : 'missing'}`)\n for (const relativePath of STRUCTURAL_CONVENTION_FILES) {\n const filePath = resolveCodeFile(base, relativePath)\n if (!filePath) {\n records.push(`missing:${base}:${relativePath}`)\n continue\n }\n const record = fileRecord(filePath, base, 'content')\n if (record) records.push(record)\n }\n }\n}\n\nfunction addScannedRecords(records: string[], roots: ModuleRoots): void {\n for (const config of CONTENT_SENSITIVE_SCAN_CONFIGS) {\n for (const scanned of scanModuleDir(roots, config)) {\n const base = scanned.fromApp ? roots.appBase : roots.pkgBase\n const filePath = path.join(base, ...config.folder.split('/'), ...scanned.relPath.split('/'))\n const record = fileRecord(filePath, base, 'content')\n if (record) records.push(`${config.folder}:${record}`)\n }\n }\n\n for (const config of ROUTE_SHAPE_SCAN_CONFIGS) {\n for (const scanned of scanModuleDir(roots, config)) {\n const base = scanned.fromApp ? roots.appBase : roots.pkgBase\n const folderPath = path.join(base, ...config.folder.split('/'))\n const filePath = path.join(folderPath, ...scanned.relPath.split('/'))\n const pageRecord = fileRecord(filePath, base, hasInlinePageMetadata(filePath) ? 'content' : 'shape')\n if (pageRecord) records.push(`${config.folder}:${pageRecord}`)\n\n const dir = path.dirname(filePath)\n const stem = stripModuleCodeExtension(path.basename(filePath))\n const metaCandidates = stem === 'page'\n ? ['page.meta', 'meta']\n : [`${stem}.meta`, 'meta']\n for (const candidate of metaCandidates) {\n const metaPath = resolveCodeFile(dir, candidate)\n if (!metaPath) continue\n const record = fileRecord(metaPath, base, 'content')\n if (record) records.push(`${config.folder}:meta:${record}`)\n }\n }\n }\n}\n\nexport function calculateGenerateWatchStructureChecksum(options: {\n modulesFile: string\n moduleRoots: ModuleRoots[]\n}): string {\n const records: string[] = []\n const modulesRecord = fileRecord(options.modulesFile, path.dirname(options.modulesFile), 'content')\n records.push(modulesRecord ?? `missing:${options.modulesFile}`)\n\n for (const roots of options.moduleRoots) {\n addConventionRecords(records, roots)\n addScannedRecords(records, roots)\n }\n\n return checksum(records.sort((a, b) => a.localeCompare(b)).join('\\n'))\n}\n"],
|
|
5
|
+
"mappings": "AAAA,OAAO,YAAY;AACnB,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAEP,MAAM,8BAA8B;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,MAAM,iCAAiC;AAAA,EACrC,aAAa;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AACf;AAEA,MAAM,2BAA2B;AAAA,EAC/B,aAAa;AAAA,EACb,aAAa;AACf;AAEA,SAAS,SAAS,OAAuB;AACvC,SAAO,OAAO,WAAW,KAAK,EAAE,OAAO,KAAK,EAAE,OAAO,KAAK;AAC5D;AAEA,SAAS,WAAW,UAAkB,MAAc,MAA0C;AAC5F,MAAI,CAAC,GAAG,WAAW,QAAQ,EAAG,QAAO;AACrC,MAAI;AACJ,MAAI;AACF,WAAO,GAAG,SAAS,QAAQ;AAAA,EAC7B,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI,CAAC,KAAK,OAAO,EAAG,QAAO;AAC3B,QAAM,MAAM,KAAK,SAAS,MAAM,QAAQ,EAAE,QAAQ,OAAO,GAAG;AAC5D,MAAI,SAAS,SAAS;AACpB,WAAO,QAAQ,GAAG;AAAA,EACpB;AACA,MAAI;AACF,WAAO,QAAQ,GAAG,IAAI,KAAK,IAAI,IAAI,SAAS,GAAG,aAAa,UAAU,MAAM,CAAC,CAAC;AAAA,EAChF,QAAQ;AACN,WAAO,QAAQ,GAAG;AAAA,EACpB;AACF;AAEA,SAAS,gBAAgB,MAAc,cAAqC;AAC1E,QAAM,WAAW,yBAAyB,YAAY;AACtD,QAAM,aAAa,uBAAuB,IAAI,CAAC,cAAc,GAAG,QAAQ,GAAG,SAAS,EAAE;AACtF,aAAW,aAAa,YAAY;AAClC,UAAM,WAAW,KAAK,KAAK,MAAM,GAAG,UAAU,MAAM,GAAG,CAAC;AACxD,QAAI,GAAG,WAAW,QAAQ,EAAG,QAAO;AAAA,EACtC;AACA,SAAO;AACT;AAEA,SAAS,sBAAsB,UAA2B;AACxD,MAAI;AACF,UAAM,SAAS,GAAG,aAAa,UAAU,MAAM;AAC/C,WAAO,2DAA2D,KAAK,MAAM,KACxE,wCAAwC,KAAK,MAAM;AAAA,EAC1D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,qBAAqB,SAAmB,OAA0B;AACzE,aAAW,QAAQ,CAAC,MAAM,SAAS,MAAM,OAAO,GAAG;AACjD,YAAQ,KAAK,eAAe,IAAI,IAAI,GAAG,WAAW,IAAI,IAAI,YAAY,SAAS,EAAE;AACjF,eAAW,gBAAgB,6BAA6B;AACtD,YAAM,WAAW,gBAAgB,MAAM,YAAY;AACnD,UAAI,CAAC,UAAU;AACb,gBAAQ,KAAK,WAAW,IAAI,IAAI,YAAY,EAAE;AAC9C;AAAA,MACF;AACA,YAAM,SAAS,WAAW,UAAU,MAAM,SAAS;AACnD,UAAI,OAAQ,SAAQ,KAAK,MAAM;AAAA,IACjC;AAAA,EACF;AACF;AAEA,SAAS,kBAAkB,SAAmB,OAA0B;AACtE,aAAW,UAAU,gCAAgC;AACnD,eAAW,WAAW,cAAc,OAAO,MAAM,GAAG;AAClD,YAAM,OAAO,QAAQ,UAAU,MAAM,UAAU,MAAM;AACrD,YAAM,WAAW,KAAK,KAAK,MAAM,GAAG,OAAO,OAAO,MAAM,GAAG,GAAG,GAAG,QAAQ,QAAQ,MAAM,GAAG,CAAC;AAC3F,YAAM,SAAS,WAAW,UAAU,MAAM,SAAS;AACnD,UAAI,OAAQ,SAAQ,KAAK,GAAG,OAAO,MAAM,IAAI,MAAM,EAAE;AAAA,IACvD;AAAA,EACF;AAEA,aAAW,UAAU,0BAA0B;AAC7C,eAAW,WAAW,cAAc,OAAO,MAAM,GAAG;AAClD,YAAM,OAAO,QAAQ,UAAU,MAAM,UAAU,MAAM;AACrD,YAAM,aAAa,KAAK,KAAK,MAAM,GAAG,OAAO,OAAO,MAAM,GAAG,CAAC;AAC9D,YAAM,WAAW,KAAK,KAAK,YAAY,GAAG,QAAQ,QAAQ,MAAM,GAAG,CAAC;AACpE,YAAM,aAAa,WAAW,UAAU,MAAM,sBAAsB,QAAQ,IAAI,YAAY,OAAO;AACnG,UAAI,WAAY,SAAQ,KAAK,GAAG,OAAO,MAAM,IAAI,UAAU,EAAE;AAE7D,YAAM,MAAM,KAAK,QAAQ,QAAQ;AACjC,YAAM,OAAO,yBAAyB,KAAK,SAAS,QAAQ,CAAC;AAC7D,YAAM,iBAAiB,SAAS,SAC5B,CAAC,aAAa,MAAM,IACpB,CAAC,GAAG,IAAI,SAAS,MAAM;AAC3B,iBAAW,aAAa,gBAAgB;AACtC,cAAM,WAAW,gBAAgB,KAAK,SAAS;AAC/C,YAAI,CAAC,SAAU;AACf,cAAM,SAAS,WAAW,UAAU,MAAM,SAAS;AACnD,YAAI,OAAQ,SAAQ,KAAK,GAAG,OAAO,MAAM,SAAS,MAAM,EAAE;AAAA,MAC5D;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,wCAAwC,SAG7C;AACT,QAAM,UAAoB,CAAC;AAC3B,QAAM,gBAAgB,WAAW,QAAQ,aAAa,KAAK,QAAQ,QAAQ,WAAW,GAAG,SAAS;AAClG,UAAQ,KAAK,iBAAiB,WAAW,QAAQ,WAAW,EAAE;AAE9D,aAAW,SAAS,QAAQ,aAAa;AACvC,yBAAqB,SAAS,KAAK;AACnC,sBAAkB,SAAS,KAAK;AAAA,EAClC;AAEA,SAAO,SAAS,QAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,cAAc,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC;AACvE;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -2345,6 +2345,20 @@ async function startEphemeralEnvironment(options) {
|
|
|
2345
2345
|
TENANT_DATA_ENCRYPTION_FALLBACK_KEY: process.env.TENANT_DATA_ENCRYPTION_FALLBACK_KEY ?? "om-ephemeral-integration-fallback-key",
|
|
2346
2346
|
AUTO_SPAWN_WORKERS: process.env.AUTO_SPAWN_WORKERS ?? "true",
|
|
2347
2347
|
AUTO_SPAWN_SCHEDULER: "false",
|
|
2348
|
+
// Hide the demo feedback floating action button — it lives at
|
|
2349
|
+
// `fixed bottom-6 right-6 z-banner` and consistently intercepts pointer
|
|
2350
|
+
// events on row-action menus and other bottom-of-viewport UI elements
|
|
2351
|
+
// (e.g. TC-WF-006 Delete menuitem click). The widget is already gated
|
|
2352
|
+
// on `DEMO_MODE !== 'false'` in the backend layout, so opting out here
|
|
2353
|
+
// only affects the integration test runtime — dev + prod stay unchanged.
|
|
2354
|
+
DEMO_MODE: "false",
|
|
2355
|
+
// Disable the feature-toggle resolution cache. Tests flip overrides
|
|
2356
|
+
// (e.g. example_customers_sync enabled/bidirectional) between cases; the
|
|
2357
|
+
// 1-minute resolution cache can serve a stale value across set/clear
|
|
2358
|
+
// under fast churn, which made the example.todo.* persistent subscribers
|
|
2359
|
+
// skip enqueueing inbound sync jobs (TC-CRM-028 inbound trio flake).
|
|
2360
|
+
// Fresh DB reads make flag-gated sync deterministic in tests only.
|
|
2361
|
+
OM_FEATURE_TOGGLES_CACHE_DISABLED: "1",
|
|
2348
2362
|
OM_CLI_QUIET: "1",
|
|
2349
2363
|
MERCATO_QUIET: "1",
|
|
2350
2364
|
QUEUE_BASE_DIR: EPHEMERAL_QUEUE_BASE_DIR,
|