@openworkflow/cli 0.5.0 → 0.5.2

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/dist/cli.js CHANGED
@@ -2,14 +2,18 @@
2
2
  /* v8 ignore file -- @preserve */
3
3
  import { dashboard, doctor, getVersion, init, workerStart, } from "./commands.js";
4
4
  import { withErrorHandling } from "./errors.js";
5
- import { Command, Option } from "commander";
5
+ import { initializeTelemetry, shutdownTelemetry, trackCommand, } from "./telemetry.js";
6
+ import { Command, CommanderError, Option } from "commander";
6
7
  // openworkflow
7
8
  const program = new Command();
9
+ initializeTelemetry(program);
8
10
  program
9
11
  .name("openworkflow")
10
12
  .description("OpenWorkflow CLI - learn more at https://openworkflow.dev")
11
13
  .usage("<command> [options]")
12
- .version(getVersion());
14
+ .exitOverride()
15
+ .version(getVersion())
16
+ .option("--no-telemetry", "disable telemetry");
13
17
  // init
14
18
  program
15
19
  .command("init")
@@ -49,4 +53,15 @@ program
49
53
  .option("--config <path>", "path to OpenWorkflow config file")
50
54
  .option("--env-file <path>", "load environment variables from file")
51
55
  .action(withErrorHandling(dashboard));
52
- await program.parseAsync(process.argv);
56
+ try {
57
+ await program.parseAsync(process.argv);
58
+ }
59
+ catch (error) {
60
+ if (!(error instanceof CommanderError))
61
+ throw error;
62
+ process.exitCode = error.exitCode;
63
+ trackCommand();
64
+ }
65
+ finally {
66
+ await shutdownTelemetry();
67
+ }
@@ -20,6 +20,7 @@ export declare function getVersion(): string;
20
20
  /**
21
21
  * openworkflow init
22
22
  * @param options - Command options
23
+ * @returns Resolves when setup finishes.
23
24
  */
24
25
  export declare function init(options?: InitOptions): Promise<void>;
25
26
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../commands.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EAIb,MAAM,aAAa,CAAC;AAwCrB,KAAK,aAAa,GAAG,QAAQ,GAAG,UAAU,GAAG,MAAM,CAAC;AAEpD,UAAU,cAAc;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,WAAY,SAAQ,cAAc;IAC1C,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,UAAU,gBAAiB,SAAQ,cAAc;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAoBnC;AAED;;;GAGG;AAEH,wBAAsB,IAAI,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqJnE;AA8BD;;;GAGG;AACH,wBAAsB,MAAM,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2DxE;AAED,MAAM,MAAM,kBAAkB,GAAG,YAAY,GAAG,cAAc,CAAC;AAE/D;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,IAAI,CAAC,CA8Ef;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG;IACvD,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,YAAY,EAAE;QACZ,KAAK,EAAE,SAAS,CAAC;QACjB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;KACzB,CAAC;CACH,CAYA;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAavE;AAED;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6D7E;AA4PD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE,MAAM,EACf,cAAc,GAAE,MAAM,EAAO,GAC5B,MAAM,EAAE,CA8CV;AA2WD,UAAU,oBAAoB;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C;AAoCD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,QAAQ,CAAC,oBAAoB,CAAC,GAAG,IAAI,GACjD,MAAM,CAER;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,QAAQ,CAAC,oBAAoB,CAAC,GAAG,IAAI,GACjD,MAAM,CAER;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,WAAW,EAAE,QAAQ,CAAC,oBAAoB,CAAC,GAAG,IAAI,GACjD,MAAM,CAER;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,QAAQ,CAAC,oBAAoB,CAAC,GAAG,IAAI,GACjD,MAAM,CAER"}
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../commands.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EAIb,MAAM,aAAa,CAAC;AAyCrB,KAAK,aAAa,GAAG,QAAQ,GAAG,UAAU,GAAG,MAAM,CAAC;AAEpD,UAAU,cAAc;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,WAAY,SAAQ,cAAc;IAC1C,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,UAAU,gBAAiB,SAAQ,cAAc;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAoBnC;AAED;;;;GAIG;AAEH,wBAAsB,IAAI,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0JnE;AAkCD;;;GAGG;AACH,wBAAsB,MAAM,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2DxE;AAED,MAAM,MAAM,kBAAkB,GAAG,YAAY,GAAG,cAAc,CAAC;AAE/D;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,IAAI,CAAC,CA8Ef;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG;IACvD,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,YAAY,EAAE;QACZ,KAAK,EAAE,SAAS,CAAC;QACjB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;KACzB,CAAC;CACH,CAYA;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAavE;AAED;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6D7E;AA4PD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE,MAAM,EACf,cAAc,GAAE,MAAM,EAAO,GAC5B,MAAM,EAAE,CA8CV;AAuXD,UAAU,oBAAoB;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C;AAoCD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,QAAQ,CAAC,oBAAoB,CAAC,GAAG,IAAI,GACjD,MAAM,CAER;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,QAAQ,CAAC,oBAAoB,CAAC,GAAG,IAAI,GACjD,MAAM,CAER;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,WAAW,EAAE,QAAQ,CAAC,oBAAoB,CAAC,GAAG,IAAI,GACjD,MAAM,CAER;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,QAAQ,CAAC,oBAAoB,CAAC,GAAG,IAAI,GACjD,MAAM,CAER"}
package/dist/commands.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import { findConfigFile, loadConfigFromPath, } from "./config.js";
2
2
  import { CLIError, exit } from "./errors.js";
