@open-mercato/shared 0.6.7-develop.6758.1.697eade236 → 0.6.7-develop.6768.1.9d2c4efc43
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/dist/lib/bootstrap/dynamicLoader.js +174 -5
- package/dist/lib/bootstrap/dynamicLoader.js.map +2 -2
- package/dist/lib/version.js +1 -1
- package/dist/lib/version.js.map +1 -1
- package/dist/modules/widgets/extension-points.js +96 -0
- package/dist/modules/widgets/extension-points.js.map +7 -0
- package/package.json +2 -2
- package/src/lib/bootstrap/__tests__/dynamicLoader.cacheRecovery.test.ts +38 -5
- package/src/lib/bootstrap/__tests__/dynamicLoader.commandInterceptors.test.ts +37 -4
- package/src/lib/bootstrap/__tests__/dynamicLoader.tsconfig.test.ts +292 -0
- package/src/lib/bootstrap/dynamicLoader.ts +239 -7
- package/src/modules/widgets/__tests__/extension-points.test.ts +101 -0
- package/src/modules/widgets/extension-points.ts +421 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
[build:shared] found
|
|
1
|
+
[build:shared] found 248 entry points
|
|
2
2
|
[build:shared] built successfully
|
|
@@ -5,9 +5,11 @@ import {
|
|
|
5
5
|
ensureMikroOrmV7GeneratedCacheCompatibility,
|
|
6
6
|
recoverMikroOrmV7GeneratedCacheFromImportError
|
|
7
7
|
} from "./generatedCacheRecovery.js";
|
|
8
|
-
import { createClientOnlyStubPlugin } from "./clientOnlyModules.js";
|
|
8
|
+
import { CLIENT_ONLY_STUB_NAMESPACE, createClientOnlyStubPlugin } from "./clientOnlyModules.js";
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import fs from "node:fs";
|
|
11
|
+
import crypto from "node:crypto";
|
|
12
|
+
import { createRequire } from "node:module";
|
|
11
13
|
import { pathToFileURL } from "node:url";
|
|
12
14
|
const logger = createLogger("shared").child({ component: "bootstrap" });
|
|
13
15
|
class GeneratedFileNotFoundError extends Error {
|
|
@@ -49,31 +51,198 @@ function createCliBundlePlugins(appRoot) {
|
|
|
49
51
|
};
|
|
50
52
|
return [createClientOnlyStubPlugin(), aliasPlugin, externalNonJsonPlugin];
|
|
51
53
|
}
|
|
54
|
+
const DYNAMIC_LOADER_CACHE_VERSION = 4;
|
|
55
|
+
function cacheMetadataPath(jsPath) {
|
|
56
|
+
return `${jsPath}.cache.json`;
|
|
57
|
+
}
|
|
58
|
+
function contentHash(content) {
|
|
59
|
+
return crypto.createHash("sha256").update(content).digest("hex");
|
|
60
|
+
}
|
|
61
|
+
function parseJsonConfig(content) {
|
|
62
|
+
let normalized = "";
|
|
63
|
+
let inString = false;
|
|
64
|
+
let escaped = false;
|
|
65
|
+
for (let index = 0; index < content.length; index += 1) {
|
|
66
|
+
const character = content[index];
|
|
67
|
+
const nextCharacter = content[index + 1];
|
|
68
|
+
if (inString) {
|
|
69
|
+
normalized += character;
|
|
70
|
+
if (escaped) {
|
|
71
|
+
escaped = false;
|
|
72
|
+
} else if (character === "\\") {
|
|
73
|
+
escaped = true;
|
|
74
|
+
} else if (character === '"') {
|
|
75
|
+
inString = false;
|
|
76
|
+
}
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (character === '"') {
|
|
80
|
+
inString = true;
|
|
81
|
+
normalized += character;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (character === "/" && nextCharacter === "/") {
|
|
85
|
+
while (index < content.length && content[index] !== "\n") index += 1;
|
|
86
|
+
normalized += "\n";
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (character === "/" && nextCharacter === "*") {
|
|
90
|
+
index += 2;
|
|
91
|
+
while (index < content.length && !(content[index] === "*" && content[index + 1] === "/")) {
|
|
92
|
+
index += 1;
|
|
93
|
+
}
|
|
94
|
+
index += 1;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (character === ",") {
|
|
98
|
+
let lookahead = index + 1;
|
|
99
|
+
while (lookahead < content.length && /\s/.test(content[lookahead])) lookahead += 1;
|
|
100
|
+
if (content[lookahead] === "}" || content[lookahead] === "]") continue;
|
|
101
|
+
}
|
|
102
|
+
normalized += character;
|
|
103
|
+
}
|
|
104
|
+
return JSON.parse(normalized);
|
|
105
|
+
}
|
|
106
|
+
function resolveExistingConfigPath(candidate) {
|
|
107
|
+
for (const configPath of [candidate, `${candidate}.json`, path.join(candidate, "tsconfig.json")]) {
|
|
108
|
+
if (fs.existsSync(configPath) && fs.statSync(configPath).isFile()) return configPath;
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
function resolvePackageConfig(configPath, reference) {
|
|
113
|
+
try {
|
|
114
|
+
const resolved = createRequire(pathToFileURL(configPath)).resolve(reference);
|
|
115
|
+
return path.extname(resolved) === ".json" ? resolved : null;
|
|
116
|
+
} catch {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function resolveExtendedConfig(configPath, reference) {
|
|
121
|
+
if (path.isAbsolute(reference) || reference.startsWith(".")) {
|
|
122
|
+
const resolved = resolveExistingConfigPath(path.resolve(path.dirname(configPath), reference));
|
|
123
|
+
if (resolved) return resolved;
|
|
124
|
+
} else {
|
|
125
|
+
for (const packageReference of [reference, `${reference}/tsconfig.json`]) {
|
|
126
|
+
const resolved = resolvePackageConfig(configPath, packageReference);
|
|
127
|
+
if (resolved) return resolved;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
throw new Error(`[internal] TypeScript config extends target not found: ${reference}`);
|
|
131
|
+
}
|
|
132
|
+
function collectTsconfigPaths(entryPath, visited = /* @__PURE__ */ new Set()) {
|
|
133
|
+
const configPath = path.resolve(entryPath);
|
|
134
|
+
if (visited.has(configPath)) return [];
|
|
135
|
+
visited.add(configPath);
|
|
136
|
+
const parsed = parseJsonConfig(fs.readFileSync(configPath, "utf8"));
|
|
137
|
+
if (typeof parsed !== "object" || parsed === null || !("extends" in parsed)) return [configPath];
|
|
138
|
+
const extendsValue = parsed.extends;
|
|
139
|
+
const references = typeof extendsValue === "string" ? [extendsValue] : Array.isArray(extendsValue) && extendsValue.every((value) => typeof value === "string") ? extendsValue : [];
|
|
140
|
+
return [
|
|
141
|
+
...references.flatMap((reference) => collectTsconfigPaths(
|
|
142
|
+
resolveExtendedConfig(configPath, reference),
|
|
143
|
+
visited
|
|
144
|
+
)),
|
|
145
|
+
configPath
|
|
146
|
+
];
|
|
147
|
+
}
|
|
148
|
+
function hashFilesRelativeTo(appRoot, filePaths) {
|
|
149
|
+
return Object.fromEntries(filePaths.map((filePath) => [
|
|
150
|
+
path.relative(appRoot, filePath).split(path.sep).join("/"),
|
|
151
|
+
contentHash(fs.readFileSync(filePath))
|
|
152
|
+
]));
|
|
153
|
+
}
|
|
154
|
+
function cacheInputHash(tsPath, appRoot, tsconfigPaths) {
|
|
155
|
+
const hash = crypto.createHash("sha256");
|
|
156
|
+
hash.update(JSON.stringify({
|
|
157
|
+
version: DYNAMIC_LOADER_CACHE_VERSION,
|
|
158
|
+
sourceHash: contentHash(fs.readFileSync(tsPath)),
|
|
159
|
+
tsconfigHashes: hashFilesRelativeTo(appRoot, tsconfigPaths)
|
|
160
|
+
}));
|
|
161
|
+
return hash.digest("hex");
|
|
162
|
+
}
|
|
163
|
+
function dependenciesAreValid(appRoot, dependencies) {
|
|
164
|
+
return Object.entries(dependencies).every(([relativePath, expectedHash]) => {
|
|
165
|
+
const dependencyPath = path.resolve(appRoot, relativePath);
|
|
166
|
+
return fs.existsSync(dependencyPath) && contentHash(fs.readFileSync(dependencyPath)) === expectedHash;
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
function collectDependencyHashes(appRoot, inputs) {
|
|
170
|
+
return Object.fromEntries(
|
|
171
|
+
Object.keys(inputs).filter((inputPath) => !inputPath.startsWith(`${CLIENT_ONLY_STUB_NAMESPACE}:`)).map((inputPath) => {
|
|
172
|
+
const absolutePath = path.isAbsolute(inputPath) ? inputPath : path.resolve(appRoot, inputPath);
|
|
173
|
+
const relativePath = path.relative(appRoot, absolutePath).split(path.sep).join("/");
|
|
174
|
+
return [relativePath, contentHash(fs.readFileSync(absolutePath))];
|
|
175
|
+
}).sort(([left], [right]) => left.localeCompare(right))
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
function readCacheMetadata(metadataPath) {
|
|
179
|
+
try {
|
|
180
|
+
const parsed = JSON.parse(fs.readFileSync(metadataPath, "utf8"));
|
|
181
|
+
if (typeof parsed === "object" && parsed !== null && "version" in parsed && parsed.version === DYNAMIC_LOADER_CACHE_VERSION && "inputHash" in parsed && typeof parsed.inputHash === "string" && "outputHash" in parsed && typeof parsed.outputHash === "string" && "dependencies" in parsed && typeof parsed.dependencies === "object" && parsed.dependencies !== null && Object.values(parsed.dependencies).every((hash) => typeof hash === "string")) {
|
|
182
|
+
return {
|
|
183
|
+
version: parsed.version,
|
|
184
|
+
inputHash: parsed.inputHash,
|
|
185
|
+
outputHash: parsed.outputHash,
|
|
186
|
+
dependencies: parsed.dependencies
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
} catch {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
function cacheIsValid(appRoot, jsPath, metadataPath, expectedInputHash) {
|
|
195
|
+
if (!fs.existsSync(jsPath)) return false;
|
|
196
|
+
const metadata = readCacheMetadata(metadataPath);
|
|
197
|
+
if (!metadata || metadata.inputHash !== expectedInputHash) return false;
|
|
198
|
+
return contentHash(fs.readFileSync(jsPath)) === metadata.outputHash && dependenciesAreValid(appRoot, metadata.dependencies);
|
|
199
|
+
}
|
|
52
200
|
async function compileAndImport(tsPath, allowRecovery = true) {
|
|
53
201
|
const jsPath = tsPath.replace(/\.ts$/, ".mjs");
|
|
54
202
|
const appRoot = path.dirname(path.dirname(path.dirname(tsPath)));
|
|
203
|
+
const appTsconfig = path.join(appRoot, "tsconfig.json");
|
|
204
|
+
const metadataPath = cacheMetadataPath(jsPath);
|
|
55
205
|
const tsExists = fs.existsSync(tsPath);
|
|
56
|
-
const
|
|
206
|
+
const tsconfigExists = fs.existsSync(appTsconfig);
|
|
57
207
|
if (!tsExists) {
|
|
58
208
|
throw new GeneratedFileNotFoundError(tsPath);
|
|
59
209
|
}
|
|
60
|
-
|
|
210
|
+
if (!tsconfigExists) {
|
|
211
|
+
throw new Error(`App TypeScript config not found: ${appTsconfig}`);
|
|
212
|
+
}
|
|
213
|
+
const tsconfigPaths = collectTsconfigPaths(appTsconfig);
|
|
214
|
+
const expectedInputHash = cacheInputHash(tsPath, appRoot, tsconfigPaths);
|
|
215
|
+
const needsCompile = !cacheIsValid(appRoot, jsPath, metadataPath, expectedInputHash);
|
|
61
216
|
if (needsCompile) {
|
|
62
217
|
const esbuild = await import("esbuild");
|
|
63
|
-
await esbuild.build({
|
|
218
|
+
const result = await esbuild.build({
|
|
64
219
|
entryPoints: [tsPath],
|
|
65
220
|
outfile: jsPath,
|
|
221
|
+
absWorkingDir: appRoot,
|
|
66
222
|
bundle: true,
|
|
223
|
+
metafile: true,
|
|
67
224
|
format: "esm",
|
|
68
225
|
platform: "node",
|
|
69
226
|
target: "node18",
|
|
227
|
+
tsconfig: appTsconfig,
|
|
70
228
|
plugins: createCliBundlePlugins(appRoot),
|
|
71
229
|
// Allow JSON imports
|
|
72
230
|
loader: { ".json": "json" }
|
|
73
231
|
});
|
|
232
|
+
const metadata = {
|
|
233
|
+
version: DYNAMIC_LOADER_CACHE_VERSION,
|
|
234
|
+
inputHash: expectedInputHash,
|
|
235
|
+
outputHash: contentHash(fs.readFileSync(jsPath)),
|
|
236
|
+
dependencies: {
|
|
237
|
+
...collectDependencyHashes(appRoot, result.metafile.inputs),
|
|
238
|
+
...hashFilesRelativeTo(appRoot, tsconfigPaths)
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
fs.writeFileSync(metadataPath, JSON.stringify(metadata));
|
|
74
242
|
}
|
|
75
243
|
try {
|
|
76
|
-
const
|
|
244
|
+
const outputHash = contentHash(fs.readFileSync(jsPath));
|
|
245
|
+
const fileUrl = `${pathToFileURL(jsPath).href}?cache=${outputHash}`;
|
|
77
246
|
return await import(fileUrl);
|
|
78
247
|
} catch (error) {
|
|
79
248
|
if (!allowRecovery) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/bootstrap/dynamicLoader.ts"],
|
|
4
|
-
"sourcesContent": ["import type { BootstrapData } from './types'\nimport { findAppRoot, type AppRoot } from './appResolver'\nimport { registerEntityIds } from '../encryption/entityIds'\nimport { createLogger } from '../logger'\nimport {\n ensureMikroOrmV7GeneratedCacheCompatibility,\n recoverMikroOrmV7GeneratedCacheFromImportError,\n} from './generatedCacheRecovery'\nimport { createClientOnlyStubPlugin } from './clientOnlyModules'\nimport path from 'node:path'\nimport fs from 'node:fs'\nimport { pathToFileURL } from 'node:url'\n\nconst logger = createLogger('shared').child({ component: 'bootstrap' })\n\n/**\n * Thrown when an expected generated source file is absent.\n *\n * Optional registries treat this as the supported compatibility case (an app\n * that never generated the file), which is what makes it distinguishable from\n * a file that exists but fails to compile or import.\n */\nclass GeneratedFileNotFoundError extends Error {\n readonly filePath: string\n\n constructor(filePath: string) {\n super(`Generated file not found: ${filePath}`)\n this.name = 'GeneratedFileNotFoundError'\n this.filePath = filePath\n }\n}\n\n/**\n * esbuild plugins for the CLI bundle, in resolution order. The client-only stub must come\n * first so it wins over the alias and external plugins for `*.client` dynamic imports.\n *\n * Exported so the wiring itself is testable: a test that only exercises\n * `createClientOnlyStubPlugin` in isolation stays green if the plugin is dropped from this\n * list, which would silently reintroduce #4623.\n */\nexport function createCliBundlePlugins(appRoot: string): import('esbuild').Plugin[] {\n // Plugin to resolve @/ alias to app root (works for @app modules)\n const aliasPlugin: import('esbuild').Plugin = {\n name: 'alias-resolver',\n setup(build) {\n // Resolve @/ alias to app root\n build.onResolve({ filter: /^@\\// }, (args) => {\n const resolved = path.join(appRoot, args.path.slice(2))\n // Try with .ts extension if base path doesn't exist\n if (!fs.existsSync(resolved) && fs.existsSync(resolved + '.ts')) {\n return { path: resolved + '.ts' }\n }\n // Also check for /index.ts if it's a directory\n if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory() && fs.existsSync(path.join(resolved, 'index.ts'))) {\n return { path: path.join(resolved, 'index.ts') }\n }\n return { path: resolved }\n })\n },\n }\n\n // Plugin to mark non-JSON package imports as external\n const externalNonJsonPlugin: import('esbuild').Plugin = {\n name: 'external-non-json',\n setup(build) {\n // Mark all package imports as external EXCEPT JSON files\n // Filter matches paths that don't start with . or / (package imports like @open-mercato/shared)\n build.onResolve({ filter: /^[^./]/ }, (args) => {\n // Skip Windows absolute paths (e.g., C:\\...) - they're local files, not packages\n if (/^[a-zA-Z]:/.test(args.path)) {\n return null // Let esbuild handle it\n }\n // If it's a JSON file, let esbuild bundle it\n if (args.path.endsWith('.json')) {\n return null // Let esbuild handle it\n }\n // Otherwise mark as external\n return { path: args.path, external: true }\n })\n },\n }\n\n return [createClientOnlyStubPlugin(), aliasPlugin, externalNonJsonPlugin]\n}\n\n/**\n * Compile a TypeScript file to JavaScript using esbuild bundler.\n * This bundles the file and all its dependencies, handling JSON imports properly.\n * The compiled file is written next to the source file with a .mjs extension.\n */\nasync function compileAndImport(tsPath: string, allowRecovery: boolean = true): Promise<Record<string, unknown>> {\n const jsPath = tsPath.replace(/\\.ts$/, '.mjs')\n const appRoot = path.dirname(path.dirname(path.dirname(tsPath)))\n\n // Check if we need to recompile (source newer than compiled)\n const tsExists = fs.existsSync(tsPath)\n const jsExists = fs.existsSync(jsPath)\n\n if (!tsExists) {\n throw new GeneratedFileNotFoundError(tsPath)\n }\n\n const needsCompile = !jsExists ||\n fs.statSync(tsPath).mtimeMs > fs.statSync(jsPath).mtimeMs\n\n if (needsCompile) {\n // Dynamically import esbuild only when needed\n const esbuild = await import('esbuild')\n\n // Use esbuild.build with bundling to handle JSON imports\n await esbuild.build({\n entryPoints: [tsPath],\n outfile: jsPath,\n bundle: true,\n format: 'esm',\n platform: 'node',\n target: 'node18',\n plugins: createCliBundlePlugins(appRoot),\n // Allow JSON imports\n loader: { '.json': 'json' },\n })\n }\n\n // Import the compiled JavaScript\n try {\n const fileUrl = `${pathToFileURL(jsPath).href}?mtime=${fs.statSync(jsPath).mtimeMs}`\n return await import(fileUrl)\n } catch (error) {\n if (!allowRecovery) {\n throw error\n }\n\n const recovered = recoverMikroOrmV7GeneratedCacheFromImportError(appRoot, error)\n if (!recovered.applied) {\n throw error\n }\n\n return compileAndImport(tsPath, false)\n }\n}\n\n\n/**\n * Load a generated registry that older apps may not have generated yet.\n *\n * An absent source file is the supported compatibility case and resolves to\n * `fallback` quietly. Any other failure \u2014 a compile error, a broken import, a\n * runtime throw at module scope \u2014 still resolves to `fallback` so bootstrap\n * keeps working, but is reported at error level: a registry that silently\n * degrades to nothing is exactly how command interceptors stopped applying in\n * worker/CLI processes (#4327, #4491).\n */\nasync function loadOptionalGeneratedModule(\n tsPath: string,\n fallback: Record<string, unknown>,\n): Promise<Record<string, unknown>> {\n try {\n return await compileAndImport(tsPath)\n } catch (error) {\n if (error instanceof GeneratedFileNotFoundError) {\n logger.debug('Optional generated registry not present, using empty fallback', {\n file: path.basename(tsPath),\n })\n return fallback\n }\n\n logger.error('Failed to load generated registry, continuing without its entries', {\n file: path.basename(tsPath),\n filePath: tsPath,\n err: error,\n })\n return fallback\n }\n}\n\n/**\n * Dynamically load bootstrap data from a resolved app directory.\n *\n * IMPORTANT: This only works in unbundled contexts (CLI, tsx).\n * Do NOT use this in Next.js bundled code - use static imports instead.\n *\n * For CLI context, we skip loading modules.generated.ts which has Next.js dependencies.\n * CLI commands are discovered separately via the CLI module system.\n *\n * @param appRoot - Optional explicit app root path. If not provided, will search from cwd.\n * @returns The loaded bootstrap data\n * @throws Error if app root cannot be found or generated files are missing\n */\nexport async function loadBootstrapData(appRoot?: string): Promise<BootstrapData> {\n const resolved: AppRoot | null = appRoot\n ? {\n generatedDir: path.join(appRoot, '.mercato', 'generated'),\n appDir: appRoot,\n mercatoDir: path.join(appRoot, '.mercato'),\n }\n : findAppRoot()\n\n if (!resolved) {\n throw new Error(\n 'Could not find app root with .mercato/generated directory. ' +\n 'Make sure you run this command from within a Next.js app directory, ' +\n 'or run \"yarn mercato generate\" first to create the generated files.',\n )\n }\n\n const { generatedDir } = resolved\n\n ensureMikroOrmV7GeneratedCacheCompatibility(resolved.appDir)\n\n // IMPORTANT: Load entity IDs FIRST and register them before loading modules.\n // This is because modules (e.g., ce.ts files) use E.xxx.xxx at module scope,\n // and they need entity IDs to be available when they're imported.\n const entityIdsModule = await compileAndImport(path.join(generatedDir, 'entities.ids.generated.ts'))\n registerEntityIds(entityIdsModule.E as BootstrapData['entityIds'])\n\n // Now load the rest of the generated files.\n // modules.cli.generated.ts excludes Next.js-dependent code (routes, APIs, widgets)\n const [\n modulesModule,\n entitiesModule,\n diModule,\n searchModule,\n commandLoadersModule,\n commandInterceptorsModule,\n workflowsModule,\n ] = await Promise.all([\n compileAndImport(path.join(generatedDir, 'modules.cli.generated.ts')),\n compileAndImport(path.join(generatedDir, 'entities.generated.ts')),\n compileAndImport(path.join(generatedDir, 'di.generated.ts')),\n loadOptionalGeneratedModule(path.join(generatedDir, 'search.generated.ts'), { searchModuleConfigs: [] }),\n loadOptionalGeneratedModule(path.join(generatedDir, 'command-loaders.generated.ts'), { commandLoaderEntries: [] }),\n loadOptionalGeneratedModule(path.join(generatedDir, 'command-interceptors.generated.ts'), {\n commandInterceptorEntries: [],\n }),\n loadOptionalGeneratedModule(path.join(generatedDir, 'workflows.generated.ts'), { allCodeWorkflows: [] }),\n ])\n\n return {\n modules: modulesModule.modules as BootstrapData['modules'],\n entities: entitiesModule.entities as BootstrapData['entities'],\n diRegistrars: diModule.diRegistrars as BootstrapData['diRegistrars'],\n entityIds: entityIdsModule.E as BootstrapData['entityIds'],\n // Search configs are needed by workers for indexing\n searchModuleConfigs: (searchModule.searchModuleConfigs ?? []) as BootstrapData['searchModuleConfigs'],\n commandLoaderEntries: (commandLoadersModule.commandLoaderEntries ?? []) as BootstrapData['commandLoaderEntries'],\n // Command interceptors must apply in worker/CLI processes too \u2014 the\n // interceptor registry is per-process, so relying on the Next.js runtime's\n // registration silently no-ops every interceptor for queued/CLI commands\n // (#4327).\n commandInterceptorEntries: (commandInterceptorsModule.commandInterceptorEntries ??\n []) as BootstrapData['commandInterceptorEntries'],\n // Code workflow definitions are needed by workers to resume code-defined instances\n codeWorkflows: (workflowsModule.allCodeWorkflows ?? []) as BootstrapData['codeWorkflows'],\n // Empty UI-related data - not needed for CLI\n dashboardWidgetEntries: [],\n injectionWidgetEntries: [],\n injectionTables: [],\n interceptorEntries: [],\n componentOverrideEntries: [],\n }\n}\n\n/**\n * Create and execute bootstrap in CLI context.\n *\n * This is a convenience function that finds the app root, loads the generated\n * data dynamically, and runs bootstrap. Use this in CLI entry points.\n *\n * Returns the loaded bootstrap data so the CLI can register modules directly\n * (avoids module resolution issues when importing @open-mercato/cli/mercato).\n *\n * @param appRoot - Optional explicit app root path\n * @returns The loaded bootstrap data (modules, entities, etc.)\n */\nexport async function bootstrapFromAppRoot(appRoot?: string): Promise<BootstrapData> {\n const { createBootstrap, waitForAsyncRegistration } = await import('./factory.js')\n const data = await loadBootstrapData(appRoot)\n const bootstrap = createBootstrap(data)\n bootstrap()\n // In CLI context, wait for async registrations (UI widgets, search configs, etc.)\n await waitForAsyncRegistration()\n\n return data\n}\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,mBAAiC;AAC1C,SAAS,yBAAyB;AAClC,SAAS,oBAAoB;AAC7B;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,kCAAkC;
|
|
4
|
+
"sourcesContent": ["import type { BootstrapData } from './types'\nimport { findAppRoot, type AppRoot } from './appResolver'\nimport { registerEntityIds } from '../encryption/entityIds'\nimport { createLogger } from '../logger'\nimport {\n ensureMikroOrmV7GeneratedCacheCompatibility,\n recoverMikroOrmV7GeneratedCacheFromImportError,\n} from './generatedCacheRecovery'\nimport { CLIENT_ONLY_STUB_NAMESPACE, createClientOnlyStubPlugin } from './clientOnlyModules'\nimport path from 'node:path'\nimport fs from 'node:fs'\nimport crypto from 'node:crypto'\nimport { createRequire } from 'node:module'\nimport { pathToFileURL } from 'node:url'\n\nconst logger = createLogger('shared').child({ component: 'bootstrap' })\n\n/**\n * Thrown when an expected generated source file is absent.\n *\n * Optional registries treat this as the supported compatibility case (an app\n * that never generated the file), which is what makes it distinguishable from\n * a file that exists but fails to compile or import.\n */\nclass GeneratedFileNotFoundError extends Error {\n readonly filePath: string\n\n constructor(filePath: string) {\n super(`Generated file not found: ${filePath}`)\n this.name = 'GeneratedFileNotFoundError'\n this.filePath = filePath\n }\n}\n\n/**\n * esbuild plugins for the CLI bundle, in resolution order. The client-only stub must come\n * first so it wins over the alias and external plugins for `*.client` dynamic imports.\n *\n * Exported so the wiring itself is testable: a test that only exercises\n * `createClientOnlyStubPlugin` in isolation stays green if the plugin is dropped from this\n * list, which would silently reintroduce #4623.\n */\nexport function createCliBundlePlugins(appRoot: string): import('esbuild').Plugin[] {\n // Plugin to resolve @/ alias to app root (works for @app modules)\n const aliasPlugin: import('esbuild').Plugin = {\n name: 'alias-resolver',\n setup(build) {\n // Resolve @/ alias to app root\n build.onResolve({ filter: /^@\\// }, (args) => {\n const resolved = path.join(appRoot, args.path.slice(2))\n // Try with .ts extension if base path doesn't exist\n if (!fs.existsSync(resolved) && fs.existsSync(resolved + '.ts')) {\n return { path: resolved + '.ts' }\n }\n // Also check for /index.ts if it's a directory\n if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory() && fs.existsSync(path.join(resolved, 'index.ts'))) {\n return { path: path.join(resolved, 'index.ts') }\n }\n return { path: resolved }\n })\n },\n }\n\n // Plugin to mark non-JSON package imports as external\n const externalNonJsonPlugin: import('esbuild').Plugin = {\n name: 'external-non-json',\n setup(build) {\n // Mark all package imports as external EXCEPT JSON files\n // Filter matches paths that don't start with . or / (package imports like @open-mercato/shared)\n build.onResolve({ filter: /^[^./]/ }, (args) => {\n // Skip Windows absolute paths (e.g., C:\\...) - they're local files, not packages\n if (/^[a-zA-Z]:/.test(args.path)) {\n return null // Let esbuild handle it\n }\n // If it's a JSON file, let esbuild bundle it\n if (args.path.endsWith('.json')) {\n return null // Let esbuild handle it\n }\n // Otherwise mark as external\n return { path: args.path, external: true }\n })\n },\n }\n\n return [createClientOnlyStubPlugin(), aliasPlugin, externalNonJsonPlugin]\n}\n\nconst DYNAMIC_LOADER_CACHE_VERSION = 4\n\ntype DynamicLoaderCacheMetadata = {\n version: number\n inputHash: string\n outputHash: string\n dependencies: Record<string, string>\n}\n\nfunction cacheMetadataPath(jsPath: string): string {\n return `${jsPath}.cache.json`\n}\n\nfunction contentHash(content: Buffer | string): string {\n return crypto.createHash('sha256').update(content).digest('hex')\n}\n\nfunction parseJsonConfig(content: string): unknown {\n let normalized = ''\n let inString = false\n let escaped = false\n\n for (let index = 0; index < content.length; index += 1) {\n const character = content[index]\n const nextCharacter = content[index + 1]\n\n if (inString) {\n normalized += character\n if (escaped) {\n escaped = false\n } else if (character === '\\\\') {\n escaped = true\n } else if (character === '\"') {\n inString = false\n }\n continue\n }\n\n if (character === '\"') {\n inString = true\n normalized += character\n continue\n }\n\n if (character === '/' && nextCharacter === '/') {\n while (index < content.length && content[index] !== '\\n') index += 1\n normalized += '\\n'\n continue\n }\n\n if (character === '/' && nextCharacter === '*') {\n index += 2\n while (index < content.length && !(content[index] === '*' && content[index + 1] === '/')) {\n index += 1\n }\n index += 1\n continue\n }\n\n if (character === ',') {\n let lookahead = index + 1\n while (lookahead < content.length && /\\s/.test(content[lookahead])) lookahead += 1\n if (content[lookahead] === '}' || content[lookahead] === ']') continue\n }\n\n normalized += character\n }\n\n return JSON.parse(normalized)\n}\n\nfunction resolveExistingConfigPath(candidate: string): string | null {\n for (const configPath of [candidate, `${candidate}.json`, path.join(candidate, 'tsconfig.json')]) {\n if (fs.existsSync(configPath) && fs.statSync(configPath).isFile()) return configPath\n }\n return null\n}\n\nfunction resolvePackageConfig(configPath: string, reference: string): string | null {\n try {\n const resolved = createRequire(pathToFileURL(configPath)).resolve(reference)\n return path.extname(resolved) === '.json' ? resolved : null\n } catch {\n return null\n }\n}\n\nfunction resolveExtendedConfig(configPath: string, reference: string): string {\n if (path.isAbsolute(reference) || reference.startsWith('.')) {\n const resolved = resolveExistingConfigPath(path.resolve(path.dirname(configPath), reference))\n if (resolved) return resolved\n } else {\n for (const packageReference of [reference, `${reference}/tsconfig.json`]) {\n const resolved = resolvePackageConfig(configPath, packageReference)\n if (resolved) return resolved\n }\n }\n\n throw new Error(`[internal] TypeScript config extends target not found: ${reference}`)\n}\n\nfunction collectTsconfigPaths(entryPath: string, visited: Set<string> = new Set()): string[] {\n const configPath = path.resolve(entryPath)\n if (visited.has(configPath)) return []\n visited.add(configPath)\n\n const parsed = parseJsonConfig(fs.readFileSync(configPath, 'utf8'))\n if (typeof parsed !== 'object' || parsed === null || !('extends' in parsed)) return [configPath]\n\n const extendsValue = parsed.extends\n const references = typeof extendsValue === 'string'\n ? [extendsValue]\n : Array.isArray(extendsValue) && extendsValue.every((value) => typeof value === 'string')\n ? extendsValue\n : []\n\n return [\n ...references.flatMap((reference) => collectTsconfigPaths(\n resolveExtendedConfig(configPath, reference),\n visited,\n )),\n configPath,\n ]\n}\n\nfunction hashFilesRelativeTo(appRoot: string, filePaths: string[]): Record<string, string> {\n return Object.fromEntries(filePaths.map((filePath) => [\n path.relative(appRoot, filePath).split(path.sep).join('/'),\n contentHash(fs.readFileSync(filePath)),\n ]))\n}\n\nfunction cacheInputHash(tsPath: string, appRoot: string, tsconfigPaths: string[]): string {\n const hash = crypto.createHash('sha256')\n hash.update(JSON.stringify({\n version: DYNAMIC_LOADER_CACHE_VERSION,\n sourceHash: contentHash(fs.readFileSync(tsPath)),\n tsconfigHashes: hashFilesRelativeTo(appRoot, tsconfigPaths),\n }))\n return hash.digest('hex')\n}\n\nfunction dependenciesAreValid(appRoot: string, dependencies: Record<string, string>): boolean {\n return Object.entries(dependencies).every(([relativePath, expectedHash]) => {\n const dependencyPath = path.resolve(appRoot, relativePath)\n return fs.existsSync(dependencyPath)\n && contentHash(fs.readFileSync(dependencyPath)) === expectedHash\n })\n}\n\nfunction collectDependencyHashes(\n appRoot: string,\n inputs: Record<string, unknown>,\n): Record<string, string> {\n return Object.fromEntries(\n Object.keys(inputs)\n .filter((inputPath) => !inputPath.startsWith(`${CLIENT_ONLY_STUB_NAMESPACE}:`))\n .map((inputPath) => {\n const absolutePath = path.isAbsolute(inputPath)\n ? inputPath\n : path.resolve(appRoot, inputPath)\n const relativePath = path.relative(appRoot, absolutePath).split(path.sep).join('/')\n return [relativePath, contentHash(fs.readFileSync(absolutePath))]\n })\n .sort(([left], [right]) => left.localeCompare(right)),\n )\n}\n\nfunction readCacheMetadata(metadataPath: string): DynamicLoaderCacheMetadata | null {\n try {\n const parsed: unknown = JSON.parse(fs.readFileSync(metadataPath, 'utf8'))\n if (\n typeof parsed === 'object'\n && parsed !== null\n && 'version' in parsed\n && parsed.version === DYNAMIC_LOADER_CACHE_VERSION\n && 'inputHash' in parsed\n && typeof parsed.inputHash === 'string'\n && 'outputHash' in parsed\n && typeof parsed.outputHash === 'string'\n && 'dependencies' in parsed\n && typeof parsed.dependencies === 'object'\n && parsed.dependencies !== null\n && Object.values(parsed.dependencies).every((hash) => typeof hash === 'string')\n ) {\n return {\n version: parsed.version,\n inputHash: parsed.inputHash,\n outputHash: parsed.outputHash,\n dependencies: parsed.dependencies as Record<string, string>,\n }\n }\n } catch {\n return null\n }\n return null\n}\n\nfunction cacheIsValid(\n appRoot: string,\n jsPath: string,\n metadataPath: string,\n expectedInputHash: string,\n): boolean {\n if (!fs.existsSync(jsPath)) return false\n const metadata = readCacheMetadata(metadataPath)\n if (!metadata || metadata.inputHash !== expectedInputHash) return false\n return contentHash(fs.readFileSync(jsPath)) === metadata.outputHash\n && dependenciesAreValid(appRoot, metadata.dependencies)\n}\n\n/**\n * Compile a TypeScript file to JavaScript using esbuild bundler.\n * This bundles the file and all its dependencies, handling JSON imports properly.\n * The compiled file is written next to the source file with a .mjs extension.\n */\nasync function compileAndImport(tsPath: string, allowRecovery: boolean = true): Promise<Record<string, unknown>> {\n const jsPath = tsPath.replace(/\\.ts$/, '.mjs')\n const appRoot = path.dirname(path.dirname(path.dirname(tsPath)))\n const appTsconfig = path.join(appRoot, 'tsconfig.json')\n const metadataPath = cacheMetadataPath(jsPath)\n\n const tsExists = fs.existsSync(tsPath)\n const tsconfigExists = fs.existsSync(appTsconfig)\n\n if (!tsExists) {\n throw new GeneratedFileNotFoundError(tsPath)\n }\n if (!tsconfigExists) {\n throw new Error(`App TypeScript config not found: ${appTsconfig}`)\n }\n\n const tsconfigPaths = collectTsconfigPaths(appTsconfig)\n const expectedInputHash = cacheInputHash(tsPath, appRoot, tsconfigPaths)\n const needsCompile = !cacheIsValid(appRoot, jsPath, metadataPath, expectedInputHash)\n\n if (needsCompile) {\n // Dynamically import esbuild only when needed\n const esbuild = await import('esbuild')\n\n // Use esbuild.build with bundling to handle JSON imports\n const result = await esbuild.build({\n entryPoints: [tsPath],\n outfile: jsPath,\n absWorkingDir: appRoot,\n bundle: true,\n metafile: true,\n format: 'esm',\n platform: 'node',\n target: 'node18',\n tsconfig: appTsconfig,\n plugins: createCliBundlePlugins(appRoot),\n // Allow JSON imports\n loader: { '.json': 'json' },\n })\n const metadata: DynamicLoaderCacheMetadata = {\n version: DYNAMIC_LOADER_CACHE_VERSION,\n inputHash: expectedInputHash,\n outputHash: contentHash(fs.readFileSync(jsPath)),\n dependencies: {\n ...collectDependencyHashes(appRoot, result.metafile.inputs),\n ...hashFilesRelativeTo(appRoot, tsconfigPaths),\n },\n }\n fs.writeFileSync(metadataPath, JSON.stringify(metadata))\n }\n\n // Import the compiled JavaScript\n try {\n const outputHash = contentHash(fs.readFileSync(jsPath))\n const fileUrl = `${pathToFileURL(jsPath).href}?cache=${outputHash}`\n return await import(fileUrl)\n } catch (error) {\n if (!allowRecovery) {\n throw error\n }\n\n const recovered = recoverMikroOrmV7GeneratedCacheFromImportError(appRoot, error)\n if (!recovered.applied) {\n throw error\n }\n\n return compileAndImport(tsPath, false)\n }\n}\n\n\n/**\n * Load a generated registry that older apps may not have generated yet.\n *\n * An absent source file is the supported compatibility case and resolves to\n * `fallback` quietly. Any other failure \u2014 a compile error, a broken import, a\n * runtime throw at module scope \u2014 still resolves to `fallback` so bootstrap\n * keeps working, but is reported at error level: a registry that silently\n * degrades to nothing is exactly how command interceptors stopped applying in\n * worker/CLI processes (#4327, #4491).\n */\nasync function loadOptionalGeneratedModule(\n tsPath: string,\n fallback: Record<string, unknown>,\n): Promise<Record<string, unknown>> {\n try {\n return await compileAndImport(tsPath)\n } catch (error) {\n if (error instanceof GeneratedFileNotFoundError) {\n logger.debug('Optional generated registry not present, using empty fallback', {\n file: path.basename(tsPath),\n })\n return fallback\n }\n\n logger.error('Failed to load generated registry, continuing without its entries', {\n file: path.basename(tsPath),\n filePath: tsPath,\n err: error,\n })\n return fallback\n }\n}\n\n/**\n * Dynamically load bootstrap data from a resolved app directory.\n *\n * IMPORTANT: This only works in unbundled contexts (CLI, tsx).\n * Do NOT use this in Next.js bundled code - use static imports instead.\n *\n * For CLI context, we skip loading modules.generated.ts which has Next.js dependencies.\n * CLI commands are discovered separately via the CLI module system.\n *\n * @param appRoot - Optional explicit app root path. If not provided, will search from cwd.\n * @returns The loaded bootstrap data\n * @throws Error if app root cannot be found or generated files are missing\n */\nexport async function loadBootstrapData(appRoot?: string): Promise<BootstrapData> {\n const resolved: AppRoot | null = appRoot\n ? {\n generatedDir: path.join(appRoot, '.mercato', 'generated'),\n appDir: appRoot,\n mercatoDir: path.join(appRoot, '.mercato'),\n }\n : findAppRoot()\n\n if (!resolved) {\n throw new Error(\n 'Could not find app root with .mercato/generated directory. ' +\n 'Make sure you run this command from within a Next.js app directory, ' +\n 'or run \"yarn mercato generate\" first to create the generated files.',\n )\n }\n\n const { generatedDir } = resolved\n\n ensureMikroOrmV7GeneratedCacheCompatibility(resolved.appDir)\n\n // IMPORTANT: Load entity IDs FIRST and register them before loading modules.\n // This is because modules (e.g., ce.ts files) use E.xxx.xxx at module scope,\n // and they need entity IDs to be available when they're imported.\n const entityIdsModule = await compileAndImport(path.join(generatedDir, 'entities.ids.generated.ts'))\n registerEntityIds(entityIdsModule.E as BootstrapData['entityIds'])\n\n // Now load the rest of the generated files.\n // modules.cli.generated.ts excludes Next.js-dependent code (routes, APIs, widgets)\n const [\n modulesModule,\n entitiesModule,\n diModule,\n searchModule,\n commandLoadersModule,\n commandInterceptorsModule,\n workflowsModule,\n ] = await Promise.all([\n compileAndImport(path.join(generatedDir, 'modules.cli.generated.ts')),\n compileAndImport(path.join(generatedDir, 'entities.generated.ts')),\n compileAndImport(path.join(generatedDir, 'di.generated.ts')),\n loadOptionalGeneratedModule(path.join(generatedDir, 'search.generated.ts'), { searchModuleConfigs: [] }),\n loadOptionalGeneratedModule(path.join(generatedDir, 'command-loaders.generated.ts'), { commandLoaderEntries: [] }),\n loadOptionalGeneratedModule(path.join(generatedDir, 'command-interceptors.generated.ts'), {\n commandInterceptorEntries: [],\n }),\n loadOptionalGeneratedModule(path.join(generatedDir, 'workflows.generated.ts'), { allCodeWorkflows: [] }),\n ])\n\n return {\n modules: modulesModule.modules as BootstrapData['modules'],\n entities: entitiesModule.entities as BootstrapData['entities'],\n diRegistrars: diModule.diRegistrars as BootstrapData['diRegistrars'],\n entityIds: entityIdsModule.E as BootstrapData['entityIds'],\n // Search configs are needed by workers for indexing\n searchModuleConfigs: (searchModule.searchModuleConfigs ?? []) as BootstrapData['searchModuleConfigs'],\n commandLoaderEntries: (commandLoadersModule.commandLoaderEntries ?? []) as BootstrapData['commandLoaderEntries'],\n // Command interceptors must apply in worker/CLI processes too \u2014 the\n // interceptor registry is per-process, so relying on the Next.js runtime's\n // registration silently no-ops every interceptor for queued/CLI commands\n // (#4327).\n commandInterceptorEntries: (commandInterceptorsModule.commandInterceptorEntries ??\n []) as BootstrapData['commandInterceptorEntries'],\n // Code workflow definitions are needed by workers to resume code-defined instances\n codeWorkflows: (workflowsModule.allCodeWorkflows ?? []) as BootstrapData['codeWorkflows'],\n // Empty UI-related data - not needed for CLI\n dashboardWidgetEntries: [],\n injectionWidgetEntries: [],\n injectionTables: [],\n interceptorEntries: [],\n componentOverrideEntries: [],\n }\n}\n\n/**\n * Create and execute bootstrap in CLI context.\n *\n * This is a convenience function that finds the app root, loads the generated\n * data dynamically, and runs bootstrap. Use this in CLI entry points.\n *\n * Returns the loaded bootstrap data so the CLI can register modules directly\n * (avoids module resolution issues when importing @open-mercato/cli/mercato).\n *\n * @param appRoot - Optional explicit app root path\n * @returns The loaded bootstrap data (modules, entities, etc.)\n */\nexport async function bootstrapFromAppRoot(appRoot?: string): Promise<BootstrapData> {\n const { createBootstrap, waitForAsyncRegistration } = await import('./factory.js')\n const data = await loadBootstrapData(appRoot)\n const bootstrap = createBootstrap(data)\n bootstrap()\n // In CLI context, wait for async registrations (UI widgets, search configs, etc.)\n await waitForAsyncRegistration()\n\n return data\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/lib/version.js
CHANGED
package/dist/lib/version.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/version.ts"],
|
|
4
|
-
"sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.6.7-develop.
|
|
4
|
+
"sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.6.7-develop.6768.1.9d2c4efc43'\nexport const appVersion = APP_VERSION\n"],
|
|
5
5
|
"mappings": "AACO,MAAM,cAAc;AACpB,MAAM,aAAa;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
function defineModuleExtensionPoints(declaration) {
|
|
2
|
+
return Object.freeze({
|
|
3
|
+
...declaration,
|
|
4
|
+
hosts: Object.freeze({ ...declaration.hosts })
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
function injectionExtensionHost(declaration) {
|
|
8
|
+
const hasExactId = typeof declaration.spotId === "string" && declaration.spotId.length > 0;
|
|
9
|
+
const hasPattern = typeof declaration.pattern === "string" && declaration.pattern.length > 0;
|
|
10
|
+
if (hasExactId === hasPattern) {
|
|
11
|
+
throw new Error("[internal] injection extension hosts require exactly one of spotId or pattern");
|
|
12
|
+
}
|
|
13
|
+
if (hasPattern && (!declaration.parameters || Object.keys(declaration.parameters).length === 0)) {
|
|
14
|
+
throw new Error("[internal] patterned injection extension hosts require named parameters");
|
|
15
|
+
}
|
|
16
|
+
return Object.freeze({ ...declaration });
|
|
17
|
+
}
|
|
18
|
+
function dataTableExtensionHost(declaration) {
|
|
19
|
+
return Object.freeze({ family: "data-table", ...declaration });
|
|
20
|
+
}
|
|
21
|
+
function crudFormExtensionHost(declaration) {
|
|
22
|
+
return Object.freeze({ family: "crud-form", ...declaration });
|
|
23
|
+
}
|
|
24
|
+
function componentExtensionHost(declaration) {
|
|
25
|
+
return Object.freeze({ family: "component-handle", ...declaration });
|
|
26
|
+
}
|
|
27
|
+
const DATA_TABLE_EXTENSION_SURFACES = [
|
|
28
|
+
{ key: "header", suffix: "header", capabilities: ["render-widget"], bound: true },
|
|
29
|
+
{ key: "footer", suffix: "footer", capabilities: ["render-widget"], bound: true },
|
|
30
|
+
{ key: "toolbar", suffix: "toolbar", capabilities: ["toolbar-widget"], bound: true },
|
|
31
|
+
{ key: "searchTrailing", suffix: "search-trailing", capabilities: ["render-widget"], bound: true },
|
|
32
|
+
{ key: "columns", suffix: "columns", capabilities: ["column-widget"], bound: true },
|
|
33
|
+
{ key: "rowActions", suffix: "row-actions", capabilities: ["row-action"], bound: true },
|
|
34
|
+
{ key: "bulkActions", suffix: "bulk-actions", capabilities: ["bulk-action"], bound: true },
|
|
35
|
+
{ key: "filters", suffix: "filters", capabilities: ["filter-widget"], bound: true },
|
|
36
|
+
{ key: "replacement", suffix: null, capabilities: ["component-replacement"], bound: true },
|
|
37
|
+
{ key: "emptyState", suffix: "empty-state", capabilities: ["render-widget"], bound: false }
|
|
38
|
+
];
|
|
39
|
+
const CRUD_FORM_EXTENSION_SURFACES = [
|
|
40
|
+
{ key: "base", suffix: null, capabilities: ["render-widget", "lifecycle-handler"], bound: true },
|
|
41
|
+
{ key: "header", suffix: "header", capabilities: ["render-widget"], bound: true },
|
|
42
|
+
{ key: "fields", suffix: "fields", capabilities: ["field-widget"], bound: true },
|
|
43
|
+
{ key: "replacement", suffix: null, capabilities: ["component-replacement"], bound: true },
|
|
44
|
+
{ key: "beforeFields", suffix: "before-fields", capabilities: ["render-widget"], bound: false },
|
|
45
|
+
{ key: "afterFields", suffix: "after-fields", capabilities: ["render-widget"], bound: false },
|
|
46
|
+
{ key: "footer", suffix: "footer", capabilities: ["render-widget"], bound: false },
|
|
47
|
+
{ key: "sidebar", suffix: "sidebar", capabilities: ["render-widget"], bound: false },
|
|
48
|
+
{ key: "group", suffix: "group:{groupId}", capabilities: ["render-widget"], bound: false },
|
|
49
|
+
{ key: "fieldBefore", suffix: "field:{fieldId}:before", capabilities: ["render-widget"], bound: false },
|
|
50
|
+
{ key: "fieldAfter", suffix: "field:{fieldId}:after", capabilities: ["render-widget"], bound: false }
|
|
51
|
+
];
|
|
52
|
+
const CRUD_FORM_LIFECYCLE_PHASES = [
|
|
53
|
+
"transformValidation",
|
|
54
|
+
"transformDisplayData",
|
|
55
|
+
"onBeforeNavigate",
|
|
56
|
+
"onAppEvent",
|
|
57
|
+
"onVisibilityChange",
|
|
58
|
+
"onBeforeDelete",
|
|
59
|
+
"onDelete",
|
|
60
|
+
"onAfterDelete",
|
|
61
|
+
"onDeleteError",
|
|
62
|
+
"onFieldChange",
|
|
63
|
+
"transformFormData",
|
|
64
|
+
"onBeforeSave",
|
|
65
|
+
"onSave",
|
|
66
|
+
"onAfterSave"
|
|
67
|
+
];
|
|
68
|
+
const CRUD_FORM_OPERATIONS = ["create", "update", "delete"];
|
|
69
|
+
function dataTableExtensionSpotId(tableId, suffix) {
|
|
70
|
+
return suffix ? `data-table:${tableId}:${suffix}` : `data-table:${tableId}`;
|
|
71
|
+
}
|
|
72
|
+
function crudFormExtensionSpotId(entityId, suffix) {
|
|
73
|
+
return suffix ? `crud-form:${entityId}:${suffix}` : `crud-form:${entityId}`;
|
|
74
|
+
}
|
|
75
|
+
function extensionSpotChildId(spotId, suffix) {
|
|
76
|
+
return `${spotId}:${suffix}`;
|
|
77
|
+
}
|
|
78
|
+
function resolveExtensionPointPattern(pattern, parameters) {
|
|
79
|
+
return pattern.replace(/\{([^}]+)\}/g, (token, parameterName) => parameters[parameterName] ?? token);
|
|
80
|
+
}
|
|
81
|
+
export {
|
|
82
|
+
CRUD_FORM_EXTENSION_SURFACES,
|
|
83
|
+
CRUD_FORM_LIFECYCLE_PHASES,
|
|
84
|
+
CRUD_FORM_OPERATIONS,
|
|
85
|
+
DATA_TABLE_EXTENSION_SURFACES,
|
|
86
|
+
componentExtensionHost,
|
|
87
|
+
crudFormExtensionHost,
|
|
88
|
+
crudFormExtensionSpotId,
|
|
89
|
+
dataTableExtensionHost,
|
|
90
|
+
dataTableExtensionSpotId,
|
|
91
|
+
defineModuleExtensionPoints,
|
|
92
|
+
extensionSpotChildId,
|
|
93
|
+
injectionExtensionHost,
|
|
94
|
+
resolveExtensionPointPattern
|
|
95
|
+
};
|
|
96
|
+
//# sourceMappingURL=extension-points.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/modules/widgets/extension-points.ts"],
|
|
4
|
+
"sourcesContent": ["export type ExtensionHostFamily =\n | 'generic'\n | 'menu'\n | 'data-table'\n | 'crud-form'\n | 'detail'\n | 'portal-page'\n | 'component-handle'\n | 'entity'\n | 'api-route'\n | 'command'\n | 'event'\n | 'query-lifecycle'\n | 'dashboard'\n | 'notification'\n | 'integration'\n | 'specialized-registry'\n | 'module-override'\n\nexport type ExtensionHostCapability =\n | 'render-widget'\n | 'headless-widget'\n | 'menu-item'\n | 'column-widget'\n | 'row-action'\n | 'bulk-action'\n | 'filter-widget'\n | 'toolbar-widget'\n | 'field-widget'\n | 'lifecycle-handler'\n | 'component-replacement'\n | 'response-enricher'\n | 'query-enricher'\n | 'api-interceptor'\n | 'command-interceptor'\n | 'mutation-guard'\n | 'entity-extension'\n | 'async-subscriber'\n | 'sync-subscriber'\n | 'browser-client'\n | 'browser-portal'\n | 'registry-contribution'\n | 'module-override'\n\nexport type ExtensionHostActivation = 'always' | 'host-opt-in' | 'caller-opt-in' | 'feature-gated'\n\nexport type ExtensionPointPatternParameter = {\n source: string\n pattern?: string\n}\n\ntype ExtensionHostDeclarationBase = {\n source: string\n contextContract?: string\n dataContract?: string\n scopeContract?: string\n runtimeContract?: string\n activation?: ExtensionHostActivation\n aliases?: readonly string[]\n fallbacks?: readonly string[]\n}\n\ntype InjectionExtensionHostDeclarationBase = ExtensionHostDeclarationBase & {\n family: Exclude<ExtensionHostFamily, 'data-table' | 'crud-form' | 'component-handle'>\n supported: readonly ExtensionHostCapability[]\n}\n\nexport type InjectionExtensionHostDeclaration = InjectionExtensionHostDeclarationBase & (\n | {\n spotId: string\n pattern?: never\n parameters?: never\n }\n | {\n spotId?: never\n pattern: string\n parameters: Readonly<Record<string, ExtensionPointPatternParameter>>\n }\n)\n\nexport type DataTableExtensionHostDeclaration = ExtensionHostDeclarationBase & {\n family: 'data-table'\n tableId: string\n baseSpotId?: string\n}\n\nexport type CrudFormExtensionHostDeclaration = ExtensionHostDeclarationBase & {\n family: 'crud-form'\n entityId: string\n spotId?: string\n}\n\nexport type ComponentExtensionHostDeclaration = ExtensionHostDeclarationBase & {\n family: 'component-handle'\n componentId: string\n propsContract?: string\n}\n\nexport type ModuleExtensionHostDeclaration =\n | InjectionExtensionHostDeclaration\n | DataTableExtensionHostDeclaration\n | CrudFormExtensionHostDeclaration\n | ComponentExtensionHostDeclaration\n\nexport type ModuleExtensionPoints<\n TModuleId extends string = string,\n THosts extends Readonly<Record<string, ModuleExtensionHostDeclaration>> = Readonly<Record<string, ModuleExtensionHostDeclaration>>,\n> = {\n moduleId: TModuleId\n hosts: THosts\n}\n\nexport function defineModuleExtensionPoints<\n const TModuleId extends string,\n const THosts extends Readonly<Record<string, ModuleExtensionHostDeclaration>>,\n>(declaration: ModuleExtensionPoints<TModuleId, THosts>): ModuleExtensionPoints<TModuleId, THosts> {\n return Object.freeze({\n ...declaration,\n hosts: Object.freeze({ ...declaration.hosts }),\n })\n}\n\nexport function injectionExtensionHost<const TDeclaration extends InjectionExtensionHostDeclaration>(\n declaration: TDeclaration,\n): Readonly<TDeclaration> {\n const hasExactId = typeof declaration.spotId === 'string' && declaration.spotId.length > 0\n const hasPattern = typeof declaration.pattern === 'string' && declaration.pattern.length > 0\n if (hasExactId === hasPattern) {\n throw new Error('[internal] injection extension hosts require exactly one of spotId or pattern')\n }\n if (hasPattern && (!declaration.parameters || Object.keys(declaration.parameters).length === 0)) {\n throw new Error('[internal] patterned injection extension hosts require named parameters')\n }\n return Object.freeze({ ...declaration })\n}\n\nexport function dataTableExtensionHost<\n const TDeclaration extends Omit<DataTableExtensionHostDeclaration, 'family'>,\n>(\n declaration: TDeclaration,\n): Readonly<TDeclaration & { family: 'data-table' }> {\n return Object.freeze({ family: 'data-table', ...declaration })\n}\n\nexport function crudFormExtensionHost<\n const TDeclaration extends Omit<CrudFormExtensionHostDeclaration, 'family'>,\n>(\n declaration: TDeclaration,\n): Readonly<TDeclaration & { family: 'crud-form' }> {\n return Object.freeze({ family: 'crud-form', ...declaration })\n}\n\nexport function componentExtensionHost<\n const TDeclaration extends Omit<ComponentExtensionHostDeclaration, 'family'>,\n>(\n declaration: TDeclaration,\n): Readonly<TDeclaration & { family: 'component-handle' }> {\n return Object.freeze({ family: 'component-handle', ...declaration })\n}\n\nexport type BoundExtensionSurface = {\n key: string\n suffix: string | null\n capabilities: readonly ExtensionHostCapability[]\n bound: boolean\n phases?: readonly string[]\n operations?: readonly string[]\n}\n\nexport const DATA_TABLE_EXTENSION_SURFACES = [\n { key: 'header', suffix: 'header', capabilities: ['render-widget'], bound: true },\n { key: 'footer', suffix: 'footer', capabilities: ['render-widget'], bound: true },\n { key: 'toolbar', suffix: 'toolbar', capabilities: ['toolbar-widget'], bound: true },\n { key: 'searchTrailing', suffix: 'search-trailing', capabilities: ['render-widget'], bound: true },\n { key: 'columns', suffix: 'columns', capabilities: ['column-widget'], bound: true },\n { key: 'rowActions', suffix: 'row-actions', capabilities: ['row-action'], bound: true },\n { key: 'bulkActions', suffix: 'bulk-actions', capabilities: ['bulk-action'], bound: true },\n { key: 'filters', suffix: 'filters', capabilities: ['filter-widget'], bound: true },\n { key: 'replacement', suffix: null, capabilities: ['component-replacement'], bound: true },\n { key: 'emptyState', suffix: 'empty-state', capabilities: ['render-widget'], bound: false },\n] as const satisfies readonly BoundExtensionSurface[]\n\nexport const CRUD_FORM_EXTENSION_SURFACES = [\n { key: 'base', suffix: null, capabilities: ['render-widget', 'lifecycle-handler'], bound: true },\n { key: 'header', suffix: 'header', capabilities: ['render-widget'], bound: true },\n { key: 'fields', suffix: 'fields', capabilities: ['field-widget'], bound: true },\n { key: 'replacement', suffix: null, capabilities: ['component-replacement'], bound: true },\n { key: 'beforeFields', suffix: 'before-fields', capabilities: ['render-widget'], bound: false },\n { key: 'afterFields', suffix: 'after-fields', capabilities: ['render-widget'], bound: false },\n { key: 'footer', suffix: 'footer', capabilities: ['render-widget'], bound: false },\n { key: 'sidebar', suffix: 'sidebar', capabilities: ['render-widget'], bound: false },\n { key: 'group', suffix: 'group:{groupId}', capabilities: ['render-widget'], bound: false },\n { key: 'fieldBefore', suffix: 'field:{fieldId}:before', capabilities: ['render-widget'], bound: false },\n { key: 'fieldAfter', suffix: 'field:{fieldId}:after', capabilities: ['render-widget'], bound: false },\n] as const satisfies readonly BoundExtensionSurface[]\n\nexport const CRUD_FORM_LIFECYCLE_PHASES = [\n 'transformValidation',\n 'transformDisplayData',\n 'onBeforeNavigate',\n 'onAppEvent',\n 'onVisibilityChange',\n 'onBeforeDelete',\n 'onDelete',\n 'onAfterDelete',\n 'onDeleteError',\n 'onFieldChange',\n 'transformFormData',\n 'onBeforeSave',\n 'onSave',\n 'onAfterSave',\n] as const\n\nexport const CRUD_FORM_OPERATIONS = ['create', 'update', 'delete'] as const\n\nexport function dataTableExtensionSpotId(tableId: string, suffix?: string): string {\n return suffix ? `data-table:${tableId}:${suffix}` : `data-table:${tableId}`\n}\n\nexport function crudFormExtensionSpotId(entityId: string, suffix?: string): string {\n return suffix ? `crud-form:${entityId}:${suffix}` : `crud-form:${entityId}`\n}\n\nexport function extensionSpotChildId(spotId: string, suffix: string): string {\n return `${spotId}:${suffix}`\n}\n\nexport function resolveExtensionPointPattern(\n pattern: string,\n parameters: Readonly<Record<string, string>>,\n): string {\n return pattern.replace(/\\{([^}]+)\\}/g, (token, parameterName: string) => parameters[parameterName] ?? token)\n}\n\nexport type ModuleExtensionSurfaceFacts = {\n hosts: ModuleExtensionHostFact[]\n contributions: ModuleExtensionContributionFact[]\n unresolved: ModuleExtensionUnresolvedFact[]\n}\n\nexport type ModuleExtensionHostFact = {\n key: string\n id: string\n resolution: 'exact' | 'pattern' | 'framework' | 'fact-ref'\n family: ExtensionHostFamily\n ownerModule: string\n capabilities: ExtensionHostCapability[]\n phases?: string[]\n operations?: string[]\n contextContract?: string\n dataContract?: string\n scopeContract?: string\n runtimeContract?: string\n activation?: ExtensionHostActivation\n bound: boolean\n stability: 'frozen' | 'stable'\n source:\n | { kind: 'declaration'; path: string; symbol: string }\n | { kind: 'fact-ref'; factSection: string; factKey: string }\n | { kind: 'framework'; path: string; symbol: string }\n aliases?: string[]\n patternParameters?: Record<string, ExtensionPointPatternParameter>\n fallbacks?: string[]\n}\n\nexport type ModuleExtensionTargetFact = {\n id: string\n resolution: 'exact' | 'pattern' | 'framework' | 'fact-ref' | 'optional-external' | 'unresolved'\n factRef?: { factSection: string; factKey: string }\n optionalOwnerPackage?: string\n}\n\nexport type ModuleExtensionContributionBase = {\n id: string\n targets: ModuleExtensionTargetFact[]\n phases?: string[]\n operations?: string[]\n features?: string[]\n scopeContract: string\n activation?: ExtensionHostActivation\n placement?: { relativeTo?: string; position?: 'first' | 'last' | 'before' | 'after'; priority?: number }\n roundTripId?: string\n override?: { domain: string; key: string; mode: 'disable-replace' | 'replace' | 'additive' }\n source: { path: string; symbol?: string }\n}\n\nexport type ModuleExtensionContributionFact = ModuleExtensionContributionBase & (\n | {\n kind: 'widget'\n details: {\n payload: 'render' | 'headless' | 'menu' | 'dashboard' | 'notification' | 'integration'\n registryKey: string\n itemIds?: string[]\n labelKeys?: string[]\n contextContract?: string\n dataContract?: string\n executionGuard: 'host' | 'contribution' | 'both'\n }\n }\n | {\n kind: 'data-table'\n details: {\n payload: 'column' | 'row-action' | 'bulk-action' | 'filter' | 'toolbar' | 'render'\n tableId: string\n executionGuard: 'host' | 'contribution' | 'both'\n }\n }\n | {\n kind: 'crud-form'\n details: {\n payload: 'render' | 'field' | 'lifecycle-handler'\n entityId: string\n fieldIds?: string[]\n groupIds?: string[]\n requestHeaderCapability: boolean\n }\n }\n | {\n kind: 'component-override'\n details: { handle: string; mode: 'replace' | 'wrapper' | 'props'; propsContract: string }\n }\n | {\n kind: 'response-enricher'\n details: {\n targetEntity: string\n surfaces: Array<'list' | 'detail'>\n timeoutMs: number\n fallback: 'none' | 'configured'\n critical: boolean\n cachePosture: 'record-pure' | 'rerun-on-list-cache-hit'\n queryEngine?: {\n engines: string[]\n applyOn: Array<'list' | 'detail'>\n activation: 'caller-opt-in'\n }\n }\n }\n | {\n kind: 'api-interceptor'\n details: {\n route: string\n methods: string[]\n phases: Array<'before' | 'after'>\n activation: 'crud-pipeline' | 'custom-route-bridge'\n timeoutMs: number\n failurePosture: 'fail-closed' | 'fallback'\n }\n }\n | {\n kind: 'command-interceptor'\n details: {\n targetCommand: string\n phases: Array<'before-execute' | 'after-execute' | 'before-undo' | 'after-undo'>\n }\n }\n | {\n kind: 'mutation-guard'\n details: {\n entityId: string\n operations: Array<'create' | 'update' | 'delete'>\n capabilities: Array<'block' | 'rewrite' | 'after-success'>\n optimisticLock: 'preserved'\n }\n }\n | {\n kind: 'entity-extension'\n details: {\n hostEntityId: string\n extensionEntityId: string\n linkId: string\n scopeContract: string\n orphanContract: string\n }\n }\n | {\n kind: 'subscriber'\n details: {\n event: string\n subscriberId: string\n persistent: boolean\n sync: boolean\n priority?: number\n }\n }\n | {\n kind: 'browser-reaction'\n details: {\n transports: Array<'client' | 'portal' | 'notification-effect'>\n hooks: string[]\n audienceScopeContract: string\n maxPayloadBytes?: number\n dedupWindowMs?: number\n }\n }\n | {\n kind: 'specialized-registry'\n details: {\n registry: 'notification' | 'integration' | 'search' | 'vector' | 'ai' | 'payment' | 'shipping' | 'currency' | 'workflow'\n registryId: string\n specialistRoute: string\n }\n }\n | {\n kind: 'module-override'\n details: {\n domain: string\n key: string\n mode: 'disable-replace' | 'replace' | 'additive'\n }\n }\n)\n\nexport type ModuleExtensionUnresolvedFact = {\n key: string\n source: { path: string; symbol?: string }\n reason:\n | 'unclassified-binding'\n | 'unbound-declaration'\n | 'dynamic-without-pattern'\n | 'unresolved-first-party-target'\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/shared",
|
|
3
|
-
"version": "0.6.7-develop.
|
|
3
|
+
"version": "0.6.7-develop.6768.1.9d2c4efc43",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
"@mikro-orm/core": "^7.1.5",
|
|
102
102
|
"@mikro-orm/decorators": "^7.1.5",
|
|
103
103
|
"@mikro-orm/postgresql": "^7.1.5",
|
|
104
|
-
"@open-mercato/cache": "0.6.7-develop.
|
|
104
|
+
"@open-mercato/cache": "0.6.7-develop.6768.1.9d2c4efc43",
|
|
105
105
|
"@types/sanitize-html": "^2.16.1",
|
|
106
106
|
"dotenv": "^17.4.2",
|
|
107
107
|
"pino": "^10.3.1",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import fs from 'node:fs'
|
|
18
18
|
import os from 'node:os'
|
|
19
19
|
import path from 'node:path'
|
|
20
|
+
import crypto from 'node:crypto'
|
|
20
21
|
import { loadBootstrapData } from '../dynamicLoader'
|
|
21
22
|
import {
|
|
22
23
|
ensureMikroOrmV7GeneratedCacheCompatibility,
|
|
@@ -50,6 +51,18 @@ const BASE_GENERATED_MODULES: Record<string, { ts: string; compiled: string }> =
|
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
let compiledCacheGeneration = 0
|
|
54
|
+
const APP_TSCONFIG = JSON.stringify({
|
|
55
|
+
compilerOptions: {
|
|
56
|
+
experimentalDecorators: true,
|
|
57
|
+
emitDecoratorMetadata: true,
|
|
58
|
+
useDefineForClassFields: false,
|
|
59
|
+
target: 'ES2022',
|
|
60
|
+
},
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
function hash(content: string): string {
|
|
64
|
+
return crypto.createHash('sha256').update(content).digest('hex')
|
|
65
|
+
}
|
|
53
66
|
|
|
54
67
|
function writeGeneratedModule(generatedDir: string, baseName: string, source: { ts: string; compiled: string }) {
|
|
55
68
|
fs.writeFileSync(path.join(generatedDir, `${baseName}.ts`), source.ts)
|
|
@@ -57,14 +70,33 @@ function writeGeneratedModule(generatedDir: string, baseName: string, source: {
|
|
|
57
70
|
}
|
|
58
71
|
|
|
59
72
|
/**
|
|
60
|
-
* Write
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* URL instead of the rejected module's cached one.
|
|
73
|
+
* Write a compiled sibling and matching sidecar so compileAndImport can take its
|
|
74
|
+
* cache path without invoking esbuild. Each rewrite changes the output hash,
|
|
75
|
+
* which gives the retry a distinct `?cache=` import URL.
|
|
64
76
|
*/
|
|
65
77
|
function writeCompiledSibling(generatedDir: string, baseName: string, compiled: string) {
|
|
78
|
+
const source = fs.readFileSync(path.join(generatedDir, `${baseName}.ts`), 'utf8')
|
|
79
|
+
const inputHash = hash(JSON.stringify({
|
|
80
|
+
version: 4,
|
|
81
|
+
sourceHash: hash(source),
|
|
82
|
+
tsconfigHashes: {
|
|
83
|
+
'tsconfig.json': hash(APP_TSCONFIG),
|
|
84
|
+
},
|
|
85
|
+
}))
|
|
86
|
+
const sourceRelativePath = path.relative(
|
|
87
|
+
path.dirname(path.dirname(generatedDir)),
|
|
88
|
+
path.join(generatedDir, `${baseName}.ts`),
|
|
89
|
+
).split(path.sep).join('/')
|
|
66
90
|
const compiledPath = path.join(generatedDir, `${baseName}.mjs`)
|
|
67
91
|
fs.writeFileSync(compiledPath, compiled)
|
|
92
|
+
fs.writeFileSync(`${compiledPath}.cache.json`, JSON.stringify({
|
|
93
|
+
version: 4,
|
|
94
|
+
inputHash,
|
|
95
|
+
outputHash: hash(compiled),
|
|
96
|
+
dependencies: {
|
|
97
|
+
[sourceRelativePath]: hash(source),
|
|
98
|
+
},
|
|
99
|
+
}))
|
|
68
100
|
compiledCacheGeneration += 1
|
|
69
101
|
const fresh = new Date(Date.now() + compiledCacheGeneration * 60_000)
|
|
70
102
|
fs.utimesSync(compiledPath, fresh, fresh)
|
|
@@ -74,6 +106,7 @@ function createAppRoot(entityIdsCache: string): string {
|
|
|
74
106
|
const appRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'om-bootstrap-4526-'))
|
|
75
107
|
const generatedDir = path.join(appRoot, '.mercato', 'generated')
|
|
76
108
|
fs.mkdirSync(generatedDir, { recursive: true })
|
|
109
|
+
fs.writeFileSync(path.join(appRoot, 'tsconfig.json'), APP_TSCONFIG)
|
|
77
110
|
for (const [baseName, source] of Object.entries(BASE_GENERATED_MODULES)) {
|
|
78
111
|
writeGeneratedModule(generatedDir, baseName, source)
|
|
79
112
|
}
|
|
@@ -87,7 +120,7 @@ function createAppRoot(entityIdsCache: string): string {
|
|
|
87
120
|
function recoverByRewritingCache(appRoot: string, compiled: string): GeneratedCacheRecoveryResult {
|
|
88
121
|
const generatedDir = path.join(appRoot, '.mercato', 'generated')
|
|
89
122
|
writeCompiledSibling(generatedDir, 'entities.ids.generated', compiled)
|
|
90
|
-
// Node busts its ESM cache through the `?
|
|
123
|
+
// Node busts its ESM cache through the `?cache=` query compileAndImport
|
|
91
124
|
// appends; Jest's registry keys on the resolved path alone, so the retry would
|
|
92
125
|
// otherwise replay the rejected evaluation instead of the rewritten file.
|
|
93
126
|
jest.resetModules()
|