@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 +121 -0
- package/bin/fc.js +63 -0
- package/package.json +40 -35
- package/postinstall.js +269 -0
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -104
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-
|
|
4
|
-
"description": "FlowConsole CLI for
|
|
5
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"!dist/test_sample.*"
|
|
25
|
+
"bin/",
|
|
26
|
+
"binaries/",
|
|
27
|
+
"postinstall.js",
|
|
28
|
+
"README.md"
|
|
16
29
|
],
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
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
|
-
"
|
|
34
|
-
|
|
35
|
-
"
|
|
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
|
-
"
|
|
43
|
-
"
|
|
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
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=
|