@open-mercato/cli 0.6.3 → 0.6.4-canary.3910.1.0ff5e9e9be
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 +0 -1
- package/dist/lib/dev-env-reload.js +1 -40
- package/dist/lib/dev-env-reload.js.map +2 -2
- package/dist/lib/testing/integration.js +0 -7
- package/dist/lib/testing/integration.js.map +2 -2
- package/dist/mercato.js +98 -244
- package/dist/mercato.js.map +2 -2
- package/package.json +8 -7
- package/src/__tests__/mercato.test.ts +3 -13
- package/src/lib/__tests__/dev-env-reload.test.ts +1 -21
- package/src/lib/dev-env-reload.ts +0 -47
- package/src/lib/testing/integration.ts +0 -7
- package/src/mercato.ts +67 -241
- package/dist/lib/generate-watch-structure.js +0 -153
- package/dist/lib/generate-watch-structure.js.map +0 -7
- package/src/lib/__tests__/generate-watch-structure.test.ts +0 -80
- package/src/lib/generate-watch-structure.ts +0 -170
|
@@ -1,153 +0,0 @@
|
|
|
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
|
|
@@ -1,7 +0,0 @@
|
|
|
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": "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",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs'
|
|
2
|
-
import os from 'node:os'
|
|
3
|
-
import path from 'node:path'
|
|
4
|
-
import { calculateGenerateWatchStructureChecksum } from '../generate-watch-structure'
|
|
5
|
-
|
|
6
|
-
function write(filePath: string, content: string): void {
|
|
7
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
8
|
-
fs.writeFileSync(filePath, content)
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
describe('calculateGenerateWatchStructureChecksum', () => {
|
|
12
|
-
let root: string
|
|
13
|
-
let appDir: string
|
|
14
|
-
let pkgModule: string
|
|
15
|
-
let appModule: string
|
|
16
|
-
|
|
17
|
-
beforeEach(() => {
|
|
18
|
-
root = fs.mkdtempSync(path.join(os.tmpdir(), 'om-generate-watch-'))
|
|
19
|
-
appDir = path.join(root, 'apps', 'mercato')
|
|
20
|
-
pkgModule = path.join(root, 'packages', 'core', 'src', 'modules', 'customers')
|
|
21
|
-
appModule = path.join(appDir, 'src', 'modules', 'customers')
|
|
22
|
-
write(path.join(appDir, 'src', 'modules.ts'), 'export const enabledModules = []\n')
|
|
23
|
-
write(path.join(pkgModule, 'index.ts'), 'export const metadata = { id: "customers" }\n')
|
|
24
|
-
write(path.join(pkgModule, 'backend', 'customers', 'people', 'page.tsx'), 'export default function Page() { return null }\n')
|
|
25
|
-
write(path.join(pkgModule, 'components', 'detail', 'PersonDetailTabs.tsx'), 'export function PersonDetailTabs() { return null }\n')
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
afterEach(() => {
|
|
29
|
-
fs.rmSync(root, { recursive: true, force: true })
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
function currentChecksum(): string {
|
|
33
|
-
return calculateGenerateWatchStructureChecksum({
|
|
34
|
-
modulesFile: path.join(appDir, 'src', 'modules.ts'),
|
|
35
|
-
moduleRoots: [{ appBase: appModule, pkgBase: pkgModule }],
|
|
36
|
-
})
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
it('ignores ordinary component edits outside generator discovery paths', () => {
|
|
40
|
-
const before = currentChecksum()
|
|
41
|
-
|
|
42
|
-
write(path.join(pkgModule, 'components', 'detail', 'PersonDetailTabs.tsx'), 'export function PersonDetailTabs() { return "changed" }\n')
|
|
43
|
-
|
|
44
|
-
expect(currentChecksum()).toBe(before)
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
it('changes when a discovered backend page is added', () => {
|
|
48
|
-
const before = currentChecksum()
|
|
49
|
-
|
|
50
|
-
write(path.join(pkgModule, 'backend', 'customers', 'companies', 'page.tsx'), 'export default function Page() { return null }\n')
|
|
51
|
-
|
|
52
|
-
expect(currentChecksum()).not.toBe(before)
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
it('changes when route metadata changes', () => {
|
|
56
|
-
write(path.join(pkgModule, 'backend', 'customers', 'people', 'page.meta.ts'), 'export const metadata = { nav: { label: "People" } }\n')
|
|
57
|
-
const before = currentChecksum()
|
|
58
|
-
|
|
59
|
-
write(path.join(pkgModule, 'backend', 'customers', 'people', 'page.meta.ts'), 'export const metadata = { nav: { label: "Contacts" } }\n')
|
|
60
|
-
|
|
61
|
-
expect(currentChecksum()).not.toBe(before)
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
it('changes when inline page metadata changes', () => {
|
|
65
|
-
write(path.join(pkgModule, 'backend', 'customers', 'people', 'page.tsx'), 'export const metadata = { nav: { label: "People" } }\nexport default function Page() { return null }\n')
|
|
66
|
-
const before = currentChecksum()
|
|
67
|
-
|
|
68
|
-
write(path.join(pkgModule, 'backend', 'customers', 'people', 'page.tsx'), 'export const metadata = { nav: { label: "Contacts" } }\nexport default function Page() { return null }\n')
|
|
69
|
-
|
|
70
|
-
expect(currentChecksum()).not.toBe(before)
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
it('changes when a convention file changes', () => {
|
|
74
|
-
const before = currentChecksum()
|
|
75
|
-
|
|
76
|
-
write(path.join(pkgModule, 'acl.ts'), 'export const features = [{ id: "customers.view" }]\n')
|
|
77
|
-
|
|
78
|
-
expect(currentChecksum()).not.toBe(before)
|
|
79
|
-
})
|
|
80
|
-
})
|
|
@@ -1,170 +0,0 @@
|
|
|
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
|
-
type ModuleRoots,
|
|
10
|
-
} from './generators/scanner'
|
|
11
|
-
|
|
12
|
-
const STRUCTURAL_CONVENTION_FILES = [
|
|
13
|
-
'index.ts',
|
|
14
|
-
'cli.ts',
|
|
15
|
-
'di.ts',
|
|
16
|
-
'acl.ts',
|
|
17
|
-
'setup.ts',
|
|
18
|
-
'encryption.ts',
|
|
19
|
-
'ce.ts',
|
|
20
|
-
'search.ts',
|
|
21
|
-
'events.ts',
|
|
22
|
-
'notifications.ts',
|
|
23
|
-
'notifications.client.ts',
|
|
24
|
-
'notifications.handlers.ts',
|
|
25
|
-
'translations.ts',
|
|
26
|
-
'generators.ts',
|
|
27
|
-
'ai-tools.ts',
|
|
28
|
-
'ai-agents.ts',
|
|
29
|
-
'analytics.ts',
|
|
30
|
-
'workflows.ts',
|
|
31
|
-
'inbox-actions.ts',
|
|
32
|
-
'message-types.ts',
|
|
33
|
-
'message-objects.ts',
|
|
34
|
-
'integration.ts',
|
|
35
|
-
'security.mfa-providers.ts',
|
|
36
|
-
'security.sudo.ts',
|
|
37
|
-
'data/entities.ts',
|
|
38
|
-
'data/extensions.ts',
|
|
39
|
-
'data/fields.ts',
|
|
40
|
-
'data/enrichers.ts',
|
|
41
|
-
'data/guards.ts',
|
|
42
|
-
'api/interceptors.ts',
|
|
43
|
-
'commands/interceptors.ts',
|
|
44
|
-
'widgets/components.ts',
|
|
45
|
-
'widgets/injection-table.ts',
|
|
46
|
-
'frontend/middleware.ts',
|
|
47
|
-
'backend/middleware.ts',
|
|
48
|
-
] as const
|
|
49
|
-
|
|
50
|
-
const CONTENT_SENSITIVE_SCAN_CONFIGS = [
|
|
51
|
-
SCAN_CONFIGS.apiRoutes,
|
|
52
|
-
SCAN_CONFIGS.apiPlainFiles,
|
|
53
|
-
SCAN_CONFIGS.subscribers,
|
|
54
|
-
SCAN_CONFIGS.workers,
|
|
55
|
-
SCAN_CONFIGS.dashboardWidgets,
|
|
56
|
-
SCAN_CONFIGS.injectionWidgets,
|
|
57
|
-
] as const
|
|
58
|
-
|
|
59
|
-
const ROUTE_SHAPE_SCAN_CONFIGS = [
|
|
60
|
-
SCAN_CONFIGS.frontendPages,
|
|
61
|
-
SCAN_CONFIGS.backendPages,
|
|
62
|
-
] as const
|
|
63
|
-
|
|
64
|
-
function checksum(value: string): string {
|
|
65
|
-
return crypto.createHash('md5').update(value).digest('hex')
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function fileRecord(filePath: string, base: string, mode: 'content' | 'shape'): string | null {
|
|
69
|
-
if (!fs.existsSync(filePath)) return null
|
|
70
|
-
let stat: fs.Stats
|
|
71
|
-
try {
|
|
72
|
-
stat = fs.statSync(filePath)
|
|
73
|
-
} catch {
|
|
74
|
-
return null
|
|
75
|
-
}
|
|
76
|
-
if (!stat.isFile()) return null
|
|
77
|
-
const rel = path.relative(base, filePath).replace(/\\/g, '/')
|
|
78
|
-
if (mode === 'shape') {
|
|
79
|
-
return `file:${rel}`
|
|
80
|
-
}
|
|
81
|
-
try {
|
|
82
|
-
return `file:${rel}:${stat.size}:${checksum(fs.readFileSync(filePath, 'utf8'))}`
|
|
83
|
-
} catch {
|
|
84
|
-
return `file:${rel}:unreadable`
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function resolveCodeFile(base: string, relativePath: string): string | null {
|
|
89
|
-
const stripped = stripModuleCodeExtension(relativePath)
|
|
90
|
-
const candidates = MODULE_CODE_EXTENSIONS.map((extension) => `${stripped}${extension}`)
|
|
91
|
-
for (const candidate of candidates) {
|
|
92
|
-
const filePath = path.join(base, ...candidate.split('/'))
|
|
93
|
-
if (fs.existsSync(filePath)) return filePath
|
|
94
|
-
}
|
|
95
|
-
return null
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function hasInlinePageMetadata(filePath: string): boolean {
|
|
99
|
-
try {
|
|
100
|
-
const source = fs.readFileSync(filePath, 'utf8')
|
|
101
|
-
return /\bexport\s+(?:const|let|var|function|class)\s+metadata\b/.test(source)
|
|
102
|
-
|| /\bexport\s+\{[^}]*\bmetadata\b[^}]*\}/.test(source)
|
|
103
|
-
} catch {
|
|
104
|
-
return false
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function addConventionRecords(records: string[], roots: ModuleRoots): void {
|
|
109
|
-
for (const base of [roots.pkgBase, roots.appBase]) {
|
|
110
|
-
records.push(`module-root:${base}:${fs.existsSync(base) ? 'present' : 'missing'}`)
|
|
111
|
-
for (const relativePath of STRUCTURAL_CONVENTION_FILES) {
|
|
112
|
-
const filePath = resolveCodeFile(base, relativePath)
|
|
113
|
-
if (!filePath) {
|
|
114
|
-
records.push(`missing:${base}:${relativePath}`)
|
|
115
|
-
continue
|
|
116
|
-
}
|
|
117
|
-
const record = fileRecord(filePath, base, 'content')
|
|
118
|
-
if (record) records.push(record)
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function addScannedRecords(records: string[], roots: ModuleRoots): void {
|
|
124
|
-
for (const config of CONTENT_SENSITIVE_SCAN_CONFIGS) {
|
|
125
|
-
for (const scanned of scanModuleDir(roots, config)) {
|
|
126
|
-
const base = scanned.fromApp ? roots.appBase : roots.pkgBase
|
|
127
|
-
const filePath = path.join(base, ...config.folder.split('/'), ...scanned.relPath.split('/'))
|
|
128
|
-
const record = fileRecord(filePath, base, 'content')
|
|
129
|
-
if (record) records.push(`${config.folder}:${record}`)
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
for (const config of ROUTE_SHAPE_SCAN_CONFIGS) {
|
|
134
|
-
for (const scanned of scanModuleDir(roots, config)) {
|
|
135
|
-
const base = scanned.fromApp ? roots.appBase : roots.pkgBase
|
|
136
|
-
const folderPath = path.join(base, ...config.folder.split('/'))
|
|
137
|
-
const filePath = path.join(folderPath, ...scanned.relPath.split('/'))
|
|
138
|
-
const pageRecord = fileRecord(filePath, base, hasInlinePageMetadata(filePath) ? 'content' : 'shape')
|
|
139
|
-
if (pageRecord) records.push(`${config.folder}:${pageRecord}`)
|
|
140
|
-
|
|
141
|
-
const dir = path.dirname(filePath)
|
|
142
|
-
const stem = stripModuleCodeExtension(path.basename(filePath))
|
|
143
|
-
const metaCandidates = stem === 'page'
|
|
144
|
-
? ['page.meta', 'meta']
|
|
145
|
-
: [`${stem}.meta`, 'meta']
|
|
146
|
-
for (const candidate of metaCandidates) {
|
|
147
|
-
const metaPath = resolveCodeFile(dir, candidate)
|
|
148
|
-
if (!metaPath) continue
|
|
149
|
-
const record = fileRecord(metaPath, base, 'content')
|
|
150
|
-
if (record) records.push(`${config.folder}:meta:${record}`)
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export function calculateGenerateWatchStructureChecksum(options: {
|
|
157
|
-
modulesFile: string
|
|
158
|
-
moduleRoots: ModuleRoots[]
|
|
159
|
-
}): string {
|
|
160
|
-
const records: string[] = []
|
|
161
|
-
const modulesRecord = fileRecord(options.modulesFile, path.dirname(options.modulesFile), 'content')
|
|
162
|
-
records.push(modulesRecord ?? `missing:${options.modulesFile}`)
|
|
163
|
-
|
|
164
|
-
for (const roots of options.moduleRoots) {
|
|
165
|
-
addConventionRecords(records, roots)
|
|
166
|
-
addScannedRecords(records, roots)
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return checksum(records.sort((a, b) => a.localeCompare(b)).join('\n'))
|
|
170
|
-
}
|