@aspects-ai/workspace-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.
Files changed (3) hide show
  1. package/README.md +9 -10
  2. package/dist/index.js +332 -332
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -105,34 +105,33 @@ Generate a `remotion-entry.tsx` file for server-side rendering of Remotion compo
105
105
  ```bash
106
106
  workspace-cli create-remotion-entry
107
107
  ```
108
-
109
108
  Options:
110
109
  - `-o, --output <path>` - Output path for remotion-entry.tsx (default: `remotion-entry.tsx`)
111
- - `-f, --frames-dir <path>` - Path to frames directory (default: `frames`)
110
+ - `-f, --compositions-dir <path>` - Path to compositions directory (default: `compositions`)
112
111
 
113
112
  This command will:
114
- 1. Scan the frames directory for all frame subdirectories
115
- 2. Validate that each frame has a `main.tsx` file
116
- 3. Generate a `remotion-entry.tsx` file with all frames imported and registered
117
- 4. Display the list of registered frames
113
+ 1. Scan the compositions directory for all composition subdirectories
114
+ 2. Validate that each composition has a `main.tsx` file
115
+ 3. Generate a `remotion-entry.tsx` file with all compositions imported and registered
116
+ 4. Display the list of registered compositions
118
117
 
119
118
  Example output:
120
119
  ```
121
- [INFO] Scanning frames in: frames
120
+ [INFO] Scanning compositions in: compositions
122
121
  [SUCCESS] Created remotion entry file: /path/to/remotion-entry.tsx
123
122
 
124
- Registered 3 frame(s):
123
+ Registered 3 compositions(s):
125
124
  • example
126
125
  • healthee
127
126
  • peregrine
128
127
 
129
128
  Next steps:
130
- Render a frame: ANIMATION_SSR_MODE=true npx remotion render remotion-entry.tsx <frame-id> output.mp4
129
+ Render a composition: ANIMATION_SSR_MODE=true npx remotion render remotion-entry.tsx <composition-id> output.mp4
131
130
  ```
132
131
 
133
132
  The generated file can be used with Remotion CLI for server-side rendering:
