@aspects-ai/workspace-cli 0.1.13 → 0.1.15

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 +344 -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.15",
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,16 +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`);
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`);
395
382
  logger.log("");
396
383
  } catch (error) {
397
384
  logger.error(error.message);
@@ -400,40 +387,9 @@ function createAddCommand() {
400
387
  });
401
388
  }
402
389
 
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`);
426
- logger.log("");
427
- } catch (error) {
428
- logger.error(error.message);
429
- process.exit(1);
430
- }
431
- });
432
- }
433
-
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,77 +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
- frameType: manifest.frameType || "animationComposition",
415
+ compositionType: manifest.compositionType || "animationComposition",
460
416
  width: manifest.width,
461
417
  height: manifest.height
462
418
  };
463
419
  if (toolName === "animate-core") {
464
- validatedManifest.frameType = "animationComposition";
420
+ validatedManifest.compositionType = "animationComposition";
465
421
  if (!validatedManifest.width) validatedManifest.width = 1920;
466
422
  if (!validatedManifest.height) validatedManifest.height = 1080;
467
423
  }
468
424
  return validatedManifest;
469
425
  }
470
- async function getNextFrameName(framesDir, customName) {
471
- let frameName;
426
+ async function getNextCompositionName(compositionsDir, customName) {
427
+ let compositionName;
472
428
  if (customName) {
473
429
  const truncatedName = customName.slice(0, 20);
474
430
  const uniqueId = generateId();
475
- frameName = `${truncatedName}-${uniqueId}`;
431
+ compositionName = `${truncatedName}-${uniqueId}`;
476
432
  } else {
477
- frameName = generateId();
433
+ compositionName = generateId();
478
434
  }
479
- while (await fs5.pathExists(path5.join(framesDir, frameName))) {
435
+ while (await fs5.pathExists(path5.join(compositionsDir, compositionName))) {
480
436
  const uniqueId = generateId();
481
- frameName = customName ? `${customName.slice(0, 20)}-${uniqueId}` : uniqueId;
437
+ compositionName = customName ? `${customName.slice(0, 20)}-${uniqueId}` : uniqueId;
482
438
  }
483
- return frameName;
439
+ return compositionName;
484
440
  }
485
- async function fetchExampleFrame(toolName, targetPath) {
441
+ async function fetchExampleComposition(toolName, targetPath) {
486
442
  const library = await getLibrary(toolName);
487
443
  const token = getToken();
488
- logger.info(`Fetching example frame from ${chalk5.cyan(library.repository.url)}`);
489
- 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");
490
446
  await fetchDirectory({
491
447
  repository: library.repository.url,
492
- directory: exampleFrameDirectory,
448
+ directory: exampleCompositionDirectory,
493
449
  branch: library.repository.branch,
494
450
  token,
495
451
  targetPath
496
452
  });
497
453
  }
498
- function createCreateFrameCommand() {
499
- 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) => {
500
456
  try {
501
457
  const workspaceRoot = await ensureWorkspaceRoot();
502
- const framesDir = path5.join(workspaceRoot, "frames");
503
- await fs5.ensureDir(framesDir);
504
- const customName = options.name && options.name !== "new-frame" ? options.name : void 0;
505
- const frameName = await getNextFrameName(framesDir, customName);
506
- const frameDir = path5.join(framesDir, frameName);
507
- logger.info(`Creating frame: ${chalk5.cyan(frameName)} for ${chalk5.cyan(toolName)}`);
508
- 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);
509
465
  const manifestData = parseManifestOption(manifestStr);
510
- const validatedManifest = validateAndFixManifest(manifestData, toolName, frameName);
511
- const manifestPath = path5.join(frameDir, "manifest.json");
466
+ const validatedManifest = validateAndFixManifest(manifestData, toolName, compositionName);
467
+ const manifestPath = path5.join(compositionDir, "manifest.json");
512
468
  await fs5.writeJson(manifestPath, validatedManifest, { spaces: 4 });
513
- logger.success(`Frame created at: ${chalk5.green(path5.relative(workspaceRoot, frameDir))}`);
469
+ logger.success(`Composition created at: ${chalk3.green(path5.relative(workspaceRoot, compositionDir))}`);
514
470
  logger.log("");
515
- logger.log(chalk5.bold("Manifest:"));
471
+ logger.log(chalk3.bold("Manifest:"));
516
472
  logger.log(JSON.stringify(validatedManifest, null, 2));
517
473
  logger.log("");
518
- logger.log(chalk5.bold("Next steps:"));
519
- 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"))}`);
520
476
  logger.log("");
