@akropolys/cli 1.6.0 → 1.6.1
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 +45 -0
- package/dist/index.js +33 -1
- package/package.json +10 -6
- package/src/commands/doctor.ts +0 -84
- package/src/commands/init.ts +0 -38
- package/src/commands/inspect.ts +0 -100
- package/src/index.ts +0 -38
- package/tsconfig.json +0 -13
- package/tsup.config.ts +0 -11
package/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# @akropolys/cli
|
|
2
|
+
|
|
3
|
+
Command-line utilities for [Akropolys](https://akropolys.cloud): scaffold local config, check connectivity, and lint catalog payloads before you ingest them.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @akropolys/cli
|
|
9
|
+
# or run on demand
|
|
10
|
+
npx @akropolys/cli <command>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Commands
|
|
14
|
+
|
|
15
|
+
### `akropolys init`
|
|
16
|
+
|
|
17
|
+
Interactively writes an `.env` with your site credentials:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
NEXT_PUBLIC_AKROPOLYS_SITE_ID=...
|
|
21
|
+
NEXT_PUBLIC_AKROPOLYS_API_TOKEN=...
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### `akropolys doctor`
|
|
25
|
+
|
|
26
|
+
Verifies your local config and that the API is reachable. Reads `NEXT_PUBLIC_AKROPOLYS_*` or `VITE_AKROPOLYS_*` from `.env` / `.env.local`.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
akropolys doctor # health check
|
|
30
|
+
akropolys doctor -v # also print the resolved config
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### `akropolys inspect [file]`
|
|
34
|
+
|
|
35
|
+
Statically checks a catalog JSON payload for ingestion issues — missing stable identifier, sparse attributes — before you push it.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
akropolys inspect catalog.json
|
|
39
|
+
cat catalog.json | akropolys inspect --stdin
|
|
40
|
+
akropolys inspect catalog.json --strict # exit 3 if any warning fires
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## License
|
|
44
|
+
|
|
45
|
+
MIT
|
package/dist/index.js
CHANGED
|
@@ -209,9 +209,41 @@ async function runInspect(filePath, options) {
|
|
|
209
209
|
process.exit(0);
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
+
// package.json
|
|
213
|
+
var package_default = {
|
|
214
|
+
name: "@akropolys/cli",
|
|
215
|
+
version: "1.6.1",
|
|
216
|
+
description: "Akropolys CLI \u2014 diagnostic, workspace setup, and payload inspection utility.",
|
|
217
|
+
license: "MIT",
|
|
218
|
+
publishConfig: {
|
|
219
|
+
access: "public"
|
|
220
|
+
},
|
|
221
|
+
main: "dist/index.js",
|
|
222
|
+
bin: {
|
|
223
|
+
akropolys: "dist/index.js"
|
|
224
|
+
},
|
|
225
|
+
files: [
|
|
226
|
+
"dist",
|
|
227
|
+
"README.md"
|
|
228
|
+
],
|
|
229
|
+
scripts: {
|
|
230
|
+
build: "tsup",
|
|
231
|
+
dev: "tsup --watch"
|
|
232
|
+
},
|
|
233
|
+
dependencies: {
|
|
234
|
+
commander: "^12.0.0",
|
|
235
|
+
picocolors: "^1.0.0"
|
|
236
|
+
},
|
|
237
|
+
devDependencies: {
|
|
238
|
+
tsup: "^8.0.0",
|
|
239
|
+
typescript: "^5.0.0",
|
|
240
|
+
"@types/node": "^20.0.0"
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
212
244
|
// src/index.ts
|
|
213
245
|
var program = new import_commander.Command();
|
|
214
|
-
program.name("akropolys").description("Akropolys Command Line Tool \u2014 Developer diagnostics, setup helper, and structural inspector.").version(
|
|
246
|
+
program.name("akropolys").description("Akropolys Command Line Tool \u2014 Developer diagnostics, setup helper, and structural inspector.").version(package_default.version);
|
|
215
247
|
program.command("init").description("Configure the local workspace by generating a default .env file template.").action(async () => {
|
|
216
248
|
await runInit();
|
|
217
249
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akropolys/cli",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"description": "Akropolys CLI — diagnostic, workspace setup, and payload inspection utility.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
"bin": {
|
|
11
11
|
"akropolys": "dist/index.js"
|
|
12
12
|
},
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"commander": "^12.0.0",
|
|
19
19
|
"picocolors": "^1.0.0"
|
|
@@ -22,5 +22,9 @@
|
|
|
22
22
|
"tsup": "^8.0.0",
|
|
23
23
|
"typescript": "^5.0.0",
|
|
24
24
|
"@types/node": "^20.0.0"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsup",
|
|
28
|
+
"dev": "tsup --watch"
|
|
25
29
|
}
|
|
26
|
-
}
|
|
30
|
+
}
|
package/src/commands/doctor.ts
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import pc from 'picocolors';
|
|
3
|
-
|
|
4
|
-
export function loadEnv() {
|
|
5
|
-
const envPaths = ['.env', '.env.local'];
|
|
6
|
-
for (const envPath of envPaths) {
|
|
7
|
-
if (fs.existsSync(envPath)) {
|
|
8
|
-
const content = fs.readFileSync(envPath, 'utf-8');
|
|
9
|
-
const lines = content.split(/\r?\n/);
|
|
10
|
-
for (const line of lines) {
|
|
11
|
-
const trimmed = line.trim();
|
|
12
|
-
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
13
|
-
const eqIdx = trimmed.indexOf('=');
|
|
14
|
-
if (eqIdx > 0) {
|
|
15
|
-
const key = trimmed.substring(0, eqIdx).trim();
|
|
16
|
-
const value = trimmed.substring(eqIdx + 1).trim().replace(/^['"]|['"]$/g, '');
|
|
17
|
-
if (key) {
|
|
18
|
-
process.env[key] = value;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export async function runDoctor(options: { verbose?: boolean }) {
|
|
27
|
-
console.log(pc.bold('\nAkropolys Doctor'));
|
|
28
|
-
console.log(pc.dim('─────────────────────────'));
|
|
29
|
-
|
|
30
|
-
loadEnv();
|
|
31
|
-
|
|
32
|
-
const siteId = process.env.NEXT_PUBLIC_AKROPOLYS_SITE_ID || process.env.VITE_AKROPOLYS_SITE_ID || '';
|
|
33
|
-
const apiToken = process.env.NEXT_PUBLIC_AKROPOLYS_API_TOKEN || process.env.VITE_AKROPOLYS_API_TOKEN || '';
|
|
34
|
-
const apiUrl = process.env.NEXT_PUBLIC_AKROPOLYS_API_URL || process.env.VITE_AKROPOLYS_API_URL || 'https://api.akropolys.cloud/v1';
|
|
35
|
-
|
|
36
|
-
if (options.verbose) {
|
|
37
|
-
console.log(pc.dim(`[Verbose] Site ID: ${siteId || '<not set>'}`));
|
|
38
|
-
console.log(pc.dim(`[Verbose] API Token: ${apiToken ? '********' : '<not set>'}`));
|
|
39
|
-
console.log(pc.dim(`[Verbose] API URL: ${apiUrl}`));
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// 1. Configuration check
|
|
43
|
-
if (!siteId) {
|
|
44
|
-
console.log(pc.red('❌ Configuration: Site ID is missing. Set NEXT_PUBLIC_AKROPOLYS_SITE_ID in your env.'));
|
|
45
|
-
process.exit(1);
|
|
46
|
-
}
|
|
47
|
-
console.log(pc.green(`✓ Configuration: Site ID detected (${siteId})`));
|
|
48
|
-
|
|
49
|
-
if (!apiToken) {
|
|
50
|
-
console.log(pc.red('❌ Environment: API Token is missing. Set NEXT_PUBLIC_AKROPOLYS_API_TOKEN in your env.'));
|
|
51
|
-
process.exit(1);
|
|
52
|
-
}
|
|
53
|
-
console.log(pc.green('✓ Environment: API Token detected'));
|
|
54
|
-
|
|
55
|
-
// 2. Connectivity check
|
|
56
|
-
const start = Date.now();
|
|
57
|
-
try {
|
|
58
|
-
const res = await fetch(`${apiUrl}/health`, {
|
|
59
|
-
method: 'GET',
|
|
60
|
-
headers: {
|
|
61
|
-
'X-Akropolys-Token': apiToken,
|
|
62
|
-
'X-Akropolys-Site': siteId,
|
|
63
|
-
},
|
|
64
|
-
}).catch(err => {
|
|
65
|
-
// Throw fetch failures
|
|
66
|
-
throw new Error(`Fetch failed: ${err.message}`);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
const duration = Date.now() - start;
|
|
70
|
-
|
|
71
|
-
if (!res.ok) {
|
|
72
|
-
console.log(pc.red(`❌ Connection: API responded with status ${res.status} (Ping: ${duration}ms)`));
|
|
73
|
-
process.exit(2);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
console.log(pc.green(`✓ Connection: Successfully connected to ${apiUrl} (ping: ${duration}ms)`));
|
|
77
|
-
|
|
78
|
-
console.log(pc.bold(pc.green('\nStatus: Healthy (All configuration and connectivity checks passed)')));
|
|
79
|
-
process.exit(0);
|
|
80
|
-
} catch (err: any) {
|
|
81
|
-
console.log(pc.red(`❌ Connection: Unreachable API at ${apiUrl}. Error: ${err.message}`));
|
|
82
|
-
process.exit(2);
|
|
83
|
-
}
|
|
84
|
-
}
|
package/src/commands/init.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import readline from 'readline';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import pc from 'picocolors';
|
|
4
|
-
|
|
5
|
-
const question = (rl: readline.Interface, query: string): Promise<string> => {
|
|
6
|
-
return new Promise((resolve) => rl.question(query, resolve));
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
export async function runInit() {
|
|
10
|
-
console.log(pc.bold(pc.cyan('\nConfiguring Akropolys Workspace...')));
|
|
11
|
-
console.log(pc.dim('─────────────────────────'));
|
|
12
|
-
|
|
13
|
-
const rl = readline.createInterface({
|
|
14
|
-
input: process.stdin,
|
|
15
|
-
output: process.stdout,
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
const siteId = await question(rl, pc.cyan('? Enter your Akropolys Site ID: '));
|
|
20
|
-
const apiToken = await question(rl, pc.cyan('? Enter your Akropolys API Token: '));
|
|
21
|
-
|
|
22
|
-
// apiUrl is optional — the SDK defaults to the shared managed backend
|
|
23
|
-
// (https://api.akropolys.cloud/v1). Only self-hosted/local dev needs to set it.
|
|
24
|
-
const envContent = `NEXT_PUBLIC_AKROPOLYS_SITE_ID=${siteId.trim()}
|
|
25
|
-
NEXT_PUBLIC_AKROPOLYS_API_TOKEN=${apiToken.trim()}
|
|
26
|
-
`;
|
|
27
|
-
|
|
28
|
-
fs.writeFileSync('.env', envContent, 'utf-8');
|
|
29
|
-
console.log(pc.dim('\n─────────────────────────'));
|
|
30
|
-
console.log(pc.green('✓ Generated .env with configuration parameters.'));
|
|
31
|
-
console.log(pc.green('✓ Saved config template.'));
|
|
32
|
-
} catch (err: any) {
|
|
33
|
-
console.error(pc.red(`\n❌ Error generating configuration: ${err.message}`));
|
|
34
|
-
process.exit(1);
|
|
35
|
-
} finally {
|
|
36
|
-
rl.close();
|
|
37
|
-
}
|
|
38
|
-
}
|
package/src/commands/inspect.ts
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import pc from 'picocolors';
|
|
3
|
-
|
|
4
|
-
function readStdin(): Promise<string> {
|
|
5
|
-
return new Promise((resolve, reject) => {
|
|
6
|
-
let data = '';
|
|
7
|
-
process.stdin.setEncoding('utf-8');
|
|
8
|
-
process.stdin.on('readable', () => {
|
|
9
|
-
let chunk;
|
|
10
|
-
while ((chunk = process.stdin.read()) !== null) {
|
|
11
|
-
data += chunk;
|
|
12
|
-
}
|
|
13
|
-
});
|
|
14
|
-
process.stdin.on('end', () => {
|
|
15
|
-
resolve(data);
|
|
16
|
-
});
|
|
17
|
-
process.stdin.on('error', (err) => {
|
|
18
|
-
reject(err);
|
|
19
|
-
});
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export async function runInspect(
|
|
24
|
-
filePath: string | undefined,
|
|
25
|
-
options: { stdin?: boolean; strict?: boolean }
|
|
26
|
-
) {
|
|
27
|
-
console.log(pc.bold('\nAkropolys Inspect'));
|
|
28
|
-
console.log(pc.dim('─────────────────────────'));
|
|
29
|
-
|
|
30
|
-
let rawData = '';
|
|
31
|
-
|
|
32
|
-
// 1. Read input
|
|
33
|
-
try {
|
|
34
|
-
if (options.stdin || (!filePath && !process.stdin.isTTY)) {
|
|
35
|
-
rawData = await readStdin();
|
|
36
|
-
} else if (filePath) {
|
|
37
|
-
if (!fs.existsSync(filePath)) {
|
|
38
|
-
console.error(pc.red(`❌ Error: File not found at "${filePath}"`));
|
|
39
|
-
process.exit(1);
|
|
40
|
-
}
|
|
41
|
-
rawData = fs.readFileSync(filePath, 'utf-8');
|
|
42
|
-
} else {
|
|
43
|
-
console.error(pc.red('❌ Error: Provide a file path or pipe via standard input using --stdin'));
|
|
44
|
-
process.exit(1);
|
|
45
|
-
}
|
|
46
|
-
} catch (err: any) {
|
|
47
|
-
console.error(pc.red(`❌ Error reading input: ${err.message}`));
|
|
48
|
-
process.exit(1);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// 2. Parse JSON
|
|
52
|
-
let items: any[] = [];
|
|
53
|
-
try {
|
|
54
|
-
const parsed = JSON.parse(rawData.trim());
|
|
55
|
-
items = Array.isArray(parsed) ? parsed : [parsed];
|
|
56
|
-
} catch (err: any) {
|
|
57
|
-
console.error(pc.red(`❌ Error parsing JSON: Invalid JSON structure. ${err.message}`));
|
|
58
|
-
process.exit(1);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
console.log(pc.cyan(`Parsed ${items.length} catalog items.`));
|
|
62
|
-
console.log(pc.dim('\nIngestion Quality Diagnostics:'));
|
|
63
|
-
|
|
64
|
-
let warningsCount = 0;
|
|
65
|
-
|
|
66
|
-
// 3. Scan items against Registry Rules
|
|
67
|
-
items.forEach((item, index) => {
|
|
68
|
-
const identifier = item.id || item.productId || item.slug || item.url || item.name || `item at index ${index}`;
|
|
69
|
-
|
|
70
|
-
// AP001: Missing Stable Identifier
|
|
71
|
-
const hasId = item.id !== undefined && item.id !== null && item.id !== '';
|
|
72
|
-
const hasProductId = item.productId !== undefined && item.productId !== null && item.productId !== '';
|
|
73
|
-
const hasSlug = item.slug !== undefined && item.slug !== null && item.slug !== '';
|
|
74
|
-
const hasUrl = item.url !== undefined && item.url !== null && item.url !== '';
|
|
75
|
-
const hasName = item.name !== undefined && item.name !== null && item.name !== '';
|
|
76
|
-
|
|
77
|
-
if (!hasId && !hasProductId && !hasSlug && !hasUrl && !hasName) {
|
|
78
|
-
console.log(` ${pc.yellow('⚠')} [AP001] Missing Stable Identifier: "${identifier}" ➔ Deduplication & correlation unavailable`);
|
|
79
|
-
warningsCount++;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// AP002: Low-Signal Payload (sparse payload: fewer than 2 keys, or only identifier is defined)
|
|
83
|
-
const keysCount = Object.keys(item).length;
|
|
84
|
-
if (keysCount < 2) {
|
|
85
|
-
console.log(` ${pc.yellow('⚠')} [AP002] Low-Signal Payload: "${identifier}" has sparse attributes ➔ Search vector quality reduced`);
|
|
86
|
-
warningsCount++;
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
console.log(pc.dim('\n─────────────────────────'));
|
|
91
|
-
console.log(pc.bold(`Inspect complete: ${items.length} items checked, ${warningsCount} warnings flagged.`));
|
|
92
|
-
|
|
93
|
-
// 4. Exit codes: strict vs non-strict
|
|
94
|
-
if (warningsCount > 0 && options.strict) {
|
|
95
|
-
console.log(pc.red('Exit code 3: strict mode failed due to warnings.'));
|
|
96
|
-
process.exit(3);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
process.exit(0);
|
|
100
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { runInit } from './commands/init';
|
|
3
|
-
import { runDoctor } from './commands/doctor';
|
|
4
|
-
import { runInspect } from './commands/inspect';
|
|
5
|
-
|
|
6
|
-
const program = new Command();
|
|
7
|
-
|
|
8
|
-
program
|
|
9
|
-
.name('akropolys')
|
|
10
|
-
.description('Akropolys Command Line Tool — Developer diagnostics, setup helper, and structural inspector.')
|
|
11
|
-
.version('1.0.0');
|
|
12
|
-
|
|
13
|
-
program
|
|
14
|
-
.command('init')
|
|
15
|
-
.description('Configure the local workspace by generating a default .env file template.')
|
|
16
|
-
.action(async () => {
|
|
17
|
-
await runInit();
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
program
|
|
21
|
-
.command('doctor')
|
|
22
|
-
.description('Perform a health check consolidating local configuration values and backend API reachability.')
|
|
23
|
-
.option('-v, --verbose', 'Include verbose logs and configuration dumps.')
|
|
24
|
-
.action(async (options) => {
|
|
25
|
-
await runDoctor(options);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
program
|
|
29
|
-
.command('inspect')
|
|
30
|
-
.description('Statically inspect a catalog payload file or stream against the Akropolys Anti-Pattern Registry.')
|
|
31
|
-
.argument('[file]', 'Path to the local catalog JSON file.')
|
|
32
|
-
.option('--stdin', 'Force reading data from standard input.')
|
|
33
|
-
.option('--strict', 'Fail the process (exit code 3) if any structural registry warnings are triggered.')
|
|
34
|
-
.action(async (file, options) => {
|
|
35
|
-
await runInspect(file, options);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
program.parse(process.argv);
|
package/tsconfig.json
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "NodeNext",
|
|
5
|
-
"moduleResolution": "NodeNext",
|
|
6
|
-
"esModuleInterop": true,
|
|
7
|
-
"forceConsistentCasingInFileNames": true,
|
|
8
|
-
"strict": true,
|
|
9
|
-
"skipLibCheck": true,
|
|
10
|
-
"outDir": "./dist"
|
|
11
|
-
},
|
|
12
|
-
"include": ["src/**/*"]
|
|
13
|
-
}
|