3
3
  import { createModuleLoader } from "./module-loader.js";
4
- import { CONFIG, HELLO_WORLD_RUNNER, HELLO_WORLD_WORKFLOW, POSTGRES_CLIENT, POSTGRES_PROD_SQLITE_DEV_CLIENT, SQLITE_CLIENT, } from "./templates.js";
4
+ import { trackCommand } from "./telemetry.js";
5
+ import { getConfigTemplate, HELLO_WORLD_RUNNER, HELLO_WORLD_WORKFLOW, POSTGRES_CLIENT, POSTGRES_PROD_SQLITE_DEV_CLIENT, SQLITE_CLIENT, } from "./templates.js";
5
6
  import * as p from "@clack/prompts";
6
7
  import { consola } from "consola";
7
- import { config as loadDotenv } from "dotenv";
8
+ import { config as loadDotenv, parse as parseDotenv } from "dotenv";
8
9
  import { spawn } from "node:child_process";
9
10
  import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync, } from "node:fs";
10
11
  import path from "node:path";
@@ -40,6 +41,7 @@ export function getVersion() {
40
41
  /**
41
42
  * openworkflow init
42
43
  * @param options - Command options
44
+ * @returns Resolves when setup finishes.
43
45
  */
44
46
  // oxlint-disable-next-line complexity
45
47
  export async function init(options = {}) {
@@ -61,7 +63,7 @@ export async function init(options = {}) {
61
63
  initialValue: false,
62
64
  });
63
65
  if (!shouldOverride || p.isCancel(shouldOverride))
64
- cancelSetup();
66
+ return cancelSetup();
65
67
  configFileToDelete = configFile;
66
68
  }
67
69
  const backendChoice = options.backend ??
@@ -87,7 +89,8 @@ export async function init(options = {}) {
87
89
  initialValue: "sqlite",
88
90
  }));
89
91
  if (typeof backendChoice === "symbol")
90
- cancelSetup();
92
+ return cancelSetup();
93
+ trackCommand(backendChoice);
91
94
  const spinner = p.spinner();
92
95
  // detect package manager & install packages
93
96
  spinner.start("Detecting package manager...");
@@ -98,7 +101,11 @@ export async function init(options = {}) {
98
101
  if (!packageJson) {
99
102
  throw new CLIError("No package.json found.", "Please create a package.json file first by running `npm init` or `npm init -y`.");
100
103
  }
101
- validateInitManifest(packageJson);
104
+ const configArg = options.config
105
+ ? ` --config '${options.config.replaceAll("'", String.raw `'\''`)}'`
106
+ : "";
107
+ const workerCommand = `npx @openworkflow/cli worker start${configArg}`;
108
+ validateInitManifest(packageJson, workerCommand);
102
109
  const configFileName = options.config ?? getConfigFileName(packageJson);
103
110
  const clientFileName = getClientFileName(packageJson);
104
111
  const exampleWorkflowFileName = getExampleWorkflowFileName(packageJson);
@@ -114,7 +121,7 @@ export async function init(options = {}) {
114
121
  initialValue: true,
115
122
  }));
