@openworkflow/cli 0.4.4 → 0.5.0
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 +13 -2
- package/dist/commands.d.ts +9 -1
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +274 -264
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +22 -8
- package/dist/errors.d.ts +5 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +14 -7
- package/dist/module-loader.d.ts +9 -0
- package/dist/module-loader.d.ts.map +1 -0
- package/dist/module-loader.js +17 -0
- package/package.json +8 -8
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
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 } from "commander";
|
|
5
|
+
import { Command, Option } from "commander";
|
|
6
6
|
// openworkflow
|
|
7
7
|
const program = new Command();
|
|
8
8
|
program
|
|
@@ -14,13 +14,22 @@ program
|
|
|
14
14
|
program
|
|
15
15
|
.command("init")
|
|
16
16
|
.description("initialize OpenWorkflow")
|
|
17
|
+
.addOption(new Option("--backend <backend>", "backend to configure").choices([
|
|
18
|
+
"sqlite",
|
|
19
|
+
"postgres",
|
|
20
|
+
"both",
|
|
21
|
+
]))
|
|
22
|
+
.option("-y, --yes", "skip prompts (requires --backend; does not allow overwrites)")
|
|
23
|
+
.option("--skip-install", "create project files without installing dependencies")
|
|
17
24
|
.option("--config <path>", "path to OpenWorkflow config file")
|
|
25
|
+
.option("--env-file <path>", "load environment variables from file")
|
|
18
26
|
.action(withErrorHandling(init));
|
|
19
27
|
// doctor
|
|
20
28
|
program
|
|
21
29
|
.command("doctor")
|
|
22
|
-
.description("check
|
|
30
|
+
.description("check worker prerequisites")
|
|
23
31
|
.option("--config <path>", "path to OpenWorkflow config file")
|
|
32
|
+
.option("--env-file <path>", "load environment variables from file")
|
|
24
33
|
.action(withErrorHandling(doctor));
|
|
25
34
|
// worker
|
|
26
35
|
const workerCmd = program.command("worker").description("manage workers");
|
|
@@ -30,6 +39,7 @@ workerCmd
|
|
|
30
39
|
.description("start a worker to process workflows")
|
|
31
40
|
.option("-c, --concurrency <number>", "number of concurrent workflows to process", Number.parseInt)
|
|
32
41
|
.option("--config <path>", "path to OpenWorkflow config file")
|
|
42
|
+
.option("--env-file <path>", "load environment variables from file")
|
|
33
43
|
.action(withErrorHandling(workerStart));
|
|
34
44
|
// dashboard
|
|
35
45
|
program
|
|
@@ -37,5 +47,6 @@ program
|
|
|
37
47
|
.description("start the dashboard to view workflow runs")
|
|
38
48
|
.option("-p, --port <number>", "custom port for the dashboard server", Number.parseInt)
|
|
39
49
|
.option("--config <path>", "path to OpenWorkflow config file")
|
|
50
|
+
.option("--env-file <path>", "load environment variables from file")
|
|
40
51
|
.action(withErrorHandling(dashboard));
|
|
41
52
|
await program.parseAsync(process.argv);
|
package/dist/commands.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { WorkerConfig } from "./config.js";
|
|
2
|
+
type BackendChoice = "sqlite" | "postgres" | "both";
|
|
2
3
|
interface CommandOptions {
|
|
3
4
|
config?: string;
|
|
5
|
+
envFile?: string;
|
|
6
|
+
}
|
|
7
|
+
interface InitOptions extends CommandOptions {
|
|
8
|
+
backend?: BackendChoice;
|
|
9
|
+
yes?: boolean;
|
|
10
|
+
skipInstall?: boolean;
|
|
4
11
|
}
|
|
5
12
|
interface DashboardOptions extends CommandOptions {
|
|
6
13
|
port?: number;
|
|
@@ -14,7 +21,7 @@ export declare function getVersion(): string;
|
|
|
14
21
|
* openworkflow init
|
|
15
22
|
* @param options - Command options
|
|
16
23
|
*/
|
|
17
|
-
export declare function init(options?:
|
|
24
|
+
export declare function init(options?: InitOptions): Promise<void>;
|
|
18
25
|
/**
|
|
19
26
|
* openworkflow doctor
|
|
20
27
|
* @param options - Command options
|
|
@@ -63,6 +70,7 @@ export declare function dashboard(options?: DashboardOptions): Promise<void>;
|
|
|
63
70
|
*/
|
|
64
71
|
export declare function discoverWorkflowFiles(dirs: string[], baseDir: string, ignorePatterns?: string[]): string[];
|
|
65
72
|
interface PackageJsonForDoctor {
|
|
73
|
+
scripts?: Record<string, string>;
|
|
66
74
|
dependencies?: Record<string, string>;
|
|
67
75
|
devDependencies?: Record<string, string>;
|
|
68
76
|
}
|
package/dist/commands.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../commands.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
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"}
|
package/dist/commands.js
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { CLIError } from "./errors.js";
|
|
1
|
+
import { findConfigFile, loadConfigFromPath, } from "./config.js";
|
|
2
|
+
import { CLIError, exit } from "./errors.js";
|
|
3
|
+
import { createModuleLoader } from "./module-loader.js";
|
|
3
4
|
import { CONFIG, HELLO_WORLD_RUNNER, HELLO_WORLD_WORKFLOW, POSTGRES_CLIENT, POSTGRES_PROD_SQLITE_DEV_CLIENT, SQLITE_CLIENT, } from "./templates.js";
|
|
4
5
|
import * as p from "@clack/prompts";
|
|
5
6
|
import { consola } from "consola";
|
|
6
7
|
import { config as loadDotenv } from "dotenv";
|
|
7
|
-
import { createJiti } from "jiti";
|
|
8
8
|
import { spawn } from "node:child_process";
|
|
9
9
|
import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
10
10
|
import path from "node:path";
|
|
11
11
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
12
|
-
import { addDependency, detectPackageManager } from "nypm";
|
|
12
|
+
import { addDependency, addDependencyCommand, detectPackageManager, } from "nypm";
|
|
13
13
|
import { OpenWorkflow } from "openworkflow";
|
|
14
14
|
import { isWorkflow } from "openworkflow/internal";
|
|
15
15
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const workflowSources = new WeakMap();
|
|
16
17
|
/**
|
|
17
18
|
* openworkflow -V | --version
|
|
18
19
|
* @returns the version string, or "-" if it cannot be determined
|
|
@@ -40,94 +41,107 @@ export function getVersion() {
|
|
|
40
41
|
* openworkflow init
|
|
41
42
|
* @param options - Command options
|
|
42
43
|
*/
|
|
44
|
+
// oxlint-disable-next-line complexity
|
|
43
45
|
export async function init(options = {}) {
|
|
44
|
-
|
|
46
|
+
if (options.yes && !options.backend) {
|
|
47
|
+
throw new CLIError("--backend is required with --yes.");
|
|
48
|
+
}
|
|
49
|
+
if (!options.yes && !process.stdin.isTTY) {
|
|
50
|
+
throw new CLIError("Interactive setup requires a terminal. Pass --backend sqlite|postgres|both --yes.");
|
|
51
|
+
}
|
|
45
52
|
p.intro("Initializing OpenWorkflow...");
|
|
46
|
-
const
|
|
53
|
+
const configFile = findConfigWithEnv(options);
|
|
47
54
|
let configFileToDelete = null;
|
|
48
|
-
if (configFile) {
|
|
55
|
+
if (configFile && existsSync(configFile)) {
|
|
56
|
+
if (options.yes) {
|
|
57
|
+
throw new CLIError(`Config file already exists at ${configFile}. --yes does not allow overwrites.`);
|
|
58
|
+
}
|
|
49
59
|
const shouldOverride = await p.confirm({
|
|
50
60
|
message: `Config file already exists at ${configFile}. Override it?`,
|
|
51
61
|
initialValue: false,
|
|
52
62
|
});
|
|
53
|
-
if (!shouldOverride || p.isCancel(shouldOverride))
|
|
54
|
-
|
|
55
|
-
// eslint-disable-next-line unicorn/no-process-exit
|
|
56
|
-
process.exit(0);
|
|
57
|
-
}
|
|
63
|
+
if (!shouldOverride || p.isCancel(shouldOverride))
|
|
64
|
+
cancelSetup();
|
|
58
65
|
configFileToDelete = configFile;
|
|
59
66
|
}
|
|
60
|
-
const backendChoice =
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
process.exit(0);
|
|
85
|
-
}
|
|
67
|
+
const backendChoice = options.backend ??
|
|
68
|
+
(await p.select({
|
|
69
|
+
message: "Select a backend for OpenWorkflow:",
|
|
70
|
+
options: [
|
|
71
|
+
{
|
|
72
|
+
value: "sqlite",
|
|
73
|
+
label: "SQLite",
|
|
74
|
+
hint: "Recommended for testing and development",
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
value: "postgres",
|
|
78
|
+
label: "PostgreSQL",
|
|
79
|
+
hint: "Recommended for production",
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
value: "both",
|
|
83
|
+
label: "Both",
|
|
84
|
+
hint: "SQLite for dev, PostgreSQL for production",
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
initialValue: "sqlite",
|
|
88
|
+
}));
|
|
89
|
+
if (typeof backendChoice === "symbol")
|
|
90
|
+
cancelSetup();
|
|
86
91
|
const spinner = p.spinner();
|
|
87
92
|
// detect package manager & install packages
|
|
88
93
|
spinner.start("Detecting package manager...");
|
|
89
94
|
const pm = await detectPackageManager(process.cwd());
|
|
90
|
-
const packageManager = pm?.name ?? "
|
|
95
|
+
const packageManager = pm?.name ?? "npm";
|
|
91
96
|
spinner.stop(`Using ${packageManager}`);
|
|
92
97
|
const packageJson = readPackageJsonForDoctor();
|
|
93
98
|
if (!packageJson) {
|
|
94
99
|
throw new CLIError("No package.json found.", "Please create a package.json file first by running `npm init` or `npm init -y`.");
|
|
95
100
|
}
|
|
96
|
-
|
|
101
|
+
validateInitManifest(packageJson);
|
|
102
|
+
const configFileName = options.config ?? getConfigFileName(packageJson);
|
|
97
103
|
const clientFileName = getClientFileName(packageJson);
|
|
98
104
|
const exampleWorkflowFileName = getExampleWorkflowFileName(packageJson);
|
|
99
105
|
const runFileName = getRunFileName(packageJson);
|
|
100
106
|
const runCommand = runFileName.endsWith(".ts")
|
|
101
107
|
? `npx tsx openworkflow/${runFileName}`
|
|
102
108
|
: `node openworkflow/${runFileName}`;
|
|
103
|
-
const shouldSetup =
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
const shouldSetup = options.yes ??
|
|
110
|
+
(await p.confirm({
|
|
111
|
+
message: options.skipInstall
|
|
112
|
+
? "Set up project files?"
|
|
113
|
+
: "Install packages and set up project files?",
|
|
114
|
+
initialValue: true,
|
|
115
|
+
}));
|
|
116
|
+
if (p.isCancel(shouldSetup))
|
|
117
|
+
cancelSetup();
|
|
112
118
|
if (!shouldSetup) {
|
|
113
119
|
p.outro("Setup skipped.");
|
|
114
120
|
return;
|
|
115
121
|
}
|
|
116
|
-
|
|
117
|
-
|
|
122
|
+
const dependencies = getDependenciesToInstall(backendChoice);
|
|
123
|
+
const devDependencies = getDevDependenciesToInstall();
|
|
124
|
+
if (options.skipInstall) {
|
|
125
|
+
p.note([
|
|
126
|
+
addDependencyCommand(packageManager, dependencies),
|
|
127
|
+
addDependencyCommand(packageManager, devDependencies, { dev: true }),
|
|
128
|
+
].join("\n"), "Install dependencies before running OpenWorkflow");
|
|
118
129
|
}
|
|
119
|
-
{
|
|
120
|
-
const dependencies = getDependenciesToInstall(backendChoice);
|
|
130
|
+
else {
|
|
121
131
|
spinner.start(`Installing ${dependencies.join(", ")}...`);
|
|
122
|
-
await addDependency(dependencies, { silent: true });
|
|
132
|
+
await addDependency(dependencies, { silent: true, packageManager });
|
|
123
133
|
spinner.stop(`Installed ${dependencies.join(", ")}`);
|
|
124
|
-
}
|
|
125
|
-
{
|
|
126
|
-
const devDependencies = getDevDependenciesToInstall();
|
|
127
134
|
spinner.start(`Installing ${devDependencies.join(", ")}...`);
|
|
128
|
-
await addDependency(devDependencies, {
|
|
135
|
+
await addDependency(devDependencies, {
|
|
136
|
+
silent: true,
|
|
137
|
+
dev: true,
|
|
138
|
+
packageManager,
|
|
139
|
+
});
|
|
129
140
|
spinner.stop(`Installed ${devDependencies.join(", ")}`);
|
|
130
141
|
}
|
|
142
|
+
if (configFileToDelete) {
|
|
143
|
+
unlinkSync(configFileToDelete);
|
|
144
|
+
}
|
|
131
145
|
createClientFile(backendChoice, clientFileName);
|
|
132
146
|
createExampleWorkflow(exampleWorkflowFileName);
|
|
133
147
|
createRunFile(runFileName);
|
|
@@ -145,56 +159,89 @@ export async function init(options = {}) {
|
|
|
145
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");
|
|
146
160
|
p.outro("✅ Setup complete!");
|
|
147
161
|
}
|
|
162
|
+
// Validate the manifest fields that init reads or updates.
|
|
163
|
+
function validateInitManifest(manifest) {
|
|
164
|
+
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
|
|
165
|
+
throw new CLIError("Invalid package.json: expected an object.");
|
|
166
|
+
}
|
|
167
|
+
for (const key of ["scripts", "dependencies", "devDependencies"]) {
|
|
168
|
+
const field = manifest[key];
|
|
169
|
+
if (field === undefined)
|
|
170
|
+
continue;
|
|
171
|
+
if (field === null ||
|
|
172
|
+
typeof field !== "object" ||
|
|
173
|
+
Array.isArray(field) ||
|
|
174
|
+
Object.values(field).some((value) => typeof value !== "string")) {
|
|
175
|
+
throw new CLIError(`Invalid package.json: ${key} must be an object containing string values.`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const { scripts } = manifest;
|
|
179
|
+
const worker = scripts?.["worker"];
|
|
180
|
+
if (worker !== undefined && worker !== "npx @openworkflow/cli worker start") {
|
|
181
|
+
throw new CLIError("Setup would overwrite package.json scripts.worker.");
|
|
182
|
+
}
|
|
183
|
+
}
|
|
148
184
|
/**
|
|
149
185
|
* openworkflow doctor
|
|
150
186
|
* @param options - Command options
|
|
151
187
|
*/
|
|
152
188
|
export async function doctor(options = {}) {
|
|
153
|
-
const configPath = options.config;
|
|
154
189
|
consola.start("Running OpenWorkflow doctor...");
|
|
155
|
-
const
|
|
190
|
+
const timer = setTimeout(() => {
|
|
191
|
+
consola.error("Doctor timed out after 30 seconds.");
|
|
192
|
+
void exit(1);
|
|
193
|
+
}, 30_000);
|
|
194
|
+
const { config, configFile } = await loadConfigWithEnv(options);
|
|
156
195
|
if (!configFile) {
|
|
157
196
|
throw new CLIError("No config file found.", "Run `npx @openworkflow/cli init` to create a config file.");
|
|
158
197
|
}
|
|
159
198
|
const backend = config.backend;
|
|
199
|
+
let cleanupFailed = false;
|
|
160
200
|
try {
|
|
201
|
+
await checkBackendConnection(backend);
|
|
202
|
+
if (config.worker?.concurrency !== undefined) {
|
|
203
|
+
assertPositiveInteger("concurrency", config.worker.concurrency);
|
|
204
|
+
}
|
|
161
205
|
consola.log("");
|
|
162
|
-
consola.info(`Config file: ${configFile}`);
|
|
206
|
+
consola.info(`Config file: ${path.relative(process.cwd(), configFile)}`);
|
|
163
207
|
const backendName = backend.constructor.name.replace("Backend", "");
|
|
164
208
|
consola.log(` • Backend: ${backendName}`);
|
|
165
|
-
const packageJson = readPackageJsonForDoctor();
|
|
166
|
-
if (packageJson) {
|
|
167
|
-
warnIfMissingBackendPackage(backendName, packageJson);
|
|
168
|
-
warnIfMissingTsconfig(packageJson);
|
|
169
|
-
}
|
|
170
209
|
// discover directories
|
|
171
|
-
const dirs = getWorkflowDirectories(config);
|
|
210
|
+
const dirs = [...new Set(getWorkflowDirectories(config))];
|
|
172
211
|
consola.log(` • Workflow directories: ${dirs.join(", ")}`);
|
|
173
212
|
// discover files
|
|
174
213
|
const configFileDir = path.dirname(configFile);
|
|
175
|
-
const {
|
|
176
|
-
|
|
177
|
-
consola.info(`Found ${String(files.length)} workflow file(s):`);
|
|
178
|
-
for (const file of files) {
|
|
179
|
-
consola.log(` • ${file}`);
|
|
180
|
-
}
|
|
214
|
+
const { workflows } = await discoverWorkflowsInDirs(dirs, configFileDir, config.ignorePatterns ?? []);
|
|
215
|
+
assertNoDuplicateWorkflows(workflows);
|
|
181
216
|
printDiscoveredWorkflows(workflows);
|
|
182
|
-
warnAboutDuplicateWorkflows(workflows);
|
|
183
|
-
consola.log("");
|
|
184
|
-
consola.success("Configuration looks good!");
|
|
185
217
|
}
|
|
186
218
|
finally {
|
|
187
|
-
|
|
219
|
+
// Imported configs can omit the backend despite the declared config type.
|
|
220
|
+
// oxlint-disable-next-line typescript/no-unnecessary-condition
|
|
221
|
+
if (typeof backend?.stop === "function") {
|
|
222
|
+
try {
|
|
223
|
+
await backend.stop();
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
cleanupFailed = true;
|
|
227
|
+
consola.error(`Backend cleanup failed: ${String(error)}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
188
230
|
}
|
|
231
|
+
clearTimeout(timer);
|
|
232
|
+
if (cleanupFailed)
|
|
233
|
+
await exit(1);
|
|
234
|
+
consola.log("");
|
|
235
|
+
consola.success("Configuration looks good!");
|
|
236
|
+
await exit(0);
|
|
189
237
|
}
|
|
190
238
|
/**
|
|
191
239
|
* openworkflow worker start
|
|
192
240
|
* @param options - Worker config and command options
|
|
193
241
|
*/
|
|
194
242
|
export async function workerStart(options = {}) {
|
|
195
|
-
const { config: configPath, ...workerConfig } = options;
|
|
196
243
|
consola.start("Starting worker...");
|
|
197
|
-
const { config, configFile } = await loadConfigWithEnv(
|
|
244
|
+
const { config, configFile } = await loadConfigWithEnv(options);
|
|
198
245
|
if (!configFile) {
|
|
199
246
|
throw new CLIError("No config file found.", "Run `npx @openworkflow/cli init` to create a config file.");
|
|
200
247
|
}
|
|
@@ -217,6 +264,7 @@ export async function workerStart(options = {}) {
|
|
|
217
264
|
consola.success("Worker stopped");
|
|
218
265
|
}
|
|
219
266
|
try {
|
|
267
|
+
await checkBackendConnection(backend);
|
|
220
268
|
// discover and import workflows
|
|
221
269
|
const dirs = getWorkflowDirectories(config);
|
|
222
270
|
consola.info(`Discovering workflows from: ${dirs.join(", ")}`);
|
|
@@ -225,7 +273,9 @@ export async function workerStart(options = {}) {
|
|
|
225
273
|
consola.info(`Found ${String(files.length)} workflow file(s)`);
|
|
226
274
|
consola.success(`Loaded ${String(workflows.length)} workflow(s): ${workflows.map((w) => w.spec.name).join(", ")}`);
|
|
227
275
|
assertNoDuplicateWorkflows(workflows);
|
|
228
|
-
const workerOptions = mergeDefinedOptions(config.worker,
|
|
276
|
+
const workerOptions = mergeDefinedOptions(config.worker, {
|
|
277
|
+
concurrency: options.concurrency,
|
|
278
|
+
});
|
|
229
279
|
if (workerOptions.concurrency !== undefined) {
|
|
230
280
|
assertPositiveInteger("concurrency", workerOptions.concurrency);
|
|
231
281
|
}
|
|
@@ -240,7 +290,12 @@ export async function workerStart(options = {}) {
|
|
|
240
290
|
consola.success("Worker started.");
|
|
241
291
|
}
|
|
242
292
|
catch (error) {
|
|
243
|
-
|
|
293
|
+
try {
|
|
294
|
+
await gracefulShutdown();
|
|
295
|
+
}
|
|
296
|
+
catch (cleanupError) {
|
|
297
|
+
consola.warn(`Backend cleanup failed: ${String(cleanupError)}`);
|
|
298
|
+
}
|
|
244
299
|
throw error;
|
|
245
300
|
}
|
|
246
301
|
}
|
|
@@ -283,10 +338,9 @@ export function validateDashboardPort(port) {
|
|
|
283
338
|
* @returns Resolves when the dashboard process exits.
|
|
284
339
|
*/
|
|
285
340
|
export async function dashboard(options = {}) {
|
|
286
|
-
const configPath = options.config;
|
|
287
341
|
const port = validateDashboardPort(options.port);
|
|
288
342
|
consola.start("Starting dashboard...");
|
|
289
|
-
const { configFile } = await loadConfigWithEnv(
|
|
343
|
+
const { configFile } = await loadConfigWithEnv(options);
|
|
290
344
|
if (!configFile) {
|
|
291
345
|
throw new CLIError("No config file found.", "Run `npx @openworkflow/cli init` to create a config file before starting the dashboard.");
|
|
292
346
|
}
|
|
@@ -324,6 +378,30 @@ export async function dashboard(options = {}) {
|
|
|
324
378
|
});
|
|
325
379
|
}
|
|
326
380
|
// -----------------------------------------------------------------------------
|
|
381
|
+
/**
|
|
382
|
+
* Show a canceled-setup message and exit the process with status 0.
|
|
383
|
+
*/
|
|
384
|
+
function cancelSetup() {
|
|
385
|
+
p.cancel("Setup canceled.");
|
|
386
|
+
// oxlint-disable-next-line unicorn/no-process-exit
|
|
387
|
+
process.exit(0);
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Exercise backend initialization, connectivity, and workflow table access.
|
|
391
|
+
* @param backend - Configured backend
|
|
392
|
+
*/
|
|
393
|
+
async function checkBackendConnection(backend) {
|
|
394
|
+
if (typeof backend?.listWorkflowRuns !== "function" ||
|
|
395
|
+
typeof backend.stop !== "function") {
|
|
396
|
+
throw new CLIError("Missing or invalid backend.", "Set config.backend to a connected OpenWorkflow backend.");
|
|
397
|
+
}
|
|
398
|
+
try {
|
|
399
|
+
await backend.listWorkflowRuns({ limit: 1 });
|
|
400
|
+
}
|
|
401
|
+
catch (error) {
|
|
402
|
+
throw new CLIError("Failed to access backend.", error instanceof Error ? error.message : String(error));
|
|
403
|
+
}
|
|
404
|
+
}
|
|
327
405
|
/**
|
|
328
406
|
* Get workflow directories from config.
|
|
329
407
|
* @param config - The loaded config
|
|
@@ -355,7 +433,7 @@ function findDuplicateWorkflows(workflows) {
|
|
|
355
433
|
for (const workflow of workflows) {
|
|
356
434
|
const name = workflow.spec.name;
|
|
357
435
|
const version = workflow.spec.version ?? null;
|
|
358
|
-
const key =
|
|
436
|
+
const key = JSON.stringify([name, version]);
|
|
359
437
|
const existing = workflowKeys.get(key);
|
|
360
438
|
if (existing) {
|
|
361
439
|
existing.count += 1;
|
|
@@ -383,32 +461,18 @@ function assertNoDuplicateWorkflows(workflows) {
|
|
|
383
461
|
const suffix = remaining > 0 ? ` (+${String(remaining)} more)` : "";
|
|
384
462
|
throw new CLIError(`Duplicate workflow name${duplicates.length === 1 ? "" : "s"} detected: ${preview}${suffix}`, "Multiple workflow files export workflows with the same name and version. Each workflow must have a unique name and version combination.");
|
|
385
463
|
}
|
|
386
|
-
/**
|
|
387
|
-
* Warn about duplicate workflows without failing.
|
|
388
|
-
* @param workflows - Discovered workflows
|
|
389
|
-
*/
|
|
390
|
-
function warnAboutDuplicateWorkflows(workflows) {
|
|
391
|
-
const duplicates = findDuplicateWorkflows(workflows);
|
|
392
|
-
for (const duplicate of duplicates) {
|
|
393
|
-
const versionStr = duplicate.version
|
|
394
|
-
? ` (version: ${duplicate.version})`
|
|
395
|
-
: "";
|
|
396
|
-
consola.warn(`Duplicate workflow detected: "${duplicate.name}"${versionStr}`);
|
|
397
|
-
consola.warn("Multiple files export a workflow with the same name and version.");
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
464
|
/**
|
|
401
465
|
* Print discovered workflows to the console.
|
|
402
466
|
* @param workflows - Array of discovered workflows
|
|
403
467
|
*/
|
|
404
468
|
function printDiscoveredWorkflows(workflows) {
|
|
405
469
|
consola.log("");
|
|
406
|
-
consola.info(`
|
|
470
|
+
consola.info(`Found ${String(workflows.length)} workflow${workflows.length === 1 ? "" : "s"}:`);
|
|
407
471
|
for (const workflow of workflows) {
|
|
408
472
|
const name = workflow.spec.name;
|
|
409
|
-
const version = workflow.spec.version
|
|
410
|
-
const versionStr = version
|
|
411
|
-
consola.log(` • ${name}${versionStr}`);
|
|
473
|
+
const version = workflow.spec.version;
|
|
474
|
+
const versionStr = version ? ` (${version})` : "";
|
|
475
|
+
consola.log(` • ${name}${versionStr} — ${workflowSources.get(workflow)}`);
|
|
412
476
|
}
|
|
413
477
|
}
|
|
414
478
|
const WORKFLOW_EXTENSIONS = ["ts", "mts", "cts", "js", "mjs", "cjs"];
|
|
@@ -480,8 +544,8 @@ function globToRegExp(pattern) {
|
|
|
480
544
|
return new RegExp(regex);
|
|
481
545
|
}
|
|
482
546
|
/**
|
|
483
|
-
* Check whether a file path matches ignore patterns.
|
|
484
|
-
* @param filePath - Absolute
|
|
547
|
+
* Check whether a file or directory path matches ignore patterns.
|
|
548
|
+
* @param filePath - Absolute path, with a trailing separator for directories
|
|
485
549
|
* @param baseDir - Base directory for relative matching
|
|
486
550
|
* @param matchers - Compiled regex matchers
|
|
487
551
|
* @returns Whether the file should be ignored
|
|
@@ -491,7 +555,11 @@ function isIgnoredFile(filePath, baseDir, matchers) {
|
|
|
491
555
|
return false;
|
|
492
556
|
const relativePath = normalizeForGlobMatch(path.relative(baseDir, filePath));
|
|
493
557
|
const fileName = path.basename(filePath);
|
|
494
|
-
|
|
558
|
+
const isDirectory = filePath.endsWith(path.sep);
|
|
559
|
+
return matchers.some((matcher) => matcher.test(relativePath) ||
|
|
560
|
+
matcher.test(fileName) ||
|
|
561
|
+
(isDirectory &&
|
|
562
|
+
(matcher.test(`${relativePath}/`) || matcher.test(`${fileName}/`))));
|
|
495
563
|
}
|
|
496
564
|
/**
|
|
497
565
|
* Discover workflow files from directories. Recursively scans directories for
|
|
@@ -516,19 +584,18 @@ export function discoverWorkflowFiles(dirs, baseDir, ignorePatterns = []) {
|
|
|
516
584
|
entries = readdirSync(absoluteDir, { withFileTypes: true });
|
|
517
585
|
}
|
|
518
586
|
catch (error) {
|
|
519
|
-
|
|
520
|
-
const errMessage = error instanceof Error ? error.message : String(error);
|
|
521
|
-
consola.debug(`Failed to read directory: ${absoluteDir} - ${errMessage}`);
|
|
522
|
-
return;
|
|
587
|
+
throw new CLIError(`Cannot read workflow directory: ${absoluteDir}`, `${String(error)}\nCorrect config.dirs or make the directory readable.`);
|
|
523
588
|
}
|
|
524
589
|
for (const entry of entries) {
|
|
525
590
|
const fullPath = path.join(absoluteDir, entry.name);
|
|
526
591
|
if (entry.isDirectory()) {
|
|
527
|
-
|
|
592
|
+
if (!isIgnoredFile(`${fullPath}${path.sep}`, baseDir, matchers)) {
|
|
593
|
+
scanDirectory(fullPath);
|
|
594
|
+
}
|
|
528
595
|
}
|
|
529
596
|
else if (entry.isFile() &&
|
|
530
597
|
WORKFLOW_EXTENSIONS.some((ext) => entry.name.endsWith(`.${ext}`)) &&
|
|
531
|
-
|
|
598
|
+
!/\.d\.(?:ts|mts|cts)$/.test(entry.name) &&
|
|
532
599
|
!isIgnoredFile(fullPath, baseDir, matchers)) {
|
|
533
600
|
discoveredFiles.push(fullPath);
|
|
534
601
|
}
|
|
@@ -537,7 +604,7 @@ export function discoverWorkflowFiles(dirs, baseDir, ignorePatterns = []) {
|
|
|
537
604
|
for (const dir of dirs) {
|
|
538
605
|
scanDirectory(dir);
|
|
539
606
|
}
|
|
540
|
-
return discoveredFiles;
|
|
607
|
+
return [...new Set(discoveredFiles)];
|
|
541
608
|
}
|
|
542
609
|
/**
|
|
543
610
|
* Import workflow files and extract workflow exports.
|
|
@@ -547,11 +614,11 @@ export function discoverWorkflowFiles(dirs, baseDir, ignorePatterns = []) {
|
|
|
547
614
|
*/
|
|
548
615
|
async function importWorkflows(files) {
|
|
549
616
|
const workflows = [];
|
|
550
|
-
const jiti = createJiti(import.meta.url);
|
|
551
617
|
for (const file of files) {
|
|
552
618
|
// import the module
|
|
553
619
|
let module;
|
|
554
620
|
try {
|
|
621
|
+
const jiti = createModuleLoader(file);
|
|
555
622
|
module = await jiti.import(pathToFileURL(file).href);
|
|
556
623
|
}
|
|
557
624
|
catch (error) {
|
|
@@ -561,13 +628,13 @@ async function importWorkflows(files) {
|
|
|
561
628
|
// extract workflow exports (named and default)
|
|
562
629
|
for (const [key, value] of Object.entries(module)) {
|
|
563
630
|
if (isWorkflow(value)) {
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
consola.debug(`Found workflow "${
|
|
631
|
+
workflows.push(value);
|
|
632
|
+
workflowSources.set(value, path.relative(process.cwd(), file));
|
|
633
|
+
consola.debug(`Found workflow "${value.spec.name}" in ${file} (${key})`);
|
|
567
634
|
}
|
|
568
635
|
}
|
|
569
636
|
}
|
|
570
|
-
return workflows;
|
|
637
|
+
return [...new Set(workflows)];
|
|
571
638
|
}
|
|
572
639
|
/**
|
|
573
640
|
* Discover workflow files and import workflows with common error handling.
|
|
@@ -646,24 +713,36 @@ function createConfigFile(configFileName) {
|
|
|
646
713
|
spinner.stop(`Config written to ${configDestPath}`);
|
|
647
714
|
}
|
|
648
715
|
/**
|
|
649
|
-
*
|
|
650
|
-
*
|
|
716
|
+
* Write a file under the `openworkflow/` project directory, skipping when a
|
|
717
|
+
* file with the same name already exists. Progress is surfaced via the clack
|
|
718
|
+
* spinner using the provided label (e.g. "client file").
|
|
719
|
+
* @param label - Lowercase label describing the file (used in spinner text)
|
|
720
|
+
* @param fileName - Filename to write inside `openworkflow/`
|
|
721
|
+
* @param content - File contents to write when the file does not exist
|
|
651
722
|
*/
|
|
652
|
-
function
|
|
723
|
+
function writeWorkflowFileIfMissing(label, fileName, content) {
|
|
653
724
|
const spinner = p.spinner();
|
|
654
725
|
const workflowsDir = path.join(process.cwd(), "openworkflow");
|
|
655
726
|
if (!existsSync(workflowsDir)) {
|
|
656
727
|
mkdirSync(workflowsDir, { recursive: true });
|
|
657
728
|
}
|
|
658
|
-
const
|
|
659
|
-
if (existsSync(
|
|
660
|
-
spinner.start(
|
|
661
|
-
|
|
729
|
+
const destPath = path.join(workflowsDir, fileName);
|
|
730
|
+
if (existsSync(destPath)) {
|
|
731
|
+
spinner.start(`Checking ${label}...`);
|
|
732
|
+
const capitalized = label.charAt(0).toUpperCase() + label.slice(1);
|
|
733
|
+
spinner.stop(`${capitalized} already exists at ${destPath}`);
|
|
662
734
|
return;
|
|
663
735
|
}
|
|
664
|
-
spinner.start(
|
|
665
|
-
writeFileSync(
|
|
666
|
-
spinner.stop(`Created
|
|
736
|
+
spinner.start(`Creating ${label}...`);
|
|
737
|
+
writeFileSync(destPath, content, "utf8");
|
|
738
|
+
spinner.stop(`Created ${label} at ${destPath}`);
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Create hello-world runner file.
|
|
742
|
+
* @param runFileName - The runner filename to write
|
|
743
|
+
*/
|
|
744
|
+
function createRunFile(runFileName) {
|
|
745
|
+
writeWorkflowFileIfMissing("hello-world runner", runFileName, HELLO_WORLD_RUNNER);
|
|
667
746
|
}
|
|
668
747
|
/**
|
|
669
748
|
* Create client file.
|
|
@@ -671,41 +750,14 @@ function createRunFile(runFileName) {
|
|
|
671
750
|
* @param clientFileName - The client filename to write
|
|
672
751
|
*/
|
|
673
752
|
function createClientFile(backendChoice, clientFileName) {
|
|
674
|
-
|
|
675
|
-
const workflowsDir = path.join(process.cwd(), "openworkflow");
|
|
676
|
-
if (!existsSync(workflowsDir)) {
|
|
677
|
-
mkdirSync(workflowsDir, { recursive: true });
|
|
678
|
-
}
|
|
679
|
-
const clientDestPath = path.join(workflowsDir, clientFileName);
|
|
680
|
-
if (existsSync(clientDestPath)) {
|
|
681
|
-
spinner.start("Checking client file...");
|
|
682
|
-
spinner.stop(`Client file already exists at ${clientDestPath}`);
|
|
683
|
-
return;
|
|
684
|
-
}
|
|
685
|
-
spinner.start("Creating client file...");
|
|
686
|
-
const clientTemplate = getClientTemplate(backendChoice);
|
|
687
|
-
writeFileSync(clientDestPath, clientTemplate, "utf8");
|
|
688
|
-
spinner.stop(`Created client file at ${clientDestPath}`);
|
|
753
|
+
writeWorkflowFileIfMissing("client file", clientFileName, getClientTemplate(backendChoice));
|
|
689
754
|
}
|
|
690
755
|
/**
|
|
691
756
|
* Create example workflow.
|
|
692
757
|
* @param exampleWorkflowFileName - The example workflow filename to write
|
|
693
758
|
*/
|
|
694
759
|
function createExampleWorkflow(exampleWorkflowFileName) {
|
|
695
|
-
|
|
696
|
-
const workflowsDir = path.join(process.cwd(), "openworkflow");
|
|
697
|
-
if (!existsSync(workflowsDir)) {
|
|
698
|
-
mkdirSync(workflowsDir, { recursive: true });
|
|
699
|
-
}
|
|
700
|
-
const helloWorldDestPath = path.join(workflowsDir, exampleWorkflowFileName);
|
|
701
|
-
if (existsSync(helloWorldDestPath)) {
|
|
702
|
-
spinner.start("Checking example (hello-world) workflow...");
|
|
703
|
-
spinner.stop(`Example (hello-world) workflow already exists at ${helloWorldDestPath}`);
|
|
704
|
-
return;
|
|
705
|
-
}
|
|
706
|
-
spinner.start("Creating example (hello-world) workflow...");
|
|
707
|
-
writeFileSync(helloWorldDestPath, HELLO_WORLD_WORKFLOW, "utf8");
|
|
708
|
-
spinner.stop(`Created example (hello-world) workflow at ${helloWorldDestPath}`);
|
|
760
|
+
writeWorkflowFileIfMissing("example (hello-world) workflow", exampleWorkflowFileName, HELLO_WORLD_WORKFLOW);
|
|
709
761
|
}
|
|
710
762
|
/**
|
|
711
763
|
* Update .gitignore for SQLite.
|
|
@@ -718,8 +770,8 @@ function updateGitignoreForSqlite() {
|
|
|
718
770
|
const gitignorePath = path.join(process.cwd(), ".gitignore");
|
|
719
771
|
const spinner = p.spinner();
|
|
720
772
|
spinner.start("Updating .gitignore...");
|
|
721
|
-
const
|
|
722
|
-
spinner.stop(
|
|
773
|
+
const added = ensureGitignoreEntry(gitignorePath, "openworkflow/backend.db*");
|
|
774
|
+
spinner.stop(added
|
|
723
775
|
? "Added openworkflow/backend.db* to .gitignore"
|
|
724
776
|
: "openworkflow/backend.db* already in .gitignore");
|
|
725
777
|
}
|
|
@@ -737,7 +789,7 @@ function addWorkerScriptToPackageJson() {
|
|
|
737
789
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
738
790
|
packageJson.scripts ??= {};
|
|
739
791
|
packageJson.scripts["worker"] = "npx @openworkflow/cli worker start";
|
|
740
|
-
writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2)
|
|
792
|
+
writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, "utf8");
|
|
741
793
|
spinner.stop('Added "worker" script to package.json');
|
|
742
794
|
}
|
|
743
795
|
catch {
|
|
@@ -745,38 +797,33 @@ function addWorkerScriptToPackageJson() {
|
|
|
745
797
|
consola.warn("Could not add worker script to package.json");
|
|
746
798
|
}
|
|
747
799
|
}
|
|
800
|
+
/**
|
|
801
|
+
* Append a line to a file if no existing line matches. Creates the file if it
|
|
802
|
+
* doesn't exist.
|
|
803
|
+
* @param filePath - Path to the file
|
|
804
|
+
* @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`
|
|
807
|
+
* @returns Whether the line was appended
|
|
808
|
+
*/
|
|
809
|
+
function appendLineIfMissing(filePath, line, matchesExisting) {
|
|
810
|
+
const content = existsSync(filePath) ? readFileSync(filePath, "utf8") : "";
|
|
811
|
+
if (content.split("\n").some((existing) => matchesExisting(existing))) {
|
|
812
|
+
return false;
|
|
813
|
+
}
|
|
814
|
+
const separator = content === "" || content.endsWith("\n") ? "" : "\n";
|
|
815
|
+
writeFileSync(filePath, `${content}${separator}${line}\n`, "utf8");
|
|
816
|
+
return true;
|
|
817
|
+
}
|
|
748
818
|
/**
|
|
749
819
|
* Ensure a specific entry exists in a .gitignore file. Creates the file if it
|
|
750
820
|
* doesn't exist, appends the entry if not present.
|
|
751
821
|
* @param gitignorePath - Path to the .gitignore file
|
|
752
822
|
* @param entry - The entry to add (e.g. "openworkflow/backend.db*")
|
|
753
|
-
* @returns
|
|
823
|
+
* @returns Whether the entry was appended
|
|
754
824
|
*/
|
|
755
825
|
function ensureGitignoreEntry(gitignorePath, entry) {
|
|
756
|
-
|
|
757
|
-
let content = "";
|
|
758
|
-
if (fileExists) {
|
|
759
|
-
content = readFileSync(gitignorePath, "utf8");
|
|
760
|
-
}
|
|
761
|
-
// check if entry already exists
|
|
762
|
-
const lines = content.split("\n");
|
|
763
|
-
const hasEntry = lines.some((line) => line.trim() === entry);
|
|
764
|
-
if (hasEntry) {
|
|
765
|
-
return { added: false, created: false };
|
|
766
|
-
}
|
|
767
|
-
// add entry to .gitignore
|
|
768
|
-
let newContent;
|
|
769
|
-
if (content === "") {
|
|
770
|
-
newContent = `${entry}\n`;
|
|
771
|
-
}
|
|
772
|
-
else if (content.endsWith("\n")) {
|
|
773
|
-
newContent = `${content}${entry}\n`;
|
|
774
|
-
}
|
|
775
|
-
else {
|
|
776
|
-
newContent = `${content}\n${entry}\n`;
|
|
777
|
-
}
|
|
778
|
-
writeFileSync(gitignorePath, newContent, "utf8");
|
|
779
|
-
return { added: true, created: !fileExists };
|
|
826
|
+
return appendLineIfMissing(gitignorePath, entry, (line) => line.trim() === entry);
|
|
780
827
|
}
|
|
781
828
|
/**
|
|
782
829
|
* Add OPENWORKFLOW_POSTGRES_URL to .env file.
|
|
@@ -785,22 +832,36 @@ function updateEnvForPostgres() {
|
|
|
785
832
|
const envPath = path.join(process.cwd(), ".env");
|
|
786
833
|
const spinner = p.spinner();
|
|
787
834
|
spinner.start("Updating .env...");
|
|
788
|
-
const
|
|
789
|
-
spinner.stop(
|
|
835
|
+
const added = ensureEnvEntry(envPath, "OPENWORKFLOW_POSTGRES_URL", "postgresql://user:password@localhost:5432/openworkflow");
|
|
836
|
+
spinner.stop(added
|
|
790
837
|
? "Added OPENWORKFLOW_POSTGRES_URL to .env"
|
|
791
838
|
: "OPENWORKFLOW_POSTGRES_URL already in .env");
|
|
792
839
|
}
|
|
793
840
|
/**
|
|
794
|
-
*
|
|
795
|
-
* @param
|
|
796
|
-
* @returns
|
|
841
|
+
* Find the config and load its environment without importing it.
|
|
842
|
+
* @param options - Config and environment file paths
|
|
843
|
+
* @returns Config path, if found.
|
|
797
844
|
*/
|
|
798
|
-
|
|
799
|
-
|
|
845
|
+
function findConfigWithEnv(options) {
|
|
846
|
+
const { envFile } = options;
|
|
847
|
+
const configPath = options.config
|
|
848
|
+
? path.resolve(options.config)
|
|
849
|
+
: findConfigFile();
|
|
850
|
+
const baseDir = configPath ? path.dirname(configPath) : process.cwd();
|
|
851
|
+
const { error } = loadDotenv({
|
|
852
|
+
path: envFile ?? path.join(baseDir, ".env"),
|
|
853
|
+
quiet: true,
|
|
854
|
+
});
|
|
855
|
+
if (envFile !== undefined && error) {
|
|
856
|
+
throw new CLIError(`Failed to load environment file: ${envFile}`, error.message);
|
|
857
|
+
}
|
|
858
|
+
return configPath;
|
|
859
|
+
}
|
|
860
|
+
// Load the environment before importing config for commands that use it.
|
|
861
|
+
async function loadConfigWithEnv(options) {
|
|
862
|
+
const configPath = findConfigWithEnv(options);
|
|
800
863
|
try {
|
|
801
|
-
return configPath
|
|
802
|
-
? await loadConfigFromPath(configPath)
|
|
803
|
-
: await loadConfig();
|
|
864
|
+
return await loadConfigFromPath(configPath ?? "openworkflow.config.ts");
|
|
804
865
|
}
|
|
805
866
|
catch (error) {
|
|
806
867
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -824,16 +885,24 @@ function readPackageJsonForDoctor() {
|
|
|
824
885
|
return null;
|
|
825
886
|
}
|
|
826
887
|
}
|
|
888
|
+
/**
|
|
889
|
+
* Pick the script file extension for generated files based on whether the
|
|
890
|
+
* project uses TypeScript.
|
|
891
|
+
* @param packageJson - Parsed package.json (or null if missing)
|
|
892
|
+
* @returns ".ts" when TypeScript is a dependency, otherwise ".js"
|
|
893
|
+
*/
|
|
894
|
+
function getScriptExtension(packageJson) {
|
|
895
|
+
return packageJson && hasDependency(packageJson, "typescript")
|
|
896
|
+
? ".ts"
|
|
897
|
+
: ".js";
|
|
898
|
+
}
|
|
827
899
|
/**
|
|
828
900
|
* Determine the config filename to write during init.
|
|
829
901
|
* @param packageJson - Parsed package.json (or null if missing)
|
|
830
902
|
* @returns The config file name to create
|
|
831
903
|
*/
|
|
832
904
|
export function getConfigFileName(packageJson) {
|
|
833
|
-
|
|
834
|
-
return "openworkflow.config.ts";
|
|
835
|
-
}
|
|
836
|
-
return "openworkflow.config.js";
|
|
905
|
+
return `openworkflow.config${getScriptExtension(packageJson)}`;
|
|
837
906
|
}
|
|
838
907
|
/**
|
|
839
908
|
* Determine the example workflow filename to write during init.
|
|
@@ -841,9 +910,7 @@ export function getConfigFileName(packageJson) {
|
|
|
841
910
|
* @returns The example workflow file name to create
|
|
842
911
|
*/
|
|
843
912
|
export function getExampleWorkflowFileName(packageJson) {
|
|
844
|
-
|
|
845
|
-
const extension = path.extname(configFileName) || ".js";
|
|
846
|
-
return `hello-world${extension}`;
|
|
913
|
+
return `hello-world${getScriptExtension(packageJson)}`;
|
|
847
914
|
}
|
|
848
915
|
/**
|
|
849
916
|
* Determine the hello-world runner filename to write during init.
|
|
@@ -851,9 +918,7 @@ export function getExampleWorkflowFileName(packageJson) {
|
|
|
851
918
|
* @returns The runner file name to create
|
|
852
919
|
*/
|
|
853
920
|
export function getRunFileName(packageJson) {
|
|
854
|
-
|
|
855
|
-
const extension = path.extname(configFileName) || ".js";
|
|
856
|
-
return `hello-world.run${extension}`;
|
|
921
|
+
return `hello-world.run${getScriptExtension(packageJson)}`;
|
|
857
922
|
}
|
|
858
923
|
/**
|
|
859
924
|
* Determine the client filename to write during init.
|
|
@@ -861,9 +926,7 @@ export function getRunFileName(packageJson) {
|
|
|
861
926
|
* @returns The client file name to create
|
|
862
927
|
*/
|
|
863
928
|
export function getClientFileName(packageJson) {
|
|
864
|
-
|
|
865
|
-
const extension = path.extname(configFileName) || ".js";
|
|
866
|
-
return `client${extension}`;
|
|
929
|
+
return `client${getScriptExtension(packageJson)}`;
|
|
867
930
|
}
|
|
868
931
|
/**
|
|
869
932
|
* Check whether a dependency is declared in package.json.
|
|
@@ -875,71 +938,18 @@ function hasDependency(packageJson, name) {
|
|
|
875
938
|
return Boolean(packageJson.dependencies?.[name] ?? packageJson.devDependencies?.[name]);
|
|
876
939
|
}
|
|
877
940
|
/**
|
|
878
|
-
*
|
|
879
|
-
*
|
|
880
|
-
* @param packageJson - Parsed package.json.
|
|
881
|
-
*/
|
|
882
|
-
function warnIfMissingBackendPackage(backendName, packageJson) {
|
|
883
|
-
const backendNameLower = backendName.toLowerCase();
|
|
884
|
-
const isPostgres = backendNameLower.includes("postgres");
|
|
885
|
-
const isSqlite = backendNameLower.includes("sqlite");
|
|
886
|
-
if ((isPostgres || isSqlite) && !hasDependency(packageJson, "openworkflow")) {
|
|
887
|
-
consola.warn(`Backend is ${backendName} but openworkflow is not installed.`);
|
|
888
|
-
}
|
|
889
|
-
if (isPostgres && !hasDependency(packageJson, "postgres")) {
|
|
890
|
-
consola.warn(`Backend is ${backendName} but the postgres driver is not installed.`);
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
/**
|
|
894
|
-
* Warn when TypeScript is installed but tsconfig.json is missing.
|
|
895
|
-
* @param packageJson - Parsed package.json.
|
|
896
|
-
*/
|
|
897
|
-
function warnIfMissingTsconfig(packageJson) {
|
|
898
|
-
if (!hasDependency(packageJson, "typescript")) {
|
|
899
|
-
return;
|
|
900
|
-
}
|
|
901
|
-
const tsconfigPath = path.join(process.cwd(), "tsconfig.json");
|
|
902
|
-
if (!existsSync(tsconfigPath)) {
|
|
903
|
-
consola.warn("TypeScript is installed but no tsconfig.json was found.");
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
/**
|
|
907
|
-
* Ensure a specific environment variable exists in a .env file. Creates the file if it
|
|
908
|
-
* doesn't exist, appends the variable if not present.
|
|
941
|
+
* Ensure a specific environment variable exists in a .env file. Creates the
|
|
942
|
+
* file if it doesn't exist, appends the variable if not present.
|
|
909
943
|
* @param envPath - Path to the .env file
|
|
910
944
|
* @param key - The environment variable key (e.g. "OPENWORKFLOW_POSTGRES_URL")
|
|
911
945
|
* @param value - The default value for the environment variable
|
|
912
|
-
* @returns
|
|
946
|
+
* @returns Whether the entry was appended
|
|
913
947
|
*/
|
|
914
948
|
function ensureEnvEntry(envPath, key, value) {
|
|
915
|
-
|
|
916
|
-
let content = "";
|
|
917
|
-
if (fileExists) {
|
|
918
|
-
content = readFileSync(envPath, "utf8");
|
|
919
|
-
}
|
|
920
|
-
// check if key already exists (looking for KEY= at start of line)
|
|
921
|
-
const lines = content.split("\n");
|
|
922
|
-
const hasKey = lines.some((line) => {
|
|
949
|
+
return appendLineIfMissing(envPath, `${key}=${value}`, (line) => {
|
|
923
950
|
const trimmed = line.trim();
|
|
924
951
|
return trimmed.startsWith(`${key}=`) || trimmed.startsWith(`${key} =`);
|
|
925
952
|
});
|
|
926
|
-
if (hasKey) {
|
|
927
|
-
return { added: false, created: false };
|
|
928
|
-
}
|
|
929
|
-
// add entry to .env
|
|
930
|
-
let newContent;
|
|
931
|
-
const envEntry = `${key}=${value}`;
|
|
932
|
-
if (content === "") {
|
|
933
|
-
newContent = `${envEntry}\n`;
|
|
934
|
-
}
|
|
935
|
-
else if (content.endsWith("\n")) {
|
|
936
|
-
newContent = `${content}${envEntry}\n`;
|
|
937
|
-
}
|
|
938
|
-
else {
|
|
939
|
-
newContent = `${content}\n${envEntry}\n`;
|
|
940
|
-
}
|
|
941
|
-
writeFileSync(envPath, newContent, "utf8");
|
|
942
|
-
return { added: true, created: !fileExists };
|
|
943
953
|
}
|
|
944
954
|
/**
|
|
945
955
|
* Validate a numeric option is a positive integer.
|
package/dist/config.d.ts
CHANGED
|
@@ -46,5 +46,11 @@ export declare function loadConfigFromPath(configPath: string, startDir?: string
|
|
|
46
46
|
* @returns The loaded configuration and metadata
|
|
47
47
|
*/
|
|
48
48
|
export declare function loadConfig(startDir?: string): Promise<LoadedConfig>;
|
|
49
|
+
/**
|
|
50
|
+
* Find the nearest config without executing it, so its environment can load first.
|
|
51
|
+
* @param startDir - Directory to search from
|
|
52
|
+
* @returns Config path, if found
|
|
53
|
+
*/
|
|
54
|
+
export declare function findConfigFile(startDir?: string): string | undefined;
|
|
49
55
|
export {};
|
|
50
56
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAErD,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACzB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;AAE9D;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,kBAAkB,GAAG,kBAAkB,CAE3E;AAED,UAAU,YAAY;IACpB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAErD,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACzB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;AAE9D;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,kBAAkB,GAAG,kBAAkB,CAE3E;AAED,UAAU,YAAY;IACpB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAKD;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,YAAY,CAAC,CAKvB;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAGzE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,QAAQ,SAAgB,GAAG,MAAM,GAAG,SAAS,CAyB3E"}
|
package/dist/config.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createModuleLoader } from "./module-loader.js";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
@@ -12,7 +12,6 @@ export function defineConfig(config) {
|
|
|
12
12
|
}
|
|
13
13
|
const CONFIG_NAME = "openworkflow.config";
|
|
14
14
|
const CONFIG_EXTENSIONS = ["ts", "mts", "cts", "js", "mjs", "cjs"];
|
|
15
|
-
const jiti = createJiti(import.meta.url);
|
|
16
15
|
/**
|
|
17
16
|
* Load OpenWorkflow config from an explicit path.
|
|
18
17
|
* @param configPath - Explicit config file path
|
|
@@ -34,15 +33,24 @@ export async function loadConfigFromPath(configPath, startDir) {
|
|
|
34
33
|
* @returns The loaded configuration and metadata
|
|
35
34
|
*/
|
|
36
35
|
export async function loadConfig(startDir) {
|
|
37
|
-
|
|
36
|
+
const configFile = findConfigFile(startDir);
|
|
37
|
+
return configFile ? importConfigFile(configFile) : getEmptyLoadedConfig();
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Find the nearest config without executing it, so its environment can load first.
|
|
41
|
+
* @param startDir - Directory to search from
|
|
42
|
+
* @returns Config path, if found
|
|
43
|
+
*/
|
|
44
|
+
export function findConfigFile(startDir = process.cwd()) {
|
|
45
|
+
let currentDir = path.resolve(startDir);
|
|
38
46
|
// search up the directory tree
|
|
39
|
-
//
|
|
47
|
+
// oxlint-disable-next-line typescript/no-unnecessary-condition
|
|
40
48
|
while (true) {
|
|
41
49
|
for (const ext of CONFIG_EXTENSIONS) {
|
|
42
50
|
const fileName = `${CONFIG_NAME}.${ext}`;
|
|
43
51
|
const filePath = path.join(currentDir, fileName);
|
|
44
52
|
if (existsSync(filePath)) {
|
|
45
|
-
return
|
|
53
|
+
return filePath;
|
|
46
54
|
}
|
|
47
55
|
}
|
|
48
56
|
const parentDir = path.dirname(currentDir);
|
|
@@ -52,7 +60,7 @@ export async function loadConfig(startDir) {
|
|
|
52
60
|
}
|
|
53
61
|
currentDir = parentDir;
|
|
54
62
|
}
|
|
55
|
-
return
|
|
63
|
+
return undefined;
|
|
56
64
|
}
|
|
57
65
|
/**
|
|
58
66
|
* Import a config file and wrap load errors with a stable message.
|
|
@@ -61,17 +69,23 @@ export async function loadConfig(startDir) {
|
|
|
61
69
|
*/
|
|
62
70
|
async function importConfigFile(filePath) {
|
|
63
71
|
try {
|
|
72
|
+
const jiti = createModuleLoader(filePath, { tryNative: false }); // bun compatibility
|
|
64
73
|
const fileUrl = pathToFileURL(filePath).href;
|
|
65
74
|
const config = await jiti.import(fileUrl, {
|
|
66
75
|
default: true,
|
|
67
76
|
});
|
|
77
|
+
if (typeof config !== "object" || config === null) {
|
|
78
|
+
throw new Error("Config must export an object.");
|
|
79
|
+
}
|
|
68
80
|
return {
|
|
69
|
-
config,
|
|
81
|
+
config: config,
|
|
70
82
|
configFile: filePath,
|
|
71
83
|
};
|
|
72
84
|
}
|
|
73
85
|
catch (error) {
|
|
74
|
-
throw new Error(`Failed to load config file ${filePath}: ${String(error)}
|
|
86
|
+
throw new Error(`Failed to load config file ${filePath}: ${String(error)}`, {
|
|
87
|
+
cause: error,
|
|
88
|
+
});
|
|
75
89
|
}
|
|
76
90
|
}
|
|
77
91
|
/**
|
package/dist/errors.d.ts
CHANGED
|
@@ -5,6 +5,11 @@ export declare class CLIError extends Error {
|
|
|
5
5
|
readonly detail: string | undefined;
|
|
6
6
|
constructor(message: string, detail?: string);
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Finish writing CLI output before exiting, including when handles remain open.
|
|
10
|
+
* @param code - Process exit code
|
|
11
|
+
*/
|
|
12
|
+
export declare function exit(code: number): Promise<never>;
|
|
8
13
|
/**
|
|
9
14
|
* Wraps a CLI action / handler function with error handling that catches
|
|
10
15
|
* errors, prints them to the console, then exits.
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -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;;;;;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,
|
|
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"}
|
package/dist/errors.js
CHANGED
|
@@ -11,6 +11,17 @@ export class CLIError extends Error {
|
|
|
11
11
|
this.detail = detail;
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Finish writing CLI output before exiting, including when handles remain open.
|
|
16
|
+
* @param code - Process exit code
|
|
17
|
+
*/
|
|
18
|
+
export async function exit(code) {
|
|
19
|
+
await Promise.all([process.stdout, process.stderr].map((stream) => new Promise((resolve) => {
|
|
20
|
+
stream.end(resolve);
|
|
21
|
+
})));
|
|
22
|
+
// oxlint-disable-next-line unicorn/no-process-exit
|
|
23
|
+
process.exit(code);
|
|
24
|
+
}
|
|
14
25
|
/**
|
|
15
26
|
* Wraps a CLI action / handler function with error handling that catches
|
|
16
27
|
* errors, prints them to the console, then exits.
|
|
@@ -24,19 +35,15 @@ export function withErrorHandling(fn) {
|
|
|
24
35
|
}
|
|
25
36
|
catch (error) {
|
|
26
37
|
if (error instanceof CLIError) {
|
|
27
|
-
consola.error(error.message);
|
|
28
|
-
|
|
29
|
-
consola.info(error.detail);
|
|
30
|
-
// eslint-disable-next-line unicorn/no-process-exit
|
|
31
|
-
process.exit(1);
|
|
38
|
+
consola.error([error.message, error.detail].filter(Boolean).join("\n"));
|
|
39
|
+
return exit(1);
|
|
32
40
|
}
|
|
33
41
|
const message = error instanceof Error ? error.message : String(error);
|
|
34
42
|
consola.error(`Unexpected error: ${message}`);
|
|
35
43
|
if (error instanceof Error && error.stack) {
|
|
36
44
|
consola.debug(error.stack);
|
|
37
45
|
}
|
|
38
|
-
|
|
39
|
-
process.exit(1);
|
|
46
|
+
return exit(1);
|
|
40
47
|
}
|
|
41
48
|
};
|
|
42
49
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type Jiti, type JitiOptions } from "jiti";
|
|
2
|
+
/**
|
|
3
|
+
* Create a loader using the requested file's nearest tsconfig when available.
|
|
4
|
+
* @param filePath - Absolute path to the file being loaded.
|
|
5
|
+
* @param options - Runtime import options.
|
|
6
|
+
* @returns A loader with optional TypeScript path resolution.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createModuleLoader(filePath: string, options?: JitiOptions): Jiti;
|
|
9
|
+
//# sourceMappingURL=module-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module-loader.d.ts","sourceRoot":"","sources":["../module-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,IAAI,EAAE,KAAK,WAAW,EAAE,MAAM,MAAM,CAAC;AAE/D;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,WAAgB,GACxB,IAAI,CAQN"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createJiti } from "jiti";
|
|
2
|
+
/**
|
|
3
|
+
* Create a loader using the requested file's nearest tsconfig when available.
|
|
4
|
+
* @param filePath - Absolute path to the file being loaded.
|
|
5
|
+
* @param options - Runtime import options.
|
|
6
|
+
* @returns A loader with optional TypeScript path resolution.
|
|
7
|
+
*/
|
|
8
|
+
export function createModuleLoader(filePath, options = {}) {
|
|
9
|
+
try {
|
|
10
|
+
return createJiti(filePath, { ...options, tsconfigPaths: true });
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
// Deployments may omit development dependencies referenced by tsconfig.
|
|
14
|
+
// Fall back before executing user code so ordinary imports still work.
|
|
15
|
+
return createJiti(filePath, { ...options, tsconfigPaths: false });
|
|
16
|
+
}
|
|
17
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openworkflow/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -27,18 +27,18 @@
|
|
|
27
27
|
"prepublishOnly": "npm run build"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@clack/prompts": "^1.
|
|
31
|
-
"commander": "^
|
|
30
|
+
"@clack/prompts": "^1.8.0",
|
|
31
|
+
"commander": "^15.0.0",
|
|
32
32
|
"consola": "^3.4.2",
|
|
33
|
-
"dotenv": "^17.4.
|
|
34
|
-
"jiti": "^2.
|
|
35
|
-
"nypm": "^0.6.
|
|
33
|
+
"dotenv": "^17.4.2",
|
|
34
|
+
"jiti": "^2.7.0",
|
|
35
|
+
"nypm": "^0.6.10"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"openworkflow": "*",
|
|
39
|
-
"vitest": "^
|
|
39
|
+
"vitest": "^5.0.0"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
|
-
"openworkflow": "^0.6.0 || ^0.7.0 || ^0.8.0 || ^0.9.0"
|
|
42
|
+
"openworkflow": "^0.6.0 || ^0.7.0 || ^0.8.0 || ^0.9.0 || ^0.10.0"
|
|
43
43
|
}
|
|
44
44
|
}
|