flowstride 1.0.15 → 1.0.17
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/index.js +98 -71
- package/dist/parser/parser.js +0 -7
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -44,7 +44,6 @@ const init_1 = require("./commands/init");
|
|
|
44
44
|
const fs = __importStar(require("fs"));
|
|
45
45
|
const path = __importStar(require("path"));
|
|
46
46
|
const os = __importStar(require("os"));
|
|
47
|
-
const tar = __importStar(require("tar"));
|
|
48
47
|
const child_process_1 = require("child_process");
|
|
49
48
|
const prompts_1 = require("@inquirer/prompts");
|
|
50
49
|
const chalk_1 = __importDefault(require("chalk"));
|
|
@@ -54,7 +53,7 @@ const API_BASE = process.env.FLOWSTRIDE_API_URL || "https://cloud.flowstride.io"
|
|
|
54
53
|
program
|
|
55
54
|
.name("flowstride")
|
|
56
55
|
.description("Flowstride: The Enterprise Flow-First Automation CLI")
|
|
57
|
-
.version("1.0.
|
|
56
|
+
.version("1.0.17");
|
|
58
57
|
program
|
|
59
58
|
.command("init")
|
|
60
59
|
.description("Initialise a new Flowstride workspace in the current directory")
|
|
@@ -64,33 +63,40 @@ program
|
|
|
64
63
|
program
|
|
65
64
|
.command("login")
|
|
66
65
|
.description("Authenticate this machine with Flowstride Enterprise Cloud")
|
|
67
|
-
.
|
|
66
|
+
.option("-t, --token <token>", "Enterprise Developer Token")
|
|
67
|
+
.option("-g, --global", "Save the token globally for all projects on this machine", false)
|
|
68
|
+
.action(async (options) => {
|
|
68
69
|
const localPackageJsonPath = path.join(process.cwd(), "package.json");
|
|
69
|
-
if
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
70
|
+
// Enforce valid project directory ONLY if doing a local login
|
|
71
|
+
if (!options.global) {
|
|
72
|
+
if (!fs.existsSync(localPackageJsonPath)) {
|
|
73
|
+
console.error(chalk_1.default.red("✖ Error: Please navigate to a valid Flowstride project directory before logging in locally, or use --global."));
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
const pkg = JSON.parse(fs.readFileSync(localPackageJsonPath, "utf8"));
|
|
77
|
+
const deps = {
|
|
78
|
+
...(pkg.dependencies || {}),
|
|
79
|
+
...(pkg.devDependencies || {}),
|
|
80
|
+
};
|
|
81
|
+
if (!deps["flowstride"] && !deps["flowstride-os"]) {
|
|
82
|
+
console.error(chalk_1.default.red("✖ Error: Please navigate to a valid Flowstride project directory before logging in locally, or use --global."));
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
81
85
|
}
|
|
82
86
|
console.log(chalk_1.default.bold.blue("\n🌐 Flowstride Enterprise Cloud Sync\n"));
|
|
83
|
-
let
|
|
84
|
-
if (process.env.
|
|
85
|
-
console.log(chalk_1.default.yellow("🤖 CI/CD Environment Detected. Authenticating via
|
|
86
|
-
authHeader = `Bearer ${process.env.FLOWSTRIDE_API_KEY}`;
|
|
87
|
+
let token = process.env.FLOWSTRIDE_TOKEN || options.token;
|
|
88
|
+
if (process.env.FLOWSTRIDE_TOKEN) {
|
|
89
|
+
console.log(chalk_1.default.yellow("🤖 CI/CD Environment Detected. Authenticating via FLOWSTRIDE_TOKEN..."));
|
|
87
90
|
}
|
|
88
|
-
else {
|
|
89
|
-
|
|
90
|
-
message: "
|
|
91
|
+
else if (!token) {
|
|
92
|
+
token = await (0, prompts_1.password)({
|
|
93
|
+
message: "Paste your Enterprise Developer Token:",
|
|
94
|
+
mask: "*",
|
|
91
95
|
});
|
|
92
|
-
|
|
93
|
-
|
|
96
|
+
}
|
|
97
|
+
if (!token || token.trim().length === 0) {
|
|
98
|
+
console.error(chalk_1.default.red("✖ Error: A valid Developer Token is required."));
|
|
99
|
+
process.exit(1);
|
|
94
100
|
}
|
|
95
101
|
const deviceName = os.hostname();
|
|
96
102
|
const spinner = (0, ora_1.default)("Authenticating with Cloud...").start();
|
|
@@ -99,34 +105,37 @@ program
|
|
|
99
105
|
method: "POST",
|
|
100
106
|
headers: {
|
|
101
107
|
"Content-Type": "application/json",
|
|
102
|
-
Authorization:
|
|
108
|
+
Authorization: `Bearer ${token.trim()}`,
|
|
103
109
|
},
|
|
104
110
|
body: JSON.stringify({ deviceName }),
|
|
105
111
|
});
|
|
106
112
|
const data = await response.json();
|
|
107
113
|
if (!response.ok) {
|
|
108
|
-
spinner.fail(chalk_1.default.red(`Authentication Failed: ${data.error || "
|
|
114
|
+
spinner.fail(chalk_1.default.red(`Authentication Failed: ${data.error || "Invalid token or network error"}`));
|
|
109
115
|
process.exit(1);
|
|
110
116
|
}
|
|
111
117
|
spinner.succeed(chalk_1.default.green("Authentication Successful!"));
|
|
112
|
-
const configDir =
|
|
118
|
+
const configDir = options.global
|
|
119
|
+
? path.join(os.homedir(), ".flowstride")
|
|
120
|
+
: path.join(process.cwd(), ".flowstride");
|
|
113
121
|
if (!fs.existsSync(configDir)) {
|
|
114
122
|
fs.mkdirSync(configDir, { recursive: true });
|
|
115
123
|
}
|
|
116
|
-
const credentialsPath = path.join(configDir, "
|
|
124
|
+
const credentialsPath = path.join(configDir, "credentials.json");
|
|
117
125
|
fs.writeFileSync(credentialsPath, JSON.stringify({
|
|
118
|
-
machineToken: data.machineToken,
|
|
126
|
+
machineToken: data.machineToken || token.trim(),
|
|
119
127
|
workspaceId: data.workspaceId,
|
|
120
128
|
deviceName: deviceName,
|
|
121
129
|
syncedAt: new Date().toISOString(),
|
|
122
130
|
}, null, 2));
|
|
123
|
-
|
|
131
|
+
const scopeText = options.global ? "GLOBALLY" : "LOCALLY to this project";
|
|
132
|
+
console.log(chalk_1.default.gray(`\nCredentials securely scoped ${scopeText} at: ${credentialsPath}`));
|
|
124
133
|
if (data.isProEligible) {
|
|
125
134
|
console.log(chalk_1.default.yellow(`\n⚙️ System Check: You have access to Enterprise features (AI Healing, Parallel Workers).`));
|
|
126
135
|
const nodeModulesPath = path.join(process.cwd(), "node_modules", "@flowstride", "pro");
|
|
127
136
|
if (!fs.existsSync(nodeModulesPath)) {
|
|
128
137
|
let installPro = true;
|
|
129
|
-
if (!process.env.
|
|
138
|
+
if (!process.env.FLOWSTRIDE_TOKEN && !process.env.CI) {
|
|
130
139
|
installPro = await (0, prompts_1.confirm)({
|
|
131
140
|
message: "The Pro Engine is not installed in this project. Would you like Flowstride to automatically install it now?",
|
|
132
141
|
default: true,
|
|
@@ -138,26 +147,18 @@ program
|
|
|
138
147
|
try {
|
|
139
148
|
const tarballResponse = await fetch(`${API_BASE}/api/workspace/download-pro`, {
|
|
140
149
|
method: "GET",
|
|
141
|
-
headers: { Authorization: `Bearer ${
|
|
150
|
+
headers: { Authorization: `Bearer ${token.trim()}` },
|
|
142
151
|
});
|
|
143
152
|
if (!tarballResponse.ok) {
|
|
144
153
|
throw new Error(`Secure proxy rejected request: ${tarballResponse.statusText}`);
|
|
145
154
|
}
|
|
146
155
|
const buffer = await tarballResponse.arrayBuffer();
|
|
147
156
|
fs.writeFileSync(tarballPath, Buffer.from(buffer));
|
|
148
|
-
installSpinner.text = "Silently extracting Enterprise Engine...";
|
|
149
|
-
installSpinner.color = "cyan";
|
|
150
|
-
if (!fs.existsSync(nodeModulesPath)) {
|
|
151
|
-
fs.mkdirSync(nodeModulesPath, { recursive: true });
|
|
152
|
-
}
|
|
153
|
-
await tar.x({
|
|
154
|
-
file: tarballPath,
|
|
155
|
-
cwd: nodeModulesPath,
|
|
156
|
-
strip: 1,
|
|
157
|
-
});
|
|
158
157
|
installSpinner.text = "Wiring up Enterprise dependencies...";
|
|
159
|
-
|
|
160
|
-
|
|
158
|
+
installSpinner.color = "cyan";
|
|
159
|
+
// Let npm natively handle the extraction and binary symlinking
|
|
160
|
+
(0, child_process_1.execSync)(`npm install "${tarballPath}" --no-save`, {
|
|
161
|
+
cwd: process.cwd(),
|
|
161
162
|
stdio: "ignore",
|
|
162
163
|
});
|
|
163
164
|
installSpinner.succeed(chalk_1.default.green("Enterprise Engine installed successfully!"));
|
|
@@ -200,21 +201,34 @@ program
|
|
|
200
201
|
.command("status")
|
|
201
202
|
.description("Display the current Flowstride CLI authentication and engine tier")
|
|
202
203
|
.action(async () => {
|
|
203
|
-
let token = process.env.
|
|
204
|
-
const
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
204
|
+
let token = process.env.FLOWSTRIDE_TOKEN || null;
|
|
205
|
+
const localCredentialsPath = path.join(process.cwd(), ".flowstride", "credentials.json");
|
|
206
|
+
const globalCredentialsPath = path.join(os.homedir(), ".flowstride", "credentials.json");
|
|
207
|
+
let activeCredentialsPath = localCredentialsPath; // Default for cleanup
|
|
208
|
+
if (!token) {
|
|
209
|
+
if (fs.existsSync(localCredentialsPath)) {
|
|
210
|
+
activeCredentialsPath = localCredentialsPath;
|
|
211
|
+
try {
|
|
212
|
+
const credentials = JSON.parse(fs.readFileSync(localCredentialsPath, "utf8"));
|
|
213
|
+
token = credentials.machineToken;
|
|
214
|
+
}
|
|
215
|
+
catch (err) { }
|
|
216
|
+
}
|
|
217
|
+
else if (fs.existsSync(globalCredentialsPath)) {
|
|
218
|
+
activeCredentialsPath = globalCredentialsPath;
|
|
219
|
+
try {
|
|
220
|
+
const credentials = JSON.parse(fs.readFileSync(globalCredentialsPath, "utf8"));
|
|
221
|
+
token = credentials.machineToken;
|
|
222
|
+
}
|
|
223
|
+
catch (err) { }
|
|
210
224
|
}
|
|
211
|
-
catch (err) { }
|
|
212
225
|
}
|
|
213
|
-
|
|
226
|
+
const nodeModulesPath = path.join(process.cwd(), "node_modules", "@flowstride", "pro");
|
|
227
|
+
if (!process.env.FLOWSTRIDE_TOKEN &&
|
|
214
228
|
token &&
|
|
215
229
|
!fs.existsSync(nodeModulesPath)) {
|
|
216
|
-
if (fs.existsSync(
|
|
217
|
-
fs.unlinkSync(
|
|
230
|
+
if (fs.existsSync(activeCredentialsPath))
|
|
231
|
+
fs.unlinkSync(activeCredentialsPath);
|
|
218
232
|
token = null;
|
|
219
233
|
}
|
|
220
234
|
if (!token) {
|
|
@@ -228,8 +242,8 @@ program
|
|
|
228
242
|
});
|
|
229
243
|
if (response.status === 401) {
|
|
230
244
|
console.log(chalk_1.default.red("🔴 AUTH EXPIRED: Your device session was revoked or expired."));
|
|
231
|
-
if (fs.existsSync(
|
|
232
|
-
fs.unlinkSync(
|
|
245
|
+
if (fs.existsSync(activeCredentialsPath))
|
|
246
|
+
fs.unlinkSync(activeCredentialsPath);
|
|
233
247
|
}
|
|
234
248
|
else if (response.ok) {
|
|
235
249
|
const data = await response.json();
|
|
@@ -261,22 +275,35 @@ program
|
|
|
261
275
|
options = targetPath;
|
|
262
276
|
targetPath = undefined;
|
|
263
277
|
}
|
|
264
|
-
let token = process.env.
|
|
278
|
+
let token = process.env.FLOWSTRIDE_TOKEN || null;
|
|
265
279
|
let forceOS = false;
|
|
266
|
-
const
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
280
|
+
const localCredentialsPath = path.join(process.cwd(), ".flowstride", "credentials.json");
|
|
281
|
+
const globalCredentialsPath = path.join(os.homedir(), ".flowstride", "credentials.json");
|
|
282
|
+
let activeCredentialsPath = localCredentialsPath;
|
|
283
|
+
if (!token) {
|
|
284
|
+
if (fs.existsSync(localCredentialsPath)) {
|
|
285
|
+
activeCredentialsPath = localCredentialsPath;
|
|
286
|
+
try {
|
|
287
|
+
const credentials = JSON.parse(fs.readFileSync(localCredentialsPath, "utf8"));
|
|
288
|
+
token = credentials.machineToken;
|
|
289
|
+
}
|
|
290
|
+
catch (err) { }
|
|
291
|
+
}
|
|
292
|
+
else if (fs.existsSync(globalCredentialsPath)) {
|
|
293
|
+
activeCredentialsPath = globalCredentialsPath;
|
|
294
|
+
try {
|
|
295
|
+
const credentials = JSON.parse(fs.readFileSync(globalCredentialsPath, "utf8"));
|
|
296
|
+
token = credentials.machineToken;
|
|
297
|
+
}
|
|
298
|
+
catch (err) { }
|
|
272
299
|
}
|
|
273
|
-
catch (err) { }
|
|
274
300
|
}
|
|
275
|
-
|
|
301
|
+
const nodeModulesPath = path.join(process.cwd(), "node_modules", "@flowstride", "pro");
|
|
302
|
+
if (!process.env.FLOWSTRIDE_TOKEN &&
|
|
276
303
|
token &&
|
|
277
304
|
!fs.existsSync(nodeModulesPath)) {
|
|
278
|
-
if (fs.existsSync(
|
|
279
|
-
fs.unlinkSync(
|
|
305
|
+
if (fs.existsSync(activeCredentialsPath))
|
|
306
|
+
fs.unlinkSync(activeCredentialsPath);
|
|
280
307
|
token = null;
|
|
281
308
|
}
|
|
282
309
|
if (!token) {
|
|
@@ -291,8 +318,8 @@ program
|
|
|
291
318
|
});
|
|
292
319
|
if (response.status === 401) {
|
|
293
320
|
console.log(chalk_1.default.red("🔴 AUTH EXPIRED: Your device session was revoked or expired."));
|
|
294
|
-
if (fs.existsSync(
|
|
295
|
-
fs.unlinkSync(
|
|
321
|
+
if (fs.existsSync(activeCredentialsPath))
|
|
322
|
+
fs.unlinkSync(activeCredentialsPath);
|
|
296
323
|
forceOS = true;
|
|
297
324
|
}
|
|
298
325
|
else if (response.ok) {
|
|
@@ -328,7 +355,7 @@ program
|
|
|
328
355
|
});
|
|
329
356
|
async function handleOfflinePrompt() {
|
|
330
357
|
console.log(chalk_1.default.yellow("🛜 OFFLINE: Unable to reach Flowstride Cloud. Enterprise Sync paused."));
|
|
331
|
-
if (process.env.CI || process.env.
|
|
358
|
+
if (process.env.CI || process.env.FLOWSTRIDE_TOKEN) {
|
|
332
359
|
console.log(chalk_1.default.yellow("⚠️ CI/CD Environment: Automatically falling back to OS Engine."));
|
|
333
360
|
return true;
|
|
334
361
|
}
|
package/dist/parser/parser.js
CHANGED
|
@@ -357,7 +357,6 @@ class Parser {
|
|
|
357
357
|
throw new Error(`Line ${viaToken.line}: Expected 'via'`);
|
|
358
358
|
payload.verb = this.consume(lexer_1.TokenType.COMMAND, "Expected API verb (e.g. POST)").value.toLowerCase();
|
|
359
359
|
payload.url = this.consume(lexer_1.TokenType.STRING, "Endpoint URL expected").value;
|
|
360
|
-
// --- ENSURE ARRAY FORMAT FOR API CLIENT ---
|
|
361
360
|
payload.headers = [];
|
|
362
361
|
this.parseApiModifiers(payload);
|
|
363
362
|
break;
|
|
@@ -368,7 +367,6 @@ class Parser {
|
|
|
368
367
|
case "delete":
|
|
369
368
|
case "graphql":
|
|
370
369
|
payload.url = this.consume(lexer_1.TokenType.STRING, "Endpoint URL expected").value;
|
|
371
|
-
// --- ENSURE ARRAY FORMAT FOR API CLIENT ---
|
|
372
370
|
payload.headers = [];
|
|
373
371
|
this.parseApiModifiers(payload);
|
|
374
372
|
break;
|
|
@@ -617,16 +615,13 @@ class Parser {
|
|
|
617
615
|
throw new Error(`Syntax Error on line ${targetToken.line}: Invalid keyword '${target}'. Flowstride uses strict camelCase. Did you mean 'reqHeader'?`);
|
|
618
616
|
}
|
|
619
617
|
if (target === "reqHeader") {
|
|
620
|
-
// --- THE NEW MULTI-FORMAT HEADER PARSER ---
|
|
621
618
|
const firstToken = this.consume(lexer_1.TokenType.STRING, "Header key or JSON block expected");
|
|
622
|
-
// Lookahead: If the next token is ALSO a string, use the legacy Key-Value parsing
|
|
623
619
|
if (!this.isAtEnd() && this.peek().type === lexer_1.TokenType.STRING) {
|
|
624
620
|
const hKey = firstToken.value;
|
|
625
621
|
const hVal = this.advance().value;
|
|
626
622
|
payload.headers.push({ key: hKey, value: hVal });
|
|
627
623
|
}
|
|
628
624
|
else {
|
|
629
|
-
// Otherwise, it must be the new multi-line JSON Block format
|
|
630
625
|
try {
|
|
631
626
|
const headerObj = JSON.parse(firstToken.value);
|
|
632
627
|
if (typeof headerObj !== "object" ||
|
|
@@ -634,7 +629,6 @@ class Parser {
|
|
|
634
629
|
Array.isArray(headerObj)) {
|
|
635
630
|
throw new Error("Parsed JSON is not a Key-Value object.");
|
|
636
631
|
}
|
|
637
|
-
// Loop through the parsed block and push them into the array for the API client
|
|
638
632
|
for (const [k, v] of Object.entries(headerObj)) {
|
|
639
633
|
payload.headers.push({ key: k, value: String(v) });
|
|
640
634
|
}
|
|
@@ -643,7 +637,6 @@ class Parser {
|
|
|
643
637
|
throw new Error(`Line ${firstToken.line}: Invalid JSON format for reqHeader block. Ensure it is a valid JSON object, or use the legacy format with two strings (Key and Value).`);
|
|
644
638
|
}
|
|
645
639
|
}
|
|
646
|
-
// ------------------------------------------
|
|
647
640
|
}
|
|
648
641
|
else if (target === "reqBody") {
|
|
649
642
|
payload.body = this.consume(lexer_1.TokenType.STRING, "Body content expected").value;
|