116
123
  if (p.isCancel(shouldSetup))
117
- cancelSetup();
124
+ return cancelSetup();
118
125
  if (!shouldSetup) {
119
126
  p.outro("Setup skipped.");
120
127
  return;
@@ -151,16 +158,16 @@ export async function init(options = {}) {
151
158
  if (backendChoice === "postgres" || backendChoice === "both") {
152
159
  updateEnvForPostgres();
153
160
  }
154
- addWorkerScriptToPackageJson();
161
+ addWorkerScriptToPackageJson(workerCommand);
155
162
  // write config file last, so canceling earlier doesn't leave a config file
156
163
  // which would prevent re-running init
157
164
  createConfigFile(configFileName);
158
165
  // wrap up
159
- p.note(`➡️ Start a worker:\n$ npx @openworkflow/cli worker start\n\n➡️ Run the example workflow:\n$ ${runCommand}\n\n➡️ View the dashboard:\n$ npx @openworkflow/cli dashboard`, "Next steps");
166
+ p.note(`➡️ Start a worker:\n$ ${workerCommand}\n\n➡️ Run the example workflow:\n$ ${runCommand}\n\n➡️ View the dashboard:\n$ npx @openworkflow/cli dashboard${configArg}`, "Next steps");
160
167
  p.outro("✅ Setup complete!");
161
168
  }
162
169
  // Validate the manifest fields that init reads or updates.
163
- function validateInitManifest(manifest) {
170
+ function validateInitManifest(manifest, workerCommand) {
164
171
  if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
165
172
  throw new CLIError("Invalid package.json: expected an object.");
166
173
  }
@@ -177,7 +184,9 @@ function validateInitManifest(manifest) {
177
184
  }
178
185
  const { scripts } = manifest;
179
186
  const worker = scripts?.["worker"];
180
- if (worker !== undefined && worker !== "npx @openworkflow/cli worker start") {
187
+ if (worker !== undefined &&
188
+ worker !== "npx @openworkflow/cli worker start" &&
189
+ worker !== workerCommand) {
181
190
  throw new CLIError("Setup would overwrite package.json scripts.worker.");
182
191
  }
183
192
  }
@@ -380,11 +389,11 @@ export async function dashboard(options = {}) {
380
389
  // -----------------------------------------------------------------------------
381
390
  /**
382
391
  * Show a canceled-setup message and exit the process with status 0.
392
+ * @returns Never resolves because the process exits.
383
393
  */
384
394
  function cancelSetup() {
385
395
  p.cancel("Setup canceled.");
386
- // oxlint-disable-next-line unicorn/no-process-exit
387
- process.exit(0);
396
+ return exit(0);
388
397
  }
389
398
  /**
390
399
  * Exercise backend initialization, connectivity, and workflow table access.
@@ -707,9 +716,15 @@ function createConfigFile(configFileName) {
707
716
  const spinner = p.spinner();
708
717
  spinner.start("Writing config...");
709
718
  const configDestPath = path.resolve(process.cwd(), configFileName);
719
+ const relativeClientPath = path
720
+ .relative(path.dirname(configDestPath), path.join(process.cwd(), "openworkflow/client.js"))
721
+ .replaceAll(path.sep, "/");
722
+ const clientImport = relativeClientPath.startsWith("../")
723
+ ? relativeClientPath
724
+ : `./${relativeClientPath}`;
710
725
  // mkdir if the user specified a config file, and they want it in a dir
711
726
  mkdirSync(path.dirname(configDestPath), { recursive: true });
712
- writeFileSync(configDestPath, CONFIG, "utf8");
727
+ writeFileSync(configDestPath, getConfigTemplate(clientImport), "utf8");
713
728
  spinner.stop(`Config written to ${configDestPath}`);
714
729
  }
715
730
  /**
@@ -777,8 +792,9 @@ function updateGitignoreForSqlite() {
777
792
  }
778
793
  /**
779
794
  * Add worker script to package.json.
795
+ * @param workerCommand - Worker command including any custom config path.
780
796
  */
781
- function addWorkerScriptToPackageJson() {
797
+ function addWorkerScriptToPackageJson(workerCommand) {
782
798
  const packageJsonPath = path.join(process.cwd(), "package.json");
783
799
  if (!existsSync(packageJsonPath)) {
784
800
  return;
@@ -788,7 +804,7 @@ function addWorkerScriptToPackageJson() {
788
804
  try {
789
805
  const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
790
806
  packageJson.scripts ??= {};
791
- packageJson.scripts["worker"] = "npx @openworkflow/cli worker start";
807
+ packageJson.scripts["worker"] = workerCommand;
792
808
  writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, "utf8");
793
809
  spinner.stop('Added "worker" script to package.json');
794
810
  }
@@ -798,17 +814,17 @@ function addWorkerScriptToPackageJson() {
798
814
  }
799
815
  }
800
816
  /**
801
- * Append a line to a file if no existing line matches. Creates the file if it
802
- * doesn't exist.
817
+ * Append a line if the file does not already contain the desired entry.
818
+ * Creates the file if it doesn't exist.
803
819
  * @param filePath - Path to the file
804
820
  * @param line - Line to append (without a trailing newline)
805
- * @param matchesExisting - Predicate that returns true when an existing line
806
- * should be treated as already representing `line`
821
+ * @param matchesExisting - Predicate that checks whether the file contents
822
+ * already contain the desired entry
807
823
  * @returns Whether the line was appended
808
824
  */
809
825
  function appendLineIfMissing(filePath, line, matchesExisting) {
810
826
  const content = existsSync(filePath) ? readFileSync(filePath, "utf8") : "";
811
- if (content.split("\n").some((existing) => matchesExisting(existing))) {
827
+ if (matchesExisting(content)) {
812
828
  return false;
813
829
  }
814
830
  const separator = content === "" || content.endsWith("\n") ? "" : "\n";
@@ -823,7 +839,7 @@ function appendLineIfMissing(filePath, line, matchesExisting) {
823
839
  * @returns Whether the entry was appended
824
840
  */
825
841
  function ensureGitignoreEntry(gitignorePath, entry) {
826
- return appendLineIfMissing(gitignorePath, entry, (line) => line.trim() === entry);
842
+ return appendLineIfMissing(gitignorePath, entry, (content) => content.split("\n").some((line) => line.trim() === entry));
827
843
  }
828
844
  /**
829
845
  * Add OPENWORKFLOW_POSTGRES_URL to .env file.
@@ -861,7 +877,9 @@ function findConfigWithEnv(options) {
861
877
  async function loadConfigWithEnv(options) {
862
878
  const configPath = findConfigWithEnv(options);
863
879
  try {
864
- return await loadConfigFromPath(configPath ?? "openworkflow.config.ts");
880
+ const loaded = await loadConfigFromPath(configPath ?? "openworkflow.config.ts");
881
+ trackCommand(loaded.config.backend);
882
+ return loaded;
865
883
  }
866
884
  catch (error) {
867
885
  const message = error instanceof Error ? error.message : String(error);
@@ -946,10 +964,7 @@ function hasDependency(packageJson, name) {
946
964
  * @returns Whether the entry was appended
947
965
  */
948
966
  function ensureEnvEntry(envPath, key, value) {
949
- return appendLineIfMissing(envPath, `${key}=${value}`, (line) => {
950
- const trimmed = line.trim();
951
- return trimmed.startsWith(`${key}=`) || trimmed.startsWith(`${key} =`);
952
- });
967
+ return appendLineIfMissing(envPath, `${key}=${value}`, (content) => Object.hasOwn(parseDotenv(content), key));
953
968
  }
954
969
  /**
955
970
  * Validate a numeric option is a positive integer.
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../errors.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,qBAAa,QAAS,SAAQ,KAAK;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;gBAExB,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;CAK7C;AAED;;;GAGG;AACH,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAWvD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,OAAO,EAAE,EACnD,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GACvC,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAiB/B"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../errors.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,qBAAa,QAAS,SAAQ,KAAK;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;gBAExB,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;CAK7C;AAED;;;GAGG;AACH,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAYvD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,OAAO,EAAE,EACnD,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GACvC,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAiB/B"}
package/dist/errors.js CHANGED
@@ -1,4 +1,5 @@
1
1
  /* v8 ignore file -- @preserve */
2
+ import { shutdownTelemetry } from "./telemetry.js";
2
3
  import { consola } from "consola";
3
4
  /**
4
5
  * User-facing CLI error.
@@ -16,6 +17,7 @@ export class CLIError extends Error {
16
17
  * @param code - Process exit code
17
18
  */
18
19
  export async function exit(code) {
20
+ await shutdownTelemetry();
19
21
  await Promise.all([process.stdout, process.stderr].map((stream) => new Promise((resolve) => {
20
22
  stream.end(resolve);
21
23
  })));
@@ -0,0 +1,16 @@
1
+ import type { Command } from "commander";
2
+ import type { Backend } from "openworkflow/internal";
3
+ /**
4
+ * Set up telemetry before the CLI prints output.
5
+ * @param program - Root CLI command
6
+ */
7
+ export declare function initializeTelemetry(program: Command): void;
8
+ /**
9
+ * Track after loading config or choosing an init backend.
10
+ * Help, version, and argument errors are tracked without a backend.
11
+ * @param backend - Configured backend or init selection
12
+ */
13
+ export declare function trackCommand(backend?: Backend | "sqlite" | "postgres" | "both"): void;
14
+ /** Send queued events before the CLI exits. */
15
+ export declare function shutdownTelemetry(): Promise<void>;
16
+ //# sourceMappingURL=telemetry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../telemetry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKzC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAoBrD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAsC1D;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,OAAO,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,UAAU,GAAG,MAAM,GACjD,IAAI,CAoCN;AAED,+CAA+C;AAC/C,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAMvD"}
@@ -0,0 +1,101 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import path from "node:path";
5
+ import { PostHog } from "posthog-node";
6
+ let telemetry;
7
+ const backendTypes = new Map([
8
+ ["BackendSqlite", "sqlite"],
9
+ ["BackendPostgres", "postgres"],
10
+ ["sqlite", "sqlite"],
11
+ ["postgres", "postgres"],
12
+ ["both", "both"],
13
+ ]);
14
+ /**
15
+ * Set up telemetry before the CLI prints output.
16
+ * @param program - Root CLI command
17
+ */
18
+ export function initializeTelemetry(program) {
19
+ telemetry = undefined;
20
+ if (process.env["DO_NOT_TRACK"] ||
21
+ process.env["OPENWORKFLOW_TELEMETRY_DISABLED"] ||
22
+ process.env["CI"] ||
23
+ process.argv.includes("--no-telemetry"))
24
+ return;
25
+ try {
26
+ const directory = path.join(homedir(), ".openworkflow");
27
+ const file = path.join(directory, "telemetry-id");
28
+ mkdirSync(directory, { recursive: true });
29
+ if (!existsSync(file)) {
30
+ writeFileSync(file, randomUUID(), { flag: "wx", mode: 0o600 });
31
+ console.error("OpenWorkflow collects CLI usage via PostHog. Set DO_NOT_TRACK=1 to opt out.\n" +
32
+ "https://openworkflow.dev/docs/cli#telemetry\n");
33
+ }
34
+ const distinctId = readFileSync(file, "utf8").trim();
35
+ if (!distinctId)
36
+ return;
37
+ const state = {
38
+ program,
39
+ distinctId,
40
+ client: new PostHog("phc_C1Cm1NAKHDFA3eKLVqcYzR5wUk8WhSbMMXltk5Qj1Ye"),
41
+ };
42
+ program.on("beforeAllHelp", ({ command }) => {
43
+ state.helpCommand = command;
44
+ });
45
+ program.on("option:version", () => {
46
+ state.version = true;
47
+ });
48
+ telemetry = state;
49
+ }
50
+ catch {
51
+ // ignore, telemetry is optional
52
+ }
53
+ }
54
+ /**
55
+ * Track after loading config or choosing an init backend.
56
+ * Help, version, and argument errors are tracked without a backend.
57
+ * @param backend - Configured backend or init selection
58
+ */
59
+ export function trackCommand(backend) {
60
+ const state = telemetry;
61
+ if (!state)
62
+ return;
63
+ try {
64
+ let command = state.helpCommand ?? state.program;
65
+ let child;
66
+ while ((child = command.commands.find((candidate) => candidate.name() === command.args[0]))) {
67
+ command = child;
68
+ }
69
+ const backendName = typeof backend === "string" ? backend : backend?.constructor.name;
70
+ const commandName = command.parent?.parent
71
+ ? `${command.parent.name()} ${command.name()}`
72
+ : command.name();
73
+ state.client.capture({
74
+ distinctId: state.distinctId,
75
+ event: "cli_command_invoked",
76
+ properties: {
77
+ command: state.version ? "version" : commandName,
78
+ backend: backendName
79
+ ? (backendTypes.get(backendName) ?? "custom")
80
+ : undefined,
81
+ help: state.helpCommand ? true : undefined,
82
+ cli_version: state.program.version(),
83
+ os: process.platform,
84
+ arch: process.arch,
85
+ node_version: process.versions.node,
86
+ },
87
+ });
88
+ }
89
+ catch {
90
+ // ignore, telemetry is optional
91
+ }
92
+ }
93
+ /** Send queued events before the CLI exits. */
94
+ export async function shutdownTelemetry() {
95
+ try {
96
+ await telemetry?.client.shutdown();
97
+ }
98
+ catch {
99
+ // ignore, telemetry is optional
100
+ }
101
+ }
@@ -1,7 +1,12 @@
1
1
  export declare const SQLITE_CLIENT = "import { BackendSqlite } from \"openworkflow/sqlite\";\nimport { OpenWorkflow } from \"openworkflow\";\n\nexport const backend = BackendSqlite.connect(\"openworkflow/backend.db\");\nexport const ow = new OpenWorkflow({ backend });\n";
2
2
  export declare const POSTGRES_CLIENT = "import { BackendPostgres } from \"openworkflow/postgres\";\nimport { OpenWorkflow } from \"openworkflow\";\n\nexport const backend = await BackendPostgres.connect(\n process.env[\"OPENWORKFLOW_POSTGRES_URL\"],\n);\nexport const ow = new OpenWorkflow({ backend });\n";
3
3
  export declare const POSTGRES_PROD_SQLITE_DEV_CLIENT = "import { BackendPostgres } from \"openworkflow/postgres\";\nimport { BackendSqlite } from \"openworkflow/sqlite\";\nimport { OpenWorkflow } from \"openworkflow\";\n\nexport const backend =\n process.env[\"NODE_ENV\"] === \"production\"\n ? await BackendPostgres.connect(process.env[\"OPENWORKFLOW_POSTGRES_URL\"])\n : BackendSqlite.connect(\"openworkflow/backend.db\");\nexport const ow = new OpenWorkflow({ backend });\n";
4
- export declare const CONFIG = "import { backend } from \"./openworkflow/client.js\";\nimport { defineConfig } from \"@openworkflow/cli\";\n\nexport default defineConfig({\n backend,\n dirs: \"./openworkflow\",\n ignorePatterns: [\"**/*.run.*\"],\n});\n";
4
+ /**
5
+ * Render config paths relative to the config file's directory.
6
+ * @param clientImport - Relative import path to the scaffolded client.
7
+ * @returns The config template string.
8
+ */
9
+ export declare function getConfigTemplate(clientImport: string): string;
5
10
  export declare const HELLO_WORLD_WORKFLOW = "import { defineWorkflow } from \"openworkflow\";\n\n/**\n * Example workflow that greets the world.\n *\n * This workflow is auto-discovered by the CLI worker.\n * To trigger it, use ow.runWorkflow() from your app:\n * ```ts\n * import { ow } from \"./openworkflow/client.js\";\n * import { helloWorld } from \"./openworkflow/hello-world.js\";\n * const handle = await ow.runWorkflow(helloWorld.spec, {});\n * const result = await handle.result();\n * ```\n */\nexport const helloWorld = defineWorkflow(\n { name: \"hello-world\" },\n async ({ step }) => {\n const greeting = await step.run({ name: \"greet\" }, () => {\n return \"Hello, World!\";\n });\n\n await step.sleep(\"wait-a-bit\", \"1s\");\n\n return { greeting };\n },\n);\n";
6
11
  export declare const HELLO_WORLD_RUNNER = "import { backend, ow } from \"./client.js\";\nimport { helloWorld } from \"./hello-world.js\";\n\n// Run this file:\n// JS: `node openworkflow/hello-world.run.js`\n// TS: `npx tsx openworkflow/hello-world.run.ts`\n//\n// This file is a temporary dev helper generated by\n// `npx @openworkflow/cli init` so you can quickly run the example workflow.\n//\n// Copy the code anywhere in your app (e.g., an API route or a script) and\n// modify it to run your workflows.\n//\n// You can delete this file once you're done with it.\n\nconsole.log(\"Running hello-world workflow...\");\nconst handle = await ow.runWorkflow(helloWorld.spec, {});\n\nconsole.log(\"Waiting for result...\");\nconst result = await handle.result(); // for very short workflows only\n\nconsole.log(`Workflow result: ${JSON.stringify(result, null, 2)}`);\n\n// Stop backend. Only needed if this is a standalone script that you want to\n// exit after running the workflow.\nawait backend.stop();\n";
7
12
  //# sourceMappingURL=templates.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../templates.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa,6OAKzB,CAAC;AAEF,eAAO,MAAM,eAAe,+QAO3B,CAAC;AAEF,eAAO,MAAM,+BAA+B,ibAS3C,CAAC;AAEF,eAAO,MAAM,MAAM,qOAQlB,CAAC;AAEF,eAAO,MAAM,oBAAoB,wvBA0BhC,CAAC;AAEF,eAAO,MAAM,kBAAkB,w8BA0B9B,CAAC"}
1
+ {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../templates.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,aAAa,6OAKzB,CAAC;AAEF,eAAO,MAAM,eAAe,+QAO3B,CAAC;AAEF,eAAO,MAAM,+BAA+B,ibAS3C,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAU9D;AAED,eAAO,MAAM,oBAAoB,wvBA0BhC,CAAC;AAEF,eAAO,MAAM,kBAAkB,w8BA0B9B,CAAC"}
package/dist/templates.js CHANGED
@@ -1,3 +1,4 @@
1
+ import path from "node:path";
1
2
  export const SQLITE_CLIENT = `import { BackendSqlite } from "openworkflow/sqlite";
2
3
  import { OpenWorkflow } from "openworkflow";
3
4
 
@@ -22,15 +23,22 @@ export const backend =
22
23
  : BackendSqlite.connect("openworkflow/backend.db");
23
24
  export const ow = new OpenWorkflow({ backend });
24
25
  `;
25
- export const CONFIG = `import { backend } from "./openworkflow/client.js";
26
+ /**
27
+ * Render config paths relative to the config file's directory.
28
+ * @param clientImport - Relative import path to the scaffolded client.
29
+ * @returns The config template string.
30
+ */
31
+ export function getConfigTemplate(clientImport) {
32
+ return `import { backend } from ${JSON.stringify(clientImport)};
26
33
  import { defineConfig } from "@openworkflow/cli";
27
34
 
28
35
  export default defineConfig({
29
36
  backend,
30
- dirs: "./openworkflow",
37
+ dirs: ${JSON.stringify(path.posix.dirname(clientImport))},
31
38
  ignorePatterns: ["**/*.run.*"],
32
39
  });
33
40
  `;
41
+ }
34
42
  export const HELLO_WORLD_WORKFLOW = `import { defineWorkflow } from "openworkflow";
35
43
 
36
44
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openworkflow/cli",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -32,7 +32,8 @@
32
32
  "consola": "^3.4.2",
33
33
  "dotenv": "^17.4.2",
34
34
  "jiti": "^2.7.0",
35
- "nypm": "^0.6.10"
35
+ "nypm": "^0.6.10",
36
+ "posthog-node": "~5.21.2"
36
37
  },
37
38
  "devDependencies": {
38
39
  "openworkflow": "*",