@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.
@@ -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
- }