521
477
  } catch (error) {
522
478
  logger.error(error.message);
@@ -525,10 +481,170 @@ function createCreateFrameCommand() {
525
481
  });
526
482
  }
527
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 stylesheetLoader = `// Load stylesheet - try compiled.css first, fall back to Tailwind CDN
524
+ (async () => {
525
+ try {
526
+ await import('./styles/compiled.css');
527
+ } catch (error) {
528
+ // If compiled.css doesn't exist, inject Tailwind CDN as fallback
529
+ console.error("Failed to load compiled styles");
530
+ if (typeof document !== 'undefined') {
531
+ const script = document.createElement('script');
532
+ script.src = 'https://cdn.tailwindcss.com';
533
+ document.head.appendChild(script);
534
+ }
535
+ }
536
+ })();`;
537
+ const content = `/**
538
+ * Auto-generated Remotion SSR Entry Point
539
+ *
540
+ * Generated by: @aspects-ai/workspace-cli create-remotion-entry
541
+ * DO NOT EDIT THIS FILE MANUALLY - it will be overwritten
542
+ *
543
+ * This file serves as the entry point for server-side rendering with Remotion.
544
+ * It automatically registers all compositions in the ./compositions directory.
545
+ *
546
+ * Usage:
547
+ * ANIMATION_SSR_MODE=true npx remotion render remotion-entry.tsx <composition-id> output.mp4
548
+ *
549
+ * To regenerate this file:
550
+ * npx @aspects-ai/workspace-cli create-remotion-entry
551
+ */
552
+
553
+ ${stylesheetLoader}
554
+
555
+ import { registerRoot } from 'remotion';
556
+ import { createRemotionRoot } from '${animateCorePath}/remotion';
557
+
558
+ // Composition imports
559
+ ${imports.map((i) => i.statement).join("\n")}
560
+
561
+ // Register all compositions with Remotion
562
+ const Root = createRemotionRoot({
563
+ ${compositionsObject}
564
+ });
565
+
566
+ registerRoot(Root);
567
+ `;
568
+ await fs6.writeFile(outputPath, content, "utf-8");
569
+ logger.success(`Created remotion entry file: ${chalk4.green(outputPath)}`);
570
+ logger.log("");
571
+ logger.log(chalk4.bold(`Registered ${validCompositions.length} composition(s):`));
572
+ validCompositions.forEach((composition) => {
573
+ logger.log(` ${chalk4.cyan("\u2022")} ${composition}`);
574
+ });
575
+ logger.log("");
576
+ logger.log(chalk4.bold("Next steps:"));
577
+ logger.log(` ${chalk4.gray("Render a composition:")} ANIMATION_SSR_MODE=true npx remotion render remotion-entry.tsx <composition-id> output.mp4`);
578
+ logger.log("");
579
+ }
580
+ function createCreateRemotionEntryCommand() {
581
+ 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) => {
582
+ try {
583
+ const workspaceRoot = await ensureWorkspaceRoot();
584
+ const compositionsDir = path6.resolve(workspaceRoot, options.compositionsDir || "compositions");
585
+ const outputPath = path6.resolve(workspaceRoot, options.output || "remotion-entry.tsx");
586
+ const animateCorePath = options.animateCorePath || "./core/animate-core";
587
+ logger.info(`Scanning compositions in: ${chalk4.cyan(path6.relative(workspaceRoot, compositionsDir))}`);
588
+ logger.info(`Using animate-core from: ${chalk4.cyan(animateCorePath)}`);
589
+ await createRemotionEntry(compositionsDir, outputPath, animateCorePath);
590
+ } catch (error) {
591
+ logger.error(error.message);
592
+ process.exit(1);
593
+ }
594
+ });
595
+ }
596
+
597
+ // src/commands/init.ts
598
+ import { Command as Command4 } from "commander";
599
+ function createInitCommand() {
600
+ return new Command4("init").description("Initialize workspace configuration").action(async () => {
601
+ try {
602
+ const workspaceRoot = await ensureWorkspaceRoot();
603
+ const existing = await loadWorkspaceConfig(workspaceRoot);
604
+ if (existing) {
605
+ logger.warn("workspace.config.json already exists");
606
+ logger.info(`Config file: ${getConfigPath(workspaceRoot)}`);
607
+ return;
608
+ }
609
+ await createDefaultConfig(workspaceRoot);
610
+ logger.success("Created workspace.config.json");
611
+ logger.log("\nNext steps:");
612
+ logger.log(" workspace-cli list # View available libraries");
613
+ logger.log(" workspace-cli add <library> # Install a library");
614
+ } catch (error) {
615
+ logger.error(error.message);
616
+ process.exit(1);
617
+ }
618
+ });
619
+ }
620
+
621
+ // src/commands/list.ts
622
+ import { Command as Command5 } from "commander";
623
+ import chalk5 from "chalk";
624
+ function createListCommand() {
625
+ return new Command5("list").description("List available libraries").action(async () => {
626
+ try {
627
+ const libraries = await listLibraries();
628
+ logger.log(chalk5.bold("\nAvailable libraries:\n"));
629
+ for (const { name, library } of libraries) {
630
+ const versionStr = library.version ? ` @${library.version}` : " @latest";
631
+ logger.log(chalk5.cyan(` ${name}`) + chalk5.gray(versionStr));
632
+ logger.log(` ${library.description}`);
633
+ logger.log("");
634
+ }
635
+ logger.log(`Total: ${libraries.length} ${libraries.length === 1 ? "library" : "libraries"}
636
+ `);
637
+ } catch (error) {
638
+ logger.error(error.message);
639
+ process.exit(1);
640
+ }
641
+ });
642
+ }
643
+
528
644
  // src/commands/update-template.ts
