@tanagram/cli 0.1.12 → 0.1.14
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 +26 -8
- package/commands/run.go +9 -6
- package/commands/sync.go +9 -6
- package/install.js +136 -24
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,10 +26,9 @@ tanagram --help
|
|
|
26
26
|
|
|
27
27
|
**Requirements:**
|
|
28
28
|
- Node.js >= 14.0.0
|
|
29
|
-
- Go >= 1.21 (for building the binary during installation)
|
|
30
29
|
- **Anthropic API Key** (required for LLM-based policy extraction)
|
|
31
30
|
|
|
32
|
-
The CLI is written in Go but distributed via npm for easier installation and version management.
|
|
31
|
+
The CLI is written in Go but distributed via npm for easier installation and version management. Pre-built binaries are automatically downloaded during installation (no Go compiler needed!).
|
|
33
32
|
|
|
34
33
|
### API Key Setup
|
|
35
34
|
|
|
@@ -237,22 +236,41 @@ npm test
|
|
|
237
236
|
go build -o bin/tanagram .
|
|
238
237
|
```
|
|
239
238
|
|
|
240
|
-
### Publishing
|
|
239
|
+
### Publishing a Release
|
|
241
240
|
|
|
242
241
|
To publish a new version:
|
|
243
242
|
|
|
244
243
|
```bash
|
|
245
|
-
# Update version in package.json
|
|
244
|
+
# 1. Update version in package.json
|
|
246
245
|
npm version patch # or minor, or major
|
|
247
246
|
|
|
248
|
-
#
|
|
249
|
-
|
|
247
|
+
# 2. Commit the version bump
|
|
248
|
+
git add package.json
|
|
249
|
+
git commit -m "Bump version to $(node -p "require('./package.json').version")"
|
|
250
250
|
|
|
251
|
-
#
|
|
251
|
+
# 3. Build binaries for all platforms
|
|
252
|
+
./build-release.sh
|
|
253
|
+
|
|
254
|
+
# 4. Create and push git tag
|
|
252
255
|
git tag v$(node -p "require('./package.json').version")
|
|
253
|
-
git push origin --tags
|
|
256
|
+
git push && git push origin --tags
|
|
257
|
+
|
|
258
|
+
# 5. Upload binaries to GitHub release (choose one):
|
|
259
|
+
|
|
260
|
+
# Option A: Using gh CLI (recommended)
|
|
261
|
+
gh release create v$(node -p "require('./package.json').version") ./dist/* \
|
|
262
|
+
--title "v$(node -p "require('./package.json').version")" \
|
|
263
|
+
--generate-notes
|
|
264
|
+
|
|
265
|
+
# Option B: Manually upload at
|
|
266
|
+
# https://github.com/tanagram/cli/releases/new
|
|
267
|
+
|
|
268
|
+
# 6. Publish to npm (binaries must be on GitHub first!)
|
|
269
|
+
npm publish
|
|
254
270
|
```
|
|
255
271
|
|
|
272
|
+
**Important:** Upload binaries to GitHub releases BEFORE publishing to npm. The install script downloads pre-built binaries from GitHub releases.
|
|
273
|
+
|
|
256
274
|
## License
|
|
257
275
|
|
|
258
276
|
MIT
|
package/commands/run.go
CHANGED
|
@@ -129,15 +129,13 @@ func Run() error {
|
|
|
129
129
|
// Collect results
|
|
130
130
|
totalPolicies := 0
|
|
131
131
|
for result := range results {
|
|
132
|
-
mu.Lock()
|
|
133
|
-
completed++
|
|
134
|
-
mu.Unlock()
|
|
135
|
-
|
|
136
132
|
if result.err != nil {
|
|
137
133
|
stop <- true
|
|
138
134
|
close(stop)
|
|
139
135
|
time.Sleep(50 * time.Millisecond)
|
|
140
|
-
|
|
136
|
+
mu.Lock()
|
|
137
|
+
fmt.Printf("\r\033[K✗ Failed to process %s\n", result.relPath)
|
|
138
|
+
mu.Unlock()
|
|
141
139
|
return fmt.Errorf("failed to extract policies from %s: %w", result.file, result.err)
|
|
142
140
|
}
|
|
143
141
|
|
|
@@ -149,7 +147,12 @@ func Run() error {
|
|
|
149
147
|
}
|
|
150
148
|
|
|
151
149
|
totalPolicies += len(result.policies)
|
|
152
|
-
|
|
150
|
+
|
|
151
|
+
// Atomic update of counter and output (prevents race with spinner)
|
|
152
|
+
mu.Lock()
|
|
153
|
+
completed++
|
|
154
|
+
fmt.Printf("\r\033[K✓ %s - %d policies\n", result.relPath, len(result.policies))
|
|
155
|
+
mu.Unlock()
|
|
153
156
|
}
|
|
154
157
|
|
|
155
158
|
// Stop spinner
|
package/commands/sync.go
CHANGED
|
@@ -117,15 +117,13 @@ func Sync() error {
|
|
|
117
117
|
// Collect results
|
|
118
118
|
totalPolicies := 0
|
|
119
119
|
for result := range results {
|
|
120
|
-
mu.Lock()
|
|
121
|
-
completed++
|
|
122
|
-
mu.Unlock()
|
|
123
|
-
|
|
124
120
|
if result.err != nil {
|
|
125
121
|
stop <- true
|
|
126
122
|
close(stop)
|
|
127
123
|
time.Sleep(50 * time.Millisecond)
|
|
128
|
-
|
|
124
|
+
mu.Lock()
|
|
125
|
+
fmt.Printf("\r\033[K✗ Failed to process %s\n", result.relPath)
|
|
126
|
+
mu.Unlock()
|
|
129
127
|
return fmt.Errorf("failed to extract policies from %s: %w", result.file, result.err)
|
|
130
128
|
}
|
|
131
129
|
|
|
@@ -137,7 +135,12 @@ func Sync() error {
|
|
|
137
135
|
}
|
|
138
136
|
|
|
139
137
|
totalPolicies += len(result.policies)
|
|
140
|
-
|
|
138
|
+
|
|
139
|
+
// Atomic update of counter and output (prevents race with spinner)
|
|
140
|
+
mu.Lock()
|
|
141
|
+
completed++
|
|
142
|
+
fmt.Printf("\r\033[K✓ %s - %d policies\n", result.relPath, len(result.policies))
|
|
143
|
+
mu.Unlock()
|
|
141
144
|
}
|
|
142
145
|
|
|
143
146
|
// Stop spinner
|
package/install.js
CHANGED
|
@@ -1,10 +1,107 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const { execSync } = require('child_process');
|
|
4
|
+
const https = require('https');
|
|
4
5
|
const fs = require('fs');
|
|
5
6
|
const path = require('path');
|
|
6
7
|
const os = require('os');
|
|
7
8
|
|
|
9
|
+
const GITHUB_REPO = 'tanagram/cli';
|
|
10
|
+
const packageJson = require('./package.json');
|
|
11
|
+
const VERSION = packageJson.version;
|
|
12
|
+
|
|
13
|
+
// Get platform-specific binary info
|
|
14
|
+
function getBinaryInfo() {
|
|
15
|
+
const platform = os.platform();
|
|
16
|
+
const arch = os.arch();
|
|
17
|
+
|
|
18
|
+
let binaryName = 'tanagram';
|
|
19
|
+
let platformString = '';
|
|
20
|
+
|
|
21
|
+
if (platform === 'darwin') {
|
|
22
|
+
platformString = arch === 'arm64' ? 'darwin-arm64' : 'darwin-amd64';
|
|
23
|
+
} else if (platform === 'linux') {
|
|
24
|
+
platformString = arch === 'arm64' ? 'linux-arm64' : 'linux-amd64';
|
|
25
|
+
} else if (platform === 'win32') {
|
|
26
|
+
platformString = 'windows-amd64';
|
|
27
|
+
binaryName = 'tanagram.exe';
|
|
28
|
+
} else {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
binaryName,
|
|
34
|
+
platformString,
|
|
35
|
+
downloadUrl: `https://github.com/${GITHUB_REPO}/releases/download/v${VERSION}/tanagram-${platformString}${platform === 'win32' ? '.exe' : ''}`
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Download file from URL
|
|
40
|
+
function downloadFile(url, destPath) {
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
https.get(url, {
|
|
43
|
+
headers: { 'User-Agent': 'tanagram-installer' },
|
|
44
|
+
followRedirect: true
|
|
45
|
+
}, (response) => {
|
|
46
|
+
if (response.statusCode === 301 || response.statusCode === 302) {
|
|
47
|
+
return downloadFile(response.headers.location, destPath)
|
|
48
|
+
.then(resolve)
|
|
49
|
+
.catch(reject);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (response.statusCode !== 200) {
|
|
53
|
+
reject(new Error(`Failed to download: HTTP ${response.statusCode}`));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const file = fs.createWriteStream(destPath);
|
|
58
|
+
response.pipe(file);
|
|
59
|
+
|
|
60
|
+
file.on('finish', () => {
|
|
61
|
+
file.close();
|
|
62
|
+
resolve();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
file.on('error', (err) => {
|
|
66
|
+
fs.unlink(destPath, () => {});
|
|
67
|
+
reject(err);
|
|
68
|
+
});
|
|
69
|
+
}).on('error', reject);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Try to download pre-built binary
|
|
74
|
+
async function downloadBinary() {
|
|
75
|
+
const binaryInfo = getBinaryInfo();
|
|
76
|
+
|
|
77
|
+
if (!binaryInfo) {
|
|
78
|
+
throw new Error(`Unsupported platform: ${os.platform()}-${os.arch()}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const binDir = path.join(__dirname, 'bin');
|
|
82
|
+
if (!fs.existsSync(binDir)) {
|
|
83
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const binaryPath = path.join(binDir, binaryInfo.binaryName);
|
|
87
|
+
|
|
88
|
+
console.log(`📦 Downloading Tanagram CLI v${VERSION} for ${binaryInfo.platformString}...`);
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
await downloadFile(binaryInfo.downloadUrl, binaryPath);
|
|
92
|
+
|
|
93
|
+
if (os.platform() !== 'win32') {
|
|
94
|
+
fs.chmodSync(binaryPath, '755');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
console.log(`✓ Tanagram CLI installed successfully`);
|
|
98
|
+
return true;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.log(`⚠ Could not download pre-built binary: ${error.message}`);
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
8
105
|
// Check if Go is installed
|
|
9
106
|
function checkGo() {
|
|
10
107
|
try {
|
|
@@ -15,55 +112,70 @@ function checkGo() {
|
|
|
15
112
|
}
|
|
16
113
|
}
|
|
17
114
|
|
|
18
|
-
// Build
|
|
19
|
-
function
|
|
20
|
-
console.log('
|
|
115
|
+
// Build from source as fallback
|
|
116
|
+
function buildFromSource() {
|
|
117
|
+
console.log('🔧 Building from source...');
|
|
21
118
|
|
|
22
119
|
const platform = os.platform();
|
|
23
120
|
const arch = os.arch();
|
|
24
|
-
|
|
25
|
-
// Map Node.js platform/arch to Go GOOS/GOARCH
|
|
26
121
|
const goos = platform === 'win32' ? 'windows' : platform;
|
|
27
122
|
const goarch = arch === 'x64' ? 'amd64' : arch === 'arm64' ? 'arm64' : arch;
|
|
28
|
-
|
|
29
123
|
const binaryName = platform === 'win32' ? 'tanagram.exe' : 'tanagram';
|
|
30
124
|
const binaryPath = path.join(__dirname, 'bin', binaryName);
|
|
31
125
|
|
|
32
|
-
// Ensure bin directory exists
|
|
33
126
|
const binDir = path.join(__dirname, 'bin');
|
|
34
127
|
if (!fs.existsSync(binDir)) {
|
|
35
128
|
fs.mkdirSync(binDir, { recursive: true });
|
|
36
129
|
}
|
|
37
130
|
|
|
38
131
|
try {
|
|
39
|
-
// Build the binary
|
|
40
132
|
execSync(`go build -o "${binaryPath}" .`, {
|
|
41
133
|
cwd: __dirname,
|
|
42
134
|
stdio: 'inherit',
|
|
43
|
-
env: {
|
|
44
|
-
...process.env,
|
|
45
|
-
GOOS: goos,
|
|
46
|
-
GOARCH: goarch,
|
|
47
|
-
}
|
|
135
|
+
env: { ...process.env, GOOS: goos, GOARCH: goarch }
|
|
48
136
|
});
|
|
49
137
|
|
|
50
|
-
// Make it executable on Unix-like systems
|
|
51
138
|
if (platform !== 'win32') {
|
|
52
139
|
fs.chmodSync(binaryPath, '755');
|
|
53
140
|
}
|
|
54
141
|
|
|
55
|
-
console.log(`✓ Tanagram CLI built successfully
|
|
142
|
+
console.log(`✓ Tanagram CLI built successfully`);
|
|
143
|
+
return true;
|
|
56
144
|
} catch (error) {
|
|
57
|
-
console.error('Failed to build
|
|
58
|
-
|
|
145
|
+
console.error('Failed to build from source:', error.message);
|
|
146
|
+
return false;
|
|
59
147
|
}
|
|
60
148
|
}
|
|
61
149
|
|
|
62
|
-
// Main
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
150
|
+
// Main installation flow
|
|
151
|
+
(async () => {
|
|
152
|
+
try {
|
|
153
|
+
const downloaded = await downloadBinary();
|
|
154
|
+
if (downloaded) {
|
|
155
|
+
process.exit(0);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
console.log('Attempting to build from source as fallback...');
|
|
68
159
|
|
|
69
|
-
|
|
160
|
+
if (!checkGo()) {
|
|
161
|
+
console.error('\n❌ Installation failed:');
|
|
162
|
+
console.error(' - Could not download pre-built binary');
|
|
163
|
+
console.error(' - Go is not installed for building from source\n');
|
|
164
|
+
console.error('Please either:');
|
|
165
|
+
console.error(' 1. Try again later (binaries may not be published yet), or');
|
|
166
|
+
console.error(' 2. Install Go from https://golang.org/dl/\n');
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const built = buildFromSource();
|
|
171
|
+
if (!built) {
|
|
172
|
+
console.error('\n❌ Installation failed');
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
process.exit(0);
|
|
177
|
+
} catch (error) {
|
|
178
|
+
console.error('Installation error:', error.message);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
})();
|