@aspects-ai/workspace-cli 0.1.0
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 +268 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +373 -0
- package/dist/registry/libraries.json +31 -0
- package/package.json +36 -0
- package/src/registry/libraries.json +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# @aspects-ai/workspace-cli
|
|
2
|
+
|
|
3
|
+
Lightweight CLI for installing libraries into workspaces. Designed to be AI-friendly and CI/CD compatible.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Simple, non-interactive commands
|
|
8
|
+
- Git-based library installation via sparse checkout
|
|
9
|
+
- Environment variable authentication only (no prompts)
|
|
10
|
+
- Automatic dependency management
|
|
11
|
+
- Tracks installed libraries in `workspace.config.json`
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g @aspects-ai/workspace-cli
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or use with npx:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx @aspects-ai/workspace-cli <command>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Prerequisites
|
|
26
|
+
|
|
27
|
+
- Node.js >= 18.0.0
|
|
28
|
+
- Git installed and available in PATH
|
|
29
|
+
- GitHub personal access token (for private repositories)
|
|
30
|
+
|
|
31
|
+
## Authentication
|
|
32
|
+
|
|
33
|
+
Set the `WORKSPACE_CLI_TOKEN` environment variable with your GitHub personal access token:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
export WORKSPACE_CLI_TOKEN=ghp_xxxxxxxxxxxx
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The token needs read access to the repositories containing the libraries.
|
|
40
|
+
|
|
41
|
+
## Commands
|
|
42
|
+
|
|
43
|
+
### `init`
|
|
44
|
+
|
|
45
|
+
Initialize workspace configuration. Creates `workspace.config.json` in the current workspace.
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
workspace-cli init
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### `list`
|
|
52
|
+
|
|
53
|
+
List all available libraries in the registry.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
workspace-cli list
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Example output:
|
|
60
|
+
```
|
|
61
|
+
Available libraries:
|
|
62
|
+
|
|
63
|
+
animate-core @0.1.3
|
|
64
|
+
Core animation components for Noodle videos
|
|
65
|
+
|
|
66
|
+
Total: 1 library
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### `add <library>`
|
|
70
|
+
|
|
71
|
+
Install a library into the workspace.
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
WORKSPACE_CLI_TOKEN=ghp_xxx workspace-cli add animate-core
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Options:
|
|
78
|
+
- `-f, --force` - Force reinstall if already installed
|
|
79
|
+
|
|
80
|
+
The command will:
|
|
81
|
+
1. Fetch the library from the git repository
|
|
82
|
+
2. Install it to `./core/<library-name>/`
|
|
83
|
+
3. Update `workspace.config.json`
|
|
84
|
+
4. Add dependencies to `package.json`
|
|
85
|
+
|
|
86
|
+
## Usage Examples
|
|
87
|
+
|
|
88
|
+
### Basic Workflow
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# Navigate to your workspace
|
|
92
|
+
cd my-workspace
|
|
93
|
+
|
|
94
|
+
# Initialize workspace config
|
|
95
|
+
workspace-cli init
|
|
96
|
+
|
|
97
|
+
# View available libraries
|
|
98
|
+
workspace-cli list
|
|
99
|
+
|
|
100
|
+
# Install a library (requires token)
|
|
101
|
+
export WORKSPACE_CLI_TOKEN=ghp_xxxxxxxxxxxx
|
|
102
|
+
workspace-cli add animate-core
|
|
103
|
+
|
|
104
|
+
# Install dependencies
|
|
105
|
+
npm install
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### CI/CD Usage
|
|
109
|
+
|
|
110
|
+
```yaml
|
|
111
|
+
# .github/workflows/setup.yml
|
|
112
|
+
name: Setup Workspace
|
|
113
|
+
|
|
114
|
+
on: [push]
|
|
115
|
+
|
|
116
|
+
jobs:
|
|
117
|
+
setup:
|
|
118
|
+
runs-on: ubuntu-latest
|
|
119
|
+
steps:
|
|
120
|
+
- uses: actions/checkout@v3
|
|
121
|
+
|
|
122
|
+
- name: Setup Node.js
|
|
123
|
+
uses: actions/setup-node@v3
|
|
124
|
+
with:
|
|
125
|
+
node-version: '18'
|
|
126
|
+
|
|
127
|
+
- name: Install libraries
|
|
128
|
+
env:
|
|
129
|
+
WORKSPACE_CLI_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
130
|
+
run: |
|
|
131
|
+
npx @aspects-ai/workspace-cli add animate-core
|
|
132
|
+
|
|
133
|
+
- name: Install dependencies
|
|
134
|
+
run: npm install
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### AI Agent Usage
|
|
138
|
+
|
|
139
|
+
The CLI is designed to be used by AI agents without any interactive prompts:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
# All commands fail fast with clear error messages
|
|
143
|
+
# No confirmation prompts
|
|
144
|
+
# Machine-readable output
|
|
145
|
+
|
|
146
|
+
WORKSPACE_CLI_TOKEN=$TOKEN workspace-cli add animate-core
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Configuration
|
|
150
|
+
|
|
151
|
+
### workspace.config.json
|
|
152
|
+
|
|
153
|
+
The CLI creates and manages `workspace.config.json` in your workspace:
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
{
|
|
157
|
+
"version": "1.0",
|
|
158
|
+
"coreDir": "./core",
|
|
159
|
+
"libraries": {
|
|
160
|
+
"animate-core": {
|
|
161
|
+
"version": "0.1.3",
|
|
162
|
+
"installedAt": "2025-10-15T12:34:56Z",
|
|
163
|
+
"commit": "097cf62abc123"
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Library Registry
|
|
170
|
+
|
|
171
|
+
Libraries are defined in `src/registry/libraries.json`:
|
|
172
|
+
|
|
173
|
+
```json
|
|
174
|
+
{
|
|
175
|
+
"libraries": {
|
|
176
|
+
"animate-core": {
|
|
177
|
+
"name": "@aspects-ai/noodle-animate-core",
|
|
178
|
+
"description": "Core animation components for Noodle videos",
|
|
179
|
+
"version": "0.1.3",
|
|
180
|
+
"repository": {
|
|
181
|
+
"url": "https://github.com/aspects-ai/noodle-templates.git",
|
|
182
|
+
"directory": "noodle-animate-core/src",
|
|
183
|
+
"branch": "main"
|
|
184
|
+
},
|
|
185
|
+
"installPath": "animate-core",
|
|
186
|
+
"dependencies": {
|
|
187
|
+
"react": "19.1.0",
|
|
188
|
+
"remotion": "4.0.356"
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## How It Works
|
|
196
|
+
|
|
197
|
+
1. **Git Sparse Checkout**: The CLI uses git sparse checkout to efficiently fetch only the required directory from the repository
|
|
198
|
+
2. **Local Installation**: Files are copied to `./core/<library-name>/` in your workspace
|
|
199
|
+
3. **Dependency Management**: Required dependencies are automatically added to your `package.json`
|
|
200
|
+
4. **Version Tracking**: The exact git commit hash is stored for reproducibility
|
|
201
|
+
|
|
202
|
+
## Error Handling
|
|
203
|
+
|
|
204
|
+
All errors are reported to stderr with clear, actionable messages:
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
# Missing token
|
|
208
|
+
Error: WORKSPACE_CLI_TOKEN environment variable is required
|
|
209
|
+
|
|
210
|
+
# Not in workspace
|
|
211
|
+
Error: Not a workspace directory. Could not find package.json.
|
|
212
|
+
|
|
213
|
+
# Library not found
|
|
214
|
+
Error: Library 'invalid-name' not found in registry.
|
|
215
|
+
Available libraries: animate-core
|
|
216
|
+
|
|
217
|
+
# Already installed
|
|
218
|
+
Error: Library 'animate-core' is already installed at version 0.1.3.
|
|
219
|
+
Use --force to reinstall.
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Development
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
# Install dependencies
|
|
226
|
+
npm install
|
|
227
|
+
|
|
228
|
+
# Build
|
|
229
|
+
npm run build
|
|
230
|
+
|
|
231
|
+
# Type check
|
|
232
|
+
npm run typecheck
|
|
233
|
+
|
|
234
|
+
# Development mode (watch)
|
|
235
|
+
npm run dev
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Adding New Libraries
|
|
239
|
+
|
|
240
|
+
To add a new library to the registry:
|
|
241
|
+
|
|
242
|
+
1. Add the library definition to `src/registry/libraries.json`
|
|
243
|
+
2. Ensure the library source is available in a git repository
|
|
244
|
+
3. Test installation with `workspace-cli add <library-name>`
|
|
245
|
+
|
|
246
|
+
Example:
|
|
247
|
+
```json
|
|
248
|
+
{
|
|
249
|
+
"my-library": {
|
|
250
|
+
"name": "@aspects-ai/my-library",
|
|
251
|
+
"description": "Description of the library",
|
|
252
|
+
"version": "1.0.0",
|
|
253
|
+
"repository": {
|
|
254
|
+
"url": "https://github.com/org/repo.git",
|
|
255
|
+
"directory": "path/to/library/src",
|
|
256
|
+
"branch": "main"
|
|
257
|
+
},
|
|
258
|
+
"installPath": "my-library",
|
|
259
|
+
"dependencies": {
|
|
260
|
+
"react": "^18.0.0"
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## License
|
|
267
|
+
|
|
268
|
+
ISC
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command as Command4 } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/init.ts
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
|
|
9
|
+
// src/utils/fs.ts
|
|
10
|
+
import fs from "fs-extra";
|
|
11
|
+
import path from "path";
|
|
12
|
+
import { fileURLToPath } from "url";
|
|
13
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
var __dirname = path.dirname(__filename);
|
|
15
|
+
async function findWorkspaceRoot(startDir = process.cwd()) {
|
|
16
|
+
let currentDir = startDir;
|
|
17
|
+
while (currentDir !== path.parse(currentDir).root) {
|
|
18
|
+
const packageJsonPath = path.join(currentDir, "package.json");
|
|
19
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
20
|
+
return currentDir;
|
|
21
|
+
}
|
|
22
|
+
currentDir = path.dirname(currentDir);
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
async function ensureWorkspaceRoot() {
|
|
27
|
+
const workspaceRoot = await findWorkspaceRoot();
|
|
28
|
+
if (!workspaceRoot) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
"Not a workspace directory. Could not find package.json.\nRun this command from within a workspace directory."
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
return workspaceRoot;
|
|
34
|
+
}
|
|
35
|
+
function getRegistryPath() {
|
|
36
|
+
const prodPath = path.resolve(__dirname, "registry/libraries.json");
|
|
37
|
+
if (fs.existsSync(prodPath)) {
|
|
38
|
+
return prodPath;
|
|
39
|
+
}
|
|
40
|
+
return path.resolve(__dirname, "../registry/libraries.json");
|
|
41
|
+
}
|
|
42
|
+
async function readJsonFile(filePath) {
|
|
43
|
+
try {
|
|
44
|
+
return await fs.readJson(filePath);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
throw new Error(`Failed to read ${filePath}: ${error}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async function writeJsonFile(filePath, data) {
|
|
50
|
+
try {
|
|
51
|
+
await fs.writeJson(filePath, data, { spaces: 2 });
|
|
52
|
+
} catch (error) {
|
|
53
|
+
throw new Error(`Failed to write ${filePath}: ${error}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/core/config.ts
|
|
58
|
+
import { z } from "zod";
|
|
59
|
+
import path2 from "path";
|
|
60
|
+
import fs2 from "fs-extra";
|
|
61
|
+
var LibraryInstallSchema = z.object({
|
|
62
|
+
version: z.string(),
|
|
63
|
+
installedAt: z.string(),
|
|
64
|
+
commit: z.string()
|
|
65
|
+
});
|
|
66
|
+
var WorkspaceConfigSchema = z.object({
|
|
67
|
+
version: z.string(),
|
|
68
|
+
coreDir: z.string(),
|
|
69
|
+
libraries: z.record(LibraryInstallSchema)
|
|
70
|
+
});
|
|
71
|
+
var CONFIG_FILE_NAME = "workspace.config.json";
|
|
72
|
+
async function loadWorkspaceConfig(workspaceRoot) {
|
|
73
|
+
const configPath = path2.join(workspaceRoot, CONFIG_FILE_NAME);
|
|
74
|
+
if (!await fs2.pathExists(configPath)) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const data = await readJsonFile(configPath);
|
|
78
|
+
return WorkspaceConfigSchema.parse(data);
|
|
79
|
+
}
|
|
80
|
+
async function saveWorkspaceConfig(workspaceRoot, config) {
|
|
81
|
+
const configPath = path2.join(workspaceRoot, CONFIG_FILE_NAME);
|
|
82
|
+
await writeJsonFile(configPath, config);
|
|
83
|
+
}
|
|
84
|
+
async function createDefaultConfig(workspaceRoot) {
|
|
85
|
+
const config = {
|
|
86
|
+
version: "1.0",
|
|
87
|
+
coreDir: "./core",
|
|
88
|
+
libraries: {}
|
|
89
|
+
};
|
|
90
|
+
await saveWorkspaceConfig(workspaceRoot, config);
|
|
91
|
+
return config;
|
|
92
|
+
}
|
|
93
|
+
async function getOrCreateConfig(workspaceRoot) {
|
|
94
|
+
const existing = await loadWorkspaceConfig(workspaceRoot);
|
|
95
|
+
if (existing) {
|
|
96
|
+
return existing;
|
|
97
|
+
}
|
|
98
|
+
return createDefaultConfig(workspaceRoot);
|
|
99
|
+
}
|
|
100
|
+
async function addLibraryToConfig(workspaceRoot, libraryName, version, commit) {
|
|
101
|
+
const config = await getOrCreateConfig(workspaceRoot);
|
|
102
|
+
config.libraries[libraryName] = {
|
|
103
|
+
version,
|
|
104
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
105
|
+
commit
|
|
106
|
+
};
|
|
107
|
+
await saveWorkspaceConfig(workspaceRoot, config);
|
|
108
|
+
}
|
|
109
|
+
async function isLibraryInstalled(workspaceRoot, libraryName) {
|
|
110
|
+
const config = await loadWorkspaceConfig(workspaceRoot);
|
|
111
|
+
return config ? libraryName in config.libraries : false;
|
|
112
|
+
}
|
|
113
|
+
function getConfigPath(workspaceRoot) {
|
|
114
|
+
return path2.join(workspaceRoot, CONFIG_FILE_NAME);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/utils/logger.ts
|
|
118
|
+
import chalk from "chalk";
|
|
119
|
+
var logger = {
|
|
120
|
+
info(message) {
|
|
121
|
+
console.log(chalk.blue("[INFO]"), message);
|
|
122
|
+
},
|
|
123
|
+
success(message) {
|
|
124
|
+
console.log(chalk.green("[SUCCESS]"), message);
|
|
125
|
+
},
|
|
126
|
+
error(message) {
|
|
127
|
+
console.error(chalk.red("[ERROR]"), message);
|
|
128
|
+
},
|
|
129
|
+
warn(message) {
|
|
130
|
+
console.warn(chalk.yellow("[WARN]"), message);
|
|
131
|
+
},
|
|
132
|
+
log(message) {
|
|
133
|
+
console.log(message);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// src/commands/init.ts
|
|
138
|
+
function createInitCommand() {
|
|
139
|
+
return new Command("init").description("Initialize workspace configuration").action(async () => {
|
|
140
|
+
try {
|
|
141
|
+
const workspaceRoot = await ensureWorkspaceRoot();
|
|
142
|
+
const existing = await loadWorkspaceConfig(workspaceRoot);
|
|
143
|
+
if (existing) {
|
|
144
|
+
logger.warn("workspace.config.json already exists");
|
|
145
|
+
logger.info(`Config file: ${getConfigPath(workspaceRoot)}`);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
await createDefaultConfig(workspaceRoot);
|
|
149
|
+
logger.success("Created workspace.config.json");
|
|
150
|
+
logger.log("\nNext steps:");
|
|
151
|
+
logger.log(" workspace-cli list # View available libraries");
|
|
152
|
+
logger.log(" workspace-cli add <library> # Install a library");
|
|
153
|
+
} catch (error) {
|
|
154
|
+
logger.error(error.message);
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// src/commands/list.ts
|
|
161
|
+
import { Command as Command2 } from "commander";
|
|
162
|
+
|
|
163
|
+
// src/core/registry.ts
|
|
164
|
+
import { z as z2 } from "zod";
|
|
165
|
+
var RepositorySchema = z2.object({
|
|
166
|
+
url: z2.string(),
|
|
167
|
+
directory: z2.string(),
|
|
168
|
+
branch: z2.string()
|
|
169
|
+
});
|
|
170
|
+
var LibrarySchema = z2.object({
|
|
171
|
+
name: z2.string(),
|
|
172
|
+
description: z2.string(),
|
|
173
|
+
version: z2.string(),
|
|
174
|
+
repository: RepositorySchema,
|
|
175
|
+
installPath: z2.string(),
|
|
176
|
+
dependencies: z2.record(z2.string())
|
|
177
|
+
});
|
|
178
|
+
var RegistrySchema = z2.object({
|
|
179
|
+
libraries: z2.record(LibrarySchema)
|
|
180
|
+
});
|
|
181
|
+
var cachedRegistry = null;
|
|
182
|
+
async function loadRegistry() {
|
|
183
|
+
if (cachedRegistry) {
|
|
184
|
+
return cachedRegistry;
|
|
185
|
+
}
|
|
186
|
+
const registryPath = getRegistryPath();
|
|
187
|
+
const data = await readJsonFile(registryPath);
|
|
188
|
+
cachedRegistry = RegistrySchema.parse(data);
|
|
189
|
+
return cachedRegistry;
|
|
190
|
+
}
|
|
191
|
+
async function getLibrary(libraryName) {
|
|
192
|
+
const registry = await loadRegistry();
|
|
193
|
+
const library = registry.libraries[libraryName];
|
|
194
|
+
if (!library) {
|
|
195
|
+
throw new Error(
|
|
196
|
+
`Library '${libraryName}' not found in registry.
|
|
197
|
+
Available libraries: ${Object.keys(registry.libraries).join(", ")}`
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
return library;
|
|
201
|
+
}
|
|
202
|
+
async function listLibraries() {
|
|
203
|
+
const registry = await loadRegistry();
|
|
204
|
+
return Object.entries(registry.libraries).map(([name, library]) => ({
|
|
205
|
+
name,
|
|
206
|
+
library
|
|
207
|
+
}));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/commands/list.ts
|
|
211
|
+
import chalk2 from "chalk";
|
|
212
|
+
function createListCommand() {
|
|
213
|
+
return new Command2("list").description("List available libraries").action(async () => {
|
|
214
|
+
try {
|
|
215
|
+
const libraries = await listLibraries();
|
|
216
|
+
logger.log(chalk2.bold("\nAvailable libraries:\n"));
|
|
217
|
+
for (const { name, library } of libraries) {
|
|
218
|
+
logger.log(chalk2.cyan(` ${name}`) + chalk2.gray(` @${library.version}`));
|
|
219
|
+
logger.log(` ${library.description}`);
|
|
220
|
+
logger.log("");
|
|
221
|
+
}
|
|
222
|
+
logger.log(`Total: ${libraries.length} ${libraries.length === 1 ? "library" : "libraries"}
|
|
223
|
+
`);
|
|
224
|
+
} catch (error) {
|
|
225
|
+
logger.error(error.message);
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// src/commands/add.ts
|
|
232
|
+
import { Command as Command3 } from "commander";
|
|
233
|
+
|
|
234
|
+
// src/core/installer.ts
|
|
235
|
+
import path4 from "path";
|
|
236
|
+
import fs4 from "fs-extra";
|
|
237
|
+
|
|
238
|
+
// src/core/git-fetcher.ts
|
|
239
|
+
import { execa } from "execa";
|
|
240
|
+
import fs3 from "fs-extra";
|
|
241
|
+
import path3 from "path";
|
|
242
|
+
import os from "os";
|
|
243
|
+
async function fetchDirectory(options) {
|
|
244
|
+
const tempDir = await fs3.mkdtemp(path3.join(os.tmpdir(), "workspace-cli-"));
|
|
245
|
+
try {
|
|
246
|
+
const repoUrl = options.repository.replace(
|
|
247
|
+
"https://github.com/",
|
|
248
|
+
`https://${options.token}@github.com/`
|
|
249
|
+
);
|
|
250
|
+
await execa("git", ["init"], { cwd: tempDir });
|
|
251
|
+
await execa("git", ["remote", "add", "origin", repoUrl], { cwd: tempDir });
|
|
252
|
+
await execa("git", ["config", "core.sparseCheckout", "true"], { cwd: tempDir });
|
|
253
|
+
const sparseFile = path3.join(tempDir, ".git", "info", "sparse-checkout");
|
|
254
|
+
await fs3.writeFile(sparseFile, `${options.directory}/*
|
|
255
|
+
`);
|
|
256
|
+
await execa("git", ["pull", "origin", options.branch, "--depth=1"], {
|
|
257
|
+
cwd: tempDir,
|
|
258
|
+
stderr: "pipe"
|
|
259
|
+
});
|
|
260
|
+
const { stdout: commit } = await execa("git", ["rev-parse", "HEAD"], { cwd: tempDir });
|
|
261
|
+
const sourceDir = path3.join(tempDir, options.directory);
|
|
262
|
+
if (!await fs3.pathExists(sourceDir)) {
|
|
263
|
+
throw new Error(`Directory '${options.directory}' not found in repository`);
|
|
264
|
+
}
|
|
265
|
+
await fs3.ensureDir(options.targetPath);
|
|
266
|
+
await fs3.copy(sourceDir, options.targetPath, {
|
|
267
|
+
overwrite: true,
|
|
268
|
+
errorOnExist: false
|
|
269
|
+
});
|
|
270
|
+
return commit.trim();
|
|
271
|
+
} catch (error) {
|
|
272
|
+
if (error.stderr && error.stderr.includes("Authentication failed")) {
|
|
273
|
+
throw new Error(
|
|
274
|
+
"Git authentication failed. Please check your WORKSPACE_CLI_TOKEN.\nThe token must have access to the repository."
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
throw new Error(`Failed to fetch library from git: ${error.message}`);
|
|
278
|
+
} finally {
|
|
279
|
+
await fs3.remove(tempDir);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function getToken() {
|
|
283
|
+
const token = process.env.WORKSPACE_CLI_TOKEN;
|
|
284
|
+
if (!token) {
|
|
285
|
+
throw new Error(
|
|
286
|
+
"WORKSPACE_CLI_TOKEN environment variable is required.\nPlease set it with your GitHub personal access token:\n export WORKSPACE_CLI_TOKEN=ghp_xxxxxxxxxxxx\n\nThe token needs read access to the repository."
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
return token;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// src/core/installer.ts
|
|
293
|
+
async function installLibrary(workspaceRoot, libraryName, options = {}) {
|
|
294
|
+
if (!options.force && await isLibraryInstalled(workspaceRoot, libraryName)) {
|
|
295
|
+
const config2 = await loadWorkspaceConfig(workspaceRoot);
|
|
296
|
+
const existingVersion = config2?.libraries[libraryName]?.version;
|
|
297
|
+
throw new Error(
|
|
298
|
+
`Library '${libraryName}' is already installed at version ${existingVersion}.
|
|
299
|
+
Use --force to reinstall.`
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
logger.info(`Fetching ${libraryName} metadata...`);
|
|
303
|
+
const library = await getLibrary(libraryName);
|
|
304
|
+
const token = getToken();
|
|
305
|
+
const config = await loadWorkspaceConfig(workspaceRoot);
|
|
306
|
+
const coreDir = config?.coreDir || "./core";
|
|
307
|
+
const targetPath = path4.join(workspaceRoot, coreDir, library.installPath);
|
|
308
|
+
logger.info(`Fetching ${libraryName}@${library.version} from git...`);
|
|
309
|
+
const commit = await fetchDirectory({
|
|
310
|
+
repository: library.repository.url,
|
|
311
|
+
directory: library.repository.directory,
|
|
312
|
+
branch: library.repository.branch,
|
|
313
|
+
token,
|
|
314
|
+
targetPath
|
|
315
|
+
});
|
|
316
|
+
logger.info(`Installed to ${path4.relative(workspaceRoot, targetPath)}`);
|
|
317
|
+
logger.info(`Updating workspace.config.json...`);
|
|
318
|
+
await addLibraryToConfig(workspaceRoot, libraryName, library.version, commit);
|
|
319
|
+
await updatePackageDependencies(workspaceRoot, library.dependencies);
|
|
320
|
+
logger.success(
|
|
321
|
+
`Installed ${libraryName}@${library.version} (commit: ${commit.substring(0, 7)})`
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
async function updatePackageDependencies(workspaceRoot, dependencies) {
|
|
325
|
+
const packageJsonPath = path4.join(workspaceRoot, "package.json");
|
|
326
|
+
if (!await fs4.pathExists(packageJsonPath)) {
|
|
327
|
+
logger.warn("No package.json found, skipping dependency updates");
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
logger.info("Adding dependencies to package.json...");
|
|
331
|
+
const packageJson = await fs4.readJson(packageJsonPath);
|
|
332
|
+
packageJson.dependencies = packageJson.dependencies || {};
|
|
333
|
+
let addedCount = 0;
|
|
334
|
+
for (const [dep, version] of Object.entries(dependencies)) {
|
|
335
|
+
if (!packageJson.dependencies[dep]) {
|
|
336
|
+
packageJson.dependencies[dep] = version;
|
|
337
|
+
addedCount++;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if (addedCount > 0) {
|
|
341
|
+
await fs4.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
342
|
+
logger.info(`Added ${addedCount} ${addedCount === 1 ? "dependency" : "dependencies"}`);
|
|
343
|
+
} else {
|
|
344
|
+
logger.info("All dependencies already present");
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// src/commands/add.ts
|
|
349
|
+
import chalk3 from "chalk";
|
|
350
|
+
function createAddCommand() {
|
|
351
|
+
return new Command3("add").description("Add a library to the workspace").argument("<library>", "Name of the library to add").option("-f, --force", "Force reinstall if already installed").action(async (libraryName, options) => {
|
|
352
|
+
try {
|
|
353
|
+
const workspaceRoot = await ensureWorkspaceRoot();
|
|
354
|
+
await getOrCreateConfig(workspaceRoot);
|
|
355
|
+
await installLibrary(workspaceRoot, libraryName, { force: options.force });
|
|
356
|
+
logger.log("\n" + chalk3.bold("Next steps:"));
|
|
357
|
+
logger.log(` ${chalk3.gray("Import components:")} import { ... } from '@/core/${libraryName.replace("animate-", "")}'`);
|
|
358
|
+
logger.log(` ${chalk3.gray("Install dependencies:")} npm install`);
|
|
359
|
+
logger.log("");
|
|
360
|
+
} catch (error) {
|
|
361
|
+
logger.error(error.message);
|
|
362
|
+
process.exit(1);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// src/index.ts
|
|
368
|
+
var program = new Command4();
|
|
369
|
+
program.name("workspace-cli").description("Lightweight CLI for installing libraries into workspaces").version("0.1.0");
|
|
370
|
+
program.addCommand(createInitCommand());
|
|
371
|
+
program.addCommand(createListCommand());
|
|
372
|
+
program.addCommand(createAddCommand());
|
|
373
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"libraries": {
|
|
3
|
+
"animate-core": {
|
|
4
|
+
"name": "@aspects-ai/noodle-animate-core",
|
|
5
|
+
"description": "Core animation components for Noodle videos",
|
|
6
|
+
"version": "0.1.3",
|
|
7
|
+
"repository": {
|
|
8
|
+
"url": "https://github.com/aspects-ai/noodle-templates.git",
|
|
9
|
+
"directory": "noodle-animate-core/src",
|
|
10
|
+
"branch": "main"
|
|
11
|
+
},
|
|
12
|
+
"installPath": "animate-core",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"react": "19.1.0",
|
|
15
|
+
"react-dom": "19.1.0",
|
|
16
|
+
"remotion": "4.0.356",
|
|
17
|
+
"react-rnd": "^10.5.2",
|
|
18
|
+
"class-variance-authority": "^0.7.1",
|
|
19
|
+
"clsx": "^2.1.1",
|
|
20
|
+
"cmdk": "^1.1.1",
|
|
21
|
+
"date-fns": "^4.1.0",
|
|
22
|
+
"embla-carousel-react": "^8.6.0",
|
|
23
|
+
"input-otp": "^1.4.2",
|
|
24
|
+
"lucide-react": "^0.542.0",
|
|
25
|
+
"tailwind-merge": "^3.3.1",
|
|
26
|
+
"tailwindcss-animate": "^1.0.7",
|
|
27
|
+
"vaul": "^1.1.2"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aspects-ai/workspace-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Lightweight CLI for installing libraries into workspaces",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"workspace-cli": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsup src/index.ts --format esm --dts --clean && npm run copy:registry",
|
|
12
|
+
"copy:registry": "mkdir -p dist/registry && cp src/registry/libraries.json dist/registry/",
|
|
13
|
+
"dev": "tsup src/index.ts --format esm --watch",
|
|
14
|
+
"typecheck": "tsc --noEmit"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"src/registry"
|
|
19
|
+
],
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"commander": "^12.0.0",
|
|
22
|
+
"chalk": "^5.3.0",
|
|
23
|
+
"execa": "^8.0.0",
|
|
24
|
+
"fs-extra": "^11.2.0",
|
|
25
|
+
"zod": "^3.22.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^20",
|
|
29
|
+
"@types/fs-extra": "^11.0.0",
|
|
30
|
+
"tsup": "^8.0.0",
|
|
31
|
+
"typescript": "^5"
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=18.0.0"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"libraries": {
|
|
3
|
+
"animate-core": {
|
|
4
|
+
"name": "@aspects-ai/noodle-animate-core",
|
|
5
|
+
"description": "Core animation components for Noodle videos",
|
|
6
|
+
"version": "0.1.3",
|
|
7
|
+
"repository": {
|
|
8
|
+
"url": "https://github.com/aspects-ai/noodle-templates.git",
|
|
9
|
+
"directory": "noodle-animate-core/src",
|
|
10
|
+
"branch": "main"
|
|
11
|
+
},
|
|
12
|
+
"installPath": "animate-core",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"react": "19.1.0",
|
|
15
|
+
"react-dom": "19.1.0",
|
|
16
|
+
"remotion": "4.0.356",
|
|
17
|
+
"react-rnd": "^10.5.2",
|
|
18
|
+
"class-variance-authority": "^0.7.1",
|
|
19
|
+
"clsx": "^2.1.1",
|
|
20
|
+
"cmdk": "^1.1.1",
|
|
21
|
+
"date-fns": "^4.1.0",
|
|
22
|
+
"embla-carousel-react": "^8.6.0",
|
|
23
|
+
"input-otp": "^1.4.2",
|
|
24
|
+
"lucide-react": "^0.542.0",
|
|
25
|
+
"tailwind-merge": "^3.3.1",
|
|
26
|
+
"tailwindcss-animate": "^1.0.7",
|
|
27
|
+
"vaul": "^1.1.2"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|