@crossdelta/platform-sdk 0.19.0 → 0.19.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -5
- package/bin/chunk-634PL24Z.mjs +20 -0
- package/bin/cli.mjs +604 -0
- package/bin/config-CKQHYOF4.mjs +2 -0
- package/bin/docs/generators/code-style.md +79 -0
- package/bin/docs/generators/natural-language.md +117 -0
- package/bin/docs/generators/service.md +129 -60
- package/bin/templates/hono-microservice/Dockerfile.hbs +3 -1
- package/bin/templates/hono-microservice/src/config/env.ts.hbs +3 -0
- package/bin/templates/nest-microservice/Dockerfile.hbs +6 -2
- package/bin/templates/nest-microservice/src/config/env.ts.hbs +17 -0
- package/bin/templates/nest-microservice/src/main.ts.hbs +2 -1
- package/bin/templates/workspace/.github/actions/prepare-build-context/action.yml +58 -6
- package/bin/templates/workspace/.github/workflows/build-and-deploy.yml.hbs +25 -3
- package/bin/templates/workspace/.github/workflows/publish-packages.yml +6 -8
- package/bin/templates/workspace/biome.json.hbs +4 -1
- package/bin/templates/workspace/infra/package.json.hbs +2 -2
- package/bin/templates/workspace/package.json.hbs +1 -0
- package/bin/templates/workspace/packages/contracts/README.md.hbs +5 -5
- package/bin/templates/workspace/packages/contracts/package.json.hbs +15 -6
- package/bin/templates/workspace/packages/contracts/src/index.ts +1 -1
- package/bin/templates/workspace/packages/contracts/tsconfig.json.hbs +6 -1
- package/bin/templates/workspace/turbo.json +8 -11
- package/bin/templates/workspace/turbo.json.hbs +6 -5
- package/dist/facade.d.mts +840 -0
- package/dist/facade.d.ts +840 -0
- package/dist/facade.js +2294 -0
- package/dist/facade.js.map +1 -0
- package/dist/facade.mjs +2221 -0
- package/dist/facade.mjs.map +1 -0
- package/dist/plugin-types-DQOv97Zh.d.mts +180 -0
- package/dist/plugin-types-DQOv97Zh.d.ts +180 -0
- package/dist/plugin-types.d.mts +1 -0
- package/dist/plugin-types.d.ts +1 -0
- package/dist/plugin-types.js +19 -0
- package/dist/plugin-types.js.map +1 -0
- package/dist/plugin-types.mjs +1 -0
- package/dist/plugin-types.mjs.map +1 -0
- package/dist/plugin.d.mts +31 -0
- package/dist/plugin.d.ts +31 -0
- package/dist/plugin.js +105 -0
- package/dist/plugin.js.map +1 -0
- package/dist/plugin.mjs +75 -0
- package/dist/plugin.mjs.map +1 -0
- package/package.json +118 -99
- package/bin/cli.js +0 -540
package/dist/facade.js
ADDED
|
@@ -0,0 +1,2294 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name in all)
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
+
for (let key of __getOwnPropNames(from))
|
|
18
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
19
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
+
mod
|
|
30
|
+
));
|
|
31
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
|
+
|
|
33
|
+
// cli/src/config/constants.ts
|
|
34
|
+
var CLI_VERSION, LOCK_FILES;
|
|
35
|
+
var init_constants = __esm({
|
|
36
|
+
"cli/src/config/constants.ts"() {
|
|
37
|
+
"use strict";
|
|
38
|
+
CLI_VERSION = process.env.npm_package_version || "0.0.0";
|
|
39
|
+
LOCK_FILES = [
|
|
40
|
+
"bun.lock",
|
|
41
|
+
"bun.lockb",
|
|
42
|
+
"package-lock.json",
|
|
43
|
+
"yarn.lock",
|
|
44
|
+
"pnpm-lock.yaml"
|
|
45
|
+
];
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// cli/src/utils/ascii-chart.ts
|
|
50
|
+
var import_chalk;
|
|
51
|
+
var init_ascii_chart = __esm({
|
|
52
|
+
"cli/src/utils/ascii-chart.ts"() {
|
|
53
|
+
"use strict";
|
|
54
|
+
import_chalk = __toESM(require("chalk"));
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// cli/src/core/errors/types.ts
|
|
59
|
+
var init_types = __esm({
|
|
60
|
+
"cli/src/core/errors/types.ts"() {
|
|
61
|
+
"use strict";
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// cli/src/core/errors/cli-error.ts
|
|
66
|
+
var init_cli_error = __esm({
|
|
67
|
+
"cli/src/core/errors/cli-error.ts"() {
|
|
68
|
+
"use strict";
|
|
69
|
+
init_types();
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// cli/src/core/errors/handler.ts
|
|
74
|
+
var import_chalk2;
|
|
75
|
+
var init_handler = __esm({
|
|
76
|
+
"cli/src/core/errors/handler.ts"() {
|
|
77
|
+
"use strict";
|
|
78
|
+
import_chalk2 = __toESM(require("chalk"));
|
|
79
|
+
init_cli_error();
|
|
80
|
+
init_types();
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// cli/src/core/errors/index.ts
|
|
85
|
+
var init_errors = __esm({
|
|
86
|
+
"cli/src/core/errors/index.ts"() {
|
|
87
|
+
"use strict";
|
|
88
|
+
init_cli_error();
|
|
89
|
+
init_handler();
|
|
90
|
+
init_types();
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// cli/src/core/filesystem/watcher.ts
|
|
95
|
+
var import_node_fs, import_node_path, import_chokidar;
|
|
96
|
+
var init_watcher = __esm({
|
|
97
|
+
"cli/src/core/filesystem/watcher.ts"() {
|
|
98
|
+
"use strict";
|
|
99
|
+
import_node_fs = require("fs");
|
|
100
|
+
import_node_path = require("path");
|
|
101
|
+
import_chokidar = __toESM(require("chokidar"));
|
|
102
|
+
init_utils();
|
|
103
|
+
init_config();
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// cli/src/utils/create-workflow-command.ts
|
|
108
|
+
var import_chalk3, import_commander, import_listr2;
|
|
109
|
+
var init_create_workflow_command = __esm({
|
|
110
|
+
"cli/src/utils/create-workflow-command.ts"() {
|
|
111
|
+
"use strict";
|
|
112
|
+
import_chalk3 = __toESM(require("chalk"));
|
|
113
|
+
import_commander = require("commander");
|
|
114
|
+
import_listr2 = require("listr2");
|
|
115
|
+
init_errors();
|
|
116
|
+
init_watcher();
|
|
117
|
+
init_config();
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// cli/src/utils/debounce.ts
|
|
122
|
+
var init_debounce = __esm({
|
|
123
|
+
"cli/src/utils/debounce.ts"() {
|
|
124
|
+
"use strict";
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// cli/src/utils/deep.ts
|
|
129
|
+
var init_deep = __esm({
|
|
130
|
+
"cli/src/utils/deep.ts"() {
|
|
131
|
+
"use strict";
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// cli/src/utils/process.ts
|
|
136
|
+
var import_execa;
|
|
137
|
+
var init_process = __esm({
|
|
138
|
+
"cli/src/utils/process.ts"() {
|
|
139
|
+
"use strict";
|
|
140
|
+
import_execa = require("execa");
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// cli/src/utils/environment.ts
|
|
145
|
+
var init_environment = __esm({
|
|
146
|
+
"cli/src/utils/environment.ts"() {
|
|
147
|
+
"use strict";
|
|
148
|
+
init_process();
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// cli/src/utils/fs.ts
|
|
153
|
+
function getCliRoot() {
|
|
154
|
+
const packageJsonPath = (0, import_package_up.packageUpSync)({ cwd: (0, import_node_path2.dirname)(getModuleFilename()) });
|
|
155
|
+
if (!packageJsonPath) {
|
|
156
|
+
throw new Error("Could not find package.json");
|
|
157
|
+
}
|
|
158
|
+
return (0, import_node_path2.dirname)(packageJsonPath);
|
|
159
|
+
}
|
|
160
|
+
var import_node_fs2, import_node_path2, import_node_url, import_package_up, import_rimraf, import_meta, getModuleFilename;
|
|
161
|
+
var init_fs = __esm({
|
|
162
|
+
"cli/src/utils/fs.ts"() {
|
|
163
|
+
"use strict";
|
|
164
|
+
import_node_fs2 = require("fs");
|
|
165
|
+
import_node_path2 = require("path");
|
|
166
|
+
import_node_url = require("url");
|
|
167
|
+
import_package_up = require("package-up");
|
|
168
|
+
import_rimraf = require("rimraf");
|
|
169
|
+
init_errors();
|
|
170
|
+
init_config();
|
|
171
|
+
import_meta = {};
|
|
172
|
+
getModuleFilename = () => {
|
|
173
|
+
if (typeof import_meta?.url === "string") {
|
|
174
|
+
return (0, import_node_url.fileURLToPath)(import_meta.url);
|
|
175
|
+
}
|
|
176
|
+
return process.cwd();
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// cli/src/utils/logger.ts
|
|
182
|
+
var import_chalk4;
|
|
183
|
+
var init_logger = __esm({
|
|
184
|
+
"cli/src/utils/logger.ts"() {
|
|
185
|
+
"use strict";
|
|
186
|
+
import_chalk4 = __toESM(require("chalk"));
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// cli/src/utils/merge-contexts.ts
|
|
191
|
+
var init_merge_contexts = __esm({
|
|
192
|
+
"cli/src/utils/merge-contexts.ts"() {
|
|
193
|
+
"use strict";
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// cli/src/utils/tasks.ts
|
|
198
|
+
var import_chalk5;
|
|
199
|
+
var init_tasks = __esm({
|
|
200
|
+
"cli/src/utils/tasks.ts"() {
|
|
201
|
+
"use strict";
|
|
202
|
+
import_chalk5 = __toESM(require("chalk"));
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// cli/src/utils/index.ts
|
|
207
|
+
var init_utils = __esm({
|
|
208
|
+
"cli/src/utils/index.ts"() {
|
|
209
|
+
"use strict";
|
|
210
|
+
init_ascii_chart();
|
|
211
|
+
init_create_workflow_command();
|
|
212
|
+
init_debounce();
|
|
213
|
+
init_deep();
|
|
214
|
+
init_environment();
|
|
215
|
+
init_fs();
|
|
216
|
+
init_logger();
|
|
217
|
+
init_merge_contexts();
|
|
218
|
+
init_process();
|
|
219
|
+
init_tasks();
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// cli/src/core/packages/config.ts
|
|
224
|
+
var import_node_fs3, import_node_path3, readJsonSync, readJSONSync, DEFAULT_WORKSPACE_PATHS, parseJsonFile, extractPathValue, getDirName, getParentDir, hasWorkspaceIndicators, isWorkspaceRoot, findWorkspaceRootDir, findWorkspaceRoot, getWorkspacePackageJson, getWorkspacePathsConfig, getContractsConfig, getRootPackageScope, readPackageConfig, _pkgJson, getPkgJson, pkgJson, discoverAvailableServices;
|
|
225
|
+
var init_config = __esm({
|
|
226
|
+
"cli/src/core/packages/config.ts"() {
|
|
227
|
+
"use strict";
|
|
228
|
+
import_node_fs3 = require("fs");
|
|
229
|
+
import_node_path3 = require("path");
|
|
230
|
+
init_constants();
|
|
231
|
+
init_utils();
|
|
232
|
+
readJsonSync = (path) => JSON.parse((0, import_node_fs3.readFileSync)(path, "utf-8"));
|
|
233
|
+
readJSONSync = readJsonSync;
|
|
234
|
+
DEFAULT_WORKSPACE_PATHS = {
|
|
235
|
+
services: "services",
|
|
236
|
+
apps: "apps",
|
|
237
|
+
packages: "packages",
|
|
238
|
+
contracts: "packages/contracts"
|
|
239
|
+
};
|
|
240
|
+
parseJsonFile = (path) => {
|
|
241
|
+
if (!(0, import_node_fs3.existsSync)(path)) return null;
|
|
242
|
+
try {
|
|
243
|
+
return JSON.parse((0, import_node_fs3.readFileSync)(path, "utf-8"));
|
|
244
|
+
} catch {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
extractPathValue = (pathConfig, defaultValue) => {
|
|
249
|
+
if (typeof pathConfig === "string") return pathConfig;
|
|
250
|
+
if (typeof pathConfig === "object" && pathConfig !== null && "path" in pathConfig) {
|
|
251
|
+
return pathConfig.path;
|
|
252
|
+
}
|
|
253
|
+
return defaultValue;
|
|
254
|
+
};
|
|
255
|
+
getDirName = (path) => path.split("/").pop() || "workspace";
|
|
256
|
+
getParentDir = (dir) => {
|
|
257
|
+
const parent = dir.slice(0, dir.lastIndexOf("/"));
|
|
258
|
+
return parent === dir ? null : parent;
|
|
259
|
+
};
|
|
260
|
+
hasWorkspaceIndicators = (pkg, dir) => {
|
|
261
|
+
if (!pkg.workspaces) return false;
|
|
262
|
+
if (pkg.pf) return true;
|
|
263
|
+
if ((0, import_node_fs3.existsSync)((0, import_node_path3.join)(dir, "turbo.json"))) return true;
|
|
264
|
+
const hasLockFile = LOCK_FILES.some((f) => (0, import_node_fs3.existsSync)((0, import_node_path3.join)(dir, f)));
|
|
265
|
+
const hasInfraDir = (0, import_node_fs3.existsSync)((0, import_node_path3.join)(dir, "infra"));
|
|
266
|
+
return hasLockFile && hasInfraDir;
|
|
267
|
+
};
|
|
268
|
+
isWorkspaceRoot = (dir) => {
|
|
269
|
+
const pkg = parseJsonFile((0, import_node_path3.join)(dir, "package.json"));
|
|
270
|
+
return pkg ? hasWorkspaceIndicators(pkg, dir) : false;
|
|
271
|
+
};
|
|
272
|
+
findWorkspaceRootDir = (startDir) => {
|
|
273
|
+
let dir = startDir;
|
|
274
|
+
while (dir) {
|
|
275
|
+
if (isWorkspaceRoot(dir)) return dir;
|
|
276
|
+
dir = getParentDir(dir);
|
|
277
|
+
}
|
|
278
|
+
return null;
|
|
279
|
+
};
|
|
280
|
+
findWorkspaceRoot = () => {
|
|
281
|
+
const root = findWorkspaceRootDir(process.cwd());
|
|
282
|
+
if (!root) {
|
|
283
|
+
throw new Error(
|
|
284
|
+
`
|
|
285
|
+
\x1B[31m\u2716\x1B[0m Not in a workspace directory
|
|
286
|
+
|
|
287
|
+
Current directory: ${process.cwd()}
|
|
288
|
+
|
|
289
|
+
This command must be run from within a workspace created with \x1B[36mpf new workspace\x1B[0m
|
|
290
|
+
|
|
291
|
+
To create a new workspace, run:
|
|
292
|
+
|
|
293
|
+
\x1B[36mpf new workspace my-platform\x1B[0m
|
|
294
|
+
`
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
return root;
|
|
298
|
+
};
|
|
299
|
+
getWorkspacePackageJson = (workspaceRoot) => {
|
|
300
|
+
const root = workspaceRoot ?? findWorkspaceRoot();
|
|
301
|
+
return parseJsonFile((0, import_node_path3.join)(root, "package.json"));
|
|
302
|
+
};
|
|
303
|
+
getWorkspacePathsConfig = (workspaceRoot) => {
|
|
304
|
+
const pkg = getWorkspacePackageJson(workspaceRoot);
|
|
305
|
+
if (!pkg?.pf?.paths) return DEFAULT_WORKSPACE_PATHS;
|
|
306
|
+
const { paths } = pkg.pf;
|
|
307
|
+
return {
|
|
308
|
+
services: extractPathValue(paths.services, DEFAULT_WORKSPACE_PATHS.services),
|
|
309
|
+
apps: extractPathValue(paths.apps, DEFAULT_WORKSPACE_PATHS.apps),
|
|
310
|
+
packages: extractPathValue(paths.packages, DEFAULT_WORKSPACE_PATHS.packages),
|
|
311
|
+
contracts: extractPathValue(paths.contracts, DEFAULT_WORKSPACE_PATHS.contracts)
|
|
312
|
+
};
|
|
313
|
+
};
|
|
314
|
+
getContractsConfig = (workspaceRoot) => {
|
|
315
|
+
const root = workspaceRoot ?? findWorkspaceRoot();
|
|
316
|
+
const paths = getWorkspacePathsConfig(root);
|
|
317
|
+
const contractsPath = (0, import_node_path3.join)(root, paths.contracts);
|
|
318
|
+
const contractsPkg = parseJsonFile((0, import_node_path3.join)(contractsPath, "package.json"));
|
|
319
|
+
const packageName = contractsPkg?.name ?? `${getRootPackageScope()}/contracts`;
|
|
320
|
+
return {
|
|
321
|
+
packagePath: contractsPath,
|
|
322
|
+
eventsPath: (0, import_node_path3.join)(contractsPath, "src", "events"),
|
|
323
|
+
indexPath: (0, import_node_path3.join)(contractsPath, "src", "index.ts"),
|
|
324
|
+
relativePath: paths.contracts,
|
|
325
|
+
packageName
|
|
326
|
+
};
|
|
327
|
+
};
|
|
328
|
+
getRootPackageScope = () => {
|
|
329
|
+
const rootPath = findWorkspaceRoot();
|
|
330
|
+
const pkg = parseJsonFile((0, import_node_path3.join)(rootPath, "package.json"));
|
|
331
|
+
const fallbackScope = `@${getDirName(rootPath)}`;
|
|
332
|
+
if (!pkg?.name) return fallbackScope;
|
|
333
|
+
if (pkg.name.startsWith("@")) {
|
|
334
|
+
return pkg.name.split("/")[0];
|
|
335
|
+
}
|
|
336
|
+
return `@${pkg.name}`;
|
|
337
|
+
};
|
|
338
|
+
readPackageConfig = (relativePath) => {
|
|
339
|
+
const root = getCliRoot();
|
|
340
|
+
return readJSONSync((0, import_node_path3.resolve)(root, relativePath));
|
|
341
|
+
};
|
|
342
|
+
_pkgJson = null;
|
|
343
|
+
getPkgJson = () => {
|
|
344
|
+
if (!_pkgJson) {
|
|
345
|
+
_pkgJson = readPackageConfig("package.json");
|
|
346
|
+
}
|
|
347
|
+
return _pkgJson;
|
|
348
|
+
};
|
|
349
|
+
pkgJson = new Proxy({}, {
|
|
350
|
+
get: (_, prop) => getPkgJson()[prop]
|
|
351
|
+
});
|
|
352
|
+
discoverAvailableServices = (workspaceRoot) => {
|
|
353
|
+
const root = workspaceRoot ?? findWorkspaceRoot();
|
|
354
|
+
const paths = getWorkspacePathsConfig(root);
|
|
355
|
+
const servicesDir = (0, import_node_path3.join)(root, paths.services);
|
|
356
|
+
if (!(0, import_node_fs3.existsSync)(servicesDir)) return [];
|
|
357
|
+
try {
|
|
358
|
+
const entries = (0, import_node_fs3.readdirSync)(servicesDir, { withFileTypes: true });
|
|
359
|
+
return entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => `${paths.services}/${entry.name}`).sort();
|
|
360
|
+
} catch {
|
|
361
|
+
return [];
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// cli/src/core/facade/index.ts
|
|
368
|
+
var facade_exports = {};
|
|
369
|
+
__export(facade_exports, {
|
|
370
|
+
SERVICE_LAYOUT_POLICY_VERSION: () => SERVICE_LAYOUT_POLICY_VERSION,
|
|
371
|
+
aggregateChanges: () => aggregateChanges,
|
|
372
|
+
applyEffects: () => applyEffects,
|
|
373
|
+
collectExecutableToolSpecs: () => collectExecutableToolSpecs,
|
|
374
|
+
collectToolSpecs: () => collectToolSpecs,
|
|
375
|
+
createContext: () => createContext,
|
|
376
|
+
createContextFromWorkspace: () => createContextFromWorkspace,
|
|
377
|
+
createErrResult: () => err,
|
|
378
|
+
createOkResult: () => ok,
|
|
379
|
+
deriveEventNames: () => import_cloudevents5.deriveEventNames,
|
|
380
|
+
findWorkspaceRoot: () => findWorkspaceRoot,
|
|
381
|
+
formatChangeDetails: () => formatChangeDetails,
|
|
382
|
+
formatChangeSummary: () => formatChangeSummary,
|
|
383
|
+
getFrameworkPolicy: () => getFrameworkPolicy,
|
|
384
|
+
getGeneratorDocsDir: () => getGeneratorDocsDir,
|
|
385
|
+
getRegisteredProviders: () => getRegisteredProviders,
|
|
386
|
+
isElicitInputEffect: () => isElicitInputEffect,
|
|
387
|
+
listGeneratorDocs: () => listGeneratorDocs,
|
|
388
|
+
loadCapabilitiesConfig: () => loadCapabilitiesConfig,
|
|
389
|
+
loadCapabilitiesConfigFromWorkspace: () => loadCapabilitiesConfigFromWorkspace,
|
|
390
|
+
loadPlugins: () => loadPlugins,
|
|
391
|
+
lookupProvider: () => lookupProvider,
|
|
392
|
+
lowerSpecToCapabilities: () => lowerSpecToCapabilities,
|
|
393
|
+
parseServicePrompt: () => parseServicePrompt,
|
|
394
|
+
planServiceGeneration: () => planServiceGeneration,
|
|
395
|
+
resolveProvider: () => resolveProvider,
|
|
396
|
+
runFlow: () => runFlow,
|
|
397
|
+
serviceLayoutPolicy: () => serviceLayoutPolicy,
|
|
398
|
+
validateGeneratedFiles: () => validateGeneratedFiles
|
|
399
|
+
});
|
|
400
|
+
module.exports = __toCommonJS(facade_exports);
|
|
401
|
+
var import_promises = require("fs/promises");
|
|
402
|
+
var import_node_path14 = require("path");
|
|
403
|
+
var import_node_url3 = require("url");
|
|
404
|
+
var import_flowcore = require("@crossdelta/flowcore");
|
|
405
|
+
|
|
406
|
+
// cli/src/core/plugins/loader.ts
|
|
407
|
+
var defaultLogger = {
|
|
408
|
+
debug: () => {
|
|
409
|
+
},
|
|
410
|
+
info: console.log,
|
|
411
|
+
warn: console.warn,
|
|
412
|
+
error: console.error
|
|
413
|
+
};
|
|
414
|
+
var isObject = (val) => val !== null && typeof val === "object";
|
|
415
|
+
var isNonEmptyString = (val) => typeof val === "string" && val.length > 0;
|
|
416
|
+
var isString = (val) => typeof val === "string";
|
|
417
|
+
var isFunction = (val) => typeof val === "function";
|
|
418
|
+
var validateCommand = (cmd, index) => {
|
|
419
|
+
if (!isObject(cmd)) return [`commands[${index}]: must be an object`];
|
|
420
|
+
return [
|
|
421
|
+
!isNonEmptyString(cmd.name) && `commands[${index}]: missing or invalid 'name'`,
|
|
422
|
+
!isString(cmd.description) && `commands[${index}]: missing or invalid 'description'`,
|
|
423
|
+
!Array.isArray(cmd.args) && `commands[${index}]: missing 'args' array`,
|
|
424
|
+
!Array.isArray(cmd.options) && `commands[${index}]: missing 'options' array`,
|
|
425
|
+
!isFunction(cmd.run) && `commands[${index}]: missing 'run' function`
|
|
426
|
+
].filter((e) => e !== false);
|
|
427
|
+
};
|
|
428
|
+
var validatePluginStrict = (obj) => {
|
|
429
|
+
if (!isObject(obj)) return ["Plugin must be an object"];
|
|
430
|
+
const baseErrors = [
|
|
431
|
+
!isNonEmptyString(obj.name) && "Missing or invalid 'name'",
|
|
432
|
+
!isString(obj.version) && "Missing or invalid 'version'",
|
|
433
|
+
!Array.isArray(obj.commands) && "Missing 'commands' array",
|
|
434
|
+
!Array.isArray(obj.flows) && "Missing 'flows' array",
|
|
435
|
+
!isFunction(obj.setup) && "Missing 'setup' function"
|
|
436
|
+
].filter((e) => e !== false);
|
|
437
|
+
const commandErrors = Array.isArray(obj.commands) ? obj.commands.flatMap((cmd, i) => validateCommand(cmd, i)) : [];
|
|
438
|
+
return [...baseErrors, ...commandErrors];
|
|
439
|
+
};
|
|
440
|
+
var createPluginContext = (workspace, logger = defaultLogger) => ({
|
|
441
|
+
workspace,
|
|
442
|
+
logger
|
|
443
|
+
});
|
|
444
|
+
var loadPluginFromModule = async (moduleName, context) => {
|
|
445
|
+
const { logger } = context;
|
|
446
|
+
logger.debug(`Loading plugin from module: ${moduleName}`);
|
|
447
|
+
const mod = await import(moduleName);
|
|
448
|
+
const createPlugin = mod.createPfPlugin || mod.default?.createPfPlugin;
|
|
449
|
+
if (!isFunction(createPlugin)) {
|
|
450
|
+
throw new Error(`Module ${moduleName} does not export createPfPlugin`);
|
|
451
|
+
}
|
|
452
|
+
const plugin = createPlugin({
|
|
453
|
+
contractsPath: context.workspace.contracts.path,
|
|
454
|
+
contractsPackage: context.workspace.contracts.packageName,
|
|
455
|
+
availableServices: context.workspace.availableServices
|
|
456
|
+
});
|
|
457
|
+
const errors = validatePluginStrict(plugin);
|
|
458
|
+
if (errors.length > 0) {
|
|
459
|
+
throw new Error(`Plugin from ${moduleName} does not conform to PfPlugin interface:
|
|
460
|
+
- ${errors.join("\n - ")}`);
|
|
461
|
+
}
|
|
462
|
+
const validatedPlugin = plugin;
|
|
463
|
+
logger.debug(`Loaded plugin: ${validatedPlugin.name} v${validatedPlugin.version}`);
|
|
464
|
+
await validatedPlugin.setup(context);
|
|
465
|
+
return {
|
|
466
|
+
plugin: validatedPlugin,
|
|
467
|
+
source: moduleName
|
|
468
|
+
};
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
// cli/src/core/facade/index.ts
|
|
472
|
+
var import_cloudevents5 = require("@crossdelta/cloudevents");
|
|
473
|
+
|
|
474
|
+
// cli/src/core/capabilities/config.ts
|
|
475
|
+
var import_zod = require("zod");
|
|
476
|
+
var ProviderMetaSchema = import_zod.z.object({
|
|
477
|
+
dependencies: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).default({}),
|
|
478
|
+
envVars: import_zod.z.array(
|
|
479
|
+
import_zod.z.object({
|
|
480
|
+
key: import_zod.z.string(),
|
|
481
|
+
description: import_zod.z.string(),
|
|
482
|
+
required: import_zod.z.boolean().default(true)
|
|
483
|
+
})
|
|
484
|
+
).default([]),
|
|
485
|
+
/** Custom adapter filename (e.g., 'push-notification.adapter.ts') */
|
|
486
|
+
adapterFile: import_zod.z.string().optional()
|
|
487
|
+
});
|
|
488
|
+
var NotifierDefaultsSchema = import_zod.z.object({
|
|
489
|
+
push: import_zod.z.string().optional(),
|
|
490
|
+
email: import_zod.z.string().optional(),
|
|
491
|
+
slack: import_zod.z.string().optional(),
|
|
492
|
+
sms: import_zod.z.string().optional()
|
|
493
|
+
}).optional();
|
|
494
|
+
var NotifierConfigSchema = import_zod.z.object({
|
|
495
|
+
defaults: NotifierDefaultsSchema,
|
|
496
|
+
providers: import_zod.z.object({
|
|
497
|
+
push: import_zod.z.record(import_zod.z.string(), ProviderMetaSchema).optional(),
|
|
498
|
+
email: import_zod.z.record(import_zod.z.string(), ProviderMetaSchema).optional(),
|
|
499
|
+
slack: import_zod.z.record(import_zod.z.string(), ProviderMetaSchema).optional(),
|
|
500
|
+
sms: import_zod.z.record(import_zod.z.string(), ProviderMetaSchema).optional()
|
|
501
|
+
}).optional()
|
|
502
|
+
});
|
|
503
|
+
var CapabilitiesConfigSchema = import_zod.z.object({
|
|
504
|
+
notifier: NotifierConfigSchema.optional()
|
|
505
|
+
});
|
|
506
|
+
var getRegisteredProviders = (workspaceConfig) => ({
|
|
507
|
+
push: Object.keys(workspaceConfig?.notifier?.providers?.push ?? {}),
|
|
508
|
+
email: Object.keys(workspaceConfig?.notifier?.providers?.email ?? {}),
|
|
509
|
+
slack: Object.keys(workspaceConfig?.notifier?.providers?.slack ?? {}),
|
|
510
|
+
sms: Object.keys(workspaceConfig?.notifier?.providers?.sms ?? {})
|
|
511
|
+
});
|
|
512
|
+
var getDefaultProvider = (channel, workspaceConfig) => workspaceConfig?.notifier?.defaults?.[channel];
|
|
513
|
+
var lookupProvider = (channel, provider, workspaceConfig) => {
|
|
514
|
+
const meta = workspaceConfig?.notifier?.providers?.[channel]?.[provider];
|
|
515
|
+
if (meta) {
|
|
516
|
+
return { found: true, provider, meta, source: "workspace" };
|
|
517
|
+
}
|
|
518
|
+
const availableProviders = Object.keys(workspaceConfig?.notifier?.providers?.[channel] ?? {});
|
|
519
|
+
const configSnippet = generateConfigSnippet(channel, provider);
|
|
520
|
+
return {
|
|
521
|
+
found: false,
|
|
522
|
+
channel,
|
|
523
|
+
provider,
|
|
524
|
+
availableProviders,
|
|
525
|
+
configSnippet
|
|
526
|
+
};
|
|
527
|
+
};
|
|
528
|
+
var normalizeProviderName = (name) => name.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
529
|
+
var extractProviderFromPrompt = (prompt) => {
|
|
530
|
+
const patterns = [
|
|
531
|
+
/(?:via|using|with|über|per)\s+([\w\s-]+?)(?:\s+(?:for|to|and|,|\.|$))/i,
|
|
532
|
+
/(?:via|using|with|über|per)\s+([\w-]+)/i
|
|
533
|
+
];
|
|
534
|
+
for (const pattern of patterns) {
|
|
535
|
+
const match = prompt.match(pattern);
|
|
536
|
+
if (match?.[1]) {
|
|
537
|
+
return match[1].trim();
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return void 0;
|
|
541
|
+
};
|
|
542
|
+
var generateConfigSnippet = (channel, provider) => {
|
|
543
|
+
const snippet = {
|
|
544
|
+
pf: {
|
|
545
|
+
capabilities: {
|
|
546
|
+
notifier: {
|
|
547
|
+
providers: {
|
|
548
|
+
[channel]: {
|
|
549
|
+
[provider]: {
|
|
550
|
+
dependencies: { "your-package": "^1.0.0" },
|
|
551
|
+
envVars: [{ key: "YOUR_API_KEY", description: "API key for provider", required: true }]
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
return JSON.stringify(snippet, null, 2);
|
|
560
|
+
};
|
|
561
|
+
var resolveProvider = (channel, explicitProvider, workspaceConfig) => {
|
|
562
|
+
if (explicitProvider) {
|
|
563
|
+
return { resolved: true, provider: explicitProvider, source: "explicit" };
|
|
564
|
+
}
|
|
565
|
+
const defaultProvider = getDefaultProvider(channel, workspaceConfig);
|
|
566
|
+
if (defaultProvider) {
|
|
567
|
+
return { resolved: true, provider: defaultProvider, source: "default" };
|
|
568
|
+
}
|
|
569
|
+
const availableProviders = Object.keys(workspaceConfig?.notifier?.providers?.[channel] ?? {});
|
|
570
|
+
return {
|
|
571
|
+
resolved: false,
|
|
572
|
+
needsElicitation: true,
|
|
573
|
+
channel,
|
|
574
|
+
availableProviders
|
|
575
|
+
};
|
|
576
|
+
};
|
|
577
|
+
var loadCapabilitiesConfig = (workspacePkg) => {
|
|
578
|
+
if (!workspacePkg) return void 0;
|
|
579
|
+
const pfConfig = workspacePkg.pf;
|
|
580
|
+
if (!pfConfig?.capabilities) return void 0;
|
|
581
|
+
const result = CapabilitiesConfigSchema.safeParse(pfConfig.capabilities);
|
|
582
|
+
return result.success ? result.data : void 0;
|
|
583
|
+
};
|
|
584
|
+
var loadCapabilitiesConfigFromWorkspace = (getWorkspacePackageJson2) => {
|
|
585
|
+
const pkg = getWorkspacePackageJson2();
|
|
586
|
+
return loadCapabilitiesConfig(pkg);
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
// cli/src/core/capabilities/lowering.ts
|
|
590
|
+
var lowerSpecToCapabilities = (spec, serviceDir) => {
|
|
591
|
+
const metadata = {
|
|
592
|
+
files: [],
|
|
593
|
+
dependencies: {},
|
|
594
|
+
envVars: [],
|
|
595
|
+
postCommands: []
|
|
596
|
+
};
|
|
597
|
+
for (const trigger of spec.triggers) {
|
|
598
|
+
if (trigger.type === "event") {
|
|
599
|
+
metadata.postCommands.push(`pf cloudevents add ${trigger.eventType} --service ${serviceDir} --fields "id:string"`);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
for (const action of spec.actions) {
|
|
603
|
+
if (action.type === "notify" && action.provider) {
|
|
604
|
+
metadata.files.push({
|
|
605
|
+
path: `${serviceDir}/src/adapters/${action.channel}.adapter.ts`,
|
|
606
|
+
intent: `${action.channel} adapter using ${action.provider}`,
|
|
607
|
+
layer: "adapter",
|
|
608
|
+
kind: "notifier"
|
|
609
|
+
});
|
|
610
|
+
metadata.files.push({
|
|
611
|
+
path: `${serviceDir}/src/use-cases/send-${action.channel}.use-case.ts`,
|
|
612
|
+
intent: `send ${action.channel} notification`,
|
|
613
|
+
layer: "useCase"
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
return metadata;
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
// cli/src/core/capabilities/types.ts
|
|
621
|
+
var import_zod2 = require("zod");
|
|
622
|
+
var EventTriggerSchema = import_zod2.z.object({
|
|
623
|
+
type: import_zod2.z.literal("event"),
|
|
624
|
+
eventType: import_zod2.z.string(),
|
|
625
|
+
stream: import_zod2.z.string().optional()
|
|
626
|
+
});
|
|
627
|
+
var HttpTriggerSchema = import_zod2.z.object({
|
|
628
|
+
type: import_zod2.z.literal("http"),
|
|
629
|
+
method: import_zod2.z.enum(["GET", "POST", "PUT", "DELETE", "PATCH"]).optional(),
|
|
630
|
+
path: import_zod2.z.string().optional()
|
|
631
|
+
});
|
|
632
|
+
var TriggerSchema = import_zod2.z.discriminatedUnion("type", [EventTriggerSchema, HttpTriggerSchema]);
|
|
633
|
+
var EmitEventActionSchema = import_zod2.z.object({
|
|
634
|
+
type: import_zod2.z.literal("emit.event"),
|
|
635
|
+
eventType: import_zod2.z.string()
|
|
636
|
+
});
|
|
637
|
+
var NotifyActionSchema = import_zod2.z.object({
|
|
638
|
+
type: import_zod2.z.literal("notify"),
|
|
639
|
+
channel: import_zod2.z.enum(["push", "email", "slack", "sms"]),
|
|
640
|
+
/** Must be explicitly provided by user or elicited - NO defaults */
|
|
641
|
+
provider: import_zod2.z.string().optional()
|
|
642
|
+
});
|
|
643
|
+
var CallHttpActionSchema = import_zod2.z.object({
|
|
644
|
+
type: import_zod2.z.literal("call.http"),
|
|
645
|
+
name: import_zod2.z.string().optional(),
|
|
646
|
+
baseUrl: import_zod2.z.string().optional()
|
|
647
|
+
});
|
|
648
|
+
var PersistActionSchema = import_zod2.z.object({
|
|
649
|
+
type: import_zod2.z.literal("persist"),
|
|
650
|
+
store: import_zod2.z.enum(["db", "kv", "cache"]),
|
|
651
|
+
entity: import_zod2.z.string().optional()
|
|
652
|
+
});
|
|
653
|
+
var ActionSchema = import_zod2.z.discriminatedUnion("type", [
|
|
654
|
+
EmitEventActionSchema,
|
|
655
|
+
NotifyActionSchema,
|
|
656
|
+
CallHttpActionSchema,
|
|
657
|
+
PersistActionSchema
|
|
658
|
+
]);
|
|
659
|
+
var ServiceSpecSchema = import_zod2.z.object({
|
|
660
|
+
serviceName: import_zod2.z.string().regex(/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/),
|
|
661
|
+
framework: import_zod2.z.enum(["hono", "nest"]).default("hono"),
|
|
662
|
+
triggers: import_zod2.z.array(TriggerSchema).min(1),
|
|
663
|
+
actions: import_zod2.z.array(ActionSchema).default([])
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
// cli/src/core/capabilities/parser.ts
|
|
667
|
+
var PROVIDER_KEYWORDS = {
|
|
668
|
+
"pusher beams": { channel: "push", provider: "pusher-beams" },
|
|
669
|
+
"pusher-beams": { channel: "push", provider: "pusher-beams" },
|
|
670
|
+
pusherbeams: { channel: "push", provider: "pusher-beams" },
|
|
671
|
+
firebase: { channel: "push", provider: "firebase" },
|
|
672
|
+
onesignal: { channel: "push", provider: "onesignal" },
|
|
673
|
+
expo: { channel: "push", provider: "expo" },
|
|
674
|
+
resend: { channel: "email", provider: "resend" },
|
|
675
|
+
sendgrid: { channel: "email", provider: "sendgrid" },
|
|
676
|
+
ses: { channel: "email", provider: "ses" },
|
|
677
|
+
postmark: { channel: "email", provider: "postmark" },
|
|
678
|
+
"slack-webhook": { channel: "slack", provider: "slack-webhook" },
|
|
679
|
+
"slack webhook": { channel: "slack", provider: "slack-webhook" },
|
|
680
|
+
"slack-api": { channel: "slack", provider: "slack-api" },
|
|
681
|
+
twilio: { channel: "sms", provider: "twilio" },
|
|
682
|
+
vonage: { channel: "sms", provider: "vonage" }
|
|
683
|
+
};
|
|
684
|
+
var detectProvider = (prompt, channel, workspaceConfig) => {
|
|
685
|
+
const lowerPrompt = prompt.toLowerCase();
|
|
686
|
+
for (const [keyword, info] of Object.entries(PROVIDER_KEYWORDS)) {
|
|
687
|
+
if (info.channel === channel && lowerPrompt.includes(keyword)) {
|
|
688
|
+
return info.provider;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
const registeredProviders = getRegisteredProviders(workspaceConfig);
|
|
692
|
+
const channelProviders = registeredProviders[channel] ?? [];
|
|
693
|
+
for (const provider of channelProviders) {
|
|
694
|
+
const patterns = [provider, provider.replace("-", " "), provider.replace("-", "")];
|
|
695
|
+
if (patterns.some((p) => lowerPrompt.includes(p.toLowerCase()))) {
|
|
696
|
+
return provider;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
const rawProvider = extractProviderFromPrompt(prompt);
|
|
700
|
+
if (rawProvider) {
|
|
701
|
+
return normalizeProviderName(rawProvider);
|
|
702
|
+
}
|
|
703
|
+
return void 0;
|
|
704
|
+
};
|
|
705
|
+
var inferServiceName = (prompt) => {
|
|
706
|
+
const patterns = [
|
|
707
|
+
// "create a notifications service" / "build order-processor service"
|
|
708
|
+
/(?:create|build|make|erstelle)\s+(?:a\s+)?(?:an?\s+)?([a-z][a-z0-9-]*)\s+service/i,
|
|
709
|
+
// "notifications service that listens" / "order-processor service which handles"
|
|
710
|
+
/([a-z][a-z0-9-]*)\s+service\s+(?:that|which|to|consumes|listens|handles)/i,
|
|
711
|
+
// "service called notifications" / "service named order-processor"
|
|
712
|
+
/service\s+(?:called|named)\s+([a-z][a-z0-9-]*)/i,
|
|
713
|
+
// "a notifications service" / "an order-processor service" (at start)
|
|
714
|
+
/^(?:a|an)\s+([a-z][a-z0-9-]*)\s+service/i,
|
|
715
|
+
// Simple: "notifications service" at start of prompt
|
|
716
|
+
/^([a-z][a-z0-9-]*)\s+service\b/i
|
|
717
|
+
];
|
|
718
|
+
for (const pattern of patterns) {
|
|
719
|
+
const match = prompt.match(pattern);
|
|
720
|
+
if (match?.[1]) {
|
|
721
|
+
return match[1].toLowerCase().replace(/\s+/g, "-");
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
return void 0;
|
|
725
|
+
};
|
|
726
|
+
var inferTriggers = (prompt) => {
|
|
727
|
+
const triggers = [];
|
|
728
|
+
const eventPatterns = [
|
|
729
|
+
/(?:listens?\s+(?:to|for)|consumes?|handles?|reacts?\s+to|when)\s+([a-z][a-z0-9.]*)\s+events?/gi,
|
|
730
|
+
/on\s+([a-z][a-z0-9.]*)\s+event/gi
|
|
731
|
+
];
|
|
732
|
+
for (const pattern of eventPatterns) {
|
|
733
|
+
for (const match of prompt.matchAll(pattern)) {
|
|
734
|
+
const eventType = match[1].toLowerCase();
|
|
735
|
+
if (!triggers.some((t) => t.type === "event" && t.eventType === eventType)) {
|
|
736
|
+
triggers.push({ type: "event", eventType });
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
const directEventPattern = /\b([a-z][a-z0-9]*\.[a-z][a-z0-9]*(?:\.[a-z][a-z0-9]*)?)\b/gi;
|
|
741
|
+
for (const match of prompt.matchAll(directEventPattern)) {
|
|
742
|
+
const eventType = match[1].toLowerCase();
|
|
743
|
+
if (!eventType.includes(".json") && !eventType.includes(".ts") && !eventType.includes(".js")) {
|
|
744
|
+
if (!triggers.some((t) => t.type === "event" && t.eventType === eventType)) {
|
|
745
|
+
triggers.push({ type: "event", eventType });
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
return triggers;
|
|
750
|
+
};
|
|
751
|
+
var inferActions = (prompt, workspaceConfig) => {
|
|
752
|
+
const actions = [];
|
|
753
|
+
if (/push\s+notification|send\s+push|notify.*push/i.test(prompt)) {
|
|
754
|
+
const provider = detectProvider(prompt, "push", workspaceConfig);
|
|
755
|
+
actions.push({ type: "notify", channel: "push", provider });
|
|
756
|
+
}
|
|
757
|
+
if (/send\s+email|email\s+notification/i.test(prompt)) {
|
|
758
|
+
const provider = detectProvider(prompt, "email", workspaceConfig);
|
|
759
|
+
actions.push({ type: "notify", channel: "email", provider });
|
|
760
|
+
}
|
|
761
|
+
if (/slack\s+(?:message|notification)/i.test(prompt)) {
|
|
762
|
+
const provider = detectProvider(prompt, "slack", workspaceConfig);
|
|
763
|
+
actions.push({ type: "notify", channel: "slack", provider });
|
|
764
|
+
}
|
|
765
|
+
if (/sms|text\s+message/i.test(prompt)) {
|
|
766
|
+
const provider = detectProvider(prompt, "sms", workspaceConfig);
|
|
767
|
+
actions.push({ type: "notify", channel: "sms", provider });
|
|
768
|
+
}
|
|
769
|
+
const emitMatch = prompt.match(/emit(?:s)?\s+([a-z][a-z0-9.]*)\s+event/i);
|
|
770
|
+
if (emitMatch) {
|
|
771
|
+
actions.push({ type: "emit.event", eventType: emitMatch[1].toLowerCase() });
|
|
772
|
+
}
|
|
773
|
+
return actions;
|
|
774
|
+
};
|
|
775
|
+
var findMissingFields = (raw, workspaceConfig) => {
|
|
776
|
+
const missing = [];
|
|
777
|
+
if (!raw.serviceName) {
|
|
778
|
+
missing.push({
|
|
779
|
+
path: "serviceName",
|
|
780
|
+
prompt: 'What should the service be named? (kebab-case, e.g., "order-notifications")',
|
|
781
|
+
type: "string",
|
|
782
|
+
required: true
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
if (!raw.triggers || raw.triggers.length === 0) {
|
|
786
|
+
missing.push({
|
|
787
|
+
path: "triggers[0].eventType",
|
|
788
|
+
prompt: 'What event should trigger this service? (e.g., "order.created")',
|
|
789
|
+
type: "string",
|
|
790
|
+
required: true
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
raw.actions?.forEach((action, index) => {
|
|
794
|
+
if (action.type === "notify" && !action.provider) {
|
|
795
|
+
const channel = action.channel;
|
|
796
|
+
const defaultProvider = getDefaultProvider(channel, workspaceConfig);
|
|
797
|
+
if (!defaultProvider) {
|
|
798
|
+
const registeredProviders = getRegisteredProviders(workspaceConfig);
|
|
799
|
+
const availableProviders = registeredProviders[channel] ?? [];
|
|
800
|
+
missing.push({
|
|
801
|
+
path: `actions[${index}].provider`,
|
|
802
|
+
prompt: availableProviders.length > 0 ? `Which provider for ${channel} notifications?` : `Which provider for ${channel} notifications? (No providers registered - add to package.json pf.capabilities)`,
|
|
803
|
+
type: "enum",
|
|
804
|
+
options: availableProviders,
|
|
805
|
+
required: true
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
if (action.type === "call.http" && !action.baseUrl) {
|
|
810
|
+
missing.push({
|
|
811
|
+
path: `actions[${index}].baseUrl`,
|
|
812
|
+
prompt: "What is the base URL for the HTTP call?",
|
|
813
|
+
type: "string",
|
|
814
|
+
required: true
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
return missing;
|
|
819
|
+
};
|
|
820
|
+
var applyWorkspaceDefaults = (raw, workspaceConfig) => {
|
|
821
|
+
if (!workspaceConfig || !raw.actions) return raw;
|
|
822
|
+
const updatedActions = raw.actions.map((action) => {
|
|
823
|
+
if (action.type === "notify" && !action.provider) {
|
|
824
|
+
const channel = action.channel;
|
|
825
|
+
const defaultProvider = getDefaultProvider(channel, workspaceConfig);
|
|
826
|
+
if (defaultProvider) {
|
|
827
|
+
return { ...action, provider: defaultProvider };
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
return action;
|
|
831
|
+
});
|
|
832
|
+
return { ...raw, actions: updatedActions };
|
|
833
|
+
};
|
|
834
|
+
var validateSpec = (raw, workspaceConfig) => {
|
|
835
|
+
const withDefaults = applyWorkspaceDefaults(raw, workspaceConfig);
|
|
836
|
+
const missingFields = findMissingFields(withDefaults, workspaceConfig);
|
|
837
|
+
if (missingFields.length > 0) {
|
|
838
|
+
return {
|
|
839
|
+
complete: false,
|
|
840
|
+
fields: missingFields,
|
|
841
|
+
partialSpec: withDefaults
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
const result = ServiceSpecSchema.safeParse(withDefaults);
|
|
845
|
+
if (!result.success) {
|
|
846
|
+
const fields = result.error.issues.map((issue) => ({
|
|
847
|
+
path: issue.path.join("."),
|
|
848
|
+
prompt: issue.message,
|
|
849
|
+
type: "string",
|
|
850
|
+
required: true
|
|
851
|
+
}));
|
|
852
|
+
return { complete: false, fields, partialSpec: withDefaults };
|
|
853
|
+
}
|
|
854
|
+
return { complete: true, spec: result.data };
|
|
855
|
+
};
|
|
856
|
+
var parseServicePrompt = async (prompt, options) => {
|
|
857
|
+
const opts = options && "extract" in options ? { interpreter: options } : options ?? {};
|
|
858
|
+
const { interpreter, workspaceConfig } = opts;
|
|
859
|
+
let raw;
|
|
860
|
+
if (interpreter) {
|
|
861
|
+
raw = await interpreter.extract(prompt);
|
|
862
|
+
} else {
|
|
863
|
+
raw = {
|
|
864
|
+
serviceName: inferServiceName(prompt),
|
|
865
|
+
framework: /nest(?:js)?/i.test(prompt) ? "nest" : "hono",
|
|
866
|
+
triggers: inferTriggers(prompt),
|
|
867
|
+
actions: inferActions(prompt, workspaceConfig)
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
return validateSpec(raw, workspaceConfig);
|
|
871
|
+
};
|
|
872
|
+
|
|
873
|
+
// cli/src/core/effects/changes.ts
|
|
874
|
+
var groupBy = (items, key) => {
|
|
875
|
+
return items.reduce((acc, item) => {
|
|
876
|
+
const group = String(item[key]);
|
|
877
|
+
const existing = acc[group];
|
|
878
|
+
acc[group] = existing ? [...existing, item] : [item];
|
|
879
|
+
return acc;
|
|
880
|
+
}, {});
|
|
881
|
+
};
|
|
882
|
+
var formatChange = (change, index) => {
|
|
883
|
+
const lines = [
|
|
884
|
+
`${index + 1}. ${change.type.toUpperCase()}: ${change.path}`,
|
|
885
|
+
` ${change.description}`
|
|
886
|
+
];
|
|
887
|
+
if (change.preview) {
|
|
888
|
+
const previewLines = change.preview.split("\n").map((line) => ` ${line}`);
|
|
889
|
+
lines.push(" Preview:", " ```", ...previewLines, " ```");
|
|
890
|
+
}
|
|
891
|
+
lines.push("");
|
|
892
|
+
return lines;
|
|
893
|
+
};
|
|
894
|
+
var formatFileList = (icon, label, items) => {
|
|
895
|
+
if (!items?.length) return [];
|
|
896
|
+
return [
|
|
897
|
+
`${icon} ${label}:`,
|
|
898
|
+
...items.map((c) => ` \u2022 ${c.path} - ${c.description}`),
|
|
899
|
+
""
|
|
900
|
+
];
|
|
901
|
+
};
|
|
902
|
+
var aggregateChanges = (results) => {
|
|
903
|
+
return results.filter((r) => r.success && r.changes).flatMap((r) => r.changes ?? []);
|
|
904
|
+
};
|
|
905
|
+
var formatChangeSummary = (changes) => {
|
|
906
|
+
if (changes.length === 0) {
|
|
907
|
+
return "\u2713 No changes";
|
|
908
|
+
}
|
|
909
|
+
const groups = groupBy(changes, "type");
|
|
910
|
+
const summary = [
|
|
911
|
+
"\u{1F4DD} Changes Summary:",
|
|
912
|
+
`${groups.create?.length || 0} files to create`,
|
|
913
|
+
`${groups.update?.length || 0} files to update`,
|
|
914
|
+
`${groups.delete?.length || 0} files to delete`,
|
|
915
|
+
"",
|
|
916
|
+
...formatFileList("\u{1F4C4}", "Create", groups.create),
|
|
917
|
+
...formatFileList("\u270F\uFE0F ", "Update", groups.update),
|
|
918
|
+
...formatFileList("\u{1F5D1}\uFE0F ", "Delete", groups.delete)
|
|
919
|
+
];
|
|
920
|
+
return summary.join("\n");
|
|
921
|
+
};
|
|
922
|
+
var formatChangeDetails = (changes) => {
|
|
923
|
+
if (changes.length === 0) {
|
|
924
|
+
return "\u2713 No changes";
|
|
925
|
+
}
|
|
926
|
+
const header = ["\u{1F4DD} Detailed Changes:", ""];
|
|
927
|
+
const details = changes.flatMap((change, index) => formatChange(change, index));
|
|
928
|
+
return [...header, ...details].join("\n");
|
|
929
|
+
};
|
|
930
|
+
|
|
931
|
+
// cli/src/core/events/index-manager.ts
|
|
932
|
+
var import_node_fs4 = require("fs");
|
|
933
|
+
var import_node_path4 = require("path");
|
|
934
|
+
var import_cloudevents = require("@crossdelta/cloudevents");
|
|
935
|
+
init_config();
|
|
936
|
+
var addExportToDomainIndex = (domainIndexPath, eventFile) => {
|
|
937
|
+
const exportLine = `export * from './${eventFile}'`;
|
|
938
|
+
if (!(0, import_node_fs4.existsSync)(domainIndexPath)) {
|
|
939
|
+
const domainDir = (0, import_node_path4.dirname)(domainIndexPath);
|
|
940
|
+
if (!(0, import_node_fs4.existsSync)(domainDir)) {
|
|
941
|
+
(0, import_node_fs4.mkdirSync)(domainDir, { recursive: true });
|
|
942
|
+
}
|
|
943
|
+
const header = `/**
|
|
944
|
+
* ${(0, import_node_path4.dirname)(domainIndexPath).split("/").pop()} Domain Events
|
|
945
|
+
*/
|
|
946
|
+
|
|
947
|
+
`;
|
|
948
|
+
(0, import_node_fs4.writeFileSync)(domainIndexPath, `${header + exportLine}
|
|
949
|
+
`, "utf-8");
|
|
950
|
+
return true;
|
|
951
|
+
}
|
|
952
|
+
const content = (0, import_node_fs4.readFileSync)(domainIndexPath, "utf-8");
|
|
953
|
+
if (content.includes(`'./${eventFile}'`)) return false;
|
|
954
|
+
const updatedContent = `${content.trimEnd()}
|
|
955
|
+
${exportLine}
|
|
956
|
+
`;
|
|
957
|
+
(0, import_node_fs4.writeFileSync)(domainIndexPath, updatedContent, "utf-8");
|
|
958
|
+
return true;
|
|
959
|
+
};
|
|
960
|
+
var addDomainToEventsIndex = (eventsIndexPath, domain) => {
|
|
961
|
+
const exportLine = `export * from './${domain}'`;
|
|
962
|
+
if (!(0, import_node_fs4.existsSync)(eventsIndexPath)) {
|
|
963
|
+
const header = `/**
|
|
964
|
+
* Event Contracts Index
|
|
965
|
+
*
|
|
966
|
+
* Re-exports all event contracts for convenient importing.
|
|
967
|
+
*/
|
|
968
|
+
|
|
969
|
+
`;
|
|
970
|
+
(0, import_node_fs4.writeFileSync)(eventsIndexPath, header + exportLine + "\n", "utf-8");
|
|
971
|
+
return true;
|
|
972
|
+
}
|
|
973
|
+
const content = (0, import_node_fs4.readFileSync)(eventsIndexPath, "utf-8");
|
|
974
|
+
const lines = content.split("\n");
|
|
975
|
+
if (lines.some((line) => line.trim() === exportLine)) {
|
|
976
|
+
return false;
|
|
977
|
+
}
|
|
978
|
+
const lastExportIndex = lines.findLastIndex((line) => line.startsWith("export"));
|
|
979
|
+
if (lastExportIndex >= 0) {
|
|
980
|
+
lines.splice(lastExportIndex + 1, 0, exportLine);
|
|
981
|
+
} else {
|
|
982
|
+
lines.push(exportLine);
|
|
983
|
+
}
|
|
984
|
+
(0, import_node_fs4.writeFileSync)(eventsIndexPath, lines.join("\n"), "utf-8");
|
|
985
|
+
return true;
|
|
986
|
+
};
|
|
987
|
+
var enableMainExports = (mainIndexPath) => {
|
|
988
|
+
if (!(0, import_node_fs4.existsSync)(mainIndexPath)) return false;
|
|
989
|
+
const content = (0, import_node_fs4.readFileSync)(mainIndexPath, "utf-8");
|
|
990
|
+
const eventsExport = "export * from './events'";
|
|
991
|
+
const hasUncommentedExport = content.split("\n").some(
|
|
992
|
+
(line) => line.trim() === eventsExport || line.trim().startsWith(eventsExport)
|
|
993
|
+
);
|
|
994
|
+
if (hasUncommentedExport) return false;
|
|
995
|
+
let updated = content;
|
|
996
|
+
updated = updated.replace(/\/\/\s*export \* from '\.\/events'/, eventsExport);
|
|
997
|
+
updated = updated.replace(/\/\/\s*export \* from '\.\/stream-policies'/, "export * from './stream-policies'");
|
|
998
|
+
if (/export \* from '\.\/events\/\w+\/\w+'/.test(updated)) {
|
|
999
|
+
updated = updated.replace(/export \* from '\.\/events\/\w+\/\w+'\n?/g, "");
|
|
1000
|
+
}
|
|
1001
|
+
if (!updated.includes(eventsExport)) {
|
|
1002
|
+
updated = `${updated.trimEnd()}
|
|
1003
|
+
${eventsExport}
|
|
1004
|
+
`;
|
|
1005
|
+
}
|
|
1006
|
+
if (updated !== content) {
|
|
1007
|
+
(0, import_node_fs4.writeFileSync)(mainIndexPath, updated, "utf-8");
|
|
1008
|
+
return true;
|
|
1009
|
+
}
|
|
1010
|
+
return false;
|
|
1011
|
+
};
|
|
1012
|
+
var addExportToIndex = (eventType, workspaceRoot) => {
|
|
1013
|
+
const { packagePath } = getContractsConfig(workspaceRoot);
|
|
1014
|
+
const { domain, action } = (0, import_cloudevents.deriveEventNames)(eventType);
|
|
1015
|
+
const eventsDir = (0, import_node_path4.join)(packagePath, "src", "events");
|
|
1016
|
+
if (!(0, import_node_fs4.existsSync)(eventsDir)) return false;
|
|
1017
|
+
const domainDir = (0, import_node_path4.join)(eventsDir, domain);
|
|
1018
|
+
const domainIndexPath = (0, import_node_path4.join)(domainDir, "index.ts");
|
|
1019
|
+
const eventsIndexPath = (0, import_node_path4.join)(eventsDir, "index.ts");
|
|
1020
|
+
const mainIndexPath = (0, import_node_path4.join)(packagePath, "src", "index.ts");
|
|
1021
|
+
const singularDomain = eventType.split(".")[0];
|
|
1022
|
+
const legacyDomainDir = (0, import_node_path4.join)(eventsDir, singularDomain);
|
|
1023
|
+
if (singularDomain !== domain && (0, import_node_fs4.existsSync)(legacyDomainDir)) {
|
|
1024
|
+
console.warn(`\u26A0\uFE0F Warning: Legacy folder '${singularDomain}/' found. Please migrate to '${domain}/' to avoid conflicts.`);
|
|
1025
|
+
console.warn(` Run: mv ${legacyDomainDir} ${domainDir}`);
|
|
1026
|
+
}
|
|
1027
|
+
let updated = false;
|
|
1028
|
+
if (addExportToDomainIndex(domainIndexPath, action)) {
|
|
1029
|
+
updated = true;
|
|
1030
|
+
}
|
|
1031
|
+
if (addDomainToEventsIndex(eventsIndexPath, domain)) {
|
|
1032
|
+
updated = true;
|
|
1033
|
+
}
|
|
1034
|
+
if (enableMainExports(mainIndexPath)) {
|
|
1035
|
+
updated = true;
|
|
1036
|
+
}
|
|
1037
|
+
return updated;
|
|
1038
|
+
};
|
|
1039
|
+
|
|
1040
|
+
// cli/src/core/effects/stream.handler.ts
|
|
1041
|
+
var import_node_fs5 = require("fs");
|
|
1042
|
+
var import_node_path5 = require("path");
|
|
1043
|
+
var import_cloudevents2 = require("@crossdelta/cloudevents");
|
|
1044
|
+
var serviceConsumesStream = (servicePath, streamName) => {
|
|
1045
|
+
const indexPath = (0, import_node_path5.join)(servicePath, "src", "index.ts");
|
|
1046
|
+
if (!(0, import_node_fs5.existsSync)(indexPath)) return false;
|
|
1047
|
+
const content = (0, import_node_fs5.readFileSync)(indexPath, "utf-8");
|
|
1048
|
+
const namespace = streamName.toLowerCase();
|
|
1049
|
+
const streamsArrayPattern = new RegExp(`streams:\\s*\\[[^\\]]*['"]${streamName}['"]`);
|
|
1050
|
+
if (streamsArrayPattern.test(content)) return true;
|
|
1051
|
+
if (content.includes(`stream: '${streamName}'`)) return true;
|
|
1052
|
+
const subjectPattern = new RegExp(`subjects:\\s*\\[.*['"]${namespace}\\.[*>]`);
|
|
1053
|
+
return subjectPattern.test(content);
|
|
1054
|
+
};
|
|
1055
|
+
var addCloudEventsImport = (content) => {
|
|
1056
|
+
if (content.includes("@crossdelta/cloudevents")) return content;
|
|
1057
|
+
const cloudEventsImport = `import { consumeJetStreams } from '@crossdelta/cloudevents'
|
|
1058
|
+
`;
|
|
1059
|
+
const telemetryMatch = content.match(/import\s+['"]@crossdelta\/telemetry['"]\s*\n/);
|
|
1060
|
+
if (telemetryMatch) {
|
|
1061
|
+
const insertPos = (telemetryMatch.index ?? 0) + telemetryMatch[0].length;
|
|
1062
|
+
return `${content.slice(0, insertPos)}
|
|
1063
|
+
${cloudEventsImport}${content.slice(insertPos)}`;
|
|
1064
|
+
}
|
|
1065
|
+
const firstImportMatch = content.match(/import\s+.*\n/);
|
|
1066
|
+
if (firstImportMatch) {
|
|
1067
|
+
const insertPos = (firstImportMatch.index ?? 0) + firstImportMatch[0].length;
|
|
1068
|
+
return content.slice(0, insertPos) + cloudEventsImport + content.slice(insertPos);
|
|
1069
|
+
}
|
|
1070
|
+
return content;
|
|
1071
|
+
};
|
|
1072
|
+
var generateStreamCode = (streamName, serviceName) => `
|
|
1073
|
+
|
|
1074
|
+
// Consume from ${streamName} stream
|
|
1075
|
+
consumeJetStreams({
|
|
1076
|
+
streams: ['${streamName}'],
|
|
1077
|
+
consumer: '${serviceName}-service',
|
|
1078
|
+
discover: './src/events/**/*.handler.ts',
|
|
1079
|
+
})`;
|
|
1080
|
+
var addStreamToService = (servicePath, eventType) => {
|
|
1081
|
+
const streamName = (0, import_cloudevents2.getStreamName)(eventType);
|
|
1082
|
+
const indexPath = (0, import_node_path5.join)(servicePath, "src", "index.ts");
|
|
1083
|
+
const mainPath = (0, import_node_path5.join)(servicePath, "src", "main.ts");
|
|
1084
|
+
if (!(0, import_node_fs5.existsSync)(indexPath) && (0, import_node_fs5.existsSync)(mainPath)) {
|
|
1085
|
+
return { added: false, streamName };
|
|
1086
|
+
}
|
|
1087
|
+
if (!(0, import_node_fs5.existsSync)(indexPath)) {
|
|
1088
|
+
return { added: false, streamName, warning: "Entry point not found (index.ts or main.ts)" };
|
|
1089
|
+
}
|
|
1090
|
+
if (serviceConsumesStream(servicePath, streamName)) {
|
|
1091
|
+
return { added: false, streamName };
|
|
1092
|
+
}
|
|
1093
|
+
let content = (0, import_node_fs5.readFileSync)(indexPath, "utf-8");
|
|
1094
|
+
const serviceName = servicePath.split("/").pop() || "unknown";
|
|
1095
|
+
if (!content.includes("consumeJetStreams")) {
|
|
1096
|
+
content = addCloudEventsImport(content);
|
|
1097
|
+
}
|
|
1098
|
+
const streamCode = generateStreamCode(streamName, serviceName);
|
|
1099
|
+
const updatedContent = `${content.trimEnd() + streamCode}
|
|
1100
|
+
`;
|
|
1101
|
+
(0, import_node_fs5.writeFileSync)(indexPath, updatedContent, "utf-8");
|
|
1102
|
+
return { added: true, streamName };
|
|
1103
|
+
};
|
|
1104
|
+
|
|
1105
|
+
// cli/src/core/effects/handlers.ts
|
|
1106
|
+
var handleStreamWired = (effect, context, options) => {
|
|
1107
|
+
const { stream, servicePath } = effect;
|
|
1108
|
+
const absolutePath = servicePath.startsWith("/") ? servicePath : `${context.workspace.workspaceRoot}/${servicePath}`;
|
|
1109
|
+
if (options?.dryRun) {
|
|
1110
|
+
return {
|
|
1111
|
+
success: true,
|
|
1112
|
+
message: `Would wire stream ${stream} to ${servicePath}`,
|
|
1113
|
+
changes: [
|
|
1114
|
+
{
|
|
1115
|
+
type: "update",
|
|
1116
|
+
path: absolutePath,
|
|
1117
|
+
description: `Wire ${stream} stream to service`,
|
|
1118
|
+
preview: `consumeJetStreams({ streams: ['${stream.toUpperCase()}'], ... })`
|
|
1119
|
+
}
|
|
1120
|
+
]
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
const result = addStreamToService(absolutePath, `${stream.toLowerCase()}.event`);
|
|
1124
|
+
if (result.warning) {
|
|
1125
|
+
context.logger.warn(result.warning);
|
|
1126
|
+
return { success: false, message: result.warning };
|
|
1127
|
+
}
|
|
1128
|
+
if (result.added) {
|
|
1129
|
+
context.logger.info(`Wired stream ${result.streamName} to service`);
|
|
1130
|
+
return { success: true, message: `Wired stream ${result.streamName}` };
|
|
1131
|
+
}
|
|
1132
|
+
context.logger.debug(`Stream ${result.streamName} already wired`);
|
|
1133
|
+
return { success: true, message: `Stream ${result.streamName} already configured` };
|
|
1134
|
+
};
|
|
1135
|
+
var handleContractCreated = (effect, context, options) => {
|
|
1136
|
+
const { path, eventType } = effect;
|
|
1137
|
+
if (options?.dryRun) {
|
|
1138
|
+
const preview = `
|
|
1139
|
+
import { createContract } from '@crossdelta/cloudevents'
|
|
1140
|
+
import { z } from 'zod'
|
|
1141
|
+
|
|
1142
|
+
export const ${toPascalCase(eventType)}Contract = createContract({
|
|
1143
|
+
type: '${eventType}',
|
|
1144
|
+
schema: z.object({ ... }),
|
|
1145
|
+
})
|
|
1146
|
+
`.trim();
|
|
1147
|
+
return {
|
|
1148
|
+
success: true,
|
|
1149
|
+
message: `Would create contract ${eventType}`,
|
|
1150
|
+
changes: [
|
|
1151
|
+
{
|
|
1152
|
+
type: "create",
|
|
1153
|
+
path,
|
|
1154
|
+
description: `Create event contract for ${eventType}`,
|
|
1155
|
+
preview: preview.slice(0, 500)
|
|
1156
|
+
}
|
|
1157
|
+
]
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
const updated = addExportToIndex(eventType, context.workspace.workspaceRoot);
|
|
1161
|
+
if (updated) {
|
|
1162
|
+
context.logger.debug(`Added export for ${eventType} to contracts index`);
|
|
1163
|
+
}
|
|
1164
|
+
context.logger.debug(`Contract created: ${eventType} at ${path}`);
|
|
1165
|
+
return {
|
|
1166
|
+
success: true,
|
|
1167
|
+
message: updated ? `Contract ${eventType} created with exports` : `Contract ${eventType} created`
|
|
1168
|
+
};
|
|
1169
|
+
};
|
|
1170
|
+
var toPascalCase = (eventType) => {
|
|
1171
|
+
return eventType.split(".").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
1172
|
+
};
|
|
1173
|
+
var handleHandlerCreated = (effect, context, options) => {
|
|
1174
|
+
const { path, eventType, servicePath } = effect;
|
|
1175
|
+
if (options?.dryRun) {
|
|
1176
|
+
const preview = `
|
|
1177
|
+
import { handleEvent } from '@crossdelta/cloudevents'
|
|
1178
|
+
import { ${toPascalCase(eventType)}Contract } from '@your-scope/contracts'
|
|
1179
|
+
|
|
1180
|
+
export default handleEvent(${toPascalCase(eventType)}Contract, async (data) => {
|
|
1181
|
+
console.log('Processing ${eventType}:', data)
|
|
1182
|
+
// TODO: Implement handler logic
|
|
1183
|
+
})
|
|
1184
|
+
`.trim();
|
|
1185
|
+
return {
|
|
1186
|
+
success: true,
|
|
1187
|
+
message: `Would create handler for ${eventType}`,
|
|
1188
|
+
changes: [
|
|
1189
|
+
{
|
|
1190
|
+
type: "create",
|
|
1191
|
+
path,
|
|
1192
|
+
description: `Create event handler for ${eventType}`,
|
|
1193
|
+
preview: preview.slice(0, 500)
|
|
1194
|
+
}
|
|
1195
|
+
]
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
context.logger.debug(`Handler created: ${eventType} at ${path} (service: ${servicePath})`);
|
|
1199
|
+
return { success: true };
|
|
1200
|
+
};
|
|
1201
|
+
var effectHandlers = {
|
|
1202
|
+
"stream.wired": handleStreamWired,
|
|
1203
|
+
"contract.created": handleContractCreated,
|
|
1204
|
+
"handler.created": handleHandlerCreated
|
|
1205
|
+
};
|
|
1206
|
+
var applyEffects = (effects, context, options) => {
|
|
1207
|
+
return effects.map((effect) => {
|
|
1208
|
+
const handler = effectHandlers[effect.kind];
|
|
1209
|
+
if (!handler) {
|
|
1210
|
+
context.logger.warn(`Unknown effect kind: ${effect.kind}`);
|
|
1211
|
+
return { success: false, message: `Unknown effect kind: ${effect.kind}` };
|
|
1212
|
+
}
|
|
1213
|
+
return handler(effect, context, options);
|
|
1214
|
+
});
|
|
1215
|
+
};
|
|
1216
|
+
|
|
1217
|
+
// cli/src/core/flows/service-generation.flow.ts
|
|
1218
|
+
var import_node_path13 = require("path");
|
|
1219
|
+
var import_cloudevents4 = require("@crossdelta/cloudevents");
|
|
1220
|
+
|
|
1221
|
+
// cli/src/core/operations/types.ts
|
|
1222
|
+
var ok = (operation, summary, options = {}) => ({
|
|
1223
|
+
ok: true,
|
|
1224
|
+
operation,
|
|
1225
|
+
summary,
|
|
1226
|
+
artifacts: options.artifacts ?? [],
|
|
1227
|
+
changes: options.changes ?? [],
|
|
1228
|
+
diagnostics: options.diagnostics ?? [],
|
|
1229
|
+
next: options.next,
|
|
1230
|
+
data: options.data
|
|
1231
|
+
});
|
|
1232
|
+
var err = (operation, error, options = {}) => ({
|
|
1233
|
+
ok: false,
|
|
1234
|
+
operation,
|
|
1235
|
+
summary: `Failed: ${error}`,
|
|
1236
|
+
artifacts: [],
|
|
1237
|
+
changes: [],
|
|
1238
|
+
diagnostics: options.diagnostics ?? [{ level: "error", message: error }],
|
|
1239
|
+
data: options.data
|
|
1240
|
+
});
|
|
1241
|
+
|
|
1242
|
+
// cli/src/core/flows/service-generation.flow.ts
|
|
1243
|
+
init_config();
|
|
1244
|
+
|
|
1245
|
+
// cli/src/commands/create/hono-microservice/templates.ts
|
|
1246
|
+
var import_node_path9 = require("path");
|
|
1247
|
+
|
|
1248
|
+
// cli/src/core/esm.ts
|
|
1249
|
+
var import_node_path6 = require("path");
|
|
1250
|
+
var import_node_url2 = require("url");
|
|
1251
|
+
var getDirname = (importMetaUrl) => (0, import_node_path6.dirname)((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
1252
|
+
|
|
1253
|
+
// cli/src/core/filesystem/index.ts
|
|
1254
|
+
var import_node_fs6 = require("fs");
|
|
1255
|
+
var import_node_path7 = require("path");
|
|
1256
|
+
init_watcher();
|
|
1257
|
+
var import_meta2 = {};
|
|
1258
|
+
var getDefaultStartDir = () => {
|
|
1259
|
+
if (typeof import_meta2?.url === "string") {
|
|
1260
|
+
return getDirname(import_meta2.url);
|
|
1261
|
+
}
|
|
1262
|
+
if (typeof __dirname === "string") return __dirname;
|
|
1263
|
+
return process.cwd();
|
|
1264
|
+
};
|
|
1265
|
+
var findPackageRoot = (startDir) => {
|
|
1266
|
+
const dir = startDir ?? getDefaultStartDir();
|
|
1267
|
+
const hasPackageJson = (d) => (0, import_node_fs6.existsSync)((0, import_node_path7.join)(d, "package.json"));
|
|
1268
|
+
const parent = (0, import_node_path7.dirname)(dir);
|
|
1269
|
+
if (hasPackageJson(dir)) return dir;
|
|
1270
|
+
if (dir === "/") throw new Error("Could not find package.json (package root)");
|
|
1271
|
+
return findPackageRoot(parent);
|
|
1272
|
+
};
|
|
1273
|
+
var findDirectory = (searchPaths, errorMessage) => {
|
|
1274
|
+
const found = searchPaths.find(import_node_fs6.existsSync);
|
|
1275
|
+
if (!found) {
|
|
1276
|
+
throw new Error(
|
|
1277
|
+
errorMessage ?? `Directory not found. Searched in: ${searchPaths.join(", ")}`
|
|
1278
|
+
);
|
|
1279
|
+
}
|
|
1280
|
+
return found;
|
|
1281
|
+
};
|
|
1282
|
+
|
|
1283
|
+
// cli/src/core/templates/renderer.ts
|
|
1284
|
+
var import_node_fs7 = require("fs");
|
|
1285
|
+
var import_node_path8 = require("path");
|
|
1286
|
+
var import_handlebars = __toESM(require("handlebars"));
|
|
1287
|
+
var findTemplatesDir = (searchPaths, errorMessage) => {
|
|
1288
|
+
return findDirectory(
|
|
1289
|
+
searchPaths,
|
|
1290
|
+
errorMessage ?? `Templates directory not found. Searched in: ${searchPaths.join(", ")}`
|
|
1291
|
+
);
|
|
1292
|
+
};
|
|
1293
|
+
var renderTemplate = (templatePath, data = {}) => {
|
|
1294
|
+
const templateContent = (0, import_node_fs7.readFileSync)(templatePath, "utf-8");
|
|
1295
|
+
const template = import_handlebars.default.compile(templateContent);
|
|
1296
|
+
return template(data);
|
|
1297
|
+
};
|
|
1298
|
+
var createTemplateRenderer = (templatesDir) => (templateName, data = {}) => {
|
|
1299
|
+
const templatePath = (0, import_node_path8.join)(templatesDir, templateName);
|
|
1300
|
+
return renderTemplate(templatePath, data);
|
|
1301
|
+
};
|
|
1302
|
+
|
|
1303
|
+
// cli/src/core/templates/strings.ts
|
|
1304
|
+
var toEnvKey = (name) => name.toUpperCase().replace(/-/g, "_");
|
|
1305
|
+
var toDisplayName = (name) => name.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
1306
|
+
|
|
1307
|
+
// cli/src/commands/create/hono-microservice/templates.ts
|
|
1308
|
+
var import_meta3 = {};
|
|
1309
|
+
var getModuleDir = () => {
|
|
1310
|
+
if (typeof import_meta3?.url === "string") {
|
|
1311
|
+
return getDirname(import_meta3.url);
|
|
1312
|
+
}
|
|
1313
|
+
if (typeof __dirname === "string") return __dirname;
|
|
1314
|
+
return process.cwd();
|
|
1315
|
+
};
|
|
1316
|
+
var getTemplatesDir = () => {
|
|
1317
|
+
const moduleDir = getModuleDir();
|
|
1318
|
+
const packageRoot = findPackageRoot();
|
|
1319
|
+
return findTemplatesDir(
|
|
1320
|
+
[
|
|
1321
|
+
(0, import_node_path9.join)(moduleDir, "templates", "hono-microservice"),
|
|
1322
|
+
(0, import_node_path9.join)(moduleDir, "..", "hono-microservice", "templates"),
|
|
1323
|
+
(0, import_node_path9.join)(packageRoot, "bin", "templates", "hono-microservice")
|
|
1324
|
+
// Global install fallback
|
|
1325
|
+
],
|
|
1326
|
+
"Hono templates directory not found."
|
|
1327
|
+
);
|
|
1328
|
+
};
|
|
1329
|
+
var renderTemplate2 = (templateName, data = {}) => createTemplateRenderer(getTemplatesDir())(templateName, data);
|
|
1330
|
+
function generateHonoTsConfig() {
|
|
1331
|
+
return renderTemplate2("tsconfig.json.hbs");
|
|
1332
|
+
}
|
|
1333
|
+
function generateHonoDockerfile(bunVersion = "1.2.23") {
|
|
1334
|
+
return renderTemplate2("Dockerfile.hbs", { bunVersion });
|
|
1335
|
+
}
|
|
1336
|
+
function generateHonoBiomeJson() {
|
|
1337
|
+
return renderTemplate2("biome.json.hbs");
|
|
1338
|
+
}
|
|
1339
|
+
function generateHonoEnvTs(serviceName, envVars = []) {
|
|
1340
|
+
return renderTemplate2("src/config/env.ts.hbs", {
|
|
1341
|
+
envKey: toEnvKey(serviceName),
|
|
1342
|
+
envVars
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
// cli/src/core/templates/hono-bun.template.ts
|
|
1347
|
+
var generateImports = (hasEvents) => {
|
|
1348
|
+
const imports = [
|
|
1349
|
+
"import './config/env'",
|
|
1350
|
+
"import '@crossdelta/telemetry'",
|
|
1351
|
+
""
|
|
1352
|
+
];
|
|
1353
|
+
if (hasEvents) {
|
|
1354
|
+
imports.push("import { consumeJetStreams } from '@crossdelta/cloudevents'");
|
|
1355
|
+
}
|
|
1356
|
+
imports.push("import { Hono } from 'hono'");
|
|
1357
|
+
return imports;
|
|
1358
|
+
};
|
|
1359
|
+
var generateServerSetup = (envKey) => [
|
|
1360
|
+
`const port = Number(process.env.${envKey}_PORT) || 8080`,
|
|
1361
|
+
"const app = new Hono()",
|
|
1362
|
+
"",
|
|
1363
|
+
"app.get('/health', (c) => {",
|
|
1364
|
+
" return c.json({ status: 'ok' })",
|
|
1365
|
+
"})"
|
|
1366
|
+
];
|
|
1367
|
+
var generateJetStreamConsumer = (serviceName, streams) => [
|
|
1368
|
+
"",
|
|
1369
|
+
"// Start NATS JetStream consumer",
|
|
1370
|
+
"consumeJetStreams({",
|
|
1371
|
+
` streams: [${streams.map((s) => `'${s}'`).join(", ")}],`,
|
|
1372
|
+
` consumer: '${serviceName}',`,
|
|
1373
|
+
" discover: './src/events/**/*.handler.ts',",
|
|
1374
|
+
"})"
|
|
1375
|
+
];
|
|
1376
|
+
var generateBunServe = () => [
|
|
1377
|
+
"",
|
|
1378
|
+
"Bun.serve({",
|
|
1379
|
+
" port,",
|
|
1380
|
+
" fetch: app.fetch,",
|
|
1381
|
+
"})",
|
|
1382
|
+
"",
|
|
1383
|
+
"console.log(`\u{1F680} Service ready at http://localhost:${port}`)"
|
|
1384
|
+
];
|
|
1385
|
+
var generateIndexTs = (vars) => {
|
|
1386
|
+
const envKey = toEnvKey(vars.serviceName);
|
|
1387
|
+
const parts = [
|
|
1388
|
+
...generateImports(vars.hasEvents),
|
|
1389
|
+
"",
|
|
1390
|
+
...generateServerSetup(envKey),
|
|
1391
|
+
...vars.hasEvents ? generateJetStreamConsumer(vars.serviceName, vars.streams) : [],
|
|
1392
|
+
...generateBunServe()
|
|
1393
|
+
];
|
|
1394
|
+
return `${parts.join("\n")}
|
|
1395
|
+
`;
|
|
1396
|
+
};
|
|
1397
|
+
var generatePackageJson = (vars) => {
|
|
1398
|
+
const dependencies = {
|
|
1399
|
+
hono: "^4.6.14",
|
|
1400
|
+
"@crossdelta/telemetry": "workspace:*",
|
|
1401
|
+
zod: "^4.1.0",
|
|
1402
|
+
// Always include contracts - user may add events later via "pf cloudevents add"
|
|
1403
|
+
[`${vars.workspaceScope}/contracts`]: "workspace:*"
|
|
1404
|
+
};
|
|
1405
|
+
if (vars.hasEvents) {
|
|
1406
|
+
dependencies["@crossdelta/cloudevents"] = "workspace:*";
|
|
1407
|
+
}
|
|
1408
|
+
const pkg = {
|
|
1409
|
+
name: vars.packageName,
|
|
1410
|
+
version: "0.0.1",
|
|
1411
|
+
type: "module",
|
|
1412
|
+
scripts: {
|
|
1413
|
+
"start:dev": "bun --watch src/index.ts",
|
|
1414
|
+
start: "bun src/index.ts",
|
|
1415
|
+
test: "bun test"
|
|
1416
|
+
},
|
|
1417
|
+
dependencies,
|
|
1418
|
+
devDependencies: {
|
|
1419
|
+
"@types/bun": "^1.2.23"
|
|
1420
|
+
}
|
|
1421
|
+
};
|
|
1422
|
+
return JSON.stringify(pkg, null, 2);
|
|
1423
|
+
};
|
|
1424
|
+
var generateReadme = (vars) => {
|
|
1425
|
+
return `# ${vars.serviceName}
|
|
1426
|
+
|
|
1427
|
+
## Development
|
|
1428
|
+
|
|
1429
|
+
\`\`\`bash
|
|
1430
|
+
bun install
|
|
1431
|
+
bun dev
|
|
1432
|
+
\`\`\`
|
|
1433
|
+
|
|
1434
|
+
Service runs on port: \`${vars.port}\`
|
|
1435
|
+
|
|
1436
|
+
## Environment Variables
|
|
1437
|
+
|
|
1438
|
+
- \`${vars.envVarName}_PORT\`: Service port (default: ${vars.port})
|
|
1439
|
+
${vars.hasEvents ? `
|
|
1440
|
+
## Events
|
|
1441
|
+
|
|
1442
|
+
This service consumes from:
|
|
1443
|
+
${vars.streams.map((s) => `- ${s}`).join("\n")}` : ""}
|
|
1444
|
+
`;
|
|
1445
|
+
};
|
|
1446
|
+
var generateEventsGitkeep = () => "";
|
|
1447
|
+
var HonoBunTemplate = {
|
|
1448
|
+
name: "hono-bun",
|
|
1449
|
+
runtime: "bun",
|
|
1450
|
+
framework: "hono",
|
|
1451
|
+
files: [
|
|
1452
|
+
{
|
|
1453
|
+
path: "src/index.ts",
|
|
1454
|
+
content: generateIndexTs
|
|
1455
|
+
},
|
|
1456
|
+
{
|
|
1457
|
+
path: "src/config/env.ts",
|
|
1458
|
+
content: (vars) => generateHonoEnvTs(vars.serviceName, vars.envVars)
|
|
1459
|
+
},
|
|
1460
|
+
{
|
|
1461
|
+
path: "package.json",
|
|
1462
|
+
content: generatePackageJson
|
|
1463
|
+
},
|
|
1464
|
+
{
|
|
1465
|
+
path: "tsconfig.json",
|
|
1466
|
+
content: () => generateHonoTsConfig()
|
|
1467
|
+
},
|
|
1468
|
+
{
|
|
1469
|
+
path: "Dockerfile",
|
|
1470
|
+
content: () => generateHonoDockerfile()
|
|
1471
|
+
},
|
|
1472
|
+
{
|
|
1473
|
+
path: "biome.json",
|
|
1474
|
+
content: () => generateHonoBiomeJson()
|
|
1475
|
+
},
|
|
1476
|
+
{
|
|
1477
|
+
path: "README.md",
|
|
1478
|
+
content: generateReadme
|
|
1479
|
+
},
|
|
1480
|
+
{
|
|
1481
|
+
path: "src/events/.gitkeep",
|
|
1482
|
+
content: generateEventsGitkeep,
|
|
1483
|
+
skip: (vars) => !vars.hasEvents
|
|
1484
|
+
}
|
|
1485
|
+
]
|
|
1486
|
+
};
|
|
1487
|
+
|
|
1488
|
+
// cli/src/commands/create/nest-microservice/templates.ts
|
|
1489
|
+
var import_node_path10 = require("path");
|
|
1490
|
+
var import_meta4 = {};
|
|
1491
|
+
var getModuleDir2 = () => {
|
|
1492
|
+
if (typeof import_meta4?.url === "string") {
|
|
1493
|
+
return getDirname(import_meta4.url);
|
|
1494
|
+
}
|
|
1495
|
+
if (typeof __dirname === "string") return __dirname;
|
|
1496
|
+
return process.cwd();
|
|
1497
|
+
};
|
|
1498
|
+
var getTemplatesDir2 = () => {
|
|
1499
|
+
const moduleDir = getModuleDir2();
|
|
1500
|
+
const packageRoot = findPackageRoot();
|
|
1501
|
+
return findTemplatesDir(
|
|
1502
|
+
[
|
|
1503
|
+
(0, import_node_path10.join)(moduleDir, "templates", "nest-microservice"),
|
|
1504
|
+
(0, import_node_path10.join)(moduleDir, "..", "nest-microservice", "templates"),
|
|
1505
|
+
(0, import_node_path10.join)(packageRoot, "bin", "templates", "nest-microservice")
|
|
1506
|
+
// Global install fallback
|
|
1507
|
+
],
|
|
1508
|
+
"NestJS templates directory not found."
|
|
1509
|
+
);
|
|
1510
|
+
};
|
|
1511
|
+
var renderTemplate3 = (templateName, data = {}) => createTemplateRenderer(getTemplatesDir2())(templateName, data);
|
|
1512
|
+
function generateNestMainTs(serviceName, defaultPort = 3e3) {
|
|
1513
|
+
return renderTemplate3("src/main.ts.hbs", {
|
|
1514
|
+
serviceName,
|
|
1515
|
+
envKey: toEnvKey(serviceName),
|
|
1516
|
+
displayName: toDisplayName(serviceName),
|
|
1517
|
+
defaultPort
|
|
1518
|
+
});
|
|
1519
|
+
}
|
|
1520
|
+
function generateAppContext() {
|
|
1521
|
+
return renderTemplate3("src/app.context.ts.hbs", {});
|
|
1522
|
+
}
|
|
1523
|
+
function generateEventsModule() {
|
|
1524
|
+
return renderTemplate3("src/events/events.module.ts.hbs", {});
|
|
1525
|
+
}
|
|
1526
|
+
function generateEventsService(serviceName) {
|
|
1527
|
+
return renderTemplate3("src/events/events.service.ts.hbs", { serviceName });
|
|
1528
|
+
}
|
|
1529
|
+
function generateNestDockerfile(nodeVersion = "24", bunVersion = "1.2.23") {
|
|
1530
|
+
return renderTemplate3("Dockerfile.hbs", { nodeVersion, bunVersion });
|
|
1531
|
+
}
|
|
1532
|
+
function generateNestBiomeJson() {
|
|
1533
|
+
return renderTemplate3("biome.json.hbs", {});
|
|
1534
|
+
}
|
|
1535
|
+
function generateNestEnvTs(serviceName, envVars = []) {
|
|
1536
|
+
return renderTemplate3("src/config/env.ts.hbs", {
|
|
1537
|
+
envKey: toEnvKey(serviceName),
|
|
1538
|
+
envVars
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
// cli/src/core/templates/nest.template.ts
|
|
1543
|
+
var generatePackageJson2 = (vars) => {
|
|
1544
|
+
const dependencies = {
|
|
1545
|
+
"@nestjs/common": "^11.0.0",
|
|
1546
|
+
"@nestjs/core": "^11.0.0",
|
|
1547
|
+
"@nestjs/platform-express": "^11.0.0",
|
|
1548
|
+
"@crossdelta/telemetry": "workspace:*",
|
|
1549
|
+
"reflect-metadata": "^0.2.0",
|
|
1550
|
+
"rxjs": "^7.8.0",
|
|
1551
|
+
"zod": "^4.1.0",
|
|
1552
|
+
// Always include contracts - user may add events later via "pf cloudevents add"
|
|
1553
|
+
[`${vars.workspaceScope}/contracts`]: "workspace:*"
|
|
1554
|
+
};
|
|
1555
|
+
if (vars.hasEvents) {
|
|
1556
|
+
dependencies["@crossdelta/cloudevents"] = "workspace:*";
|
|
1557
|
+
}
|
|
1558
|
+
const pkg = {
|
|
1559
|
+
name: vars.packageName,
|
|
1560
|
+
version: "0.0.1",
|
|
1561
|
+
scripts: {
|
|
1562
|
+
"start:dev": "bun --watch src/main.ts",
|
|
1563
|
+
start: "node dist/main.js",
|
|
1564
|
+
build: "tsc",
|
|
1565
|
+
test: "bun test"
|
|
1566
|
+
},
|
|
1567
|
+
dependencies,
|
|
1568
|
+
devDependencies: {
|
|
1569
|
+
"@nestjs/cli": "^11.0.0",
|
|
1570
|
+
"@types/node": "^24.0.0",
|
|
1571
|
+
typescript: "^5.7.0"
|
|
1572
|
+
}
|
|
1573
|
+
};
|
|
1574
|
+
return JSON.stringify(pkg, null, 2);
|
|
1575
|
+
};
|
|
1576
|
+
var generateTsConfig = () => {
|
|
1577
|
+
const config = {
|
|
1578
|
+
extends: "../../../../tsconfig.json",
|
|
1579
|
+
compilerOptions: {
|
|
1580
|
+
outDir: "./dist",
|
|
1581
|
+
rootDir: "./src",
|
|
1582
|
+
experimentalDecorators: true,
|
|
1583
|
+
emitDecoratorMetadata: true
|
|
1584
|
+
},
|
|
1585
|
+
include: ["src/**/*"],
|
|
1586
|
+
exclude: ["node_modules", "dist", "**/*.test.ts"]
|
|
1587
|
+
};
|
|
1588
|
+
return JSON.stringify(config, null, 2);
|
|
1589
|
+
};
|
|
1590
|
+
var generateAppModule = (vars) => {
|
|
1591
|
+
const imports = ["Module", "HttpModule"];
|
|
1592
|
+
const moduleImports = ["HttpModule"];
|
|
1593
|
+
if (vars.hasEvents) {
|
|
1594
|
+
imports.push("OnModuleInit");
|
|
1595
|
+
moduleImports.push("EventsModule");
|
|
1596
|
+
}
|
|
1597
|
+
return `import { ${imports.join(", ")} } from '@nestjs/common'
|
|
1598
|
+
${vars.hasEvents ? "import { EventsModule } from './events/events.module'" : ""}
|
|
1599
|
+
|
|
1600
|
+
@Module({
|
|
1601
|
+
imports: [${moduleImports.join(", ")}],
|
|
1602
|
+
controllers: [],
|
|
1603
|
+
providers: [],
|
|
1604
|
+
})
|
|
1605
|
+
export class AppModule${vars.hasEvents ? " implements OnModuleInit" : ""} {
|
|
1606
|
+
${vars.hasEvents ? "async onModuleInit() {\n // Events module initializes NATS consumer\n }" : ""}
|
|
1607
|
+
}
|
|
1608
|
+
`;
|
|
1609
|
+
};
|
|
1610
|
+
var generateAppController = () => {
|
|
1611
|
+
return `import { Controller, Get } from '@nestjs/common'
|
|
1612
|
+
|
|
1613
|
+
@Controller()
|
|
1614
|
+
export class AppController {
|
|
1615
|
+
@Get('health')
|
|
1616
|
+
health() {
|
|
1617
|
+
return { status: 'ok' }
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
`;
|
|
1621
|
+
};
|
|
1622
|
+
var generateReadme2 = (vars) => {
|
|
1623
|
+
return `# ${vars.serviceName}
|
|
1624
|
+
|
|
1625
|
+
## Development
|
|
1626
|
+
|
|
1627
|
+
\`\`\`bash
|
|
1628
|
+
bun install
|
|
1629
|
+
bun dev
|
|
1630
|
+
\`\`\`
|
|
1631
|
+
|
|
1632
|
+
Service runs on port: \`${vars.port}\`
|
|
1633
|
+
|
|
1634
|
+
## Environment Variables
|
|
1635
|
+
|
|
1636
|
+
- \`${vars.envVarName}_PORT\`: Service port (default: ${vars.port})
|
|
1637
|
+
${vars.hasEvents ? `
|
|
1638
|
+
## Events
|
|
1639
|
+
|
|
1640
|
+
This service consumes from:
|
|
1641
|
+
${vars.streams.map((s) => `- ${s}`).join("\n")}` : ""}
|
|
1642
|
+
`;
|
|
1643
|
+
};
|
|
1644
|
+
var generateEventsGitkeep2 = () => "";
|
|
1645
|
+
var NestTemplate = {
|
|
1646
|
+
name: "nest",
|
|
1647
|
+
runtime: "node",
|
|
1648
|
+
framework: "nest",
|
|
1649
|
+
files: [
|
|
1650
|
+
{
|
|
1651
|
+
path: "src/config/env.ts",
|
|
1652
|
+
content: (vars) => generateNestEnvTs(vars.serviceName, vars.envVars)
|
|
1653
|
+
},
|
|
1654
|
+
{
|
|
1655
|
+
path: "src/main.ts",
|
|
1656
|
+
content: (vars) => generateNestMainTs(vars.serviceName, vars.port)
|
|
1657
|
+
},
|
|
1658
|
+
{
|
|
1659
|
+
path: "src/app.module.ts",
|
|
1660
|
+
content: generateAppModule
|
|
1661
|
+
},
|
|
1662
|
+
{
|
|
1663
|
+
path: "src/app.controller.ts",
|
|
1664
|
+
content: () => generateAppController()
|
|
1665
|
+
},
|
|
1666
|
+
{
|
|
1667
|
+
path: "src/app.context.ts",
|
|
1668
|
+
content: () => generateAppContext()
|
|
1669
|
+
},
|
|
1670
|
+
{
|
|
1671
|
+
path: "package.json",
|
|
1672
|
+
content: generatePackageJson2
|
|
1673
|
+
},
|
|
1674
|
+
{
|
|
1675
|
+
path: "tsconfig.json",
|
|
1676
|
+
content: () => generateTsConfig()
|
|
1677
|
+
},
|
|
1678
|
+
{
|
|
1679
|
+
path: "Dockerfile",
|
|
1680
|
+
content: () => generateNestDockerfile()
|
|
1681
|
+
},
|
|
1682
|
+
{
|
|
1683
|
+
path: "biome.json",
|
|
1684
|
+
content: () => generateNestBiomeJson()
|
|
1685
|
+
},
|
|
1686
|
+
{
|
|
1687
|
+
path: "README.md",
|
|
1688
|
+
content: generateReadme2
|
|
1689
|
+
},
|
|
1690
|
+
{
|
|
1691
|
+
path: "src/events/events.module.ts",
|
|
1692
|
+
content: () => generateEventsModule(),
|
|
1693
|
+
skip: (vars) => !vars.hasEvents
|
|
1694
|
+
},
|
|
1695
|
+
{
|
|
1696
|
+
path: "src/events/events.service.ts",
|
|
1697
|
+
content: (vars) => generateEventsService(vars.serviceName),
|
|
1698
|
+
skip: (vars) => !vars.hasEvents
|
|
1699
|
+
},
|
|
1700
|
+
{
|
|
1701
|
+
path: "src/events/handlers/.gitkeep",
|
|
1702
|
+
content: generateEventsGitkeep2,
|
|
1703
|
+
skip: (vars) => !vars.hasEvents
|
|
1704
|
+
}
|
|
1705
|
+
]
|
|
1706
|
+
};
|
|
1707
|
+
|
|
1708
|
+
// cli/src/core/templates/index.ts
|
|
1709
|
+
var templates = [HonoBunTemplate, NestTemplate];
|
|
1710
|
+
var selectTemplate = (type) => {
|
|
1711
|
+
const normalized = normalizeTemplateType(type);
|
|
1712
|
+
const template = templates.find((t) => t.name === normalized);
|
|
1713
|
+
if (!template) {
|
|
1714
|
+
throw new Error(`Template '${type}' not found. Available: ${templates.map((t) => t.name).join(", ")}`);
|
|
1715
|
+
}
|
|
1716
|
+
return template;
|
|
1717
|
+
};
|
|
1718
|
+
var normalizeTemplateType = (type) => {
|
|
1719
|
+
const aliases = {
|
|
1720
|
+
"hono-micro": "hono-bun",
|
|
1721
|
+
"nest-micro": "nest"
|
|
1722
|
+
};
|
|
1723
|
+
return aliases[type] || type;
|
|
1724
|
+
};
|
|
1725
|
+
|
|
1726
|
+
// cli/src/core/flows/effect-builder.ts
|
|
1727
|
+
var import_node_path11 = require("path");
|
|
1728
|
+
var import_cloudevents3 = require("@crossdelta/cloudevents");
|
|
1729
|
+
|
|
1730
|
+
// cli/src/core/policies/service-layout.ts
|
|
1731
|
+
var SERVICE_LAYOUT_POLICY_VERSION = 1;
|
|
1732
|
+
var defaultCodingConstraints = {
|
|
1733
|
+
appliesTo: ["src/domain", "src/use-cases"],
|
|
1734
|
+
forbiddenPatterns: [
|
|
1735
|
+
{
|
|
1736
|
+
pattern: "console\\.(log|error|warn|info|debug)\\s*\\(",
|
|
1737
|
+
description: "console.* is forbidden in business logic (use structured logging)",
|
|
1738
|
+
code: "CONSOLE_IN_DOMAIN"
|
|
1739
|
+
},
|
|
1740
|
+
{
|
|
1741
|
+
pattern: "process\\.env\\b",
|
|
1742
|
+
description: "process.env is forbidden in business logic (use dependency injection)",
|
|
1743
|
+
code: "PROCESS_ENV_IN_DOMAIN"
|
|
1744
|
+
},
|
|
1745
|
+
{
|
|
1746
|
+
pattern: "\\bfetch\\s*\\(",
|
|
1747
|
+
description: "fetch() is forbidden in business logic (use adapters/services)",
|
|
1748
|
+
code: "FETCH_IN_DOMAIN"
|
|
1749
|
+
}
|
|
1750
|
+
]
|
|
1751
|
+
};
|
|
1752
|
+
var serviceLayoutPolicy = {
|
|
1753
|
+
hono: {
|
|
1754
|
+
businessLogicDir: "use-cases",
|
|
1755
|
+
eventsDir: "events",
|
|
1756
|
+
filePattern: "{name}.use-case.ts",
|
|
1757
|
+
suffix: "use-case",
|
|
1758
|
+
handlerDelegatesToBusinessLogic: true,
|
|
1759
|
+
pathGuards: {
|
|
1760
|
+
forbidDirs: ["src/services"]
|
|
1761
|
+
},
|
|
1762
|
+
codingConstraints: defaultCodingConstraints
|
|
1763
|
+
},
|
|
1764
|
+
nestjs: {
|
|
1765
|
+
businessLogicDir: "",
|
|
1766
|
+
eventsDir: "events",
|
|
1767
|
+
filePattern: "{name}.service.ts",
|
|
1768
|
+
suffix: "service",
|
|
1769
|
+
handlerDelegatesToBusinessLogic: false,
|
|
1770
|
+
pathGuards: {
|
|
1771
|
+
forbidDirs: []
|
|
1772
|
+
},
|
|
1773
|
+
codingConstraints: {
|
|
1774
|
+
appliesTo: ["src/domain"],
|
|
1775
|
+
forbiddenPatterns: defaultCodingConstraints.forbiddenPatterns
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
};
|
|
1779
|
+
var toCamelCase = (name) => {
|
|
1780
|
+
return name.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
|
|
1781
|
+
};
|
|
1782
|
+
var calculateImportPath = (businessLogicDir, fileNameWithoutExt) => {
|
|
1783
|
+
return `../${businessLogicDir}/${fileNameWithoutExt}`;
|
|
1784
|
+
};
|
|
1785
|
+
var resolveServiceLayout = (framework, name) => {
|
|
1786
|
+
const layout = serviceLayoutPolicy[framework];
|
|
1787
|
+
const fileName = layout.filePattern.replace("{name}", name);
|
|
1788
|
+
const fileNameWithoutExt = fileName.replace(/\.ts$/, "");
|
|
1789
|
+
const dirPath = layout.businessLogicDir ? `src/${layout.businessLogicDir}` : "src";
|
|
1790
|
+
return {
|
|
1791
|
+
filePath: dirPath ? `${dirPath}/${fileName}` : `src/${fileName}`,
|
|
1792
|
+
dirPath,
|
|
1793
|
+
fileName,
|
|
1794
|
+
functionName: toCamelCase(name),
|
|
1795
|
+
delegateFromHandler: layout.handlerDelegatesToBusinessLogic,
|
|
1796
|
+
importFromEvents: layout.businessLogicDir ? calculateImportPath(layout.businessLogicDir, fileNameWithoutExt) : `./${fileNameWithoutExt}`
|
|
1797
|
+
};
|
|
1798
|
+
};
|
|
1799
|
+
var resolveEventLayout = (framework, eventName) => {
|
|
1800
|
+
const layout = serviceLayoutPolicy[framework];
|
|
1801
|
+
const dirPath = `src/${layout.eventsDir}`;
|
|
1802
|
+
const fileName = `${eventName}.handler.ts`;
|
|
1803
|
+
return {
|
|
1804
|
+
filePath: `${dirPath}/${fileName}`,
|
|
1805
|
+
dirPath,
|
|
1806
|
+
fileName
|
|
1807
|
+
};
|
|
1808
|
+
};
|
|
1809
|
+
var deriveUseCaseName = (eventType) => {
|
|
1810
|
+
const normalized = eventType.replace(/\./g, "-");
|
|
1811
|
+
return `handle-${normalized}`;
|
|
1812
|
+
};
|
|
1813
|
+
var normalizePath = (filePath) => {
|
|
1814
|
+
let normalized = filePath.replace(/\\/g, "/");
|
|
1815
|
+
normalized = normalized.replace(/^\.\//, "").replace(/^\//, "");
|
|
1816
|
+
const segments = normalized.split("/");
|
|
1817
|
+
const resolved = [];
|
|
1818
|
+
for (const segment of segments) {
|
|
1819
|
+
if (segment === "..") {
|
|
1820
|
+
resolved.pop();
|
|
1821
|
+
} else if (segment !== "." && segment !== "") {
|
|
1822
|
+
resolved.push(segment);
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
return resolved.join("/");
|
|
1826
|
+
};
|
|
1827
|
+
var matchesForbiddenDir = (filePath, forbidDirs) => {
|
|
1828
|
+
const normalizedPath = normalizePath(filePath);
|
|
1829
|
+
for (const dir of forbidDirs) {
|
|
1830
|
+
const normalizedDir = normalizePath(dir);
|
|
1831
|
+
if (normalizedPath.startsWith(`${normalizedDir}/`) || normalizedPath === normalizedDir) {
|
|
1832
|
+
return dir;
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
return null;
|
|
1836
|
+
};
|
|
1837
|
+
var isInConstrainedDir = (filePath, appliesTo) => {
|
|
1838
|
+
const normalizedPath = normalizePath(filePath);
|
|
1839
|
+
return appliesTo.some((dir) => {
|
|
1840
|
+
const normalizedDir = normalizePath(dir);
|
|
1841
|
+
return normalizedPath.startsWith(`${normalizedDir}/`) || normalizedPath === normalizedDir;
|
|
1842
|
+
});
|
|
1843
|
+
};
|
|
1844
|
+
var findPatternViolations = (content, patterns, filePath) => {
|
|
1845
|
+
const violations = [];
|
|
1846
|
+
const lines = content.split("\n");
|
|
1847
|
+
for (const { pattern, description, code } of patterns) {
|
|
1848
|
+
const regex = new RegExp(pattern, "g");
|
|
1849
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1850
|
+
if (regex.test(lines[i])) {
|
|
1851
|
+
violations.push({
|
|
1852
|
+
code,
|
|
1853
|
+
message: description,
|
|
1854
|
+
path: filePath,
|
|
1855
|
+
line: i + 1
|
|
1856
|
+
});
|
|
1857
|
+
}
|
|
1858
|
+
regex.lastIndex = 0;
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
return violations;
|
|
1862
|
+
};
|
|
1863
|
+
var validateFile = (file, layout) => {
|
|
1864
|
+
const violations = [];
|
|
1865
|
+
const forbiddenDir = matchesForbiddenDir(file.path, layout.pathGuards.forbidDirs);
|
|
1866
|
+
if (forbiddenDir) {
|
|
1867
|
+
violations.push({
|
|
1868
|
+
code: "FORBIDDEN_DIR",
|
|
1869
|
+
message: `Path '${file.path}' is in forbidden directory '${forbiddenDir}'`,
|
|
1870
|
+
path: file.path
|
|
1871
|
+
});
|
|
1872
|
+
}
|
|
1873
|
+
if (isInConstrainedDir(file.path, layout.codingConstraints.appliesTo)) {
|
|
1874
|
+
const patternViolations = findPatternViolations(
|
|
1875
|
+
file.content,
|
|
1876
|
+
layout.codingConstraints.forbiddenPatterns,
|
|
1877
|
+
file.path
|
|
1878
|
+
);
|
|
1879
|
+
violations.push(...patternViolations);
|
|
1880
|
+
}
|
|
1881
|
+
return violations;
|
|
1882
|
+
};
|
|
1883
|
+
var validateGeneratedFiles = (framework, files) => {
|
|
1884
|
+
const layout = serviceLayoutPolicy[framework];
|
|
1885
|
+
const violations = [];
|
|
1886
|
+
for (const file of files) {
|
|
1887
|
+
violations.push(...validateFile(file, layout));
|
|
1888
|
+
}
|
|
1889
|
+
return {
|
|
1890
|
+
valid: violations.length === 0,
|
|
1891
|
+
violations
|
|
1892
|
+
};
|
|
1893
|
+
};
|
|
1894
|
+
var getFrameworkPolicy = (framework) => serviceLayoutPolicy[framework];
|
|
1895
|
+
|
|
1896
|
+
// cli/src/core/flows/effect-builder.ts
|
|
1897
|
+
var buildFileEffects = (servicePath, files) => {
|
|
1898
|
+
return files.map((file) => ({
|
|
1899
|
+
kind: "file:write",
|
|
1900
|
+
path: (0, import_node_path11.join)(servicePath, file.path),
|
|
1901
|
+
content: file.content
|
|
1902
|
+
}));
|
|
1903
|
+
};
|
|
1904
|
+
var buildInfraEffect = (serviceName, port, template) => ({
|
|
1905
|
+
kind: "infra:add",
|
|
1906
|
+
serviceName,
|
|
1907
|
+
port,
|
|
1908
|
+
framework: template.framework,
|
|
1909
|
+
runtime: template.runtime
|
|
1910
|
+
});
|
|
1911
|
+
var buildEnvEffect = (envVarName, port) => ({
|
|
1912
|
+
kind: "env:add",
|
|
1913
|
+
key: `${envVarName}_PORT`,
|
|
1914
|
+
value: port.toString()
|
|
1915
|
+
});
|
|
1916
|
+
var generateUseCaseContent = (eventType, workspaceScope) => {
|
|
1917
|
+
const { typeName } = (0, import_cloudevents3.deriveEventNames)(eventType);
|
|
1918
|
+
const useCaseName = deriveUseCaseName(eventType);
|
|
1919
|
+
const layout = resolveServiceLayout("hono", useCaseName);
|
|
1920
|
+
return `import type { ${typeName} } from '${workspaceScope}/contracts'
|
|
1921
|
+
|
|
1922
|
+
/**
|
|
1923
|
+
* Handle ${eventType} event
|
|
1924
|
+
*
|
|
1925
|
+
* Business logic for processing the event.
|
|
1926
|
+
* Keep this pure where possible - side effects via returned data or adapters.
|
|
1927
|
+
*/
|
|
1928
|
+
export const ${layout.functionName} = async (data: ${typeName}): Promise<void> => {
|
|
1929
|
+
// TODO: Implement business logic
|
|
1930
|
+
// Example: validate data, transform, call external services
|
|
1931
|
+
}
|
|
1932
|
+
`;
|
|
1933
|
+
};
|
|
1934
|
+
var generateEventHandlerContent = (eventType, workspaceScope, framework) => {
|
|
1935
|
+
const { contractName, typeName } = (0, import_cloudevents3.deriveEventNames)(eventType);
|
|
1936
|
+
const useCaseName = deriveUseCaseName(eventType);
|
|
1937
|
+
const layout = resolveServiceLayout(framework, useCaseName);
|
|
1938
|
+
if (layout.delegateFromHandler) {
|
|
1939
|
+
return `import { handleEvent } from '@crossdelta/cloudevents'
|
|
1940
|
+
import { ${contractName}, type ${typeName} } from '${workspaceScope}/contracts'
|
|
1941
|
+
import { ${layout.functionName} } from '${layout.importFromEvents}'
|
|
1942
|
+
|
|
1943
|
+
export default handleEvent(${contractName}, async (data: ${typeName}) => {
|
|
1944
|
+
console.log('\u{1F4E6} [${eventType}] Event received:', data)
|
|
1945
|
+
await ${layout.functionName}(data)
|
|
1946
|
+
})
|
|
1947
|
+
`;
|
|
1948
|
+
}
|
|
1949
|
+
return `import { handleEvent } from '@crossdelta/cloudevents'
|
|
1950
|
+
import { ${contractName}, type ${typeName} } from '${workspaceScope}/contracts'
|
|
1951
|
+
|
|
1952
|
+
export default handleEvent(${contractName}, async (data: ${typeName}) => {
|
|
1953
|
+
console.log('\u{1F4E6} [${eventType}] Event received:', data)
|
|
1954
|
+
// TODO: Inject and call service
|
|
1955
|
+
})
|
|
1956
|
+
`;
|
|
1957
|
+
};
|
|
1958
|
+
var buildUseCaseEffects = (servicePath, vars, framework) => {
|
|
1959
|
+
if (!vars.hasEvents || vars.events.length === 0) {
|
|
1960
|
+
return [];
|
|
1961
|
+
}
|
|
1962
|
+
const layout = resolveServiceLayout(framework, "stub");
|
|
1963
|
+
if (!layout.delegateFromHandler) {
|
|
1964
|
+
return [];
|
|
1965
|
+
}
|
|
1966
|
+
return vars.events.map((eventType) => {
|
|
1967
|
+
const useCaseName = deriveUseCaseName(eventType);
|
|
1968
|
+
const resolved = resolveServiceLayout(framework, useCaseName);
|
|
1969
|
+
return {
|
|
1970
|
+
kind: "file:write",
|
|
1971
|
+
path: (0, import_node_path11.join)(servicePath, resolved.filePath),
|
|
1972
|
+
content: generateUseCaseContent(eventType, vars.workspaceScope)
|
|
1973
|
+
};
|
|
1974
|
+
});
|
|
1975
|
+
};
|
|
1976
|
+
var buildEventHandlerEffects = (servicePath, vars, framework) => {
|
|
1977
|
+
if (!vars.hasEvents || vars.events.length === 0) {
|
|
1978
|
+
return [];
|
|
1979
|
+
}
|
|
1980
|
+
return vars.events.map((eventType) => {
|
|
1981
|
+
const { kebab } = (0, import_cloudevents3.deriveEventNames)(eventType);
|
|
1982
|
+
const eventLayout = resolveEventLayout(framework, kebab);
|
|
1983
|
+
return {
|
|
1984
|
+
kind: "file:write",
|
|
1985
|
+
path: (0, import_node_path11.join)(servicePath, eventLayout.filePath),
|
|
1986
|
+
content: generateEventHandlerContent(eventType, vars.workspaceScope, framework)
|
|
1987
|
+
};
|
|
1988
|
+
});
|
|
1989
|
+
};
|
|
1990
|
+
var mapFramework = (templateFramework) => {
|
|
1991
|
+
if (templateFramework === "hono") return "hono";
|
|
1992
|
+
if (templateFramework === "nest") return "nestjs";
|
|
1993
|
+
return "hono";
|
|
1994
|
+
};
|
|
1995
|
+
var buildServiceEffects = (servicePath, files, vars, template) => {
|
|
1996
|
+
const framework = mapFramework(template.framework);
|
|
1997
|
+
const fileEffects = buildFileEffects(servicePath, files);
|
|
1998
|
+
const useCaseEffects = buildUseCaseEffects(servicePath, vars, framework);
|
|
1999
|
+
const eventHandlerEffects = buildEventHandlerEffects(servicePath, vars, framework);
|
|
2000
|
+
const infraEffect = buildInfraEffect(vars.serviceName, vars.port, template);
|
|
2001
|
+
const envEffect = buildEnvEffect(vars.envVarName, vars.port);
|
|
2002
|
+
return [...fileEffects, ...useCaseEffects, ...eventHandlerEffects, infraEffect, envEffect];
|
|
2003
|
+
};
|
|
2004
|
+
|
|
2005
|
+
// cli/src/core/flows/port-allocation.ts
|
|
2006
|
+
var import_node_fs8 = require("fs");
|
|
2007
|
+
var import_node_path12 = require("path");
|
|
2008
|
+
var INTERNAL_PORT_START = 4001;
|
|
2009
|
+
var extractPort = (content) => {
|
|
2010
|
+
const portsMatch = content.match(/ports\(\)\.(?:http|https|grpc|primary)\((\d+)\)/);
|
|
2011
|
+
if (portsMatch) {
|
|
2012
|
+
return Number.parseInt(portsMatch[1], 10);
|
|
2013
|
+
}
|
|
2014
|
+
const containerMatch = content.match(/containerPort:\s*(\d+)/);
|
|
2015
|
+
if (containerMatch) {
|
|
2016
|
+
return Number.parseInt(containerMatch[1], 10);
|
|
2017
|
+
}
|
|
2018
|
+
return null;
|
|
2019
|
+
};
|
|
2020
|
+
var scanUsedPorts = (workspaceRoot) => {
|
|
2021
|
+
try {
|
|
2022
|
+
const infraServicesPath = (0, import_node_path12.join)(workspaceRoot, "infra", "services");
|
|
2023
|
+
if (!(0, import_node_fs8.existsSync)(infraServicesPath)) {
|
|
2024
|
+
return /* @__PURE__ */ new Set();
|
|
2025
|
+
}
|
|
2026
|
+
const files = (0, import_node_fs8.readdirSync)(infraServicesPath).filter((f) => f.endsWith(".ts"));
|
|
2027
|
+
return files.reduce((ports, file) => {
|
|
2028
|
+
const content = (0, import_node_fs8.readFileSync)((0, import_node_path12.join)(infraServicesPath, file), "utf-8");
|
|
2029
|
+
const port = extractPort(content);
|
|
2030
|
+
if (port !== null) {
|
|
2031
|
+
ports.add(port);
|
|
2032
|
+
}
|
|
2033
|
+
return ports;
|
|
2034
|
+
}, /* @__PURE__ */ new Set());
|
|
2035
|
+
} catch (error) {
|
|
2036
|
+
console.warn("Failed to scan ports:", error);
|
|
2037
|
+
return /* @__PURE__ */ new Set();
|
|
2038
|
+
}
|
|
2039
|
+
};
|
|
2040
|
+
var allocateNextPort = (usedPorts, startPort) => {
|
|
2041
|
+
return usedPorts.has(startPort) ? allocateNextPort(usedPorts, startPort + 1) : startPort;
|
|
2042
|
+
};
|
|
2043
|
+
|
|
2044
|
+
// cli/src/core/flows/service-generation.flow.ts
|
|
2045
|
+
var validateServiceName = (name) => {
|
|
2046
|
+
if (!name || name.trim().length === 0) {
|
|
2047
|
+
throw new Error("Service name cannot be empty");
|
|
2048
|
+
}
|
|
2049
|
+
if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(name)) {
|
|
2050
|
+
throw new Error(`Service name '${name}' must be kebab-case (lowercase, alphanumeric, hyphens only)`);
|
|
2051
|
+
}
|
|
2052
|
+
};
|
|
2053
|
+
var resolveServicePath = (name, customPath, workspaceRoot) => {
|
|
2054
|
+
if (customPath) {
|
|
2055
|
+
if (customPath.startsWith("/")) {
|
|
2056
|
+
return customPath;
|
|
2057
|
+
}
|
|
2058
|
+
if (customPath.startsWith(".")) {
|
|
2059
|
+
return (0, import_node_path13.join)(workspaceRoot, customPath);
|
|
2060
|
+
}
|
|
2061
|
+
if (customPath.includes("/")) {
|
|
2062
|
+
return (0, import_node_path13.join)(workspaceRoot, customPath);
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
const paths = getWorkspacePathsConfig(workspaceRoot);
|
|
2066
|
+
return (0, import_node_path13.join)(workspaceRoot, paths.services, name);
|
|
2067
|
+
};
|
|
2068
|
+
var deriveStreamsFromEvents = (events) => [
|
|
2069
|
+
...new Set(events.map((e) => (0, import_cloudevents4.deriveEventNames)(e).streamName))
|
|
2070
|
+
];
|
|
2071
|
+
var buildTemplateVars = (inputs, workspaceRoot, servicePath, port) => {
|
|
2072
|
+
const contractsConfig = getContractsConfig(workspaceRoot);
|
|
2073
|
+
const workspaceScope = contractsConfig.packageName.split("/")[0];
|
|
2074
|
+
const events = inputs.events ?? [];
|
|
2075
|
+
const hasEvents = events.length > 0 || inputs.hasEvents || false;
|
|
2076
|
+
const streams = events.length > 0 ? deriveStreamsFromEvents(events) : inputs.streams ?? [];
|
|
2077
|
+
return {
|
|
2078
|
+
serviceName: inputs.name,
|
|
2079
|
+
packageName: `${workspaceScope}/${inputs.name}`,
|
|
2080
|
+
servicePath: servicePath.replace(`${workspaceRoot}/`, ""),
|
|
2081
|
+
port,
|
|
2082
|
+
envVarName: toEnvKey(inputs.name),
|
|
2083
|
+
hasEvents,
|
|
2084
|
+
streams,
|
|
2085
|
+
events,
|
|
2086
|
+
workspaceScope,
|
|
2087
|
+
envVars: inputs.envVars
|
|
2088
|
+
};
|
|
2089
|
+
};
|
|
2090
|
+
var generateFiles = (template, vars) => {
|
|
2091
|
+
return template.files.filter((f) => !f.skip || !f.skip(vars)).map((f) => ({
|
|
2092
|
+
path: f.path,
|
|
2093
|
+
content: f.content(vars)
|
|
2094
|
+
}));
|
|
2095
|
+
};
|
|
2096
|
+
var buildArtifacts = (files, serviceName, servicePath) => {
|
|
2097
|
+
const fileArtifacts = files.map((f) => ({
|
|
2098
|
+
type: "file",
|
|
2099
|
+
path: (0, import_node_path13.join)(servicePath, f.path),
|
|
2100
|
+
description: `Service file: ${f.path}`
|
|
2101
|
+
}));
|
|
2102
|
+
return [
|
|
2103
|
+
...fileArtifacts,
|
|
2104
|
+
{
|
|
2105
|
+
type: "config",
|
|
2106
|
+
path: `infra/services/${serviceName}.ts`,
|
|
2107
|
+
description: "Infrastructure configuration"
|
|
2108
|
+
},
|
|
2109
|
+
{
|
|
2110
|
+
type: "env",
|
|
2111
|
+
path: ".env.local",
|
|
2112
|
+
description: "Environment variables"
|
|
2113
|
+
}
|
|
2114
|
+
];
|
|
2115
|
+
};
|
|
2116
|
+
var buildNextActions = (servicePath) => {
|
|
2117
|
+
return [
|
|
2118
|
+
{
|
|
2119
|
+
command: `cd ${servicePath} && bun dev`,
|
|
2120
|
+
description: "Start this service"
|
|
2121
|
+
},
|
|
2122
|
+
{
|
|
2123
|
+
command: "pf dev",
|
|
2124
|
+
description: "Start all services"
|
|
2125
|
+
}
|
|
2126
|
+
];
|
|
2127
|
+
};
|
|
2128
|
+
var planServiceGeneration = (inputs) => {
|
|
2129
|
+
validateServiceName(inputs.name);
|
|
2130
|
+
const workspaceRoot = inputs.workspaceRoot ?? findWorkspaceRoot();
|
|
2131
|
+
const servicePath = resolveServicePath(inputs.name, inputs.path, workspaceRoot);
|
|
2132
|
+
const port = inputs.port ?? allocateNextPort(scanUsedPorts(workspaceRoot), INTERNAL_PORT_START);
|
|
2133
|
+
const vars = buildTemplateVars(inputs, workspaceRoot, servicePath, port);
|
|
2134
|
+
const template = selectTemplate(inputs.type);
|
|
2135
|
+
const files = generateFiles(template, vars);
|
|
2136
|
+
const effects = buildServiceEffects(servicePath, files, vars, template);
|
|
2137
|
+
const artifacts = buildArtifacts(files, inputs.name, servicePath);
|
|
2138
|
+
const next = buildNextActions(servicePath);
|
|
2139
|
+
return ok("service.generate", `Generated ${inputs.name} service (port ${port})`, {
|
|
2140
|
+
artifacts,
|
|
2141
|
+
changes: effects,
|
|
2142
|
+
next,
|
|
2143
|
+
data: {
|
|
2144
|
+
serviceName: inputs.name,
|
|
2145
|
+
servicePath,
|
|
2146
|
+
port,
|
|
2147
|
+
vars
|
|
2148
|
+
}
|
|
2149
|
+
});
|
|
2150
|
+
};
|
|
2151
|
+
|
|
2152
|
+
// cli/src/core/facade/index.ts
|
|
2153
|
+
init_config();
|
|
2154
|
+
|
|
2155
|
+
// cli/src/core/plugins/types.ts
|
|
2156
|
+
var isElicitInputEffect = (effect) => effect.kind === "elicit:input";
|
|
2157
|
+
|
|
2158
|
+
// cli/src/core/facade/context.ts
|
|
2159
|
+
init_config();
|
|
2160
|
+
var createContextFromWorkspace = async (workspaceRoot, logger) => {
|
|
2161
|
+
const root = workspaceRoot ?? findWorkspaceRoot();
|
|
2162
|
+
const contractsConfig = getContractsConfig(root);
|
|
2163
|
+
const availableServices = discoverAvailableServices(root);
|
|
2164
|
+
const workspace = {
|
|
2165
|
+
workspaceRoot: root,
|
|
2166
|
+
contracts: {
|
|
2167
|
+
path: contractsConfig.packagePath,
|
|
2168
|
+
packageName: contractsConfig.packageName
|
|
2169
|
+
},
|
|
2170
|
+
availableServices
|
|
2171
|
+
};
|
|
2172
|
+
return createPluginContext(workspace, logger);
|
|
2173
|
+
};
|
|
2174
|
+
var createContext = createPluginContext;
|
|
2175
|
+
|
|
2176
|
+
// cli/src/core/facade/tools.ts
|
|
2177
|
+
var argToProperty = (arg) => [
|
|
2178
|
+
arg.name,
|
|
2179
|
+
{ type: "string", description: arg.description }
|
|
2180
|
+
];
|
|
2181
|
+
var parseOptionName = (flags) => {
|
|
2182
|
+
const match = flags.match(/--([a-zA-Z0-9-]+)/);
|
|
2183
|
+
return match ? match[1] : null;
|
|
2184
|
+
};
|
|
2185
|
+
var optionToProperty = (opt) => {
|
|
2186
|
+
const name = parseOptionName(opt.flags);
|
|
2187
|
+
if (!name) return null;
|
|
2188
|
+
return [
|
|
2189
|
+
name,
|
|
2190
|
+
{
|
|
2191
|
+
type: "string",
|
|
2192
|
+
description: opt.description,
|
|
2193
|
+
...opt.default !== void 0 && { default: opt.default }
|
|
2194
|
+
}
|
|
2195
|
+
];
|
|
2196
|
+
};
|
|
2197
|
+
var buildInputSchema = (command) => {
|
|
2198
|
+
const argProperties = Object.fromEntries(command.args.map(argToProperty));
|
|
2199
|
+
const optionProperties = Object.fromEntries(
|
|
2200
|
+
command.options.map(optionToProperty).filter((entry) => entry !== null)
|
|
2201
|
+
);
|
|
2202
|
+
const required = command.args.filter((arg) => arg.required !== false).map((arg) => arg.name);
|
|
2203
|
+
return {
|
|
2204
|
+
type: "object",
|
|
2205
|
+
properties: { ...argProperties, ...optionProperties },
|
|
2206
|
+
...required.length > 0 && { required }
|
|
2207
|
+
};
|
|
2208
|
+
};
|
|
2209
|
+
var buildToolName = (pluginName, commandName) => `${pluginName}.${commandName}`;
|
|
2210
|
+
var commandToToolSpec = (plugin, command) => ({
|
|
2211
|
+
name: buildToolName(plugin.name, command.name),
|
|
2212
|
+
description: command.description,
|
|
2213
|
+
inputSchema: buildInputSchema(command)
|
|
2214
|
+
});
|
|
2215
|
+
var commandToExecutableToolSpec = (plugin, command) => ({
|
|
2216
|
+
...commandToToolSpec(plugin, command),
|
|
2217
|
+
execute: async (args) => {
|
|
2218
|
+
const result = await command.run(args, {});
|
|
2219
|
+
return {
|
|
2220
|
+
ok: true,
|
|
2221
|
+
operation: buildToolName(plugin.name, command.name),
|
|
2222
|
+
summary: formatOutput(result.output) ?? `Executed ${buildToolName(plugin.name, command.name)}`,
|
|
2223
|
+
effects: result.effects ?? [],
|
|
2224
|
+
output: result.output
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
2227
|
+
});
|
|
2228
|
+
var formatOutput = (output) => {
|
|
2229
|
+
if (!output) return void 0;
|
|
2230
|
+
return Array.isArray(output) ? output.join("\n") : output;
|
|
2231
|
+
};
|
|
2232
|
+
var collectToolSpecs = (plugins) => plugins.flatMap(({ plugin }) => plugin.commands.map((cmd) => commandToToolSpec(plugin, cmd)));
|
|
2233
|
+
var collectExecutableToolSpecs = (plugins, _context) => plugins.flatMap(({ plugin }) => plugin.commands.map((cmd) => commandToExecutableToolSpec(plugin, cmd)));
|
|
2234
|
+
|
|
2235
|
+
// cli/src/core/facade/index.ts
|
|
2236
|
+
var import_meta5 = {};
|
|
2237
|
+
var loadPlugins = async (moduleNames, context) => Promise.all(moduleNames.map((moduleName) => loadPluginFromModule(moduleName, context)));
|
|
2238
|
+
var runFlow = async (plugin, flowName, options) => {
|
|
2239
|
+
const flow = plugin.flows.find((f) => f.name === flowName);
|
|
2240
|
+
if (!flow) {
|
|
2241
|
+
throw new Error(`Flow '${flowName}' not found in plugin '${plugin.name}'`);
|
|
2242
|
+
}
|
|
2243
|
+
const steps = await flow.getSteps(options.initialContext);
|
|
2244
|
+
return (0, import_flowcore.runFlow)(steps, options);
|
|
2245
|
+
};
|
|
2246
|
+
var getGeneratorDocsDir = () => {
|
|
2247
|
+
if (typeof import_meta5?.url === "string") {
|
|
2248
|
+
const __filename = (0, import_node_url3.fileURLToPath)(import_meta5.url);
|
|
2249
|
+
const __dirname2 = (0, import_node_path14.dirname)(__filename);
|
|
2250
|
+
return (0, import_node_path14.join)(__dirname2, "../bin/docs/generators");
|
|
2251
|
+
}
|
|
2252
|
+
if (typeof __dirname !== "undefined") {
|
|
2253
|
+
return (0, import_node_path14.join)(__dirname, "../bin/docs/generators");
|
|
2254
|
+
}
|
|
2255
|
+
throw new Error("Cannot determine docs directory: neither import.meta.url nor __dirname available");
|
|
2256
|
+
};
|
|
2257
|
+
var listGeneratorDocs = async () => {
|
|
2258
|
+
const docsDir = getGeneratorDocsDir();
|
|
2259
|
+
const files = await (0, import_promises.readdir)(docsDir);
|
|
2260
|
+
return files.filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
|
|
2261
|
+
};
|
|
2262
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2263
|
+
0 && (module.exports = {
|
|
2264
|
+
SERVICE_LAYOUT_POLICY_VERSION,
|
|
2265
|
+
aggregateChanges,
|
|
2266
|
+
applyEffects,
|
|
2267
|
+
collectExecutableToolSpecs,
|
|
2268
|
+
collectToolSpecs,
|
|
2269
|
+
createContext,
|
|
2270
|
+
createContextFromWorkspace,
|
|
2271
|
+
createErrResult,
|
|
2272
|
+
createOkResult,
|
|
2273
|
+
deriveEventNames,
|
|
2274
|
+
findWorkspaceRoot,
|
|
2275
|
+
formatChangeDetails,
|
|
2276
|
+
formatChangeSummary,
|
|
2277
|
+
getFrameworkPolicy,
|
|
2278
|
+
getGeneratorDocsDir,
|
|
2279
|
+
getRegisteredProviders,
|
|
2280
|
+
isElicitInputEffect,
|
|
2281
|
+
listGeneratorDocs,
|
|
2282
|
+
loadCapabilitiesConfig,
|
|
2283
|
+
loadCapabilitiesConfigFromWorkspace,
|
|
2284
|
+
loadPlugins,
|
|
2285
|
+
lookupProvider,
|
|
2286
|
+
lowerSpecToCapabilities,
|
|
2287
|
+
parseServicePrompt,
|
|
2288
|
+
planServiceGeneration,
|
|
2289
|
+
resolveProvider,
|
|
2290
|
+
runFlow,
|
|
2291
|
+
serviceLayoutPolicy,
|
|
2292
|
+
validateGeneratedFiles
|
|
2293
|
+
});
|
|
2294
|
+
//# sourceMappingURL=facade.js.map
|