134
133
  ```bash
135
- # Render a specific frame
134
+ # Render a specific composition
136
135
  ANIMATION_SSR_MODE=true npx remotion render remotion-entry.tsx example output.mp4
137
136
 
138
137
  # List available compositions
package/dist/index.js CHANGED
@@ -3,7 +3,45 @@
3
3
  // src/index.ts
4
4
  import { Command as Command8 } from "commander";
5
5
 
6
- // src/commands/init.ts
6
+ // package.json
7
+ var package_default = {
8
+ name: "@aspects-ai/workspace-cli",
9
+ version: "0.1.14",
10
+ private: false,
11
+ description: "Lightweight CLI for installing libraries into workspaces",
12
+ type: "module",
13
+ bin: {
14
+ "workspace-cli": "./dist/index.js"
15
+ },
16
+ scripts: {
17
+ build: "tsup src/index.ts --format esm --dts --clean && npm run copy:registry",
18
+ "copy:registry": "mkdir -p dist/registry && cp src/registry/libraries.json dist/registry/",
19
+ dev: "tsup src/index.ts --format esm --watch",
20
+ typecheck: "tsc --noEmit"
21
+ },
22
+ files: [
23
+ "dist",
24
+ "src/registry"
25
+ ],
26
+ dependencies: {
27
+ chalk: "^5.3.0",
28
+ commander: "^12.0.0",
29
+ execa: "^8.0.0",
30
+ "fs-extra": "^11.2.0",
31
+ zod: "^3.22.0"
32
+ },
33
+ devDependencies: {
34
+ "@types/fs-extra": "^11.0.0",
35
+ "@types/node": "^20",
36
+ tsup: "^8.0.0",
37
+ typescript: "^5"
38
+ },
39
+ engines: {
40
+ node: ">=18.0.0"
41
+ }
42
+ };
43
+
44
+ // src/commands/add.ts
7
45
  import { Command } from "commander";
8
46
 
9
47
  // src/utils/fs.ts
@@ -114,128 +152,6 @@ function getConfigPath(workspaceRoot) {
114
152
  return path2.join(workspaceRoot, CONFIG_FILE_NAME);
115
153
  }
116
154
 
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
- packageJsonPath: z2.string().optional()
170
- // Path to package.json in repo (e.g., "noodle-animate-core/package.json")
171
- });
172
- var LibrarySchema = z2.object({
173
- name: z2.string(),
174
- description: z2.string(),
175
- version: z2.string().optional(),
176
- // Optional - if not provided, will be read from package.json in repo
177
- repository: RepositorySchema,
178
- installPath: z2.string(),
179
- dependencies: z2.record(z2.string()).optional()
180
- // Optional - if not provided, will be read from package.json in repo
181
- });
182
- var RegistrySchema = z2.object({
183
- libraries: z2.record(LibrarySchema)
184
- });
185
- var cachedRegistry = null;
186
- async function loadRegistry() {
187
- if (cachedRegistry) {
188
- return cachedRegistry;
189
- }
190
- const registryPath = getRegistryPath();
191
- const data = await readJsonFile(registryPath);
192
- cachedRegistry = RegistrySchema.parse(data);
193
- return cachedRegistry;
194
- }
195
- async function getLibrary(libraryName) {
196
- const registry = await loadRegistry();
197
- const library = registry.libraries[libraryName];
198
- if (!library) {
199
- throw new Error(
200
- `Library '${libraryName}' not found in registry.
201
- Available libraries: ${Object.keys(registry.libraries).join(", ")}`
202
- );
203
- }
204
- return library;
205
- }
206
- async function listLibraries() {
207
- const registry = await loadRegistry();
208
- return Object.entries(registry.libraries).map(([name, library]) => ({
209
- name,
210
- library
211
- }));
212
- }
213
-
214
- // src/commands/list.ts
215
- import chalk2 from "chalk";
216
- function createListCommand() {
217
- return new Command2("list").description("List available libraries").action(async () => {
218
- try {
219
- const libraries = await listLibraries();
220
- logger.log(chalk2.bold("\nAvailable libraries:\n"));
221
- for (const { name, library } of libraries) {
222
- const versionStr = library.version ? ` @${library.version}` : " @latest";
223
- logger.log(chalk2.cyan(` ${name}`) + chalk2.gray(versionStr));
224
- logger.log(` ${library.description}`);
225
- logger.log("");
226
- }
227
- logger.log(`Total: ${libraries.length} ${libraries.length === 1 ? "library" : "libraries"}
228
- `);
229
- } catch (error) {
230
- logger.error(error.message);
231
- process.exit(1);
232
- }
233
- });
234
- }
235
-
236
- // src/commands/add.ts
237
- import { Command as Command3 } from "commander";
238
-
239
155
  // src/core/installer.ts
240
156
  import path4 from "path";
241
157
  import fs4 from "fs-extra";
@@ -321,6 +237,77 @@ function getToken() {
321
237
  return token;
322
238
  }
323
239
 
240
+ // src/core/registry.ts
241
+ import { z as z2 } from "zod";
242
+ var RepositorySchema = z2.object({
243
+ url: z2.string(),
244
+ directory: z2.string(),
245
+ branch: z2.string(),
246
+ packageJsonPath: z2.string().optional()
247
+ // Path to package.json in repo (e.g., "noodle-animate-core/package.json")
248
+ });
249
+ var LibrarySchema = z2.object({
250
+ name: z2.string(),
251
+ description: z2.string(),
252
+ version: z2.string().optional(),
253
+ // Optional - if not provided, will be read from package.json in repo
254
+ repository: RepositorySchema,
255
+ installPath: z2.string(),
256
+ dependencies: z2.record(z2.string()).optional()
257
+ // Optional - if not provided, will be read from package.json in repo
258
+ });
259
+ var RegistrySchema = z2.object({
260
+ libraries: z2.record(LibrarySchema)
261
+ });
262
+ var cachedRegistry = null;
263
+ async function loadRegistry() {
264
+ if (cachedRegistry) {
265
+ return cachedRegistry;
266
+ }
267
+ const registryPath = getRegistryPath();
268
+ const data = await readJsonFile(registryPath);
269
+ cachedRegistry = RegistrySchema.parse(data);
270
+ return cachedRegistry;
271
+ }
272
+ async function getLibrary(libraryName) {
273
+ const registry = await loadRegistry();
274
+ const library = registry.libraries[libraryName];
275
+ if (!library) {
276
+ throw new Error(
277
+ `Library '${libraryName}' not found in registry.
278
+ Available libraries: ${Object.keys(registry.libraries).join(", ")}`
279
+ );
280
+ }
281
+ return library;
282
+ }
283
+ async function listLibraries() {
284
+ const registry = await loadRegistry();
285
+ return Object.entries(registry.libraries).map(([name, library]) => ({
286
+ name,
287
+ library
288
+ }));
289
+ }
290
+
291
+ // src/utils/logger.ts
292
+ import chalk from "chalk";
293
+ var logger = {
294
+ info(message) {
295
+ console.log(chalk.blue("[INFO]"), message);
296
+ },
297
+ success(message) {
298
+ console.log(chalk.green("[SUCCESS]"), message);
299
+ },
300
+ error(message) {
301
+ console.error(chalk.red("[ERROR]"), message);
302
+ },
303
+ warn(message) {
304
+ console.warn(chalk.yellow("[WARN]"), message);
305
+ },
306
+ log(message) {
307
+ console.log(message);
308
+ }
309
+ };
310
+
324
311
  // src/core/installer.ts
325
312
  async function installLibrary(workspaceRoot, libraryName, options = {}) {
326
313
  if (!options.force && await isLibraryInstalled(workspaceRoot, libraryName)) {
@@ -382,47 +369,16 @@ async function updatePackageDependencies(workspaceRoot, dependencies) {
382
369
  }
383
370
 
384
371
  // src/commands/add.ts
385
- import chalk3 from "chalk";
372
+ import chalk2 from "chalk";
386
373
  function createAddCommand() {
387
- 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) => {
374
+ return new Command("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) => {
388
375
  try {
389
376
  const workspaceRoot = await ensureWorkspaceRoot();
390
377
  await getOrCreateConfig(workspaceRoot);
391
378
  await installLibrary(workspaceRoot, libraryName, { force: options.force });
392
- logger.log("\n" + chalk3.bold("Next steps:"));
393
- logger.log(` ${chalk3.gray("Import components:")} import { ... } from '@/core/${libraryName.replace("animate-", "")}'`);
394
- logger.log(` ${chalk3.gray("Install dependencies:")} npm install`);
395
- logger.log("");
396
- } catch (error) {
397
- logger.error(error.message);
398
- process.exit(1);
399
- }
400
- });
401
- }
402
-
403
- // src/commands/update.ts
404
- import { Command as Command4 } from "commander";
405
- import chalk4 from "chalk";
406
- function createUpdateCommand() {
407
- return new Command4("update").description("Update an installed library to the latest version").argument("<library>", "Name of the library to update").action(async (libraryName) => {
408
- try {
409
- const workspaceRoot = await ensureWorkspaceRoot();
410
- await getOrCreateConfig(workspaceRoot);
411
- if (!await isLibraryInstalled(workspaceRoot, libraryName)) {
412
- throw new Error(
413
- `Library '${libraryName}' is not installed.
414
- Use 'workspace-cli add ${libraryName}' to install it first.`
415
- );
416
- }
417
- const config = await loadWorkspaceConfig(workspaceRoot);
418
- const currentVersion = config?.libraries[libraryName]?.version || "unknown";
419
- const currentCommit = config?.libraries[libraryName]?.commit || "unknown";
420
- logger.info(`Current version: ${currentVersion} (commit: ${currentCommit.substring(0, 7)})`);
421
- logger.info(`Updating ${libraryName}...`);
422
- await installLibrary(workspaceRoot, libraryName, { force: true });
423
- logger.log("\n" + chalk4.bold("Next steps:"));
424
- logger.log(` ${chalk4.gray("Review changes:")} git diff ./core/${libraryName.replace("animate-", "")}`);
425
- logger.log(` ${chalk4.gray("Install updated dependencies:")} npm install`);
379
+ logger.log("\n" + chalk2.bold("Next steps:"));
380
+ logger.log(` ${chalk2.gray("Import components:")} import { ... } from '@/core/${libraryName.replace("animate-", "")}'`);
381
+ logger.log(` ${chalk2.gray("Install dependencies:")} npm install`);
426
382
  logger.log("");
427
383
  } catch (error) {
428
384
  logger.error(error.message);
@@ -431,9 +387,9 @@ Use 'workspace-cli add ${libraryName}' to install it first.`
431
387
  });
