@fluojs/cli 1.1.0 → 2.0.1

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.
Files changed (44) hide show
  1. package/README.ko.md +16 -3
  2. package/README.md +16 -3
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +4 -3
  5. package/dist/commands/generate.d.ts +1 -49
  6. package/dist/commands/generate.d.ts.map +1 -1
  7. package/dist/commands/generate.js +1 -214
  8. package/dist/commands/inspect.d.ts +0 -6
  9. package/dist/commands/inspect.d.ts.map +1 -1
  10. package/dist/commands/inspect.js +1 -48
  11. package/dist/commands/new.d.ts +0 -6
  12. package/dist/commands/new.d.ts.map +1 -1
  13. package/dist/commands/new.js +14 -88
  14. package/dist/commands/scripts.d.ts +1 -1
  15. package/dist/commands/scripts.d.ts.map +1 -1
  16. package/dist/commands/scripts.js +11 -5
  17. package/dist/dev-runner/node-restart-runner.d.ts +6 -0
  18. package/dist/dev-runner/node-restart-runner.d.ts.map +1 -1
  19. package/dist/dev-runner/node-restart-runner.js +10 -6
  20. package/dist/generate-command.d.ts +50 -0
  21. package/dist/generate-command.d.ts.map +1 -0
  22. package/dist/generate-command.js +214 -0
  23. package/dist/index.d.ts +3 -3
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +3 -3
  26. package/dist/new/scaffold.d.ts.map +1 -1
  27. package/dist/new/scaffold.js +115 -23
  28. package/dist/new/types.d.ts +0 -2
  29. package/dist/new/types.d.ts.map +1 -1
  30. package/dist/public-generate.d.ts +2 -0
  31. package/dist/public-generate.d.ts.map +1 -0
  32. package/dist/public-generate.js +1 -0
  33. package/dist/public-inspect.d.ts +13 -0
  34. package/dist/public-inspect.d.ts.map +1 -0
  35. package/dist/public-inspect.js +16 -0
  36. package/dist/public-new.d.ts +13 -0
  37. package/dist/public-new.d.ts.map +1 -0
  38. package/dist/public-new.js +16 -0
  39. package/dist/studio/sidecar.d.ts.map +1 -1
  40. package/dist/studio/sidecar.js +71 -5
  41. package/dist/usage.d.ts +13 -0
  42. package/dist/usage.d.ts.map +1 -0
  43. package/dist/usage.js +131 -0
  44. package/package.json +3 -3
@@ -1050,13 +1050,36 @@ const codec = JSONCodec();
1050
1050
 
