cli4ai 1.2.0 → 1.2.2
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 +39 -0
- package/dist/bin.d.ts +6 -0
- package/dist/bin.js +105 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +335 -0
- package/dist/commands/add.d.ts +11 -0
- package/dist/commands/add.js +464 -0
- package/dist/commands/browse.d.ts +4 -0
- package/dist/commands/browse.js +382 -0
- package/dist/commands/config.d.ts +10 -0
- package/dist/commands/config.js +121 -0
- package/dist/commands/info.d.ts +9 -0
- package/dist/commands/info.js +125 -0
- package/dist/commands/init.d.ts +10 -0
- package/dist/commands/init.js +458 -0
- package/dist/commands/list.d.ts +10 -0
- package/dist/commands/list.js +76 -0
- package/dist/commands/mcp-config.d.ts +10 -0
- package/dist/commands/mcp-config.js +49 -0
- package/dist/commands/remotes.d.ts +22 -0
- package/dist/commands/remotes.js +196 -0
- package/dist/commands/remove.d.ts +8 -0
- package/dist/commands/remove.js +61 -0
- package/dist/commands/routines.d.ts +29 -0
- package/dist/commands/routines.js +363 -0
- package/dist/commands/run.d.ts +12 -0
- package/dist/commands/run.js +104 -0
- package/dist/commands/scheduler.d.ts +27 -0
- package/dist/commands/scheduler.js +350 -0
- package/dist/commands/search.d.ts +9 -0
- package/dist/commands/search.js +162 -0
- package/dist/commands/secrets.d.ts +28 -0
- package/dist/commands/secrets.js +236 -0
- package/dist/commands/serve.d.ts +13 -0
- package/dist/commands/serve.js +49 -0
- package/dist/commands/start.d.ts +8 -0
- package/dist/commands/start.js +27 -0
- package/dist/commands/update.d.ts +17 -0
- package/dist/commands/update.js +210 -0
- package/dist/core/config.d.ts +91 -0
- package/dist/core/config.js +738 -0
- package/dist/core/execute.d.ts +51 -0
- package/dist/core/execute.js +475 -0
- package/dist/core/link.d.ts +39 -0
- package/dist/core/link.js +214 -0
- package/dist/core/lockfile.d.ts +63 -0
- package/dist/core/lockfile.js +140 -0
- package/dist/core/manifest.d.ts +96 -0
- package/dist/core/manifest.js +224 -0
- package/dist/core/registry.d.ts +74 -0
- package/dist/core/registry.js +116 -0
- package/dist/core/remote-client.d.ts +98 -0
- package/dist/core/remote-client.js +252 -0
- package/dist/core/remotes.d.ts +88 -0
- package/dist/core/remotes.js +206 -0
- package/dist/core/routine-engine.d.ts +124 -0
- package/dist/core/routine-engine.js +699 -0
- package/dist/core/routines.d.ts +36 -0
- package/dist/core/routines.js +132 -0
- package/dist/core/scheduler-daemon.d.ts +10 -0
- package/dist/core/scheduler-daemon.js +77 -0
- package/dist/core/scheduler.d.ts +131 -0
- package/dist/core/scheduler.js +492 -0
- package/dist/core/secrets.d.ts +48 -0
- package/dist/core/secrets.js +384 -0
- package/dist/lib/cli.d.ts +84 -0
- package/dist/lib/cli.js +216 -0
- package/dist/mcp/adapter.d.ts +35 -0
- package/dist/mcp/adapter.js +94 -0
- package/dist/mcp/config-gen.d.ts +31 -0
- package/dist/mcp/config-gen.js +75 -0
- package/dist/mcp/server.d.ts +41 -0
- package/dist/mcp/server.js +296 -0
- package/dist/server/service.d.ts +85 -0
- package/dist/server/service.js +304 -0
- package/package.json +6 -3
- package/src/bin.ts +0 -118
- package/src/cli.ts +0 -412
- package/src/commands/add.ts +0 -562
- package/src/commands/browse.ts +0 -449
- package/src/commands/config.ts +0 -154
- package/src/commands/info.ts +0 -133
- package/src/commands/init.ts +0 -514
- package/src/commands/list.ts +0 -95
- package/src/commands/mcp-config.ts +0 -69
- package/src/commands/remotes.ts +0 -253
- package/src/commands/remove.ts +0 -78
- package/src/commands/routines.ts +0 -427
- package/src/commands/run.ts +0 -127
- package/src/commands/scheduler.ts +0 -438
- package/src/commands/search.ts +0 -185
- package/src/commands/secrets.ts +0 -292
- package/src/commands/serve.ts +0 -66
- package/src/commands/start.ts +0 -40
- package/src/commands/update.ts +0 -252
- package/src/core/config.ts +0 -845
- package/src/core/execute.ts +0 -569
- package/src/core/link.ts +0 -246
- package/src/core/lockfile.ts +0 -187
- package/src/core/manifest.ts +0 -327
- package/src/core/registry.ts +0 -165
- package/src/core/remote-client.ts +0 -419
- package/src/core/remotes.ts +0 -268
- package/src/core/routine-engine.ts +0 -895
- package/src/core/routines.ts +0 -171
- package/src/core/scheduler-daemon.ts +0 -94
- package/src/core/scheduler.ts +0 -606
- package/src/core/secrets.ts +0 -430
- package/src/lib/cli.ts +0 -261
- package/src/mcp/adapter.ts +0 -131
- package/src/mcp/config-gen.ts +0 -106
- package/src/mcp/server.ts +0 -365
- package/src/server/service.ts +0 -434
package/src/core/link.ts
DELETED
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PATH linking for global packages
|
|
3
|
-
*
|
|
4
|
-
* Creates executable symlinks in ~/.cli4ai/bin/ that can be added to PATH
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { existsSync, mkdirSync, symlinkSync, unlinkSync, writeFileSync, chmodSync } from 'fs';
|
|
8
|
-
import { resolve, join } from 'path';
|
|
9
|
-
import { homedir } from 'os';
|
|
10
|
-
import { type Manifest } from './manifest.js';
|
|
11
|
-
|
|
12
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
13
|
-
// SECURITY: Shell escaping for safe script generation
|
|
14
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Validate that a string is safe for shell script embedding.
|
|
18
|
-
* Only allows alphanumeric, hyphens, underscores, and dots.
|
|
19
|
-
* This is a strict allowlist approach for defense in depth.
|
|
20
|
-
*/
|
|
21
|
-
function isShellSafe(value: string): boolean {
|
|
22
|
-
return /^[a-zA-Z0-9._-]+$/.test(value);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Escape a string for safe embedding in shell scripts.
|
|
27
|
-
* Uses single quotes which prevent all shell interpretation except for single quotes themselves.
|
|
28
|
-
*/
|
|
29
|
-
function shellEscape(value: string): string {
|
|
30
|
-
// Single quotes prevent shell interpretation; escape any single quotes in the value
|
|
31
|
-
// by ending the single-quoted string, adding an escaped single quote, and starting a new single-quoted string
|
|
32
|
-
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
36
|
-
// PATHS
|
|
37
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
38
|
-
|
|
39
|
-
export const C4AI_BIN = resolve(homedir(), '.cli4ai', 'bin');
|
|
40
|
-
|
|
41
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
42
|
-
// FUNCTIONS
|
|
43
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Ensure bin directory exists
|
|
47
|
-
*/
|
|
48
|
-
export function ensureBinDir(): void {
|
|
49
|
-
if (!existsSync(C4AI_BIN)) {
|
|
50
|
-
mkdirSync(C4AI_BIN, { recursive: true });
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Create executable wrapper script for a package
|
|
56
|
-
*
|
|
57
|
-
* Creates a shell script that invokes `cli4ai run <package> [args]`
|
|
58
|
-
*/
|
|
59
|
-
export function linkPackage(manifest: Manifest, packagePath: string): string {
|
|
60
|
-
ensureBinDir();
|
|
61
|
-
|
|
62
|
-
// SECURITY: Validate manifest values before embedding in shell script
|
|
63
|
-
if (!isShellSafe(manifest.name)) {
|
|
64
|
-
throw new Error(`Invalid package name for shell script: ${manifest.name}`);
|
|
65
|
-
}
|
|
66
|
-
if (!isShellSafe(manifest.version)) {
|
|
67
|
-
throw new Error(`Invalid package version for shell script: ${manifest.version}`);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const binPath = join(C4AI_BIN, manifest.name);
|
|
71
|
-
|
|
72
|
-
// Create wrapper script with escaped values for defense in depth
|
|
73
|
-
const safeName = shellEscape(manifest.name);
|
|
74
|
-
const safeVersion = shellEscape(manifest.version);
|
|
75
|
-
|
|
76
|
-
const script = `#!/bin/sh
|
|
77
|
-
# cli4ai wrapper for ${safeName}@${safeVersion}
|
|
78
|
-
# Auto-generated - do not edit
|
|
79
|
-
|
|
80
|
-
exec cli4ai run ${safeName} "$@"
|
|
81
|
-
`;
|
|
82
|
-
|
|
83
|
-
writeFileSync(binPath, script);
|
|
84
|
-
if (process.platform !== 'win32') {
|
|
85
|
-
chmodSync(binPath, 0o755);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Windows compatibility: generate .cmd and .ps1 launchers
|
|
89
|
-
if (process.platform === 'win32') {
|
|
90
|
-
const cmdContent = `@echo off\r\ncli4ai run ${manifest.name} %*\r\nexit /b %errorlevel%\r\n`;
|
|
91
|
-
writeFileSync(binPath + '.cmd', cmdContent);
|
|
92
|
-
|
|
93
|
-
const ps1Content = `& cli4ai run ${manifest.name} @args\nexit $LASTEXITCODE\n`;
|
|
94
|
-
writeFileSync(binPath + '.ps1', ps1Content);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return binPath;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Create direct executable wrapper that runs the tool directly
|
|
102
|
-
*
|
|
103
|
-
* This is faster than going through cli4ai run
|
|
104
|
-
*/
|
|
105
|
-
export function linkPackageDirect(manifest: Manifest, packagePath: string): string {
|
|
106
|
-
ensureBinDir();
|
|
107
|
-
|
|
108
|
-
// SECURITY: Validate manifest values before embedding in shell script
|
|
109
|
-
if (!isShellSafe(manifest.name)) {
|
|
110
|
-
throw new Error(`Invalid package name for shell script: ${manifest.name}`);
|
|
111
|
-
}
|
|
112
|
-
if (!isShellSafe(manifest.version)) {
|
|
113
|
-
throw new Error(`Invalid package version for shell script: ${manifest.version}`);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const binPath = join(C4AI_BIN, manifest.name);
|
|
117
|
-
const entryPath = resolve(packagePath, manifest.entry);
|
|
118
|
-
|
|
119
|
-
// SECURITY: Shell-escape the entry path to prevent injection
|
|
120
|
-
const safeEntryPath = shellEscape(entryPath);
|
|
121
|
-
const safeName = shellEscape(manifest.name);
|
|
122
|
-
const safeVersion = shellEscape(manifest.version);
|
|
123
|
-
|
|
124
|
-
// Build runtime command - use tsx for TypeScript, node for JavaScript
|
|
125
|
-
let execCommand: string;
|
|
126
|
-
if (entryPath.endsWith('.ts') || entryPath.endsWith('.tsx')) {
|
|
127
|
-
execCommand = `npx tsx ${safeEntryPath}`;
|
|
128
|
-
} else {
|
|
129
|
-
execCommand = `node ${safeEntryPath}`;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Create wrapper script that runs the tool directly
|
|
133
|
-
const script = `#!/bin/sh
|
|
134
|
-
# cli4ai wrapper for ${safeName}@${safeVersion}
|
|
135
|
-
# Auto-generated - do not edit
|
|
136
|
-
|
|
137
|
-
exec ${execCommand} "$@"
|
|
138
|
-
`;
|
|
139
|
-
|
|
140
|
-
writeFileSync(binPath, script);
|
|
141
|
-
if (process.platform !== 'win32') {
|
|
142
|
-
chmodSync(binPath, 0o755);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Windows compatibility: generate .cmd and .ps1 launchers
|
|
146
|
-
if (process.platform === 'win32') {
|
|
147
|
-
const quotedEntry = `"${entryPath.replaceAll('"', '""')}"`;
|
|
148
|
-
|
|
149
|
-
let cmdContent: string;
|
|
150
|
-
let ps1Content: string;
|
|
151
|
-
if (entryPath.endsWith('.ts') || entryPath.endsWith('.tsx')) {
|
|
152
|
-
cmdContent = `@echo off\r\nnpx tsx ${quotedEntry} %*\r\nexit /b %errorlevel%\r\n`;
|
|
153
|
-
ps1Content = `& npx tsx ${quotedEntry} @args\nexit $LASTEXITCODE\n`;
|
|
154
|
-
} else {
|
|
155
|
-
cmdContent = `@echo off\r\nnode ${quotedEntry} %*\r\nexit /b %errorlevel%\r\n`;
|
|
156
|
-
ps1Content = `& node ${quotedEntry} @args\nexit $LASTEXITCODE\n`;
|
|
157
|
-
}
|
|
158
|
-
writeFileSync(binPath + '.cmd', cmdContent);
|
|
159
|
-
writeFileSync(binPath + '.ps1', ps1Content);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return binPath;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Remove executable link for a package
|
|
167
|
-
*/
|
|
168
|
-
export function unlinkPackage(packageName: string): boolean {
|
|
169
|
-
// SECURITY: Validate package name to prevent path traversal
|
|
170
|
-
if (!isShellSafe(packageName) || packageName.includes('..')) {
|
|
171
|
-
throw new Error(`Invalid package name: ${packageName}`);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const basePath = join(C4AI_BIN, packageName);
|
|
175
|
-
const candidates = process.platform === 'win32'
|
|
176
|
-
? [basePath, basePath + '.cmd', basePath + '.ps1']
|
|
177
|
-
: [basePath];
|
|
178
|
-
|
|
179
|
-
let removed = false;
|
|
180
|
-
for (const path of candidates) {
|
|
181
|
-
if (existsSync(path)) {
|
|
182
|
-
unlinkSync(path);
|
|
183
|
-
removed = true;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
return removed;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Check if a package is linked
|
|
192
|
-
*/
|
|
193
|
-
export function isPackageLinked(packageName: string): boolean {
|
|
194
|
-
const basePath = join(C4AI_BIN, packageName);
|
|
195
|
-
if (existsSync(basePath)) return true;
|
|
196
|
-
if (process.platform === 'win32') {
|
|
197
|
-
if (existsSync(basePath + '.cmd')) return true;
|
|
198
|
-
if (existsSync(basePath + '.ps1')) return true;
|
|
199
|
-
}
|
|
200
|
-
return false;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Get PATH setup instructions
|
|
205
|
-
*/
|
|
206
|
-
export function getPathInstructions(): string {
|
|
207
|
-
if (process.platform === 'win32') {
|
|
208
|
-
return `
|
|
209
|
-
To use globally installed cli4ai packages from anywhere, add the bin directory to your PATH:
|
|
210
|
-
|
|
211
|
-
${C4AI_BIN}
|
|
212
|
-
|
|
213
|
-
On Windows, you can:
|
|
214
|
-
1. Use System Settings > Edit environment variables (GUI)
|
|
215
|
-
2. Or run in PowerShell (current session): $env:PATH += ";${C4AI_BIN}"
|
|
216
|
-
3. Or run in PowerShell (permanent): [Environment]::SetEnvironmentVariable("PATH", $env:PATH + ";${C4AI_BIN}", "User")
|
|
217
|
-
|
|
218
|
-
Then restart your terminal.
|
|
219
|
-
`.trim();
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const shell = process.env.SHELL || '/bin/bash';
|
|
223
|
-
const isZsh = shell.includes('zsh');
|
|
224
|
-
const rcFile = isZsh ? '~/.zshrc' : '~/.bashrc';
|
|
225
|
-
|
|
226
|
-
return `
|
|
227
|
-
To use globally installed cli4ai packages from anywhere, add this to your ${rcFile}:
|
|
228
|
-
|
|
229
|
-
export PATH="${C4AI_BIN}:$PATH"
|
|
230
|
-
|
|
231
|
-
Then reload your shell:
|
|
232
|
-
|
|
233
|
-
source ${rcFile}
|
|
234
|
-
|
|
235
|
-
Or start a new terminal session.
|
|
236
|
-
`.trim();
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Check if bin directory is in PATH
|
|
241
|
-
*/
|
|
242
|
-
export function isBinInPath(): boolean {
|
|
243
|
-
const pathEnv = process.env.PATH || '';
|
|
244
|
-
const separator = process.platform === 'win32' ? ';' : ':';
|
|
245
|
-
return pathEnv.split(separator).some(p => resolve(p) === C4AI_BIN);
|
|
246
|
-
}
|
package/src/core/lockfile.ts
DELETED
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Lock file management (cli4ai.lock)
|
|
3
|
-
*
|
|
4
|
-
* Tracks installed packages with exact versions and sources
|
|
5
|
-
* for reproducible installations.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { readFileSync, writeFileSync, renameSync, existsSync } from 'fs';
|
|
9
|
-
import { resolve } from 'path';
|
|
10
|
-
|
|
11
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
12
|
-
// TYPES
|
|
13
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
14
|
-
|
|
15
|
-
export interface LockedPackage {
|
|
16
|
-
name: string;
|
|
17
|
-
version: string;
|
|
18
|
-
resolved: string; // Source path or registry URL
|
|
19
|
-
integrity?: string; // Future: content hash
|
|
20
|
-
dependencies?: Record<string, string>;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface Lockfile {
|
|
24
|
-
lockfileVersion: number;
|
|
25
|
-
packages: Record<string, LockedPackage>;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
29
|
-
// CONSTANTS
|
|
30
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
31
|
-
|
|
32
|
-
export const LOCKFILE_NAME = 'cli4ai.lock';
|
|
33
|
-
export const LOCKFILE_VERSION = 1;
|
|
34
|
-
|
|
35
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
36
|
-
// FUNCTIONS
|
|
37
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Get lock file path for a project
|
|
41
|
-
*/
|
|
42
|
-
export function getLockfilePath(projectDir: string): string {
|
|
43
|
-
return resolve(projectDir, LOCKFILE_NAME);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Check if lock file exists
|
|
48
|
-
*/
|
|
49
|
-
export function lockfileExists(projectDir: string): boolean {
|
|
50
|
-
return existsSync(getLockfilePath(projectDir));
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Load lock file, returns empty lockfile if doesn't exist
|
|
55
|
-
*/
|
|
56
|
-
export function loadLockfile(projectDir: string): Lockfile {
|
|
57
|
-
const lockfilePath = getLockfilePath(projectDir);
|
|
58
|
-
|
|
59
|
-
if (!existsSync(lockfilePath)) {
|
|
60
|
-
return {
|
|
61
|
-
lockfileVersion: LOCKFILE_VERSION,
|
|
62
|
-
packages: {}
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
const content = readFileSync(lockfilePath, 'utf-8');
|
|
68
|
-
const data = JSON.parse(content) as Lockfile;
|
|
69
|
-
|
|
70
|
-
// Validate version
|
|
71
|
-
if (data.lockfileVersion !== LOCKFILE_VERSION) {
|
|
72
|
-
console.error(`Warning: Lock file version mismatch (${data.lockfileVersion} vs ${LOCKFILE_VERSION})`);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return data;
|
|
76
|
-
} catch (err) {
|
|
77
|
-
console.error('Warning: Could not parse lock file, starting fresh');
|
|
78
|
-
return {
|
|
79
|
-
lockfileVersion: LOCKFILE_VERSION,
|
|
80
|
-
packages: {}
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Save lock file
|
|
87
|
-
*/
|
|
88
|
-
export function saveLockfile(projectDir: string, lockfile: Lockfile): void {
|
|
89
|
-
const lockfilePath = getLockfilePath(projectDir);
|
|
90
|
-
const content = JSON.stringify(lockfile, null, 2) + '\n';
|
|
91
|
-
const tmpPath = lockfilePath + '.tmp';
|
|
92
|
-
writeFileSync(tmpPath, content);
|
|
93
|
-
renameSync(tmpPath, lockfilePath);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Add or update a package in the lock file
|
|
98
|
-
*/
|
|
99
|
-
export function lockPackage(
|
|
100
|
-
projectDir: string,
|
|
101
|
-
pkg: LockedPackage
|
|
102
|
-
): void {
|
|
103
|
-
const lockfile = loadLockfile(projectDir);
|
|
104
|
-
lockfile.packages[pkg.name] = pkg;
|
|
105
|
-
saveLockfile(projectDir, lockfile);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Remove a package from the lock file
|
|
110
|
-
*/
|
|
111
|
-
export function unlockPackage(projectDir: string, packageName: string): void {
|
|
112
|
-
const lockfile = loadLockfile(projectDir);
|
|
113
|
-
|
|
114
|
-
if (lockfile.packages[packageName]) {
|
|
115
|
-
delete lockfile.packages[packageName];
|
|
116
|
-
saveLockfile(projectDir, lockfile);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Get a locked package by name
|
|
122
|
-
*/
|
|
123
|
-
export function getLockedPackage(
|
|
124
|
-
projectDir: string,
|
|
125
|
-
packageName: string
|
|
126
|
-
): LockedPackage | null {
|
|
127
|
-
const lockfile = loadLockfile(projectDir);
|
|
128
|
-
return lockfile.packages[packageName] || null;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Get all locked packages
|
|
133
|
-
*/
|
|
134
|
-
export function getLockedPackages(projectDir: string): LockedPackage[] {
|
|
135
|
-
const lockfile = loadLockfile(projectDir);
|
|
136
|
-
return Object.values(lockfile.packages);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Check if a package is locked
|
|
141
|
-
*/
|
|
142
|
-
export function isPackageLocked(projectDir: string, packageName: string): boolean {
|
|
143
|
-
const lockfile = loadLockfile(projectDir);
|
|
144
|
-
return packageName in lockfile.packages;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Clear all packages from lock file
|
|
149
|
-
*/
|
|
150
|
-
export function clearLockfile(projectDir: string): void {
|
|
151
|
-
saveLockfile(projectDir, {
|
|
152
|
-
lockfileVersion: LOCKFILE_VERSION,
|
|
153
|
-
packages: {}
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Format lock file as human-readable string
|
|
159
|
-
*/
|
|
160
|
-
export function formatLockfile(lockfile: Lockfile): string {
|
|
161
|
-
const lines: string[] = [
|
|
162
|
-
`# cli4ai.lock - Auto-generated, do not edit`,
|
|
163
|
-
`# lockfileVersion: ${lockfile.lockfileVersion}`,
|
|
164
|
-
``
|
|
165
|
-
];
|
|
166
|
-
|
|
167
|
-
const sortedPackages = Object.values(lockfile.packages).sort((a, b) =>
|
|
168
|
-
a.name.localeCompare(b.name)
|
|
169
|
-
);
|
|
170
|
-
|
|
171
|
-
for (const pkg of sortedPackages) {
|
|
172
|
-
lines.push(`${pkg.name}@${pkg.version}:`);
|
|
173
|
-
lines.push(` resolved: "${pkg.resolved}"`);
|
|
174
|
-
if (pkg.integrity) {
|
|
175
|
-
lines.push(` integrity: ${pkg.integrity}`);
|
|
176
|
-
}
|
|
177
|
-
if (pkg.dependencies && Object.keys(pkg.dependencies).length > 0) {
|
|
178
|
-
lines.push(` dependencies:`);
|
|
179
|
-
for (const [depName, depVersion] of Object.entries(pkg.dependencies)) {
|
|
180
|
-
lines.push(` ${depName}: "${depVersion}"`);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
lines.push(``);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return lines.join('\n');
|
|
187
|
-
}
|