432
388
  }
433
389
 
434
- // src/commands/create-frame.ts
435
- import chalk5 from "chalk";
436
- import { Command as Command5 } from "commander";
390
+ // src/commands/create-composition.ts
391
+ import chalk3 from "chalk";
392
+ import { Command as Command2 } from "commander";
437
393
  import fs5 from "fs-extra";
438
394
  import path5 from "path";
439
395
  function generateId() {
@@ -446,73 +402,77 @@ function parseManifestOption(manifestStr) {
446
402
  throw new Error(`Invalid manifest JSON: ${error instanceof Error ? error.message : "Unknown error"}`);
447
403
  }
448
404
  }
449
- function validateAndFixManifest(manifest, toolName, frameName) {
405
+ function validateAndFixManifest(manifest, toolName, compositionName) {
450
406
  const now = (/* @__PURE__ */ new Date()).toISOString();
451
407
  const validatedManifest = {
452
- id: frameName,
408
+ id: compositionName,
453
409
  // Always match the directory name
454
410
  projectId: manifest.projectId || "default-project",
455
- title: manifest.title || frameName.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" "),
456
- description: manifest.description || `Frame for ${toolName}`,
411
+ title: manifest.title || compositionName.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" "),
412
+ description: manifest.description || `Composition for ${toolName}`,
457
413
  createdAt: now,
458
414
  // Always update createdAt
459
- type: manifest.type || "animationComposition"
415
+ compositionType: manifest.compositionType || "animationComposition",
416
+ width: manifest.width,
417
+ height: manifest.height
460
418
  };
461
419
  if (toolName === "animate-core") {
462
- validatedManifest.type = "animationComposition";
420
+ validatedManifest.compositionType = "animationComposition";
421
+ if (!validatedManifest.width) validatedManifest.width = 1920;
422
+ if (!validatedManifest.height) validatedManifest.height = 1080;
463
423
  }
464
424
  return validatedManifest;
465
425
  }