529
645
  import { Command as Command6 } from "commander";
530
- import path6 from "path";
531
- import fs6 from "fs-extra";
646
+ import path7 from "path";
647
+ import fs7 from "fs-extra";
532
648
  import { execa as execa2 } from "execa";
533
649
  import os2 from "os";
534
650
  import chalk6 from "chalk";
@@ -539,9 +655,9 @@ function createUpdateTemplateCommand() {
539
655
  return new Command6("update-template").description("Update EDITING.md file from noodle-base template").action(async () => {
540
656
  try {
541
657
  const workspaceRoot = await ensureWorkspaceRoot();
542
- const targetFile = path6.join(workspaceRoot, "EDITING.md");
658
+ const targetFile = path7.join(workspaceRoot, "EDITING.md");
543
659
  const token = getToken();
544
- const targetExists = await fs6.pathExists(targetFile);
660
+ const targetExists = await fs7.pathExists(targetFile);
545
661
  if (targetExists) {
546
662
  logger.info("Existing EDITING.md found, it will be overwritten.");
547
663
  }
@@ -552,7 +668,7 @@ function createUpdateTemplateCommand() {
552
668
  branch: NOODLE_BASE_BRANCH,
553
669
  token
554
670
  });
555
- await fs6.writeFile(targetFile, content, "utf-8");
671
+ await fs7.writeFile(targetFile, content, "utf-8");
556
672
  logger.success(
557
673
  `Successfully ${targetExists ? "updated" : "copied"} EDITING.md to workspace`
558
674
  );
@@ -566,7 +682,7 @@ function createUpdateTemplateCommand() {
566
682
  });
567
683
  }
568
684
  async function fetchFileFromGit(options) {
569
- const tempDir = await fs6.mkdtemp(path6.join(os2.tmpdir(), "workspace-cli-"));
685
+ const tempDir = await fs7.mkdtemp(path7.join(os2.tmpdir(), "workspace-cli-"));
570
686
  try {
571
687
  const repoUrl = options.repository.replace(
572
688
  "https://github.com/",
@@ -575,18 +691,18 @@ async function fetchFileFromGit(options) {
575
691
  await execa2("git", ["init"], { cwd: tempDir });
576
692
  await execa2("git", ["remote", "add", "origin", repoUrl], { cwd: tempDir });
577
693
  await execa2("git", ["config", "core.sparseCheckout", "true"], { cwd: tempDir });
578
- const sparseFile = path6.join(tempDir, ".git", "info", "sparse-checkout");
579
- await fs6.writeFile(sparseFile, `${options.filePath}
694
+ const sparseFile = path7.join(tempDir, ".git", "info", "sparse-checkout");
695
+ await fs7.writeFile(sparseFile, `${options.filePath}
580
696
  `);
581
697
  await execa2("git", ["pull", "origin", options.branch, "--depth=1"], {
582
698
  cwd: tempDir,
583
699
  stderr: "pipe"
584
700
  });
585
- const sourceFile = path6.join(tempDir, options.filePath);
586
- if (!await fs6.pathExists(sourceFile)) {
701
+ const sourceFile = path7.join(tempDir, options.filePath);
702
+ if (!await fs7.pathExists(sourceFile)) {
587
703
  throw new Error(`File '${options.filePath}' not found in repository`);
588
704
  }
589
- return await fs6.readFile(sourceFile, "utf-8");
705
+ return await fs7.readFile(sourceFile, "utf-8");
590
706
  } catch (error) {
591
707
  if (error.stderr && error.stderr.includes("Authentication failed")) {
592
708
  throw new Error(
@@ -595,100 +711,34 @@ async function fetchFileFromGit(options) {
595
711
  }
596
712
  throw new Error(`Failed to fetch file from git: ${error.message}`);
597
713
  } finally {
598
- await fs6.remove(tempDir);
714
+ await fs7.remove(tempDir);
599
715
  }
600
716
  }
601
717
 
602
- // src/commands/create-remotion-entry.ts
603
- import chalk7 from "chalk";
718
+ // src/commands/update.ts
604
719
  import { Command as Command7 } from "commander";
605
- import fs7 from "fs-extra";
606
- import path7 from "path";
607
- async function createRemotionEntry(framesDir, outputPath, animateCorePath) {
608
- if (!await fs7.pathExists(framesDir)) {
609
- throw new Error(`Frames directory not found: ${framesDir}`);
610
- }
611
- const entries = await fs7.readdir(framesDir, { withFileTypes: true });
612
- const frameDirs = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
613
- if (frameDirs.length === 0) {
614
- throw new Error(`No frame directories found in ${framesDir}`);
615
- }
616
- const validFrames = [];
617
- for (const frameDir of frameDirs) {
618
- const mainPath = path7.join(framesDir, frameDir, "main.tsx");
619
- if (await fs7.pathExists(mainPath)) {
620
- validFrames.push(frameDir);
621
- } else {
622
- logger.warn(`Skipping ${frameDir}: main.tsx not found`);
623
- }
624
- }
625
- if (validFrames.length === 0) {
626
- throw new Error("No valid frames found (frames must contain main.tsx)");
627
- }
628
- const imports = validFrames.map((frameDir, index) => {
629
- const varName = `Frame${index}`;
630
- const defName = `frame${index}Def`;
631
- return {
632
- frameDir,
633
- varName,
634
- defName,
635
- statement: `import ${varName}, { compositionDefinition as ${defName} } from './frames/${frameDir}/main';`
636
- };
637
- });
638
- const framesObject = imports.map(({ frameDir, varName, defName }) => {
639
- return ` '${frameDir}': { component: ${varName}, compositionDefinition: ${defName} },`;
640
- }).join("\n");
641
- const content = `/**
642
- * Auto-generated Remotion SSR Entry Point
643
- *
644
- * Generated by: @aspects-ai/workspace-cli create-remotion-entry
645
- * DO NOT EDIT THIS FILE MANUALLY - it will be overwritten
646
- *
647
- * This file serves as the entry point for server-side rendering with Remotion.
648
- * It automatically registers all frames in the ./frames directory.
649
- *
650
- * Usage:
651
- * ANIMATION_SSR_MODE=true npx remotion render remotion-entry.tsx <frame-id> output.mp4
652
- *
653
- * To regenerate this file:
654
- * npx @aspects-ai/workspace-cli create-remotion-entry
655
- */
656
-
657
- import { registerRoot } from 'remotion';
658
- import { createRemotionRoot } from '${animateCorePath}/remotion';
659
-
660
- // Frame imports
661
- ${imports.map((i) => i.statement).join("\n")}
662
-
663
- // Register all frames with Remotion
664
- const Root = createRemotionRoot({
665
- ${framesObject}
666
- });
667
-
668
- registerRoot(Root);
669
- `;
670
- await fs7.writeFile(outputPath, content, "utf-8");
671
- logger.success(`Created remotion entry file: ${chalk7.green(outputPath)}`);
672
- logger.log("");
673
- logger.log(chalk7.bold(`Registered ${validFrames.length} frame(s):`));
674
- validFrames.forEach((frame) => {
675
- logger.log(` ${chalk7.cyan("\u2022")} ${frame}`);
676
- });
677
- logger.log("");
678
- logger.log(chalk7.bold("Next steps:"));
679
- logger.log(` ${chalk7.gray("Render a frame:")} ANIMATION_SSR_MODE=true npx remotion render remotion-entry.tsx <frame-id> output.mp4`);
680
- logger.log("");
681
- }
682
- function createCreateRemotionEntryCommand() {
683
- 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) => {
720
+ import chalk7 from "chalk";
721
+ function createUpdateCommand() {
722
+ return new Command7("update").description("Update an installed library to the latest version").argument("<library>", "Name of the library to update").action(async (libraryName) => {
684
723
  try {
685
724
  const workspaceRoot = await ensureWorkspaceRoot();
686
- const framesDir = path7.resolve(workspaceRoot, options.framesDir || "frames");
687
- const outputPath = path7.resolve(workspaceRoot, options.output || "remotion-entry.tsx");
688
- const animateCorePath = options.animateCorePath || "./core/animate-core";
689
- logger.info(`Scanning frames in: ${chalk7.cyan(path7.relative(workspaceRoot, framesDir))}`);
690
- logger.info(`Using animate-core from: ${chalk7.cyan(animateCorePath)}`);
691
- await createRemotionEntry(framesDir, outputPath, animateCorePath);
725
+ await getOrCreateConfig(workspaceRoot);
726
+ if (!await isLibraryInstalled(workspaceRoot, libraryName)) {
727
+ throw new Error(
728
+ `Library '${libraryName}' is not installed.
729
+ Use 'workspace-cli add ${libraryName}' to install it first.`
730
+ );
731
+ }
732
+ const config = await loadWorkspaceConfig(workspaceRoot);
733
+ const currentVersion = config?.libraries[libraryName]?.version || "unknown";
734
+ const currentCommit = config?.libraries[libraryName]?.commit || "unknown";
735
+ logger.info(`Current version: ${currentVersion} (commit: ${currentCommit.substring(0, 7)})`);
736
+ logger.info(`Updating ${libraryName}...`);
737
+ await installLibrary(workspaceRoot, libraryName, { force: true });
738
+ logger.log("\n" + chalk7.bold("Next steps:"));
739
+ logger.log(` ${chalk7.gray("Review changes:")} git diff ./core/${libraryName.replace("animate-", "")}`);
740
+ logger.log(` ${chalk7.gray("Install updated dependencies:")} npm install`);
741
+ logger.log("");
692
742
  } catch (error) {
693
743
  logger.error(error.message);
694
744
  process.exit(1);
@@ -696,44 +746,6 @@ function createCreateRemotionEntryCommand() {
696
746
  });
697
747
  }
698
748
 
699
- // package.json
700
- var package_default = {
701
- name: "@aspects-ai/workspace-cli",
702
- version: "0.1.13",
703
- private: false,
704
- description: "Lightweight CLI for installing libraries into workspaces",
705
- type: "module",
706
- bin: {
707
- "workspace-cli": "./dist/index.js"
708
- },
709
- scripts: {
710
- build: "tsup src/index.ts --format esm --dts --clean && npm run copy:registry",
711
- "copy:registry": "mkdir -p dist/registry && cp src/registry/libraries.json dist/registry/",
712
- dev: "tsup src/index.ts --format esm --watch",
713
- typecheck: "tsc --noEmit"
714
- },
715
- files: [
716
- "dist",
717
- "src/registry"
718
- ],
719
- dependencies: {
720
- chalk: "^5.3.0",
721
- commander: "^12.0.0",
722
- execa: "^8.0.0",
723
- "fs-extra": "^11.2.0",
724
- zod: "^3.22.0"
725
- },
726
- devDependencies: {
727
- "@types/fs-extra": "^11.0.0",
728
- "@types/node": "^20",
729
- tsup: "^8.0.0",
730
- typescript: "^5"
731
- },
732
- engines: {
733
- node: ">=18.0.0"
734
- }
735
- };
736
-
737
749
  // src/index.ts
738
750
  var program = new Command8();
739
751
  program.name("workspace-cli").description("Lightweight CLI for installing libraries into workspaces").version(package_default.version);
@@ -741,7 +753,7 @@ program.addCommand(createInitCommand());
741
753
  program.addCommand(createListCommand());
742
754
  program.addCommand(createAddCommand());
743
755
  program.addCommand(createUpdateCommand());
744
- program.addCommand(createCreateFrameCommand());
756
+ program.addCommand(createCreateCompositionCommand());
745
757
  program.addCommand(createUpdateTemplateCommand());
746
758
  program.addCommand(createCreateRemotionEntryCommand());
747
759
  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.13",
3
+ "version": "0.1.15",
4
4
  "private": false,
5
5
  "description": "Lightweight CLI for installing libraries into workspaces",
6
6
  "type": "module",