@flink-app/flink 2.0.0-alpha.58 → 2.0.0-alpha.59
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/CHANGELOG.md +10 -0
- package/cli/cli-utils.ts +1 -0
- package/cli/dev.ts +13 -4
- package/dist/cli/cli-utils.js +13 -10
- package/dist/cli/dev.js +11 -4
- package/dist/src/TypeScriptCompiler.d.ts +20 -0
- package/dist/src/TypeScriptCompiler.js +99 -4
- package/dist/src/utils/loadFlinkConfig.d.ts +23 -1
- package/dist/src/utils/loadFlinkConfig.js +3 -3
- package/package.json +1 -1
- package/src/TypeScriptCompiler.ts +93 -2
- package/src/utils/loadFlinkConfig.ts +26 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @flink-app/flink
|
|
2
2
|
|
|
3
|
+
## 2.0.0-alpha.59
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- dbc2119: feat: add generic compiler extension system and @flink-app/inbound-email-plugin
|
|
8
|
+
|
|
9
|
+
Add `compilerPlugins` to `FlinkConfig` and `FlinkCompilerPlugin` interface so plugins can declare custom scan directories for auto-discovery. The TypeScript compiler reads these at build time, generates the corresponding `.flink/generatedXxx.ts` registration files, and includes them in the start script.
|
|
10
|
+
|
|
11
|
+
Introduce `@flink-app/inbound-email-plugin` as the first consumer of this system. It starts an SMTP server and automatically routes inbound emails to matching `EmailHandler` files discovered in `src/email-handlers/`. User and permission context are injected via `AsyncLocalStorage` for seamless integration with Flink tools and agents.
|
|
12
|
+
|
|
3
13
|
## 2.0.0-alpha.58
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
package/cli/cli-utils.ts
CHANGED
|
@@ -116,6 +116,7 @@ export async function compile(opts: CompileOptions): Promise<void> {
|
|
|
116
116
|
await compiler.parseTools();
|
|
117
117
|
await compiler.parseAgents();
|
|
118
118
|
await compiler.parseJobs();
|
|
119
|
+
await compiler.parseAllExtensionDirs();
|
|
119
120
|
await compiler.parseHandlers();
|
|
120
121
|
await compiler.generateStartScript(entry);
|
|
121
122
|
await compiler.generateAllSchemas();
|
package/cli/dev.ts
CHANGED
|
@@ -181,15 +181,24 @@ function startTscWatch(dir: string): ChildProcess {
|
|
|
181
181
|
|
|
182
182
|
if (!trimmed) continue;
|
|
183
183
|
|
|
184
|
-
|
|
184
|
+
// Filter out noisy watch-mode status lines
|
|
185
|
+
if (
|
|
186
|
+
trimmed.includes("Starting compilation") ||
|
|
187
|
+
trimmed.includes("File change detected") ||
|
|
188
|
+
trimmed.match(/^\[\d+:\d+:\d+\s+[AP]M\] Watching for file changes/)
|
|
189
|
+
) {
|
|
185
190
|
continue;
|
|
186
191
|
}
|
|
187
192
|
|
|
188
193
|
if (trimmed.includes("Found 0 errors")) {
|
|
189
194
|
console.log(`[tsc] ✓ No type errors`);
|
|
190
|
-
} else if (trimmed.
|
|
191
|
-
|
|
192
|
-
|
|
195
|
+
} else if (trimmed.match(/Found \d+ errors?/)) {
|
|
196
|
+
// Pretty-print the error count summary
|
|
197
|
+
const match = trimmed.match(/Found (\d+) errors?/);
|
|
198
|
+
const count = match?.[1];
|
|
199
|
+
console.log(`[tsc] ❌ Found ${count} type error${count === "1" ? "" : "s"} — see details above`);
|
|
200
|
+
} else {
|
|
201
|
+
// Show everything else: error details, file paths, code context, etc.
|
|
193
202
|
console.log(`[tsc] ${trimmed}`);
|
|
194
203
|
}
|
|
195
204
|
}
|
package/dist/cli/cli-utils.js
CHANGED
|
@@ -125,17 +125,20 @@ function compile(opts) {
|
|
|
125
125
|
return [4 /*yield*/, compiler.parseJobs()];
|
|
126
126
|
case 5:
|
|
127
127
|
_c.sent();
|
|
128
|
-
return [4 /*yield*/, compiler.
|
|
128
|
+
return [4 /*yield*/, compiler.parseAllExtensionDirs()];
|
|
129
129
|
case 6:
|
|
130
130
|
_c.sent();
|
|
131
|
-
return [4 /*yield*/, compiler.
|
|
131
|
+
return [4 /*yield*/, compiler.parseHandlers()];
|
|
132
132
|
case 7:
|
|
133
133
|
_c.sent();
|
|
134
|
-
return [4 /*yield*/, compiler.
|
|
134
|
+
return [4 /*yield*/, compiler.generateStartScript(entry)];
|
|
135
135
|
case 8:
|
|
136
136
|
_c.sent();
|
|
137
|
-
return [4 /*yield*/, compiler.
|
|
137
|
+
return [4 /*yield*/, compiler.generateAllSchemas()];
|
|
138
138
|
case 9:
|
|
139
|
+
_c.sent();
|
|
140
|
+
return [4 /*yield*/, compiler.saveAllModifiedFiles()];
|
|
141
|
+
case 10:
|
|
139
142
|
_c.sent();
|
|
140
143
|
if (typeCheck) {
|
|
141
144
|
stepStart = Date.now();
|
|
@@ -145,14 +148,14 @@ function compile(opts) {
|
|
|
145
148
|
time("Type checking (getPreEmitDiagnostics)", stepStart);
|
|
146
149
|
}
|
|
147
150
|
stepStart = Date.now();
|
|
148
|
-
if (!(process.env.FLINK_USE_TSC === "true")) return [3 /*break*/,
|
|
151
|
+
if (!(process.env.FLINK_USE_TSC === "true")) return [3 /*break*/, 11];
|
|
149
152
|
compiler.emitWithTsc();
|
|
150
|
-
return [3 /*break*/,
|
|
151
|
-
case
|
|
152
|
-
case 11:
|
|
153
|
-
_c.sent();
|
|
154
|
-
_c.label = 12;
|
|
153
|
+
return [3 /*break*/, 13];
|
|
154
|
+
case 11: return [4 /*yield*/, compiler.emit()];
|
|
155
155
|
case 12:
|
|
156
|
+
_c.sent();
|
|
157
|
+
_c.label = 13;
|
|
158
|
+
case 13:
|
|
156
159
|
time("Transpilation (".concat(process.env.FLINK_USE_TSC === "true" ? "tsc" : "swc", ")"), stepStart);
|
|
157
160
|
if (timingLogs) {
|
|
158
161
|
console.log("Compilation done in ".concat(Date.now() - start, "ms"));
|
package/dist/cli/dev.js
CHANGED
|
@@ -215,16 +215,23 @@ function startTscWatch(dir) {
|
|
|
215
215
|
var trimmed = line.replace(/\x1Bc/g, "").trim();
|
|
216
216
|
if (!trimmed)
|
|
217
217
|
continue;
|
|
218
|
-
|
|
218
|
+
// Filter out noisy watch-mode status lines
|
|
219
|
+
if (trimmed.includes("Starting compilation") ||
|
|
220
|
+
trimmed.includes("File change detected") ||
|
|
221
|
+
trimmed.match(/^\[\d+:\d+:\d+\s+[AP]M\] Watching for file changes/)) {
|
|
219
222
|
continue;
|
|
220
223
|
}
|
|
221
224
|
if (trimmed.includes("Found 0 errors")) {
|
|
222
225
|
console.log("[tsc] \u2713 No type errors");
|
|
223
226
|
}
|
|
224
|
-
else if (trimmed.
|
|
225
|
-
|
|
227
|
+
else if (trimmed.match(/Found \d+ errors?/)) {
|
|
228
|
+
// Pretty-print the error count summary
|
|
229
|
+
var match = trimmed.match(/Found (\d+) errors?/);
|
|
230
|
+
var count = match === null || match === void 0 ? void 0 : match[1];
|
|
231
|
+
console.log("[tsc] \u274C Found ".concat(count, " type error").concat(count === "1" ? "" : "s", " \u2014 see details above"));
|
|
226
232
|
}
|
|
227
|
-
else
|
|
233
|
+
else {
|
|
234
|
+
// Show everything else: error details, file paths, code context, etc.
|
|
228
235
|
console.log("[tsc] ".concat(trimmed));
|
|
229
236
|
}
|
|
230
237
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SourceFile } from "ts-morph";
|
|
2
|
+
import { FlinkCompilerPlugin } from "./utils/loadFlinkConfig";
|
|
2
3
|
declare class TypeScriptCompiler {
|
|
3
4
|
private cwd;
|
|
4
5
|
private project;
|
|
@@ -24,6 +25,14 @@ declare class TypeScriptCompiler {
|
|
|
24
25
|
* Tool ID registry for agent validation (built during segmentation)
|
|
25
26
|
*/
|
|
26
27
|
private toolIdRegistry;
|
|
28
|
+
/**
|
|
29
|
+
* Compiler plugins loaded from flink.config.js
|
|
30
|
+
*/
|
|
31
|
+
private compilerPlugins;
|
|
32
|
+
/**
|
|
33
|
+
* Extension files collected during segmentation, keyed by generatedFile name
|
|
34
|
+
*/
|
|
35
|
+
private extensionFiles;
|
|
27
36
|
/**
|
|
28
37
|
* Generates a schema $id from a file path and type name using the same algorithm
|
|
29
38
|
* as the schema generator's defineId callback.
|
|
@@ -206,5 +215,16 @@ declare class TypeScriptCompiler {
|
|
|
206
215
|
* Scans project for jobs so they can be registered during start.
|
|
207
216
|
*/
|
|
208
217
|
parseJobs(): Promise<SourceFile>;
|
|
218
|
+
/**
|
|
219
|
+
* Generates a .flink/generatedXxx.ts file for a single compiler plugin extension.
|
|
220
|
+
* Mirrors the same namespace-import + spread pattern used by parseJobs.
|
|
221
|
+
*/
|
|
222
|
+
parseExtensionDir(ext: FlinkCompilerPlugin): Promise<SourceFile>;
|
|
223
|
+
/**
|
|
224
|
+
* Iterates all compilerPlugins from flink.config.js and generates
|
|
225
|
+
* a .flink/generatedXxx.ts file for each one.
|
|
226
|
+
* Call this after parseJobs() and before generateStartScript().
|
|
227
|
+
*/
|
|
228
|
+
parseAllExtensionDirs(): Promise<void>;
|
|
209
229
|
}
|
|
210
230
|
export default TypeScriptCompiler;
|
|
@@ -90,11 +90,12 @@ var FlinkLogFactory_1 = require("./FlinkLogFactory");
|
|
|
90
90
|
var FsUtils_1 = require("./FsUtils");
|
|
91
91
|
var schema_extraction_1 = require("./schema-extraction");
|
|
92
92
|
var utils_1 = require("./utils");
|
|
93
|
+
var loadFlinkConfig_1 = require("./utils/loadFlinkConfig");
|
|
93
94
|
var perfLog = FlinkLogFactory_1.FlinkLogFactory.createLogger("flink.perf");
|
|
94
95
|
var initLog = FlinkLogFactory_1.FlinkLogFactory.createLogger("flink.init");
|
|
95
96
|
var TypeScriptCompiler = /** @class */ (function () {
|
|
96
97
|
function TypeScriptCompiler(cwd) {
|
|
97
|
-
var _a, _b;
|
|
98
|
+
var _a, _b, _c;
|
|
98
99
|
/**
|
|
99
100
|
* Handler schemas collected during parseHandlers, to be generated later
|
|
100
101
|
*/
|
|
@@ -115,6 +116,14 @@ var TypeScriptCompiler = /** @class */ (function () {
|
|
|
115
116
|
* Tool ID registry for agent validation (built during segmentation)
|
|
116
117
|
*/
|
|
117
118
|
this.toolIdRegistry = new Set();
|
|
119
|
+
/**
|
|
120
|
+
* Compiler plugins loaded from flink.config.js
|
|
121
|
+
*/
|
|
122
|
+
this.compilerPlugins = [];
|
|
123
|
+
/**
|
|
124
|
+
* Extension files collected during segmentation, keyed by generatedFile name
|
|
125
|
+
*/
|
|
126
|
+
this.extensionFiles = new Map();
|
|
118
127
|
var path = require("path");
|
|
119
128
|
// Convert to absolute path to ensure consistent path comparisons
|
|
120
129
|
this.cwd = path.resolve(cwd);
|
|
@@ -189,6 +198,15 @@ var TypeScriptCompiler = /** @class */ (function () {
|
|
|
189
198
|
var loadTime = Date.now() - loadStartTime;
|
|
190
199
|
var fileCount = this.project.getSourceFiles().length;
|
|
191
200
|
perfLog.debug("\u2713 All source files loaded in ".concat(loadTime, "ms (").concat(fileCount, " files)"));
|
|
201
|
+
// Load compiler plugins from flink.config.js
|
|
202
|
+
var flinkCfg = (0, loadFlinkConfig_1.loadFlinkConfig)(this.cwd);
|
|
203
|
+
this.compilerPlugins = (_c = flinkCfg === null || flinkCfg === void 0 ? void 0 : flinkCfg.compilerPlugins) !== null && _c !== void 0 ? _c : [];
|
|
204
|
+
if (this.compilerPlugins.length > 0) {
|
|
205
|
+
initLog.info("Compiler plugins loaded (".concat(this.compilerPlugins.length, "):"), this.compilerPlugins.map(function (p) { return "".concat(p.package, " \u2192 ").concat(p.scanDir); }).join(", "));
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
initLog.info("No compiler plugins configured");
|
|
209
|
+
}
|
|
192
210
|
// Segment files by type for efficient processing
|
|
193
211
|
this.segmentSourceFiles();
|
|
194
212
|
console.log("Loaded", this.project.getSourceFiles().length, "source file(s) from", cwd);
|
|
@@ -423,6 +441,7 @@ var TypeScriptCompiler = /** @class */ (function () {
|
|
|
423
441
|
* multiple full-file iterations during parse methods.
|
|
424
442
|
*/
|
|
425
443
|
TypeScriptCompiler.prototype.segmentSourceFiles = function () {
|
|
444
|
+
var _a;
|
|
426
445
|
var startTime = Date.now();
|
|
427
446
|
var allFiles = this.project.getSourceFiles();
|
|
428
447
|
var handlerTime = 0, repoTime = 0, toolTime = 0, agentTime = 0, jobTime = 0;
|
|
@@ -470,7 +489,19 @@ var TypeScriptCompiler = /** @class */ (function () {
|
|
|
470
489
|
if (filePath.includes("src/jobs/")) {
|
|
471
490
|
this.jobFiles.push(sf);
|
|
472
491
|
jobTime += Date.now() - fileStartTime;
|
|
473
|
-
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
// Extension dirs from compiler plugins
|
|
495
|
+
for (var _b = 0, _c = this.compilerPlugins; _b < _c.length; _b++) {
|
|
496
|
+
var ext = _c[_b];
|
|
497
|
+
if (filePath.includes(ext.scanDir)) {
|
|
498
|
+
if (!ext.detectBy || ext.detectBy(sf.getFullText(), filePath)) {
|
|
499
|
+
var list = (_a = this.extensionFiles.get(ext.generatedFile)) !== null && _a !== void 0 ? _a : [];
|
|
500
|
+
list.push(sf);
|
|
501
|
+
this.extensionFiles.set(ext.generatedFile, list);
|
|
502
|
+
}
|
|
503
|
+
break;
|
|
504
|
+
}
|
|
474
505
|
}
|
|
475
506
|
}
|
|
476
507
|
var segmentTime = Date.now() - startTime;
|
|
@@ -1112,7 +1143,8 @@ var TypeScriptCompiler = /** @class */ (function () {
|
|
|
1112
1143
|
*/
|
|
1113
1144
|
TypeScriptCompiler.prototype.generateStartScript = function () {
|
|
1114
1145
|
return __awaiter(this, arguments, void 0, function (appEntryScript) {
|
|
1115
|
-
var path, entryScriptPath, specFiles, sf;
|
|
1146
|
+
var path, entryScriptPath, specFiles, extensionImports, sf;
|
|
1147
|
+
var _this = this;
|
|
1116
1148
|
if (appEntryScript === void 0) { appEntryScript = "/src/index.ts"; }
|
|
1117
1149
|
return __generator(this, function (_a) {
|
|
1118
1150
|
switch (_a.label) {
|
|
@@ -1137,7 +1169,10 @@ var TypeScriptCompiler = /** @class */ (function () {
|
|
|
1137
1169
|
this.resolveImportedFiles();
|
|
1138
1170
|
_a.label = 3;
|
|
1139
1171
|
case 3:
|
|
1140
|
-
|
|
1172
|
+
extensionImports = this.compilerPlugins
|
|
1173
|
+
.map(function (ext) { return "import \"./".concat(ext.generatedFile).concat(_this.isEsm ? ".js" : "", "\";"); })
|
|
1174
|
+
.join("\n");
|
|
1175
|
+
sf = this.createSourceFile(["start.ts"], "// Generated ".concat(new Date(), "\nimport \"./generatedHandlers").concat(this.isEsm ? ".js" : "", "\";\nimport \"./generatedRepos").concat(this.isEsm ? ".js" : "", "\";\nimport \"./generatedTools").concat(this.isEsm ? ".js" : "", "\";\nimport \"./generatedAgents").concat(this.isEsm ? ".js" : "", "\";\nimport \"./generatedJobs").concat(this.isEsm ? ".js" : "", "\";\n").concat(extensionImports ? extensionImports + "\n" : "", "import \"..").concat(appEntryScript.replace(/\.ts/g, "")).concat(this.isEsm ? ".js" : "", "\";\nexport default {}; // Export an empty object to make it a module\n"));
|
|
1141
1176
|
// Defer save until batch save at end (performance optimization)
|
|
1142
1177
|
return [2 /*return*/, sf];
|
|
1143
1178
|
}
|
|
@@ -1482,6 +1517,66 @@ var TypeScriptCompiler = /** @class */ (function () {
|
|
|
1482
1517
|
});
|
|
1483
1518
|
});
|
|
1484
1519
|
};
|
|
1520
|
+
/**
|
|
1521
|
+
* Generates a .flink/generatedXxx.ts file for a single compiler plugin extension.
|
|
1522
|
+
* Mirrors the same namespace-import + spread pattern used by parseJobs.
|
|
1523
|
+
*/
|
|
1524
|
+
TypeScriptCompiler.prototype.parseExtensionDir = function (ext) {
|
|
1525
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
1526
|
+
var startTime, files, generatedFile, itemsArr, imports, itemElements, elapsed;
|
|
1527
|
+
var _this = this;
|
|
1528
|
+
var _a;
|
|
1529
|
+
return __generator(this, function (_b) {
|
|
1530
|
+
startTime = Date.now();
|
|
1531
|
+
files = (_a = this.extensionFiles.get(ext.generatedFile)) !== null && _a !== void 0 ? _a : [];
|
|
1532
|
+
generatedFile = this.createSourceFile(["".concat(ext.generatedFile, ".ts")], "// Generated ".concat(new Date(), "\nimport { ").concat(ext.registrationVar, " } from \"").concat(ext.package, "\";\nexport const items: any[] = [];\n").concat(ext.registrationVar, ".push(...items);\n"));
|
|
1533
|
+
itemsArr = generatedFile.getVariableDeclarationOrThrow("items").getFirstDescendantByKindOrThrow(ts_morph_1.SyntaxKind.ArrayLiteralExpression);
|
|
1534
|
+
imports = [];
|
|
1535
|
+
itemElements = [];
|
|
1536
|
+
files.forEach(function (sf, i) {
|
|
1537
|
+
var namespaceImport = sf.getBaseNameWithoutExtension().replace(/\./g, "_") + "_" + i;
|
|
1538
|
+
imports.push({
|
|
1539
|
+
defaultImport: "* as " + namespaceImport,
|
|
1540
|
+
moduleSpecifier: _this.getModuleSpecifier(generatedFile, sf),
|
|
1541
|
+
});
|
|
1542
|
+
itemElements.push("{...".concat(namespaceImport, ", __file: \"").concat(_this.getRelativePath(sf), "\"}"));
|
|
1543
|
+
});
|
|
1544
|
+
itemsArr.addElements(itemElements);
|
|
1545
|
+
generatedFile.addImportDeclarations(imports);
|
|
1546
|
+
elapsed = Date.now() - startTime;
|
|
1547
|
+
initLog.info("\u2713 Extension dir \"".concat(ext.scanDir, "\" parsed in ").concat(elapsed, "ms (").concat(files.length, " files)"));
|
|
1548
|
+
return [2 /*return*/, generatedFile];
|
|
1549
|
+
});
|
|
1550
|
+
});
|
|
1551
|
+
};
|
|
1552
|
+
/**
|
|
1553
|
+
* Iterates all compilerPlugins from flink.config.js and generates
|
|
1554
|
+
* a .flink/generatedXxx.ts file for each one.
|
|
1555
|
+
* Call this after parseJobs() and before generateStartScript().
|
|
1556
|
+
*/
|
|
1557
|
+
TypeScriptCompiler.prototype.parseAllExtensionDirs = function () {
|
|
1558
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
1559
|
+
var _i, _a, ext;
|
|
1560
|
+
return __generator(this, function (_b) {
|
|
1561
|
+
switch (_b.label) {
|
|
1562
|
+
case 0:
|
|
1563
|
+
_i = 0, _a = this.compilerPlugins;
|
|
1564
|
+
_b.label = 1;
|
|
1565
|
+
case 1:
|
|
1566
|
+
if (!(_i < _a.length)) return [3 /*break*/, 4];
|
|
1567
|
+
ext = _a[_i];
|
|
1568
|
+
return [4 /*yield*/, this.parseExtensionDir(ext)];
|
|
1569
|
+
case 2:
|
|
1570
|
+
_b.sent();
|
|
1571
|
+
_b.label = 3;
|
|
1572
|
+
case 3:
|
|
1573
|
+
_i++;
|
|
1574
|
+
return [3 /*break*/, 1];
|
|
1575
|
+
case 4: return [2 /*return*/];
|
|
1576
|
+
}
|
|
1577
|
+
});
|
|
1578
|
+
});
|
|
1579
|
+
};
|
|
1485
1580
|
return TypeScriptCompiler;
|
|
1486
1581
|
}());
|
|
1487
1582
|
exports.default = TypeScriptCompiler;
|
|
@@ -1,9 +1,31 @@
|
|
|
1
1
|
import { LoggingConfig } from "../FlinkLogFactory";
|
|
2
|
+
/**
|
|
3
|
+
* Compiler plugin descriptor — declared in flink.config.js, consumed by the TypeScript compiler.
|
|
4
|
+
* Plugin packages export a compilerPlugin() factory that returns this shape.
|
|
5
|
+
*/
|
|
6
|
+
export interface FlinkCompilerPlugin {
|
|
7
|
+
/** npm package name, e.g. "@flink-app/inbound-email-plugin" */
|
|
8
|
+
package: string;
|
|
9
|
+
/** Directory to scan for handler files, e.g. "src/email-handlers" */
|
|
10
|
+
scanDir: string;
|
|
11
|
+
/** Base name for the generated .flink/generatedXxx.ts file, e.g. "generatedEmailHandlers" */
|
|
12
|
+
generatedFile: string;
|
|
13
|
+
/** Name of the singleton array exported by the plugin package, e.g. "autoRegisteredEmailHandlers" */
|
|
14
|
+
registrationVar: string;
|
|
15
|
+
/**
|
|
16
|
+
* Optional callback to confirm a file should be registered.
|
|
17
|
+
* Receives the raw file content and the absolute file path.
|
|
18
|
+
* Return true to include the file, false to skip it.
|
|
19
|
+
*/
|
|
20
|
+
detectBy?: (fileContent: string, filePath: string) => boolean;
|
|
21
|
+
}
|
|
2
22
|
/**
|
|
3
23
|
* Flink configuration file structure
|
|
4
24
|
*/
|
|
5
25
|
export interface FlinkConfig {
|
|
6
26
|
logging?: LoggingConfig;
|
|
27
|
+
/** Compiler plugins that extend auto-discovery to custom directories */
|
|
28
|
+
compilerPlugins?: FlinkCompilerPlugin[];
|
|
7
29
|
}
|
|
8
30
|
/**
|
|
9
31
|
* Load Flink configuration from flink.config.js in current working directory
|
|
@@ -26,4 +48,4 @@ export interface FlinkConfig {
|
|
|
26
48
|
* };
|
|
27
49
|
* ```
|
|
28
50
|
*/
|
|
29
|
-
export declare function loadFlinkConfig(): FlinkConfig | undefined;
|
|
51
|
+
export declare function loadFlinkConfig(cwd?: string): FlinkConfig | undefined;
|
|
@@ -47,10 +47,10 @@ var fs = __importStar(require("fs"));
|
|
|
47
47
|
* };
|
|
48
48
|
* ```
|
|
49
49
|
*/
|
|
50
|
-
function loadFlinkConfig() {
|
|
50
|
+
function loadFlinkConfig(cwd) {
|
|
51
51
|
try {
|
|
52
|
-
// Look for flink.config.js in current working directory
|
|
53
|
-
var configPath = path.join(process.cwd(), "flink.config.js");
|
|
52
|
+
// Look for flink.config.js in current working directory (or provided cwd)
|
|
53
|
+
var configPath = path.join(cwd !== null && cwd !== void 0 ? cwd : process.cwd(), "flink.config.js");
|
|
54
54
|
// Check if file exists
|
|
55
55
|
if (!fs.existsSync(configPath)) {
|
|
56
56
|
return undefined;
|
package/package.json
CHANGED
|
@@ -6,6 +6,7 @@ import { FlinkLogFactory } from "./FlinkLogFactory";
|
|
|
6
6
|
import { writeJsonFile } from "./FsUtils";
|
|
7
7
|
import { TypeScriptSourceParser } from "./schema-extraction";
|
|
8
8
|
import { getCollectionNameForRepo, getHttpMethodFromHandlerName, getRepoInstanceName } from "./utils";
|
|
9
|
+
import { FlinkCompilerPlugin, loadFlinkConfig } from "./utils/loadFlinkConfig";
|
|
9
10
|
|
|
10
11
|
const perfLog = FlinkLogFactory.createLogger("flink.perf");
|
|
11
12
|
const initLog = FlinkLogFactory.createLogger("flink.init");
|
|
@@ -50,6 +51,16 @@ class TypeScriptCompiler {
|
|
|
50
51
|
*/
|
|
51
52
|
private toolIdRegistry: Set<string> = new Set();
|
|
52
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Compiler plugins loaded from flink.config.js
|
|
56
|
+
*/
|
|
57
|
+
private compilerPlugins: FlinkCompilerPlugin[] = [];
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Extension files collected during segmentation, keyed by generatedFile name
|
|
61
|
+
*/
|
|
62
|
+
private extensionFiles: Map<string, SourceFile[]> = new Map();
|
|
63
|
+
|
|
53
64
|
/**
|
|
54
65
|
* Generates a schema $id from a file path and type name using the same algorithm
|
|
55
66
|
* as the schema generator's defineId callback.
|
|
@@ -216,6 +227,19 @@ class TypeScriptCompiler {
|
|
|
216
227
|
const fileCount = this.project.getSourceFiles().length;
|
|
217
228
|
perfLog.debug(`✓ All source files loaded in ${loadTime}ms (${fileCount} files)`);
|
|
218
229
|
|
|
230
|
+
// Load compiler plugins from flink.config.js
|
|
231
|
+
const flinkCfg = loadFlinkConfig(this.cwd);
|
|
232
|
+
this.compilerPlugins = flinkCfg?.compilerPlugins ?? [];
|
|
233
|
+
|
|
234
|
+
if (this.compilerPlugins.length > 0) {
|
|
235
|
+
initLog.info(
|
|
236
|
+
`Compiler plugins loaded (${this.compilerPlugins.length}):`,
|
|
237
|
+
this.compilerPlugins.map((p) => `${p.package} → ${p.scanDir}`).join(", ")
|
|
238
|
+
);
|
|
239
|
+
} else {
|
|
240
|
+
initLog.info("No compiler plugins configured");
|
|
241
|
+
}
|
|
242
|
+
|
|
219
243
|
// Segment files by type for efficient processing
|
|
220
244
|
this.segmentSourceFiles();
|
|
221
245
|
|
|
@@ -457,7 +481,19 @@ class TypeScriptCompiler {
|
|
|
457
481
|
if (filePath.includes("src/jobs/")) {
|
|
458
482
|
this.jobFiles.push(sf);
|
|
459
483
|
jobTime += Date.now() - fileStartTime;
|
|
460
|
-
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Extension dirs from compiler plugins
|
|
488
|
+
for (const ext of this.compilerPlugins) {
|
|
489
|
+
if (filePath.includes(ext.scanDir)) {
|
|
490
|
+
if (!ext.detectBy || ext.detectBy(sf.getFullText(), filePath)) {
|
|
491
|
+
const list = this.extensionFiles.get(ext.generatedFile) ?? [];
|
|
492
|
+
list.push(sf);
|
|
493
|
+
this.extensionFiles.set(ext.generatedFile, list);
|
|
494
|
+
}
|
|
495
|
+
break;
|
|
496
|
+
}
|
|
461
497
|
}
|
|
462
498
|
}
|
|
463
499
|
|
|
@@ -1157,6 +1193,10 @@ autoRegisteredAgents.push(...agents);
|
|
|
1157
1193
|
this.resolveImportedFiles();
|
|
1158
1194
|
}
|
|
1159
1195
|
|
|
1196
|
+
const extensionImports = this.compilerPlugins
|
|
1197
|
+
.map((ext) => `import "./${ext.generatedFile}${this.isEsm ? ".js" : ""}";`)
|
|
1198
|
+
.join("\n");
|
|
1199
|
+
|
|
1160
1200
|
const sf = this.createSourceFile(
|
|
1161
1201
|
["start.ts"],
|
|
1162
1202
|
`// Generated ${new Date()}
|
|
@@ -1165,7 +1205,7 @@ import "./generatedRepos${this.isEsm ? ".js" : ""}";
|
|
|
1165
1205
|
import "./generatedTools${this.isEsm ? ".js" : ""}";
|
|
1166
1206
|
import "./generatedAgents${this.isEsm ? ".js" : ""}";
|
|
1167
1207
|
import "./generatedJobs${this.isEsm ? ".js" : ""}";
|
|
1168
|
-
import "..${appEntryScript.replace(/\.ts/g, "")}${this.isEsm ? ".js" : ""}";
|
|
1208
|
+
${extensionImports ? extensionImports + "\n" : ""}import "..${appEntryScript.replace(/\.ts/g, "")}${this.isEsm ? ".js" : ""}";
|
|
1169
1209
|
export default {}; // Export an empty object to make it a module
|
|
1170
1210
|
`
|
|
1171
1211
|
);
|
|
@@ -1560,6 +1600,57 @@ autoRegisteredJobs.push(...jobs);
|
|
|
1560
1600
|
|
|
1561
1601
|
return generatedFile;
|
|
1562
1602
|
}
|
|
1603
|
+
|
|
1604
|
+
/**
|
|
1605
|
+
* Generates a .flink/generatedXxx.ts file for a single compiler plugin extension.
|
|
1606
|
+
* Mirrors the same namespace-import + spread pattern used by parseJobs.
|
|
1607
|
+
*/
|
|
1608
|
+
async parseExtensionDir(ext: FlinkCompilerPlugin): Promise<SourceFile> {
|
|
1609
|
+
const startTime = Date.now();
|
|
1610
|
+
const files = this.extensionFiles.get(ext.generatedFile) ?? [];
|
|
1611
|
+
|
|
1612
|
+
const generatedFile = this.createSourceFile(
|
|
1613
|
+
[`${ext.generatedFile}.ts`],
|
|
1614
|
+
`// Generated ${new Date()}
|
|
1615
|
+
import { ${ext.registrationVar} } from "${ext.package}";
|
|
1616
|
+
export const items: any[] = [];
|
|
1617
|
+
${ext.registrationVar}.push(...items);
|
|
1618
|
+
`
|
|
1619
|
+
);
|
|
1620
|
+
|
|
1621
|
+
const itemsArr = generatedFile.getVariableDeclarationOrThrow("items").getFirstDescendantByKindOrThrow(SyntaxKind.ArrayLiteralExpression);
|
|
1622
|
+
|
|
1623
|
+
const imports: OptionalKind<ImportDeclarationStructure>[] = [];
|
|
1624
|
+
const itemElements: string[] = [];
|
|
1625
|
+
|
|
1626
|
+
files.forEach((sf, i) => {
|
|
1627
|
+
const namespaceImport = sf.getBaseNameWithoutExtension().replace(/\./g, "_") + "_" + i;
|
|
1628
|
+
imports.push({
|
|
1629
|
+
defaultImport: "* as " + namespaceImport,
|
|
1630
|
+
moduleSpecifier: this.getModuleSpecifier(generatedFile, sf),
|
|
1631
|
+
});
|
|
1632
|
+
itemElements.push(`{...${namespaceImport}, __file: "${this.getRelativePath(sf)}"}`);
|
|
1633
|
+
});
|
|
1634
|
+
|
|
1635
|
+
itemsArr.addElements(itemElements);
|
|
1636
|
+
generatedFile.addImportDeclarations(imports);
|
|
1637
|
+
|
|
1638
|
+
const elapsed = Date.now() - startTime;
|
|
1639
|
+
initLog.info(`✓ Extension dir "${ext.scanDir}" parsed in ${elapsed}ms (${files.length} files)`);
|
|
1640
|
+
|
|
1641
|
+
return generatedFile;
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
/**
|
|
1645
|
+
* Iterates all compilerPlugins from flink.config.js and generates
|
|
1646
|
+
* a .flink/generatedXxx.ts file for each one.
|
|
1647
|
+
* Call this after parseJobs() and before generateStartScript().
|
|
1648
|
+
*/
|
|
1649
|
+
public async parseAllExtensionDirs(): Promise<void> {
|
|
1650
|
+
for (const ext of this.compilerPlugins) {
|
|
1651
|
+
await this.parseExtensionDir(ext);
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1563
1654
|
}
|
|
1564
1655
|
|
|
1565
1656
|
export default TypeScriptCompiler;
|
|
@@ -2,11 +2,34 @@ import * as path from "path";
|
|
|
2
2
|
import * as fs from "fs";
|
|
3
3
|
import { LoggingConfig } from "../FlinkLogFactory";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Compiler plugin descriptor — declared in flink.config.js, consumed by the TypeScript compiler.
|
|
7
|
+
* Plugin packages export a compilerPlugin() factory that returns this shape.
|
|
8
|
+
*/
|
|
9
|
+
export interface FlinkCompilerPlugin {
|
|
10
|
+
/** npm package name, e.g. "@flink-app/inbound-email-plugin" */
|
|
11
|
+
package: string;
|
|
12
|
+
/** Directory to scan for handler files, e.g. "src/email-handlers" */
|
|
13
|
+
scanDir: string;
|
|
14
|
+
/** Base name for the generated .flink/generatedXxx.ts file, e.g. "generatedEmailHandlers" */
|
|
15
|
+
generatedFile: string;
|
|
16
|
+
/** Name of the singleton array exported by the plugin package, e.g. "autoRegisteredEmailHandlers" */
|
|
17
|
+
registrationVar: string;
|
|
18
|
+
/**
|
|
19
|
+
* Optional callback to confirm a file should be registered.
|
|
20
|
+
* Receives the raw file content and the absolute file path.
|
|
21
|
+
* Return true to include the file, false to skip it.
|
|
22
|
+
*/
|
|
23
|
+
detectBy?: (fileContent: string, filePath: string) => boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
5
26
|
/**
|
|
6
27
|
* Flink configuration file structure
|
|
7
28
|
*/
|
|
8
29
|
export interface FlinkConfig {
|
|
9
30
|
logging?: LoggingConfig;
|
|
31
|
+
/** Compiler plugins that extend auto-discovery to custom directories */
|
|
32
|
+
compilerPlugins?: FlinkCompilerPlugin[];
|
|
10
33
|
}
|
|
11
34
|
|
|
12
35
|
/**
|
|
@@ -30,10 +53,10 @@ export interface FlinkConfig {
|
|
|
30
53
|
* };
|
|
31
54
|
* ```
|
|
32
55
|
*/
|
|
33
|
-
export function loadFlinkConfig(): FlinkConfig | undefined {
|
|
56
|
+
export function loadFlinkConfig(cwd?: string): FlinkConfig | undefined {
|
|
34
57
|
try {
|
|
35
|
-
// Look for flink.config.js in current working directory
|
|
36
|
-
const configPath = path.join(process.cwd(), "flink.config.js");
|
|
58
|
+
// Look for flink.config.js in current working directory (or provided cwd)
|
|
59
|
+
const configPath = path.join(cwd ?? process.cwd(), "flink.config.js");
|
|
37
60
|
|
|
38
61
|
// Check if file exists
|
|
39
62
|
if (!fs.existsSync(configPath)) {
|