1051
1051
  class LazyNatsTransport implements MicroserviceTransport {
1052
1052
  private connection: NatsConnection | undefined;
1053
+ private initializing: Promise<NatsMicroserviceTransport> | undefined;
1053
1054
  private transport: NatsMicroserviceTransport | undefined;
1054
1055
 
1055
1056
  async close() {
1056
- await this.transport?.close();
1057
- await this.connection?.close();
1058
- this.transport = undefined;
1059
- this.connection = undefined;
1057
+ const transport = this.initializing ? await this.initializing.catch(() => undefined) : this.transport;
1058
+ let closeError: unknown;
1059
+ let closeFailed = false;
1060
+ try {
1061
+ await transport?.close();
1062
+ } catch (error) {
1063
+ closeError = error;
1064
+ closeFailed = true;
1065
+ } finally {
1066
+ try {
1067
+ await this.connection?.close();
1068
+ } catch (error) {
1069
+ if (!closeFailed) {
1070
+ closeError = error;
1071
+ closeFailed = true;
1072
+ }
1073
+ } finally {
1074
+ this.initializing = undefined;
1075
+ this.transport = undefined;
1076
+ this.connection = undefined;
1077
+ }
1078
+ }
1079
+
1080
+ if (closeFailed) {
1081
+ throw closeError;
1082
+ }
1060
1083
  }
1061
1084
 
1062
1085
  async emit(pattern: string, payload: unknown) {
@@ -1076,6 +1099,19 @@ class LazyNatsTransport implements MicroserviceTransport {
1076
1099
  return this.transport;
1077
1100
  }
1078
1101
 
1102
+ this.initializing ??= this.createTransport();
1103
+ try {
1104
+ return await this.initializing;
1105
+ } finally {
1106
+ this.initializing = undefined;
1107
+ }
1108
+ }
1109
+
1110
+ private async createTransport() {
1111
+ if (this.transport) {
1112
+ return this.transport;
1113
+ }
1114
+
1079
1115
  const connection = await connect({
1080
1116
  name: 'fluo-microservice-starter',
1081
1117
  servers,
@@ -1149,18 +1185,24 @@ const responseTopic = process.env.KAFKA_RESPONSE_TOPIC ?? 'fluo.microservices.re
1149
1185
 
1150
1186
  class LazyKafkaTransport implements MicroserviceTransport {
1151
1187
  private consumer: Consumer | undefined;
1188
+ private initializing: Promise<KafkaMicroserviceTransport> | undefined;
1152
1189
  private producer: Producer | undefined;
1153
1190
  private transport: KafkaMicroserviceTransport | undefined;
1154
1191
 
1155
1192
  async close() {
1156
- await this.transport?.close();
1157
- await Promise.all([
1158
- this.consumer?.disconnect().catch(() => undefined),
1159
- this.producer?.disconnect().catch(() => undefined),
1160
- ]);
1161
- this.consumer = undefined;
1162
- this.producer = undefined;
1163
- this.transport = undefined;
1193
+ const transport = this.initializing ? await this.initializing.catch(() => undefined) : this.transport;
1194
+ try {
1195
+ await transport?.close();
1196
+ } finally {
1197
+ await Promise.all([
1198
+ this.consumer?.disconnect().catch(() => undefined),
1199
+ this.producer?.disconnect().catch(() => undefined),
1200
+ ]);
1201
+ this.initializing = undefined;
1202
+ this.consumer = undefined;
1203
+ this.producer = undefined;
1204
+ this.transport = undefined;
1205
+ }
1164
1206
  }
1165
1207
 
1166
1208
  async emit(pattern: string, payload: unknown) {
@@ -1180,6 +1222,19 @@ class LazyKafkaTransport implements MicroserviceTransport {
1180
1222
  return this.transport;
1181
1223
  }
1182
1224
 
1225
+ this.initializing ??= this.createTransport();
1226
+ try {
1227
+ return await this.initializing;
1228
+ } finally {
1229
+ this.initializing = undefined;
1230
+ }
1231
+ }
1232
+
1233
+ private async createTransport() {
1234
+ if (this.transport) {
1235
+ return this.transport;
1236
+ }
1237
+
1183
1238
  const kafka = new Kafka({
1184
1239
  brokers,
1185
1240
  clientId,
@@ -1187,13 +1242,24 @@ class LazyKafkaTransport implements MicroserviceTransport {
1187
1242
  });
1188
1243
  const producer = kafka.producer();
1189
1244
  const consumer = kafka.consumer({ groupId: consumerGroup });
1190
- await Promise.all([producer.connect(), consumer.connect()]);
1245
+ this.producer = producer;
1246
+ this.consumer = consumer;
1247
+ try {
1248
+ await producer.connect();
1249
+ await consumer.connect();
1250
+ } catch (error) {
1251
+ await Promise.all([
1252
+ consumer.disconnect().catch(() => undefined),
1253
+ producer.disconnect().catch(() => undefined),
1254
+ ]);
1255
+ this.consumer = undefined;
1256
+ this.producer = undefined;
1257
+ throw error;
1258
+ }
1191
1259
 
1192
1260
  const handlers = new Map<string, (message: string) => Promise<void> | void>();
1193
1261
  let consumerRunning = false;
1194
1262
 
1195
- this.producer = producer;
1196
- this.consumer = consumer;
1197
1263
  this.transport = new KafkaMicroserviceTransport({
1198
1264
  consumer: {
1199
1265
  async subscribe(topic: string, handler: (message: string) => Promise<void> | void) {
@@ -1277,15 +1343,21 @@ const responseQueue = process.env.RABBITMQ_RESPONSE_QUEUE ?? 'fluo.microservices
1277
1343
  class LazyRabbitMqTransport implements MicroserviceTransport {
1278
1344
  private channel: Awaited<ReturnType<Awaited<ReturnType<typeof connect>>['createConfirmChannel']>> | undefined;
1279
1345
  private connection: Awaited<ReturnType<typeof connect>> | undefined;
1346
+ private initializing: Promise<RabbitMqMicroserviceTransport> | undefined;
1280
1347
  private transport: RabbitMqMicroserviceTransport | undefined;
1281
1348
 
1282
1349
  async close() {
1283
- await this.transport?.close();
1284
- await this.channel?.close().catch(() => undefined);
1285
- await this.connection?.close().catch(() => undefined);
1286
- this.channel = undefined;
1287
- this.connection = undefined;
1288
- this.transport = undefined;
1350
+ const transport = this.initializing ? await this.initializing.catch(() => undefined) : this.transport;
1351
+ try {
1352
+ await transport?.close();
1353
+ } finally {
1354
+ await this.channel?.close().catch(() => undefined);
1355
+ await this.connection?.close().catch(() => undefined);
1356
+ this.initializing = undefined;
1357
+ this.channel = undefined;
1358
+ this.connection = undefined;
1359
+ this.transport = undefined;
1360
+ }
1289
1361
  }
1290
1362
 
1291
1363
  async emit(pattern: string, payload: unknown) {
@@ -1305,11 +1377,31 @@ class LazyRabbitMqTransport implements MicroserviceTransport {
1305
1377
  return this.transport;
1306
1378
  }
1307
1379
 
1380
+ this.initializing ??= this.createTransport();
1381
+ try {
1382
+ return await this.initializing;
1383
+ } finally {
1384
+ this.initializing = undefined;
1385
+ }
1386
+ }
1387
+
1388
+ private async createTransport() {
1389
+ if (this.transport) {
1390
+ return this.transport;
1391
+ }
1392
+
1308
1393
  const connection = await connect(url);
1309
- const channel = await connection.createConfirmChannel();
1394
+ this.connection = connection;
1395
+ let channel: Awaited<ReturnType<typeof connection.createConfirmChannel>>;
1396
+ try {
1397
+ channel = await connection.createConfirmChannel();
1398
+ } catch (error) {
1399
+ await connection.close().catch(() => undefined);
1400
+ this.connection = undefined;
1401
+ throw error;
1402
+ }
1310
1403
  const consumerTags = new Map<string, string>();
1311
1404
 
1312
- this.connection = connection;
1313
1405
  this.channel = channel;
1314
1406
  this.transport = new RabbitMqMicroserviceTransport({
1315
1407
  consumer: {
@@ -53,11 +53,9 @@ export interface BootstrapAnswers extends BootstrapSchema {
53
53
  }
54
54
  /** Programmatic overrides for `runNewCommand(...)`. */
55
55
  export interface NewCommandOptions {
56
- dependencySource?: DependencySource;
57
56
  force?: boolean;
58
57
  initializeGit?: boolean;
59
58
  installDependencies?: boolean;
60
- repoRoot?: string;
61
59
  skipInstall?: boolean;
62
60
  }
63
61
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/new/types.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAC7D,yDAAyD;AACzD,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,WAAW,CAAC;AACrD,+CAA+C;AAC/C,MAAM,MAAM,cAAc,GAAG,aAAa,GAAG,cAAc,GAAG,OAAO,CAAC;AACtE,iEAAiE;AACjE,MAAM,MAAM,gBAAgB,GAAG,KAAK,GAAG,oBAAoB,GAAG,MAAM,GAAG,MAAM,CAAC;AAC9E,kEAAkE;AAClE,MAAM,MAAM,iBAAiB,GAAG,KAAK,GAAG,oBAAoB,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAC;AAClH,mEAAmE;AACnE,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,eAAe,GAAG,KAAK,CAAC;AACpH,gEAAgE;AAChE,MAAM,MAAM,sBAAsB,GAAG,UAAU,CAAC;AAEhD,0DAA0D;AAC1D,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,gBAAgB,CAAC;CACxB;AAED,mEAAmE;AACnE,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,OAAO,EAAE,gBAAgB,CAAC;IAC1B,KAAK,EAAE,cAAc,CAAC;IACtB,OAAO,EAAE,sBAAsB,CAAC;IAChC,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,SAAS,EAAE,kBAAkB,CAAC;CAC/B;AAED,2EAA2E;AAC3E,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACvD,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,cAAc,EAAE,cAAc,CAAC;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,yDAAyD;AACzD,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,gBAAgB,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,mFAAmF;AACnF,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACvD,aAAa,EAAE,OAAO,CAAC;IACvB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,cAAc,EAAE,cAAc,CAAC;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,uDAAuD;AACvD,MAAM,WAAW,iBAAiB;IAChC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/new/types.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAC7D,yDAAyD;AACzD,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,WAAW,CAAC;AACrD,+CAA+C;AAC/C,MAAM,MAAM,cAAc,GAAG,aAAa,GAAG,cAAc,GAAG,OAAO,CAAC;AACtE,iEAAiE;AACjE,MAAM,MAAM,gBAAgB,GAAG,KAAK,GAAG,oBAAoB,GAAG,MAAM,GAAG,MAAM,CAAC;AAC9E,kEAAkE;AAClE,MAAM,MAAM,iBAAiB,GAAG,KAAK,GAAG,oBAAoB,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAC;AAClH,mEAAmE;AACnE,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,eAAe,GAAG,KAAK,CAAC;AACpH,gEAAgE;AAChE,MAAM,MAAM,sBAAsB,GAAG,UAAU,CAAC;AAEhD,0DAA0D;AAC1D,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,gBAAgB,CAAC;CACxB;AAED,mEAAmE;AACnE,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,OAAO,EAAE,gBAAgB,CAAC;IAC1B,KAAK,EAAE,cAAc,CAAC;IACtB,OAAO,EAAE,sBAAsB,CAAC;IAChC,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,SAAS,EAAE,kBAAkB,CAAC;CAC/B;AAED,2EAA2E;AAC3E,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACvD,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,cAAc,EAAE,cAAc,CAAC;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,yDAAyD;AACzD,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,gBAAgB,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,mFAAmF;AACnF,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACvD,aAAa,EAAE,OAAO,CAAC;IACvB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,cAAc,EAAE,cAAc,CAAC;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,uDAAuD;AACvD,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB"}
@@ -0,0 +1,2 @@
1
+ export { type GeneratePlanAction, type GeneratePlanEntry, type GenerateResult, runGenerateCommand } from './generate-command.js';
2
+ //# sourceMappingURL=public-generate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public-generate.d.ts","sourceRoot":"","sources":["../src/public-generate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,KAAK,cAAc,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1 @@
1
+ export { runGenerateCommand } from './generate-command.js';
@@ -0,0 +1,13 @@
1
+ import type { InspectCommandRuntimeOptions } from './commands/inspect.js';
2
+ import { inspectUsage } from './usage.js';
3
+ export type { InspectCommandRuntimeOptions } from './commands/inspect.js';
4
+ export { inspectUsage };
5
+ /**
6
+ * Runs the inspect command through a lazy implementation import.
7
+ *
8
+ * @param argv Command arguments after `inspect`.
9
+ * @param runtime Runtime overrides for programmatic callers.
10
+ * @returns Process-style exit code from the inspect command.
11
+ */
12
+ export declare function runInspectCommand(argv: string[], runtime?: InspectCommandRuntimeOptions): Promise<number>;
13
+ //# sourceMappingURL=public-inspect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public-inspect.d.ts","sourceRoot":"","sources":["../src/public-inspect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,YAAY,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,CAAC;AAExB;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,4BAAiC,GAAG,OAAO,CAAC,MAAM,CAAC,CAGnH"}
@@ -0,0 +1,16 @@
1
+ import { inspectUsage } from './usage.js';
2
+ export { inspectUsage };
3
+
4
+ /**
5
+ * Runs the inspect command through a lazy implementation import.
6
+ *
7
+ * @param argv Command arguments after `inspect`.
8
+ * @param runtime Runtime overrides for programmatic callers.
9
+ * @returns Process-style exit code from the inspect command.
10
+ */
11
+ export async function runInspectCommand(argv, runtime = {}) {
12
+ const {
13
+ runInspectCommand: runInspectCommandImplementation
14
+ } = await import('./commands/inspect.js');
15
+ return runInspectCommandImplementation(argv, runtime);
16
+ }
@@ -0,0 +1,13 @@
1
+ import type { NewCommandRuntimeOptions } from './commands/new.js';
2
+ import { newUsage } from './usage.js';
3
+ export type { NewCommandRuntimeOptions } from './commands/new.js';
4
+ export { newUsage };
5
+ /**
6
+ * Runs the new command through a lazy implementation import.
7
+ *
8
+ * @param argv Command arguments after `new` or `create`.
9
+ * @param runtime Runtime overrides for programmatic callers.
10
+ * @returns Process-style exit code from the new command.
11
+ */
12
+ export declare function runNewCommand(argv: string[], runtime?: NewCommandRuntimeOptions): Promise<number>;
13
+ //# sourceMappingURL=public-new.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public-new.d.ts","sourceRoot":"","sources":["../src/public-new.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,YAAY,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,CAAC;AAEpB;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,wBAA6B,GAAG,OAAO,CAAC,MAAM,CAAC,CAG3G"}
@@ -0,0 +1,16 @@
1
+ import { newUsage } from './usage.js';
2
+ export { newUsage };
3
+
4
+ /**
5
+ * Runs the new command through a lazy implementation import.
6
+ *
7
+ * @param argv Command arguments after `new` or `create`.
8
+ * @param runtime Runtime overrides for programmatic callers.
9
+ * @returns Process-style exit code from the new command.
10
+ */
11
+ export async function runNewCommand(argv, runtime = {}) {
12
+ const {
13
+ runNewCommand: runNewCommandImplementation
14
+ } = await import('./commands/new.js');
15
+ return runNewCommandImplementation(argv, runtime);
16
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"sidecar.d.ts","sourceRoot":"","sources":["../../src/studio/sidecar.ts"],"names":[],"mappings":"AAOA;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;AAEvE;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,oBAAoB,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAwQD;;;;;GAKG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,GAAE,oBAAyB,GAAG,OAAO,CAAC,aAAa,CAAC,CAmLnG"}
1
+ {"version":3,"file":"sidecar.d.ts","sourceRoot":"","sources":["../../src/studio/sidecar.ts"],"names":[],"mappings":"AAOA;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;AAEvE;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,oBAAoB,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AA4UD;;;;;GAKG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,GAAE,oBAAyB,GAAG,OAAO,CAAC,aAAa,CAAC,CAyLnG"}
@@ -21,10 +21,36 @@ const DEFAULT_HOST = '127.0.0.1';
21
21
  const DEFAULT_HEARTBEAT_MS = 15_000;
22
22
  const MAX_EVENT_REPLAY = 1_000;
23
23
  const MAX_REQUEST_BYTES = 1_048_576;
24
+ const BODY_LIKE_PAYLOAD_FIELDS = new Set(['body', 'headers', 'payload', 'rawBody', 'requestBody', 'responseBody']);
24
25
  const require = createRequire(import.meta.url);
25
26
  function isRecord(value) {
26
27
  return typeof value === 'object' && value !== null;
27
28
  }
29
+ function findBodyLikePayloadField(value, path = 'payload') {
30
+ if (Array.isArray(value)) {
31
+ for (const [index, item] of value.entries()) {
32
+ const match = findBodyLikePayloadField(item, `${path}[${String(index)}]`);
33
+ if (match) {
34
+ return match;
35
+ }
36
+ }
37
+ return undefined;
38
+ }
39
+ if (!isRecord(value)) {
40
+ return undefined;
41
+ }
42
+ for (const [key, nestedValue] of Object.entries(value)) {
43
+ const nestedPath = `${path}.${key}`;
44
+ if (BODY_LIKE_PAYLOAD_FIELDS.has(key)) {
45
+ return nestedPath;
46
+ }
47
+ const match = findBodyLikePayloadField(nestedValue, nestedPath);
48
+ if (match) {
49
+ return match;
50
+ }
51
+ }
52
+ return undefined;
53
+ }
28
54
  function isRestartEpochBoundary(incoming) {
29
55
  if (incoming.type !== 'restart' || !isRecord(incoming.payload)) {
30
56
  return false;
@@ -60,20 +86,53 @@ function createDefaultAppId() {
60
86
  }
61
87
  function readBody(request) {
62
88
  return new Promise((resolve, reject) => {
89
+ let settled = false;
63
90
  let body = '';
64
91
  request.setEncoding('utf8');
65
- request.on('data', chunk => {
92
+ const settle = (action, value) => {
93
+ if (settled) {
94
+ return;
95
+ }
96
+ settled = true;
97
+ request.off('data', onData);
98
+ request.off('end', onEnd);
99
+ request.off('error', onError);
100
+ request.off('close', onClose);
101
+ if (action === 'resolve') {
102
+ resolve(value);
103
+ } else {
104
+ reject(value);
105
+ }
106
+ };
107
+ const onData = chunk => {
66
108
  body += chunk;
67
109
  if (body.length > MAX_REQUEST_BYTES) {
68
- reject(new Error('Studio event payload is too large.'));
110
+ settle('reject', new Error('Studio event payload is too large.'));
69
111
  request.destroy();
70
112
  }
71
- });
72
- request.on('end', () => resolve(body));
73
- request.on('error', reject);
113
+ };
114
+ const onEnd = () => settle('resolve', body);
115
+ const onError = error => settle('reject', error);
116
+ // A client that closes the socket after sending only a partial request body
117
+ // may never emit `end` or `error`. Bind `close` to body-reader cancellation
118
+ // so the sidecar cannot hang on a malformed local client indefinitely.
119
+ const onClose = () => {
120
+ if (body.length === 0) {
121
+ settle('reject', new Error('Studio sidecar request closed before any body was received.'));
122
+ return;
123
+ }
124
+ settle('reject', new Error('Studio sidecar request closed before the full body was received.'));
125
+ };
126
+ request.on('data', onData);
127
+ request.on('end', onEnd);
128
+ request.on('error', onError);
129
+ request.on('close', onClose);
74
130
  });
75
131
  }
76
132
  function writeJson(response, statusCode, payload) {
133
+ if (response.writableEnded) {
134
+ return;
135
+ }
77
136
  response.writeHead(statusCode, {
78
137
  'cache-control': 'no-store',
79
138
  'content-type': 'application/json; charset=utf-8'
@@ -335,6 +394,13 @@ export async function startStudioSidecar(options = {}) {
335
394
  });
336
395
  return;
337
396
  }
397
+ const bodyLikeField = findBodyLikePayloadField(parsed.payload);
398
+ if (bodyLikeField) {
399
+ writeJson(response, 400, {
400
+ error: `Studio runtime event payload must not include body-like field ${bodyLikeField}.`
401
+ });
402
+ return;
403
+ }
338
404
  const event = publish(parsed);
339
405
  writeJson(response, 202, {
340
406
  accepted: true,
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Renders CLI help text for `fluo new` without importing the scaffold implementation.
3
+ *
4
+ * @returns Stable help output for the scaffolding command.
5
+ */
6
+ export declare function newUsage(): string;
7
+ /**
8
+ * Returns the usage information string for the inspect command without importing runtime inspection logic.
9
+ *
10
+ * @returns Formatted help text including usage and options.
11
+ */
12
+ export declare function inspectUsage(): string;
13
+ //# sourceMappingURL=usage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"AAiIA;;;;GAIG;AACH,wBAAgB,QAAQ,IAAI,MAAM,CA0BjC;AAED;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAarC"}
package/dist/usage.js ADDED
@@ -0,0 +1,131 @@
1
+ import { renderAliasList, renderHelpTable } from './help.js';
2
+ const NEW_OPTION_HELP = [{
3
+ aliases: [],
4
+ description: 'Provide the project name without using the positional argument.',
5
+ option: '--name <project-name>'
6
+ }, {
7
+ aliases: [],
8
+ description: 'Select the scaffold shape explicitly (application for HTTP, microservice for the transport-driven starter path, mixed for the API + microservice starter).',
9
+ option: '--shape <application|microservice|mixed>'
10
+ }, {
11
+ aliases: [],
12
+ description: 'Select the transport path explicitly (http for applications, tcp for the runnable microservice starter, plus shipped microservice starter transports).',
13
+ option: '--transport <http|tcp|redis-streams|nats|kafka|rabbitmq|mqtt|grpc>'
14
+ }, {
15
+ aliases: [],
16
+ description: 'Select the runtime explicitly (node, bun, deno, or cloudflare-workers for application starters; node for microservice and mixed starters).',
17
+ option: '--runtime <node|bun|deno|cloudflare-workers>'
18
+ }, {
19
+ aliases: [],
20
+ description: 'Select the platform adapter explicitly (fastify, express, or nodejs on node; bun/deno/cloudflare-workers on their native runtimes; none for microservices).',
21
+ option: '--platform <fastify|express|nodejs|bun|deno|cloudflare-workers|none>'
22
+ }, {
23
+ aliases: [],
24
+ description: 'Select the starter tooling preset explicitly (currently only standard).',
25
+ option: '--tooling <standard>'
26
+ }, {
27
+ aliases: [],
28
+ description: 'Select the starter topology mode explicitly (currently only single-package).',
29
+ option: '--topology <single-package>'
30
+ }, {
31
+ aliases: [],
32
+ description: 'Choose which package manager installs the starter dependencies.',
33
+ option: '--package-manager <pnpm|npm|yarn|bun>'
34
+ }, {
35
+ aliases: [],
36
+ description: 'Write the new app to a custom target directory (always overrides positional name path).',
37
+ option: '--target-directory <path>'
38
+ }, {
39
+ aliases: [],
40
+ description: 'Overwrite files in a non-empty target directory without prompting.',
41
+ option: '--force'
42
+ }, {
43
+ aliases: [],
44
+ description: 'Install starter dependencies after writing files.',
45
+ option: '--install'
46
+ }, {
47
+ aliases: [],
48
+ description: 'Skip starter dependency installation.',
49
+ option: '--no-install'
50
+ }, {
51
+ aliases: [],
52
+ description: 'Initialize a git repository in the generated starter.',
53
+ option: '--git'
54
+ }, {
55
+ aliases: [],
56
+ description: 'Skip git repository initialization in the generated starter.',
57
+ option: '--no-git'
58
+ }, {
59
+ aliases: [],
60
+ description: 'Print the resolved scaffold plan without writing files, installing dependencies, or initializing git.',
61
+ option: '--print-plan'
62
+ }, {
63
+ aliases: ['-h'],
64
+ description: 'Show help for the new command.',
65
+ option: '--help'
66
+ }];
67
+ const INSPECT_OPTION_HELP = [{
68
+ aliases: [],
69
+ description: 'Emit the runtime platform snapshot/diagnostics payload as JSON (default when no output mode is selected).',
70
+ option: '--json'
71
+ }, {
72
+ aliases: [],
73
+ description: 'Emit a Mermaid graph through the optional @fluojs/studio rendering contract.',
74
+ option: '--mermaid'
75
+ }, {
76
+ aliases: [],
77
+ description: 'Include bootstrap timing diagnostics next to JSON inspect output.',
78
+ option: '--timing'
79
+ }, {
80
+ aliases: [],
81
+ description: 'Emit a CI-friendly JSON report with summary, snapshot, diagnostics, and timing.',
82
+ option: '--report'
83
+ }, {
84
+ aliases: [],
85
+ description: 'Write the selected inspect payload to a file instead of stdout.',
86
+ option: '--output <path>'
87
+ }, {
88
+ aliases: [],
89
+ description: 'Select the exported module symbol name (default: AppModule).',
90
+ option: '--export <name>'
91
+ }, {
92
+ aliases: ['-h'],
93
+ description: 'Show help for the inspect command.',
94
+ option: '--help'
95
+ }];
96
+
97
+ /**
98
+ * Renders CLI help text for `fluo new` without importing the scaffold implementation.
99
+ *
100
+ * @returns Stable help output for the scaffolding command.
101
+ */
102
+ export function newUsage() {
103
+ return ['Usage: fluo new|create [project-name] [options]', '', 'Options', renderHelpTable(NEW_OPTION_HELP, [{
104
+ header: 'Option',
105
+ render: entry => entry.option
106
+ }, {
107
+ header: 'Aliases',
108
+ render: entry => renderAliasList(entry.aliases)
109
+ }, {
110
+ header: 'Description',
111
+ render: entry => entry.description
112
+ }]), '', 'Next steps:', ' cd <app-name>', ' pnpm dev # runs fluo dev from the generated package.json script', '', 'Docs: https://github.com/fluojs/fluo/tree/main/docs/getting-started/quick-start.md'].join('\n');
113
+ }
114
+
115
+ /**
116
+ * Returns the usage information string for the inspect command without importing runtime inspection logic.
117
+ *
118
+ * @returns Formatted help text including usage and options.
119
+ */
120
+ export function inspectUsage() {
121
+ return ['Usage: fluo inspect <module-path> [options]', '', 'Options', renderHelpTable(INSPECT_OPTION_HELP, [{
122
+ header: 'Option',
123
+ render: entry => entry.option
124
+ }, {
125
+ header: 'Aliases',
126
+ render: entry => renderAliasList(entry.aliases)
127
+ }, {
128
+ header: 'Description',
129
+ render: entry => entry.description
130
+ }]), '', 'Docs: https://github.com/fluojs/fluo/tree/main/docs/getting-started/quick-start.md'].join('\n');
131
+ }
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "migration",
10
10
  "diagnostics"
11
11
  ],
12
- "version": "1.1.0",
12
+ "version": "2.0.1",
13
13
  "private": false,
14
14
  "license": "MIT",
15
15
  "repository": {
@@ -44,10 +44,10 @@
44
44
  "ejs": "^3.1.10",
45
45
  "tsx": "^4.20.4",
46
46
  "typescript": "^6.0.2",
47
- "@fluojs/runtime": "^1.1.8"
47
+ "@fluojs/runtime": "^2.0.1"
48
48
  },
49
49
  "peerDependencies": {
50
- "@fluojs/studio": "^1.0.7"
50
+ "@fluojs/studio": "^1.0.8"
51
51
  },
52
52
  "peerDependenciesMeta": {
53
53
  "@fluojs/studio": {