466
- async function getNextFrameName(framesDir, customName) {
467
- let frameName;
426
+ async function getNextCompositionName(compositionsDir, customName) {
427
+ let compositionName;
468
428
  if (customName) {
469
429
  const truncatedName = customName.slice(0, 20);
470
430
  const uniqueId = generateId();
471
- frameName = `${truncatedName}-${uniqueId}`;
431
+ compositionName = `${truncatedName}-${uniqueId}`;
472
432
  } else {
473
- frameName = generateId();
433
+ compositionName = generateId();
474
434
  }
475
- while (await fs5.pathExists(path5.join(framesDir, frameName))) {
435
+ while (await fs5.pathExists(path5.join(compositionsDir, compositionName))) {
476
436
  const uniqueId = generateId();
477
- frameName = customName ? `${customName.slice(0, 20)}-${uniqueId}` : uniqueId;
437
+ compositionName = customName ? `${customName.slice(0, 20)}-${uniqueId}` : uniqueId;
478
438
  }
479
- return frameName;
439
+ return compositionName;
480
440
  }
481
- async function fetchExampleFrame(toolName, targetPath) {
441
+ async function fetchExampleComposition(toolName, targetPath) {
482
442
  const library = await getLibrary(toolName);
483
443
  const token = getToken();
484
- logger.info(`Fetching example frame from ${chalk5.cyan(library.repository.url)}`);
485
- const exampleFrameDirectory = library.repository.directory.replace("/src", "/example-frame-123");
444
+ logger.info(`Fetching example composition from ${chalk3.cyan(library.repository.url)}`);
445
+ const exampleCompositionDirectory = library.repository.directory.replace("/src", "/starter-composition-123");
486
446
  await fetchDirectory({
487
447
  repository: library.repository.url,
488
- directory: exampleFrameDirectory,
448
+ directory: exampleCompositionDirectory,
489
449
  branch: library.repository.branch,
490
450
  token,
491
451
  targetPath
492
452
  });
493
453
  }
494
- function createCreateFrameCommand() {
495
- return new Command5("create-frame").description("Create a new frame for a given tool").argument("<tool>", "Name of the tool (e.g., animate-core)").argument("<manifest>", "Manifest data as JSON string").option("-n, --name <name>", "Name for the new frame", "new-frame").action(async (toolName, manifestStr, options) => {
454
+ function createCreateCompositionCommand() {
455
+ return new Command2("create-composition").description("Create a new composition for a given tool").argument("<tool>", "Name of the tool (e.g., animate-core)").argument("<manifest>", "Manifest data as JSON string").option("-n, --name <name>", "Name for the new composition", "new-composition").action(async (toolName, manifestStr, options) => {
496
456
  try {
497
457
  const workspaceRoot = await ensureWorkspaceRoot();
498
- const framesDir = path5.join(workspaceRoot, "frames");
499
- await fs5.ensureDir(framesDir);
500
- const customName = options.name && options.name !== "new-frame" ? options.name : void 0;
501
- const frameName = await getNextFrameName(framesDir, customName);
502
- const frameDir = path5.join(framesDir, frameName);
503
- logger.info(`Creating frame: ${chalk5.cyan(frameName)} for ${chalk5.cyan(toolName)}`);
504
- await fetchExampleFrame(toolName, frameDir);
458
+ const compositionsDir = path5.join(workspaceRoot, "compositions");
459
+ await fs5.ensureDir(compositionsDir);
460
+ const customName = options.name && options.name !== "new-composition" ? options.name : void 0;
461
+ const compositionName = await getNextCompositionName(compositionsDir, customName);
462
+ const compositionDir = path5.join(compositionsDir, compositionName);
463
+ logger.info(`Creating composition: ${chalk3.cyan(compositionName)} for ${chalk3.cyan(toolName)}`);
464
+ await fetchExampleComposition(toolName, compositionDir);
505
465
  const manifestData = parseManifestOption(manifestStr);
506
- const validatedManifest = validateAndFixManifest(manifestData, toolName, frameName);
507
- const manifestPath = path5.join(frameDir, "manifest.json");
466
+ const validatedManifest = validateAndFixManifest(manifestData, toolName, compositionName);
467
+ const manifestPath = path5.join(compositionDir, "manifest.json");
508
468
  await fs5.writeJson(manifestPath, validatedManifest, { spaces: 4 });
509
- logger.success(`Frame created at: ${chalk5.green(path5.relative(workspaceRoot, frameDir))}`);
469
+ logger.success(`Composition created at: ${chalk3.green(path5.relative(workspaceRoot, compositionDir))}`);
510
470
  logger.log("");
511
- logger.log(chalk5.bold("Manifest:"));
471
+ logger.log(chalk3.bold("Manifest:"));
512
472
  logger.log(JSON.stringify(validatedManifest, null, 2));
513
473
  logger.log("");
514
- logger.log(chalk5.bold("Next steps:"));
515
- logger.log(` ${chalk5.gray("Edit frame:")} ${path5.relative(workspaceRoot, path5.join(frameDir, "main.tsx"))}`);
474
+ logger.log(chalk3.bold("Next steps:"));
475
+ logger.log(` ${chalk3.gray("Edit composition:")} ${path5.relative(workspaceRoot, path5.join(compositionDir, "main.tsx"))}`);
516
476
  logger.log("");
517
477
  } catch (error) {
518
478
  logger.error(error.message);
@@ -521,10 +481,154 @@ function createCreateFrameCommand() {
521
481
  });
522
482
  }
523
483
 
484
+ // src/commands/create-remotion-entry.ts
485
+ import chalk4 from "chalk";
486
+ import { Command as Command3 } from "commander";
487
+ import fs6 from "fs-extra";
488
+ import path6 from "path";
489
+ async function createRemotionEntry(compositionsDir, outputPath, animateCorePath) {
490
+ if (!await fs6.pathExists(compositionsDir)) {
491
+ throw new Error(`Compositions directory not found: ${compositionsDir}`);
492
+ }
493
+ const entries = await fs6.readdir(compositionsDir, { withFileTypes: true });
494
+ const compositionDirs = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
495
+ if (compositionDirs.length === 0) {
496
+ throw new Error(`No composition directories found in ${compositionsDir}`);
497
+ }
498
+ const validCompositions = [];
499
+ for (const compositionDir of compositionDirs) {
500
+ const mainPath = path6.join(compositionsDir, compositionDir, "main.tsx");
501
+ if (await fs6.pathExists(mainPath)) {
502
+ validCompositions.push(compositionDir);
503
+ } else {
504
+ logger.warn(`Skipping ${compositionDir}: main.tsx not found`);
505
+ }
506
+ }
507
+ if (validCompositions.length === 0) {
508
+ throw new Error("No valid compositions found (compositions must contain main.tsx)");
509
+ }
510
+ const imports = validCompositions.map((compositionDir, index) => {
511
+ const varName = `Composition${index}`;
512
+ const defName = `composition${index}Def`;
513
+ return {
514
+ compositionDir,
515
+ varName,
516
+ defName,
517
+ statement: `import ${varName}, { compositionDefinition as ${defName} } from './compositions/${compositionDir}/main';`
518
+ };
519
+ });
520
+ const compositionsObject = imports.map(({ compositionDir, varName, defName }) => {
521
+ return ` '${compositionDir}': { component: ${varName}, compositionDefinition: ${defName} },`;
522
+ }).join("\n");
523
+ const content = `/**
524
+ * Auto-generated Remotion SSR Entry Point
525
+ *
526
+ * Generated by: @aspects-ai/workspace-cli create-remotion-entry
527
+ * DO NOT EDIT THIS FILE MANUALLY - it will be overwritten
528
+ *
529
+ * This file serves as the entry point for server-side rendering with Remotion.
530
+ * It automatically registers all compositions in the ./compositions directory.
531
+ *
532
+ * Usage:
533
+ * ANIMATION_SSR_MODE=true npx remotion render remotion-entry.tsx <composition-id> output.mp4
534
+ *
535
+ * To regenerate this file:
536
+ * npx @aspects-ai/workspace-cli create-remotion-entry
537
+ */
538
+
539
+ import { registerRoot } from 'remotion';
540
+ import { createRemotionRoot } from '${animateCorePath}/remotion';
541
+
542
+ // Composition imports
543
+ ${imports.map((i) => i.statement).join("\n")}
544
+
545
+ // Register all compositions with Remotion
546
+ const Root = createRemotionRoot({
547
+ ${compositionsObject}
548
+ });
549
+
550
+ registerRoot(Root);
551
+ `;
552
+ await fs6.writeFile(outputPath, content, "utf-8");
553
+ logger.success(`Created remotion entry file: ${chalk4.green(outputPath)}`);
554
+ logger.log("");
555
+ logger.log(chalk4.bold(`Registered ${validCompositions.length} composition(s):`));
556
+ validCompositions.forEach((composition) => {
557
+ logger.log(` ${chalk4.cyan("\u2022")} ${composition}`);
558
+ });
559
+ logger.log("");
560
+ logger.log(chalk4.bold("Next steps:"));
561
+ logger.log(` ${chalk4.gray("Render a composition:")} ANIMATION_SSR_MODE=true npx remotion render remotion-entry.tsx <composition-id> output.mp4`);
562
+ logger.log("");
563
+ }
564
+ function createCreateRemotionEntryCommand() {
565
+ return new Command3("create-remotion-entry").description("Create a remotion-entry.tsx file for server-side rendering").option("-o, --output <path>", "Output path for remotion-entry.tsx", "remotion-entry.tsx").option("-f, --compositions-dir <path>", "Path to compositions directory", "compositions").option("-a, --animate-core-path <path>", "Path to animate-core (e.g., ./core/animate-core or @aspects-ai/noodle-animate-core)", "./core/animate-core").action(async (options) => {
566
+ try {
567
+ const workspaceRoot = await ensureWorkspaceRoot();
568
+ const compositionsDir = path6.resolve(workspaceRoot, options.compositionsDir || "compositions");
569
+ const outputPath = path6.resolve(workspaceRoot, options.output || "remotion-entry.tsx");
570
+ const animateCorePath = options.animateCorePath || "./core/animate-core";
571
+ logger.info(`Scanning compositions in: ${chalk4.cyan(path6.relative(workspaceRoot, compositionsDir))}`);
572
+ logger.info(`Using animate-core from: ${chalk4.cyan(animateCorePath)}`);
573
+ await createRemotionEntry(compositionsDir, outputPath, animateCorePath);
574
+ } catch (error) {
575
+ logger.error(error.message);
576
+ process.exit(1);
577
+ }
578
+ });
579
+ }
580
+
581
+ // src/commands/init.ts
582
+ import { Command as Command4 } from "commander";
583
+ function createInitCommand() {
584
+ return new Command4("init").description("Initialize workspace configuration").action(async () => {
585
+ try {
586
+ const workspaceRoot = await ensureWorkspaceRoot();
587
+ const existing = await loadWorkspaceConfig(workspaceRoot);
588
+ if (existing) {
589
+ logger.warn("workspace.config.json already exists");
590
+ logger.info(`Config file: ${getConfigPath(workspaceRoot)}`);
591
+ return;
592
+ }
593
+ await createDefaultConfig(workspaceRoot);
594
+ logger.success("Created workspace.config.json");
595
+ logger.log("\nNext steps:");
596
+ logger.log(" workspace-cli list # View available libraries");
597
+ logger.log(" workspace-cli add <library> # Install a library");
598
+ } catch (error) {
599
+ logger.error(error.message);
600
+ process.exit(1);
601
+ }
602
+ });
603
+ }
604
+
605
+ // src/commands/list.ts
606
+ import { Command as Command5 } from "commander";
607
+ import chalk5 from "chalk";
608
+ function createListCommand() {
609
+ return new Command5("list").description("List available libraries").action(async () => {
610
+ try {
611
+ const libraries = await listLibraries();
612
+ logger.log(chalk5.bold("\nAvailable libraries:\n"));
613
+ for (const { name, library } of libraries) {
614
+ const versionStr = library.version ? ` @${library.version}` : " @latest";
615
+ logger.log(chalk5.cyan(` ${name}`) + chalk5.gray(versionStr));
616
+ logger.log(` ${library.description}`);
617
+ logger.log("");
618
+ }
619
+ logger.log(`Total: ${libraries.length} ${libraries.length === 1 ? "library" : "libraries"}
620
+ `);
621
+ } catch (error) {
622
+ logger.error(error.message);
623
+ process.exit(1);
624
+ }
625
+ });
626
+ }
627
+
524
628
  // src/commands/update-template.ts
525
629
  import { Command as Command6 } from "commander";
526
- import path6 from "path";
527
- import fs6 from "fs-extra";
630
+ import path7 from "path";
631
+ import fs7 from "fs-extra";
528
632
  import { execa as execa2 } from "execa";
529
633
  import os2 from "os";
530
634
  import chalk6 from "chalk";
@@ -535,9 +639,9 @@ function createUpdateTemplateCommand() {
535
639
  return new Command6("update-template").description("Update EDITING.md file from noodle-base template").action(async () => {
536
640
  try {
537
641
  const workspaceRoot = await ensureWorkspaceRoot();
538
- const targetFile = path6.join(workspaceRoot, "EDITING.md");
642
+ const targetFile = path7.join(workspaceRoot, "EDITING.md");
539
643
  const token = getToken();
540
- const targetExists = await fs6.pathExists(targetFile);
644
+ const targetExists = await fs7.pathExists(targetFile);
541
645
  if (targetExists) {
542
646
  logger.info("Existing EDITING.md found, it will be overwritten.");
543
647
  }
@@ -548,7 +652,7 @@ function createUpdateTemplateCommand() {
548
652
  branch: NOODLE_BASE_BRANCH,
549
653
  token
550
654
  });
551
- await fs6.writeFile(targetFile, content, "utf-8");
655
+ await fs7.writeFile(targetFile, content, "utf-8");
552
656
  logger.success(
553
657
  `Successfully ${targetExists ? "updated" : "copied"} EDITING.md to workspace`
554
658
  );
@@ -562,7 +666,7 @@ function createUpdateTemplateCommand() {
562
666
  });
563
667
  }
564
668
  async function fetchFileFromGit(options) {
565
- const tempDir = await fs6.mkdtemp(path6.join(os2.tmpdir(), "workspace-cli-"));
669
+ const tempDir = await fs7.mkdtemp(path7.join(os2.tmpdir(), "workspace-cli-"));
566
670
  try {
567
671
  const repoUrl = options.repository.replace(
568
672
  "https://github.com/",
@@ -571,18 +675,18 @@ async function fetchFileFromGit(options) {
571
675
  await execa2("git", ["init"], { cwd: tempDir });
572
676
  await execa2("git", ["remote", "add", "origin", repoUrl], { cwd: tempDir });
573
677
  await execa2("git", ["config", "core.sparseCheckout", "true"], { cwd: tempDir });
574
- const sparseFile = path6.join(tempDir, ".git", "info", "sparse-checkout");
575
- await fs6.writeFile(sparseFile, `${options.filePath}
678
+ const sparseFile = path7.join(tempDir, ".git", "info", "sparse-checkout");
679
+ await fs7.writeFile(sparseFile, `${options.filePath}
576
680
  `);
577
681
  await execa2("git", ["pull", "origin", options.branch, "--depth=1"], {
578
682
  cwd: tempDir,
579
683
  stderr: "pipe"
580
684
  });
581
- const sourceFile = path6.join(tempDir, options.filePath);
582
- if (!await fs6.pathExists(sourceFile)) {
685
+ const sourceFile = path7.join(tempDir, options.filePath);
686
+ if (!await fs7.pathExists(sourceFile)) {
583
687
  throw new Error(`File '${options.filePath}' not found in repository`);
584
688
  }
585
- return await fs6.readFile(sourceFile, "utf-8");
689
+ return await fs7.readFile(sourceFile, "utf-8");
586
690
  } catch (error) {
587
691
  if (error.stderr && error.stderr.includes("Authentication failed")) {
588
692
  throw new Error(
@@ -591,100 +695,34 @@ async function fetchFileFromGit(options) {
591
695
  }
592
696
  throw new Error(`Failed to fetch file from git: ${error.message}`);
593
697
  } finally {
594
- await fs6.remove(tempDir);
698
+ await fs7.remove(tempDir);
595
699
  }
596
700
  }
597
701
 
598
- // src/commands/create-remotion-entry.ts
599
- import chalk7 from "chalk";
702
+ // src/commands/update.ts
600
703
  import { Command as Command7 } from "commander";
601
- import fs7 from "fs-extra";
602
- import path7 from "path";
603
- async function createRemotionEntry(framesDir, outputPath, animateCorePath) {
604
- if (!await fs7.pathExists(framesDir)) {
605
- throw new Error(`Frames directory not found: ${framesDir}`);
606
- }
607
- const entries = await fs7.readdir(framesDir, { withFileTypes: true });
608
- const frameDirs = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
609
- if (frameDirs.length === 0) {
610
- throw new Error(`No frame directories found in ${framesDir}`);
611
- }
612
- const validFrames = [];
613
- for (const frameDir of frameDirs) {
614
- const mainPath = path7.join(framesDir, frameDir, "main.tsx");
615
- if (await fs7.pathExists(mainPath)) {
616
- validFrames.push(frameDir);
617
- } else {
618
- logger.warn(`Skipping ${frameDir}: main.tsx not found`);
619
- }
620
- }
621
- if (validFrames.length === 0) {
622
- throw new Error("No valid frames found (frames must contain main.tsx)");
623
- }
624
- const imports = validFrames.map((frameDir, index) => {
625
- const varName = `Frame${index}`;
626
- const defName = `frame${index}Def`;
627
- return {
628
- frameDir,
629
- varName,
630
- defName,
631
- statement: `import ${varName}, { compositionDefinition as ${defName} } from './frames/${frameDir}/main';`
632
- };
633
- });
634
- const framesObject = imports.map(({ frameDir, varName, defName }) => {
635
- return ` '${frameDir}': { component: ${varName}, compositionDefinition: ${defName} },`;
636
- }).join("\n");
637
- const content = `/**
638
- * Auto-generated Remotion SSR Entry Point
639
- *
640
- * Generated by: @aspects-ai/workspace-cli create-remotion-entry
641
- * DO NOT EDIT THIS FILE MANUALLY - it will be overwritten
642
- *
643
- * This file serves as the entry point for server-side rendering with Remotion.
644
- * It automatically registers all frames in the ./frames directory.
645
- *
646
- * Usage:
647
- * ANIMATION_SSR_MODE=true npx remotion render remotion-entry.tsx <frame-id> output.mp4
648
- *
649
- * To regenerate this file:
650
- * npx @aspects-ai/workspace-cli create-remotion-entry
651
- */
652
-
653
- import { registerRoot } from 'remotion';
654
- import { createRemotionRoot } from '${animateCorePath}/remotion';
655
-
656
- // Frame imports
657
- ${imports.map((i) => i.statement).join("\n")}
658
-
659
- // Register all frames with Remotion
660
- const Root = createRemotionRoot({
661
- ${framesObject}
662
- });
663
-
664
- registerRoot(Root);
665
- `;
666
- await fs7.writeFile(outputPath, content, "utf-8");
667
- logger.success(`Created remotion entry file: ${chalk7.green(outputPath)}`);
668
- logger.log("");
669
- logger.log(chalk7.bold(`Registered ${validFrames.length} frame(s):`));
670
- validFrames.forEach((frame) => {
671
- logger.log(` ${chalk7.cyan("\u2022")} ${frame}`);
672
- });
673
- logger.log("");
674
- logger.log(chalk7.bold("Next steps:"));
675
- logger.log(` ${chalk7.gray("Render a frame:")} ANIMATION_SSR_MODE=true npx remotion render remotion-entry.tsx <frame-id> output.mp4`);
676
- logger.log("");
677
- }
678
- function createCreateRemotionEntryCommand() {
679
- return new Command7("create-remotion-entry").description("Create a remotion-entry.tsx file for server-side rendering").option("-o, --output <path>", "Output path for remotion-entry.tsx", "remotion-entry.tsx").option("-f, --frames-dir <path>", "Path to frames directory", "frames").option("-a, --animate-core-path <path>", "Path to animate-core (e.g., ./core/animate-core or @aspects-ai/noodle-animate-core)", "./core/animate-core").action(async (options) => {
704
+ import chalk7 from "chalk";
705
+ function createUpdateCommand() {
706
+ return new Command7("update").description("Update an installed library to the latest version").argument("<library>", "Name of the library to update").action(async (libraryName) => {
680
707
  try {
681
708
  const workspaceRoot = await ensureWorkspaceRoot();
682
- const framesDir = path7.resolve(workspaceRoot, options.framesDir || "frames");
683
- const outputPath = path7.resolve(workspaceRoot, options.output || "remotion-entry.tsx");
684
- const animateCorePath = options.animateCorePath || "./core/animate-core";
685
- logger.info(`Scanning frames in: ${chalk7.cyan(path7.relative(workspaceRoot, framesDir))}`);
686
- logger.info(`Using animate-core from: ${chalk7.cyan(animateCorePath)}`);
687
- await createRemotionEntry(framesDir, outputPath, animateCorePath);
709
+ await getOrCreateConfig(workspaceRoot);
710
+ if (!await isLibraryInstalled(workspaceRoot, libraryName)) {
711
+ throw new Error(
712
+ `Library '${libraryName}' is not installed.
713
+ Use 'workspace-cli add ${libraryName}' to install it first.`
714
+ );
715
+ }
716
+ const config = await loadWorkspaceConfig(workspaceRoot);
717
+ const currentVersion = config?.libraries[libraryName]?.version || "unknown";
718
+ const currentCommit = config?.libraries[libraryName]?.commit || "unknown";
719
+ logger.info(`Current version: ${currentVersion} (commit: ${currentCommit.substring(0, 7)})`);
720
+ logger.info(`Updating ${libraryName}...`);
721
+ await installLibrary(workspaceRoot, libraryName, { force: true });
722
+ logger.log("\n" + chalk7.bold("Next steps:"));
723
+ logger.log(` ${chalk7.gray("Review changes:")} git diff ./core/${libraryName.replace("animate-", "")}`);
724
+ logger.log(` ${chalk7.gray("Install updated dependencies:")} npm install`);
725
+ logger.log("");
688
726
  } catch (error) {
689
727
  logger.error(error.message);
690
728
  process.exit(1);
@@ -692,44 +730,6 @@ function createCreateRemotionEntryCommand() {
692
730
  });
693
731
  }
694
732
 
695
- // package.json
696
- var package_default = {
697
- name: "@aspects-ai/workspace-cli",
698
- version: "0.1.12",
699
- private: false,
700
- description: "Lightweight CLI for installing libraries into workspaces",
701
- type: "module",
702
- bin: {
703
- "workspace-cli": "./dist/index.js"
704
- },
705
- scripts: {
706
- build: "tsup src/index.ts --format esm --dts --clean && npm run copy:registry",
707
- "copy:registry": "mkdir -p dist/registry && cp src/registry/libraries.json dist/registry/",
708
- dev: "tsup src/index.ts --format esm --watch",
709
- typecheck: "tsc --noEmit"
710
- },
711
- files: [
712
- "dist",
713
- "src/registry"
714
- ],
715
- dependencies: {
716
- chalk: "^5.3.0",
717
- commander: "^12.0.0",
718
- execa: "^8.0.0",
719
- "fs-extra": "^11.2.0",
720
- zod: "^3.22.0"
721
- },
722
- devDependencies: {
723
- "@types/fs-extra": "^11.0.0",
724
- "@types/node": "^20",
725
- tsup: "^8.0.0",
726
- typescript: "^5"
727
- },
728
- engines: {
729
- node: ">=18.0.0"
730
- }
731
- };
732
-
733
733
  // src/index.ts
734
734
  var program = new Command8();
735
735
  program.name("workspace-cli").description("Lightweight CLI for installing libraries into workspaces").version(package_default.version);
@@ -737,7 +737,7 @@ program.addCommand(createInitCommand());
737
737
  program.addCommand(createListCommand());
738
738
  program.addCommand(createAddCommand());
739
739
  program.addCommand(createUpdateCommand());
740
- program.addCommand(createCreateFrameCommand());
740
+ program.addCommand(createCreateCompositionCommand());
741
741
  program.addCommand(createUpdateTemplateCommand());
742
742
  program.addCommand(createCreateRemotionEntryCommand());
743
743
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aspects-ai/workspace-cli",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "private": false,
5
5
  "description": "Lightweight CLI for installing libraries into workspaces",
6
6
  "type": "module",