@flowconsole/cli 0.0.0-beta-20260128114951 → 0.0.0-beta-20260428172441

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,121 @@
1
+ # @flowconsole/cli
2
+
3
+ npm wrapper for the FlowConsole CLI (`fcon`) — architecture-as-code scanning, validation, diff, and push.
4
+
5
+ > **Note:** This package (`@flowconsole/cli@2.x`) replaces the legacy TypeScript CLI (`@flowconsole/cli@1.x`). The new CLI is a .NET self-contained binary distributed via this npm wrapper. `@flowconsole/cli@1.x` is deprecated but remains published on npm.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install -g @flowconsole/cli
11
+ ```
12
+
13
+ After installation, two equivalent commands are available globally:
14
+
15
+ | Command | When to use |
16
+ |---------|-------------|
17
+ | `fcon` | Short alias without the shell-builtin conflict. Recommended when scripting in zsh/bash. |
18
+ | `flowconsole` | Explicit, fully-qualified name. Best for CI scripts and documentation where readability matters more than brevity. |
19
+
20
+ All resolve to the same underlying binary.
21
+
22
+ ## Platform Support
23
+
24
+ | Platform | Architecture | RID |
25
+ |----------|-------------|-----|
26
+ | Linux | x64 | linux-x64 |
27
+ | Linux | arm64 | linux-arm64 |
28
+ | macOS | x64 (Intel) | osx-x64 |
29
+ | macOS | arm64 (Apple Silicon) | osx-arm64 |
30
+ | Windows | x64 | win-x64 |
31
+ | Windows | arm64 | win-arm64 |
32
+
33
+ The `postinstall` script automatically detects your platform and downloads the appropriate binary from GitHub Releases.
34
+
35
+ ## Usage
36
+
37
+ ```bash
38
+ # Scan a project directory
39
+ fcon scan ./my-project
40
+
41
+ # Validate a snapshot against rules
42
+ fcon validate snapshot.json rules/
43
+
44
+ # Compare two snapshots
45
+ fcon diff before.json after.json --format markdown
46
+
47
+ # Push snapshot to FlowConsole backend
48
+ export FLOWCONSOLE_API_KEY=fcp_your_token_here
49
+ fcon push snapshot snapshot.json --model <model-id>
50
+
51
+ # Push validation findings
52
+ fcon push findings findings.json --model <model-id>
53
+
54
+ # Manage telemetry
55
+ fcon telemetry status
56
+ fcon telemetry off
57
+
58
+ # CI pipeline example
59
+ fcon scan ./project -o snapshot.json && \
60
+ fcon validate snapshot.json rules/ && \
61
+ fcon push snapshot snapshot.json --model <model-id>
62
+ ```
63
+
64
+ ## Environment Variables
65
+
66
+ | Variable | Description |
67
+ |----------|-------------|
68
+ | `FLOWCONSOLE_API_KEY` | Personal Access Token for `fcon push` (required; `--api-key` flag is refused for security) |
69
+ | `FLOWCONSOLE_API_URL` | Backend API URL (default from `.flowconsole.yaml`) |
70
+ | `FLOWCONSOLE_TELEMETRY` | Set to `off` to disable telemetry |
71
+ | `DO_NOT_TRACK` | Set to `1` to disable telemetry (standard) |
72
+ | `FLOWCONSOLE_CLI_DOWNLOAD_URL` | Override binary download base URL (for mirrors/air-gap) |
73
+ | `FLOWCONSOLE_CLI_SKIP_DOWNLOAD` | Set to `1` to skip binary download in postinstall |
74
+
75
+ ## Troubleshooting
76
+
77
+ ### postinstall fails
78
+
79
+ - Check your internet connection — the binary is downloaded from GitHub Releases
80
+ - If behind a proxy, configure `https_proxy` / `HTTPS_PROXY` environment variable
81
+ - If on an unsupported platform, download the binary manually from the [Releases page](https://github.com/flowconsole/flowconsole/releases)
82
+ - Set `FLOWCONSOLE_CLI_DOWNLOAD_URL` to point to a mirror if GitHub is blocked
83
+
84
+ ### Binary not found after install
85
+
86
+ Run `npm rebuild @flowconsole/cli` to re-trigger the download.
87
+
88
+ ### macOS Gatekeeper warning
89
+
90
+ The alpha binaries are unsigned. On first run, macOS may block execution. To allow:
91
+
92
+ ```bash
93
+ xattr -d com.apple.quarantine $(which fc)
94
+ ```
95
+
96
+ Or go to System Settings > Privacy & Security and click "Allow Anyway".
97
+
98
+ ### Windows SmartScreen warning
99
+
100
+ The alpha binaries are unsigned. Windows SmartScreen may show "Windows protected your PC". Click "More info" then "Run anyway".
101
+
102
+ > Signing and notarization are planned for v1.0.0. Alpha binaries are unsigned.
103
+
104
+ ## Deprecation of v1
105
+
106
+ `@flowconsole/cli@1.x` was a TypeScript-based CLI. `@flowconsole/cli@2.x` is a complete rewrite as a .NET self-contained binary, offering:
107
+
108
+ - Faster scanning via Tree-sitter native parsers
109
+ - Built-in rule engine with CEL expressions
110
+ - Schema validation against the model-snapshot contract
111
+ - `fcon push` for CI/CD integration with PAT authentication
112
+ - `fcon diff` for offline snapshot comparison
113
+ - Anonymous telemetry (opt-out via `fcon telemetry off`)
114
+
115
+ To upgrade: `npm install -g @flowconsole/cli@next`
116
+
117
+ The v1 package remains published for backward compatibility but receives no updates.
118
+
119
+ ## License
120
+
121
+ Apache-2.0
package/bin/fc.js ADDED
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+
3
+ "use strict";
4
+
5
+ const { execFileSync } = require("child_process");
6
+ const path = require("path");
7
+ const fs = require("fs");
8
+
9
+ const PLATFORM_MAP = {
10
+ darwin: "osx",
11
+ linux: "linux",
12
+ win32: "win",
13
+ };
14
+
15
+ const ARCH_MAP = {
16
+ x64: "x64",
17
+ arm64: "arm64",
18
+ };
19
+
20
+ function getBinaryPath() {
21
+ const platform = PLATFORM_MAP[process.platform];
22
+ const arch = ARCH_MAP[process.arch];
23
+
24
+ if (!platform || !arch) {
25
+ console.error(
26
+ `Unsupported platform: ${process.platform}-${process.arch}\n` +
27
+ `Run 'npm rebuild @flowconsole/cli' or download the binary manually.`
28
+ );
29
+ process.exit(1);
30
+ }
31
+
32
+ const rid = `${platform}-${arch}`;
33
+ const binaryName = process.platform === "win32" ? "fcon.exe" : "fcon";
34
+ const binaryPath = path.join(__dirname, "..", "binaries", rid, binaryName);
35
+
36
+ if (!fs.existsSync(binaryPath)) {
37
+ console.error(
38
+ `FlowConsole CLI binary not found at ${binaryPath}\n` +
39
+ `Run 'npm rebuild @flowconsole/cli' to download it, or set\n` +
40
+ `FLOWCONSOLE_CLI_DOWNLOAD_URL to a custom mirror.`
41
+ );
42
+ process.exit(1);
43
+ }
44
+
45
+ return binaryPath;
46
+ }
47
+
48
+ const binaryPath = getBinaryPath();
49
+
50
+ try {
51
+ const result = execFileSync(binaryPath, process.argv.slice(2), {
52
+ stdio: "inherit",
53
+ env: process.env,
54
+ });
55
+ } catch (err) {
56
+ // execFileSync throws on non-zero exit code when stdio is inherit
57
+ if (err.status != null) {
58
+ process.exitCode = err.status;
59
+ } else {
60
+ console.error(`Failed to execute FlowConsole CLI: ${err.message}`);
61
+ process.exitCode = 1;
62
+ }
63
+ }
package/package.json CHANGED
@@ -1,46 +1,51 @@
1
1
  {
2
2
  "name": "@flowconsole/cli",
3
- "version": "0.0.0-beta-20260128114951",
4
- "description": "FlowConsole CLI for building your architecture",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
3
+ "version": "0.0.0-beta-20260428172441",
4
+ "description": "FlowConsole CLI (fc / fcon / flowconsole) — architecture scanner and rule engine for your CI/CD",
5
+ "license": "Apache-2.0",
7
6
  "bin": {
8
- "flowconsole": "dist/index.js"
7
+ "fc": "./bin/fc.js",
8
+ "fcon": "./bin/fc.js",
9
+ "flowconsole": "./bin/fc.js"
10
+ },
11
+ "cliVersion": "0.2.0-beta-20260428172441",
12
+ "engines": {
13
+ "node": ">=18.0.0"
9
14
  },
15
+ "os": [
16
+ "darwin",
17
+ "linux",
18
+ "win32"
19
+ ],
20
+ "cpu": [
21
+ "x64",
22
+ "arm64"
23
+ ],
10
24
  "files": [
11
- "dist/**/*.js",
12
- "dist/**/*.d.ts",
13
- "!dist/**/*.tsbuildinfo",
14
- "!dist/vitest.config.*",
15
- "!dist/test_sample.*"
25
+ "bin/",
26
+ "binaries/",
27
+ "postinstall.js",
28
+ "README.md"
16
29
  ],
17
- "publishConfig": {
18
- "access": "public"
19
- },
20
- "keywords": [],
21
- "author": "",
22
- "license": "Apache-2.0",
23
- "dependencies": {
24
- "@ast-grep/lang-csharp": "^0.0.5",
25
- "@ast-grep/lang-go": "^0.0.5",
26
- "@ast-grep/lang-java": "^0.0.6",
27
- "@ast-grep/lang-python": "^0.0.5",
28
- "@ast-grep/napi": "^0.40.5",
29
- "uuidv7": "^1.1.0",
30
- "yargs": "^18.0.0",
31
- "@flowconsole/core": "0.0.0-beta-20260128114951"
30
+ "keywords": [
31
+ "architecture",
32
+ "architecture-as-code",
33
+ "diagrams",
34
+ "cli",
35
+ "flowconsole"
36
+ ],
37
+ "author": "FlowConsole <v@flowconsole.tech> (https://flowconsole.tech)",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "https://github.com/flowconsole/flowconsole.git",
41
+ "directory": "packages/cli"
32
42
  },
33
- "devDependencies": {
34
- "@types/node": "^22.0.0",
35
- "@types/yargs": "^17.0.33",
36
- "ts-node": "^10.9.2",
37
- "typescript": "^5.9.3",
38
- "vitest": "^4.0.17",
39
- "@flowconsole/sdk": "0.0.0-beta-20260128114951"
43
+ "homepage": "https://flowconsole.tech",
44
+ "bugs": {
45
+ "url": "https://github.com/flowconsole/flowconsole/issues"
40
46
  },
41
47
  "scripts": {
42
- "build": "tsc --build",
43
- "test": "vitest run --config vitest.config.ts",
44
- "test:watch": "vitest --config vitest.config.ts"
48
+ "postinstall": "node postinstall.js",
49
+ "dev:reinstall": "bash scripts/dev-reinstall.sh"
45
50
  }
46
51
  }
package/postinstall.js ADDED
@@ -0,0 +1,269 @@
1
+ #!/usr/bin/env node
2
+
3
+ "use strict";
4
+
5
+ const https = require("https");
6
+ const http = require("http");
7
+ const fs = require("fs");
8
+ const path = require("path");
9
+ const { createHash } = require("crypto");
10
+ const { execFileSync } = require("child_process");
11
+ const { pipeline } = require("stream/promises");
12
+ const zlib = require("zlib");
13
+
14
+ const PLATFORM_MAP = {
15
+ darwin: "osx",
16
+ linux: "linux",
17
+ win32: "win",
18
+ };
19
+
20
+ const ARCH_MAP = {
21
+ x64: "x64",
22
+ arm64: "arm64",
23
+ };
24
+
25
+ const SUPPORTED_RIDS = [
26
+ "linux-x64",
27
+ "linux-arm64",
28
+ "osx-x64",
29
+ "osx-arm64",
30
+ "win-x64",
31
+ "win-arm64",
32
+ ];
33
+
34
+ function getRid() {
35
+ const platform = PLATFORM_MAP[process.platform];
36
+ const arch = ARCH_MAP[process.arch];
37
+
38
+ if (!platform || !arch) {
39
+ const supported = SUPPORTED_RIDS.join(", ");
40
+ console.error(
41
+ `Unsupported platform: ${process.platform}-${process.arch}\n` +
42
+ `Supported platforms: ${supported}\n` +
43
+ `You can download the binary manually from the GitHub Releases page.`
44
+ );
45
+ process.exit(1);
46
+ }
47
+
48
+ return `${platform}-${arch}`;
49
+ }
50
+
51
+ function getVersion() {
52
+ const pkg = JSON.parse(
53
+ fs.readFileSync(path.join(__dirname, "package.json"), "utf8")
54
+ );
55
+ return pkg.cliVersion || "0.2.0-alpha";
56
+ }
57
+
58
+ function getDownloadBaseUrl() {
59
+ // Allow override via env for mirrors or private registries
60
+ if (process.env.FLOWCONSOLE_CLI_DOWNLOAD_URL) {
61
+ return process.env.FLOWCONSOLE_CLI_DOWNLOAD_URL;
62
+ }
63
+ const pkg = JSON.parse(
64
+ fs.readFileSync(path.join(__dirname, "package.json"), "utf8")
65
+ );
66
+ const repoUrl =
67
+ (pkg.repository && pkg.repository.url) || "";
68
+ // Extract GitHub owner/repo from git URL
69
+ const match = repoUrl.match(
70
+ /github\.com[/:]([^/]+\/[^/.]+?)(?:\.git)?$/
71
+ );
72
+ if (match) {
73
+ return `https://github.com/${match[1]}/releases/download`;
74
+ }
75
+ return "https://github.com/flowconsole/flowconsole/releases/download";
76
+ }
77
+
78
+ function fetch(url, maxRedirects = 5) {
79
+ return new Promise((resolve, reject) => {
80
+ const client = url.startsWith("https") ? https : http;
81
+ client
82
+ .get(url, { headers: { "User-Agent": "flowconsole-cli-npm" } }, (res) => {
83
+ //Follow redirects (GitHub releases redirect to S3)
84
+ if (
85
+ (res.statusCode === 301 ||
86
+ res.statusCode === 302 ||
87
+ res.statusCode === 307) &&
88
+ res.headers.location
89
+ ) {
90
+ if (maxRedirects <= 0) {
91
+ reject(new Error(`Too many redirects following ${url}`));
92
+ return;
93
+ }
94
+ return fetch(res.headers.location, maxRedirects - 1).then(resolve, reject);
95
+ }
96
+ if (res.statusCode !== 200) {
97
+ reject(
98
+ new Error(
99
+ `Failed to download ${url}: HTTP ${res.statusCode}`
100
+ )
101
+ );
102
+ res.resume();
103
+ return;
104
+ }
105
+ resolve(res);
106
+ })
107
+ .on("error", reject);
108
+ });
109
+ }
110
+
111
+ async function downloadToBuffer(url) {
112
+ const res = await fetch(url);
113
+ const chunks = [];
114
+ for await (const chunk of res) {
115
+ chunks.push(chunk);
116
+ }
117
+ return Buffer.concat(chunks);
118
+ }
119
+
120
+ async function downloadAndVerify(rid, version) {
121
+ const baseUrl = getDownloadBaseUrl();
122
+ const tag = `cli-v${version}`;
123
+ const isWindows = rid.startsWith("win-");
124
+ const archiveExt = isWindows ? "zip" : "tar.gz";
125
+ const archiveName = `fcon-${rid}.${archiveExt}`;
126
+ const checksumName = `fcon-${rid}.sha256`;
127
+
128
+ const archiveUrl = `${baseUrl}/${tag}/${archiveName}`;
129
+ const checksumUrl = `${baseUrl}/${tag}/${checksumName}`;
130
+
131
+ console.log(`Downloading FlowConsole CLI ${version} for ${rid}...`);
132
+ console.log(` Archive: ${archiveUrl}`);
133
+
134
+ // Download checksum first
135
+ let expectedChecksum;
136
+ try {
137
+ const checksumBuf = await downloadToBuffer(checksumUrl);
138
+ // Format: "<hash> <filename>" or "<hash> <filename>"
139
+ expectedChecksum = checksumBuf.toString("utf8").trim().split(/\s+/)[0];
140
+ } catch (err) {
141
+ console.error(
142
+ `Failed to download checksum file: ${err.message}\n` +
143
+ `Cannot verify binary integrity. Aborting installation.\n` +
144
+ `Download the binary manually from: ${archiveUrl}`
145
+ );
146
+ process.exit(1);
147
+ }
148
+
149
+ // Download archive
150
+ const archiveBuf = await downloadToBuffer(archiveUrl);
151
+
152
+ // Verify checksum
153
+ const actualChecksum = createHash("sha256")
154
+ .update(archiveBuf)
155
+ .digest("hex");
156
+
157
+ if (actualChecksum !== expectedChecksum) {
158
+ console.error(
159
+ `SHA-256 checksum mismatch!\n` +
160
+ ` Expected: ${expectedChecksum}\n` +
161
+ ` Actual: ${actualChecksum}\n` +
162
+ `The downloaded binary may be corrupted or tampered with.\n` +
163
+ `Aborting installation.`
164
+ );
165
+ process.exit(1);
166
+ }
167
+
168
+ console.log(` Checksum verified: ${actualChecksum.substring(0, 16)}...`);
169
+
170
+ return { archiveBuf, isWindows };
171
+ }
172
+
173
+ function extractTarGz(buf, destDir) {
174
+ // Write to temp file and use system tar (Node.js has no built-in tar extraction)
175
+ const tmpFile = path.join(destDir, "_archive.tar.gz");
176
+ fs.writeFileSync(tmpFile, buf);
177
+ try {
178
+ execFileSync("tar", ["xzf", tmpFile, "-C", destDir], {
179
+ stdio: "pipe",
180
+ });
181
+ } finally {
182
+ try {
183
+ fs.unlinkSync(tmpFile);
184
+ } catch (_) {}
185
+ }
186
+ }
187
+
188
+ function extractZip(buf, destDir) {
189
+ // Write to temp file and use system tools
190
+ const tmpFile = path.join(destDir, "_archive.zip");
191
+ fs.writeFileSync(tmpFile, buf);
192
+ try {
193
+ // Try unzip first (available on most systems including Windows via Git Bash)
194
+ try {
195
+ execFileSync("unzip", ["-o", tmpFile, "-d", destDir], {
196
+ stdio: "pipe",
197
+ });
198
+ } catch (_) {
199
+ // Fallback to PowerShell on Windows
200
+ // Escape single quotes for PowerShell (double them inside single-quoted strings)
201
+ const escapedTmpFile = tmpFile.replace(/'/g, "''");
202
+ const escapedDestDir = destDir.replace(/'/g, "''");
203
+ execFileSync(
204
+ "powershell",
205
+ [
206
+ "-NoProfile",
207
+ "-Command",
208
+ `Expand-Archive -Path '${escapedTmpFile}' -DestinationPath '${escapedDestDir}' -Force`,
209
+ ],
210
+ { stdio: "pipe" }
211
+ );
212
+ }
213
+ } finally {
214
+ try {
215
+ fs.unlinkSync(tmpFile);
216
+ } catch (_) {}
217
+ }
218
+ }
219
+
220
+ async function main() {
221
+ // Skip in CI if FLOWCONSOLE_CLI_SKIP_DOWNLOAD is set
222
+ if (process.env.FLOWCONSOLE_CLI_SKIP_DOWNLOAD === "1") {
223
+ console.log("FLOWCONSOLE_CLI_SKIP_DOWNLOAD=1, skipping binary download.");
224
+ return;
225
+ }
226
+
227
+ const rid = getRid();
228
+ const version = getVersion();
229
+ const binDir = path.join(__dirname, "binaries", rid);
230
+
231
+ // Check if already downloaded
232
+ const binaryName = rid.startsWith("win-") ? "fcon.exe" : "fcon";
233
+ const binaryPath = path.join(binDir, binaryName);
234
+ if (fs.existsSync(binaryPath)) {
235
+ console.log(`FlowConsole CLI binary already exists at ${binaryPath}`);
236
+ return;
237
+ }
238
+
239
+ const { archiveBuf, isWindows } = await downloadAndVerify(rid, version);
240
+
241
+ // Extract
242
+ fs.mkdirSync(binDir, { recursive: true });
243
+
244
+ if (isWindows) {
245
+ extractZip(archiveBuf, binDir);
246
+ } else {
247
+ extractTarGz(archiveBuf, binDir);
248
+ }
249
+
250
+ // chmod +x on POSIX
251
+ if (!isWindows && fs.existsSync(binaryPath)) {
252
+ fs.chmodSync(binaryPath, 0o755);
253
+ }
254
+
255
+ if (!fs.existsSync(binaryPath)) {
256
+ console.error(
257
+ `Binary not found after extraction at ${binaryPath}.\n` +
258
+ `Archive may have a different structure. Please report this issue.`
259
+ );
260
+ process.exit(1);
261
+ }
262
+
263
+ console.log(`FlowConsole CLI ${version} installed successfully for ${rid}.`);
264
+ }
265
+
266
+ main().catch((err) => {
267
+ console.error(`Failed to install FlowConsole CLI: ${err.message}`);
268
+ process.exit(1);
269
+ });
package/dist/index.d.ts DELETED
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};
package/dist/index.js DELETED
@@ -1,104 +0,0 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
- Object.defineProperty(exports, "__esModule", { value: true });
4
- const fs = require("node:fs");
5
- const path = require("node:path");
6
- const yargs = require("yargs");
7
- const helpers = require("yargs/helpers");
8
- const napi_1 = require("@ast-grep/napi");
9
- const parser_registry_1 = require("@flowconsole/core/infra/parser-registry");
10
- const language_detector_1 = require("@flowconsole/core/infra/language-detector");
11
- const typescript_parser_1 = require("@flowconsole/core/parsers/typescript-parser");
12
- const python_parser_1 = require("@flowconsole/core/parsers/python-parser");
13
- const csharp_parser_1 = require("@flowconsole/core/parsers/csharp-parser");
14
- const java_parser_1 = require("@flowconsole/core/parsers/java-parser");
15
- const go_parser_1 = require("@flowconsole/core/parsers/go-parser");
16
- // ast-grep lang packages export CommonJS objects without a default field; make sure we pass the raw object.
17
- function loadLang(mod) {
18
- return (mod?.default ?? mod);
19
- }
20
- const csharp = loadLang(require('@ast-grep/lang-csharp'));
21
- const python = loadLang(require('@ast-grep/lang-python'));
22
- const java = loadLang(require('@ast-grep/lang-java'));
23
- const go = loadLang(require('@ast-grep/lang-go'));
24
- (0, napi_1.registerDynamicLanguage)({
25
- csharp,
26
- python,
27
- java,
28
- go,
29
- });
30
- const registry = new parser_registry_1.ParserRegistry();
31
- registry.register(new typescript_parser_1.TypeScriptParser());
32
- registry.register(new python_parser_1.PythonParser());
33
- registry.register(new csharp_parser_1.CSharpParser());
34
- registry.register(new java_parser_1.JavaParser());
35
- registry.register(new go_parser_1.GoParser());
36
- async function parseFile(options) {
37
- const { file, lang, output } = options;
38
- const source = fs.readFileSync(path.resolve(file), 'utf8');
39
- const detectedLang = lang || language_detector_1.LanguageDetector.detectFromFilePath(file);
40
- if (!detectedLang) {
41
- console.error(`Error: Could not detect language for ${file}. Please specify with --lang`);
42
- console.error(`\nSupported languages: ${registry.getSupportedLanguages().join(', ')}`);
43
- process.exit(1);
44
- }
45
- const parser = registry.getParser(detectedLang);
46
- if (!parser) {
47
- console.error(`Error: No parser available for language: ${detectedLang}`);
48
- console.error(`Supported languages: ${registry.getSupportedLanguages().join(', ')}`);
49
- process.exit(1);
50
- }
51
- try {
52
- const result = parser.parse(source);
53
- const json = JSON.stringify(result, null, 2);
54
- if (output) {
55
- fs.writeFileSync(output, json, 'utf8');
56
- console.log(`Output written to: ${output}`);
57
- }
58
- else {
59
- console.log(json);
60
- }
61
- }
62
- catch (error) {
63
- console.error(`Error parsing file:`, error);
64
- process.exit(1);
65
- }
66
- }
67
- yargs(helpers.hideBin(process.argv))
68
- .command('parse <file>', 'Parse architecture file and extract nodes and flows', (yargs) => {
69
- return yargs
70
- .positional('file', {
71
- describe: 'Path to architecture file',
72
- type: 'string',
73
- demandOption: true,
74
- })
75
- .option('lang', {
76
- alias: 'l',
77
- describe: 'Language of the source file',
78
- choices: ['typescript', 'python', 'csharp', 'java', 'go'],
79
- type: 'string',
80
- })
81
- .option('output', {
82
- alias: 'o',
83
- describe: 'Output file path (default: stdout)',
84
- type: 'string',
85
- });
86
- }, (argv) => {
87
- parseFile(argv).catch((error) => {
88
- console.error(error);
89
- process.exit(1);
90
- });
91
- })
92
- .command('languages', 'List supported languages', () => { }, () => {
93
- console.log('Supported languages:');
94
- registry.getSupportedLanguages().forEach((lang) => {
95
- console.log(` - ${lang}`);
96
- });
97
- })
98
- .demandCommand(1, 'You must provide a command')
99
- .help()
100
- .alias('help', 'h')
101
- .version('0.1.0')
102
- .alias('version', 'v')
103
- .parse();
104
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFDQSw4QkFBOEI7QUFDOUIsa0NBQWtDO0FBQ2xDLCtCQUErQjtBQUMvQix5Q0FBeUM7QUFDekMseUNBQXlEO0FBQ3pELDZFQUF5RTtBQUN6RSxpRkFBNkU7QUFDN0UsbUZBQStFO0FBQy9FLDJFQUF1RTtBQUN2RSwyRUFBdUU7QUFDdkUsdUVBQW1FO0FBQ25FLG1FQUErRDtBQVUvRCw0R0FBNEc7QUFDNUcsU0FBUyxRQUFRLENBQUMsR0FBUTtJQUN4QixPQUFPLENBQUMsR0FBRyxFQUFFLE9BQU8sSUFBSSxHQUFHLENBQXFCLENBQUM7QUFDbkQsQ0FBQztBQUVELE1BQU0sTUFBTSxHQUFHLFFBQVEsQ0FBQyxPQUFPLENBQUMsdUJBQXVCLENBQUMsQ0FBQyxDQUFDO0FBQzFELE1BQU0sTUFBTSxHQUFHLFFBQVEsQ0FBQyxPQUFPLENBQUMsdUJBQXVCLENBQUMsQ0FBQyxDQUFDO0FBQzFELE1BQU0sSUFBSSxHQUFHLFFBQVEsQ0FBQyxPQUFPLENBQUMscUJBQXFCLENBQUMsQ0FBQyxDQUFDO0FBQ3RELE1BQU0sRUFBRSxHQUFHLFFBQVEsQ0FBQyxPQUFPLENBQUMsbUJBQW1CLENBQUMsQ0FBQyxDQUFDO0FBUWxELElBQUEsOEJBQXVCLEVBQUM7SUFDdEIsTUFBTTtJQUNOLE1BQU07SUFDTixJQUFJO0lBQ0osRUFBRTtDQUNILENBQUMsQ0FBQztBQUVILE1BQU0sUUFBUSxHQUFHLElBQUksZ0NBQWMsRUFBRSxDQUFDO0FBRXRDLFFBQVEsQ0FBQyxRQUFRLENBQUMsSUFBSSxvQ0FBZ0IsRUFBRSxDQUFDLENBQUM7QUFDMUMsUUFBUSxDQUFDLFFBQVEsQ0FBQyxJQUFJLDRCQUFZLEVBQUUsQ0FBQyxDQUFDO0FBQ3RDLFFBQVEsQ0FBQyxRQUFRLENBQUMsSUFBSSw0QkFBWSxFQUFFLENBQUMsQ0FBQztBQUN0QyxRQUFRLENBQUMs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S0FBSyxFQUFFLEdBQUc7UUFDVixRQUFRLEVBQUUsb0NBQW9DO1FBQzlDLElBQUksRUFBRSxRQUFRO0tBQ2YsQ0FBQyxDQUFDO0FBQ1AsQ0FBQyxFQUNELENBQUMsSUFBSSxFQUFFLEVBQUU7SUFDUCxTQUFTLENBQUMsSUFBb0IsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLEtBQUssRUFBRSxFQUFFO1FBQzlDLE9BQU8sQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDckIsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUNsQixDQUFDLENBQUMsQ0FBQztBQUNMLENBQUMsQ0FDRjtLQUNBLE9BQU8sQ0FDTixXQUFXLEVBQ1gsMEJBQTBCLEVBQzFCLEdBQUcsRUFBRSxHQUFFLENBQUMsRUFDUixHQUFHLEVBQUU7SUFDSCxPQUFPLENBQUMsR0FBRyxDQUFDLHNCQUFzQixDQUFDLENBQUM7SUFDcEMsUUFBUSxDQUFDLHFCQUFxQixFQUFFLENBQUMsT0FBTyxDQUFDLENBQUMsSUFBSSxFQUFFLEVBQUU7UUFDaEQsT0FBTyxDQUFDLEdBQUcsQ0FBQyxPQUFPLElBQUksRUFBRSxDQUFDLENBQUM7SUFDN0IsQ0FBQyxDQUFDLENBQUM7QUFDTCxDQUFDLENBQ0Y7S0FDQSxhQUFhLENBQUMsQ0FBQyxFQUFFLDRCQUE0QixDQUFDO0tBQzlDLElBQUksRUFBRTtLQUNOLEtBQUssQ0FBQyxNQUFNLEVBQUUsR0FBRyxDQUFDO0tBQ2xCLE9BQU8sQ0FBQyxPQUFPLENBQUM7S0FDaEIsS0FBSyxDQUFDLFNBQVMsRUFBRSxHQUFHLENBQUM7S0FDckIsS0FBSyxFQUFFLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIjIS91c3IvYmluL2VudiBub2RlXG5pbXBvcnQgKiBhcyBmcyBmcm9tICdub2RlOmZzJztcbmltcG9ydCAqIGFzIHBhdGggZnJvbSAnbm9kZTpwYXRoJztcbmltcG9ydCAqIGFzIHlhcmdzIGZyb20gJ3lhcmdzJztcbmltcG9ydCAqIGFzIGhlbHBlcnMgZnJvbSAneWFyZ3MvaGVscGVycyc7XG5pbXBvcnQgeyByZWdpc3RlckR5bmFtaWNMYW5ndWFnZSB9IGZyb20gJ0Bhc3QtZ3JlcC9uYXBpJztcbmltcG9ydCB7IFBhcnNlclJlZ2lzdHJ5IH0gZnJvbSAnQGZsb3djb25zb2xlL2NvcmUvaW5mcmEvcGFyc2VyLXJlZ2lzdHJ5JztcbmltcG9ydCB7IExhbmd1YWdlRGV0ZWN0b3IgfSBmcm9tICdAZmxvd2NvbnNvbGUvY29yZS9pbmZyYS9sYW5ndWFnZS1kZXRlY3Rvcic7XG5pbXBvcnQgeyBUeXBlU2NyaXB0UGFyc2VyIH0gZnJvbSAnQGZsb3djb25zb2xlL2NvcmUvcGFyc2Vycy90eXBlc2NyaXB0LXBhcnNlcic7XG5pbXBvcnQgeyBQeXRob25QYXJzZXIgfSBmcm9tICdAZmxvd2NvbnNvbGUvY29yZS9wYXJzZXJzL3B5dGhvbi1wYXJzZXInO1xuaW1wb3J0IHsgQ1NoYXJwUGFyc2VyIH0gZnJvbSAnQGZsb3djb25zb2xlL2NvcmUvcGFyc2Vycy9jc2hhcnAtcGFyc2VyJztcbmltcG9ydCB7IEphdmFQYXJzZXIgfSBmcm9tICdAZmxvd2NvbnNvbGUvY29yZS9wYXJzZXJzL2phdmEtcGFyc2VyJztcbmltcG9ydCB7IEdvUGFyc2VyIH0gZnJvbSAnQGZsb3djb25zb2xlL2NvcmUvcGFyc2Vycy9nby1wYXJzZXInO1xuaW1wb3J0IHR5cGUgeyBTdXBwb3J0ZWRMYW5ndWFnZSB9IGZyb20gJ0BmbG93Y29uc29sZS9jb3JlL3R5cGVzL2NvbW1vbic7XG50eXBlIExhbmdSZWdpc3RyYXRpb24gPSB7XG4gIGxpYnJhcnlQYXRoOiBzdHJpbmc7XG4gIGV4dGVuc2lvbnM6IHN0cmluZ1tdO1xuICBsYW5ndWFnZVN5bWJvbD86IHN0cmluZztcbiAgbWV0YVZhckNoYXI/OiBzdHJpbmc7XG4gIGV4cGFuZG9DaGFyPzogc3RyaW5nO1xufTtcblxuLy8gYXN0LWdyZXAgbGFuZyBwYWNrYWdlcyBleHBvcnQgQ29tbW9uSlMgb2JqZWN0cyB3aXRob3V0IGEgZGVmYXVsdCBmaWVsZDsgbWFrZSBzdXJlIHdlIHBhc3MgdGhlIHJhdyBvYmplY3QuXG5mdW5jdGlvbiBsb2FkTGFuZyhtb2Q6IGFueSk6IExhbmdSZWdpc3RyYXRpb24ge1xuICByZXR1cm4gKG1vZD8uZGVmYXVsdCA/PyBtb2QpIGFzIExhbmdSZWdpc3RyYXRpb247XG59XG5cbmNvbnN0IGNzaGFycCA9IGxvYWRMYW5nKHJlcXVpcmUoJ0Bhc3QtZ3JlcC9sYW5nLWNzaGFycCcpKTtcbmNvbnN0IHB5dGhvbiA9IGxvYWRMYW5nKHJlcXVpcmUoJ0Bhc3QtZ3JlcC9sYW5nLXB5dGhvbicpKTtcbmNvbnN0IGphdmEgPSBsb2FkTGFuZyhyZXF1aXJlKCdAYXN0LWdyZXAvbGFuZy1qYXZhJykpO1xuY29uc3QgZ28gPSBsb2FkTGFuZyhyZXF1aXJlKCdAYXN0LWdyZXAvbGFuZy1nbycpKTtcblxuaW50ZXJmYWNlIFBhcnNlT3B0aW9ucyB7XG4gIGZpbGU6IHN0cmluZztcbiAgbGFuZz86IFN1cHBvcnRlZExhbmd1YWdlO1xuICBvdXRwdXQ/OiBzdHJpbmc7XG59XG5cbnJlZ2lzdGVyRHluYW1pY0xhbmd1YWdlKHtcbiAgY3NoYXJwLFxuICBweXRob24sXG4gIGphdmEsXG4gIGdvLFxufSk7XG5cbmNvbnN0IHJlZ2lzdHJ5ID0gbmV3IFBhcnNlclJlZ2lzdHJ5KCk7XG5cbnJlZ2lzdHJ5LnJlZ2lzdGVyKG5ldyBUeXBlU2NyaXB0UGFyc2VyKCkpO1xucmVnaXN0cnkucmVnaXN0ZXIobmV3IFB5dGhvblBhcnNlcigpKTtcbnJlZ2lzdHJ5LnJlZ2lzdGVyKG5ldyBDU2hhcnBQYXJzZXIoKSk7XG5yZWdpc3RyeS5yZWdpc3RlcihuZXcgSmF2YVBhcnNlcigpKTtcbnJlZ2lzdHJ5LnJlZ2lzdGVyKG5ldyBHb1BhcnNlcigpKTtcblxuYXN5bmMgZnVuY3Rpb24gcGFyc2VGaWxlKG9wdGlvbnM6IFBhcnNlT3B0aW9ucyk6IFByb21pc2U8dm9pZD4ge1xuICBjb25zdCB7IGZpbGUsIGxhbmcsIG91dHB1dCB9ID0gb3B0aW9ucztcblxuICBjb25zdCBzb3VyY2UgPSBmcy5yZWFkRmlsZVN5bmMocGF0aC5yZXNvbHZlKGZpbGUpLCAndXRmOCcpO1xuXG4gIGNvbnN0IGRldGVjdGVkTGFuZyA9IGxhbmcgfHwgTGFuZ3VhZ2VEZXRlY3Rvci5kZXRlY3RGcm9tRmlsZVBhdGgoZmlsZSk7XG5cbiAgaWYgKCFkZXRlY3RlZExhbmcpIHtcbiAgICBjb25zb2xlLmVycm9yKGBFcnJvcjogQ291bGQgbm90IGRldGVjdCBsYW5ndWFnZSBmb3IgJHtmaWxlfS4gUGxlYXNlIHNwZWNpZnkgd2l0aCAtLWxhbmdgKTtcbiAgICBjb25zb2xlLmVycm9yKGBcXG5TdXBwb3J0ZWQgbGFuZ3VhZ2VzOiAke3JlZ2lzdHJ5LmdldFN1cHBvcnRlZExhbmd1YWdlcygpLmpvaW4oJywgJyl9YCk7XG4gICAgcHJvY2Vzcy5leGl0KDEpO1xuICB9XG5cbiAgY29uc3QgcGFyc2VyID0gcmVnaXN0cnkuZ2V0UGFyc2VyKGRldGVjdGVkTGFuZyk7XG5cbiAgaWYgKCFwYXJzZXIpIHtcbiAgICBjb25zb2xlLmVycm9yKGBFcnJvcjogTm8gcGFyc2VyIGF2YWlsYWJsZSBmb3IgbGFuZ3VhZ2U6ICR7ZGV0ZWN0ZWRMYW5nfWApO1xuICAgIGNvbnNvbGUuZXJyb3IoYFN1cHBvcnRlZCBsYW5ndWFnZXM6ICR7cmVnaXN0cnkuZ2V0U3VwcG9ydGVkTGFuZ3VhZ2VzKCkuam9pbignLCAnKX1gKTtcbiAgICBwcm9jZXNzLmV4aXQoMSk7XG4gIH1cblxuICB0cnkge1xuICAgIGNvbnN0IHJlc3VsdCA9IHBhcnNlci5wYXJzZShzb3VyY2UpO1xuICAgIGNvbnN0IGpzb24gPSBKU09OLnN0cmluZ2lmeShyZXN1bHQsIG51bGwsIDIpO1xuXG4gICAgaWYgKG91dHB1dCkge1xuICAgICAgZnMud3JpdGVGaWxlU3luYyhvdXRwdXQsIGpzb24sICd1dGY4Jyk7XG4gICAgICBjb25zb2xlLmxvZyhgT3V0cHV0IHdyaXR0ZW4gdG86ICR7b3V0cHV0fWApO1xuICAgIH0gZWxzZSB7XG4gICAgICBjb25zb2xlLmxvZyhqc29uKTtcbiAgICB9XG4gIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgY29uc29sZS5lcnJvcihgRXJyb3IgcGFyc2luZyBmaWxlOmAsIGVycm9yKTtcbiAgICBwcm9jZXNzLmV4aXQoMSk7XG4gIH1cbn1cblxueWFyZ3MoaGVscGVycy5oaWRlQmluKHByb2Nlc3MuYXJndikpXG4gIC5jb21tYW5kKFxuICAgICdwYXJzZSA8ZmlsZT4nLFxuICAgICdQYXJzZSBhcmNoaXRlY3R1cmUgZmlsZSBhbmQgZXh0cmFjdCBub2RlcyBhbmQgZmxvd3MnLFxuICAgICh5YXJncykgPT4ge1xuICAgICAgcmV0dXJuIHlhcmdzXG4gICAgICAgIC5wb3NpdGlvbmFsKCdmaWxlJywge1xuICAgICAgICAgIGRlc2NyaWJlOiAnUGF0aCB0byBhcmNoaXRlY3R1cmUgZmlsZScsXG4gICAgICAgICAgdHlwZTogJ3N0cmluZycsXG4gICAgICAgICAgZGVtYW5kT3B0aW9uOiB0cnVlLFxuICAgICAgICB9KVxuICAgICAgICAub3B0aW9uKCdsYW5nJywge1xuICAgICAgICAgIGFsaWFzOiAnbCcsXG4gICAgICAgICAgZGVzY3JpYmU6ICdMYW5ndWFnZSBvZiB0aGUgc291cmNlIGZpbGUnLFxuICAgICAgICAgIGNob2ljZXM6IFsndHlwZXNjcmlwdCcsICdweXRob24nLCAnY3NoYXJwJywgJ2phdmEnLCAnZ28nXSBhcyBjb25zdCxcbiAgICAgICAgICB0eXBlOiAnc3RyaW5nJyxcbiAgICAgICAgfSlcbiAgICAgICAgLm9wdGlvbignb3V0cHV0Jywge1xuICAgICAgICAgIGFsaWFzOiAnbycsXG4gICAgICAgICAgZGVzY3JpYmU6ICdPdXRwdXQgZmlsZSBwYXRoIChkZWZhdWx0OiBzdGRvdXQpJyxcbiAgICAgICAgICB0eXBlOiAnc3RyaW5nJyxcbiAgICAgICAgfSk7XG4gICAgfSxcbiAgICAoYXJndikgPT4ge1xuICAgICAgcGFyc2VGaWxlKGFyZ3YgYXMgUGFyc2VPcHRpb25zKS5jYXRjaCgoZXJyb3IpID0+IHtcbiAgICAgICAgY29uc29sZS5lcnJvcihlcnJvcik7XG4gICAgICAgIHByb2Nlc3MuZXhpdCgxKTtcbiAgICAgIH0pO1xuICAgIH1cbiAgKVxuICAuY29tbWFuZChcbiAgICAnbGFuZ3VhZ2VzJyxcbiAgICAnTGlzdCBzdXBwb3J0ZWQgbGFuZ3VhZ2VzJyxcbiAgICAoKSA9PiB7fSxcbiAgICAoKSA9PiB7XG4gICAgICBjb25zb2xlLmxvZygnU3VwcG9ydGVkIGxhbmd1YWdlczonKTtcbiAgICAgIHJlZ2lzdHJ5LmdldFN1cHBvcnRlZExhbmd1YWdlcygpLmZvckVhY2goKGxhbmcpID0+IHtcbiAgICAgICAgY29uc29sZS5sb2coYCAgLSAke2xhbmd9YCk7XG4gICAgICB9KTtcbiAgICB9XG4gIClcbiAgLmRlbWFuZENvbW1hbmQoMSwgJ1lvdSBtdXN0IHByb3ZpZGUgYSBjb21tYW5kJylcbiAgLmhlbHAoKVxuICAuYWxpYXMoJ2hlbHAnLCAnaCcpXG4gIC52ZXJzaW9uKCcwLjEuMCcpXG4gIC5hbGlhcygndmVyc2lvbicsICd2JylcbiAgLnBhcnNlKCk7XG4iXX0=