@embeddables/cli 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,146 +1,68 @@
1
- # Embeddables CLI
1
+ # Embeddables CLI (development)
2
2
 
3
- A CLI for authoring and managing Embeddables locally using TypeScript/TSX.
3
+ This repo is the source for **@embeddables/cli** — the CLI for authoring and managing Embeddables locally with TypeScript/TSX. If you only want to _use_ the CLI, see the [package on npm](https://www.npmjs.com/package/@embeddables/cli) or the [user-facing README](./package-readme.md) (same content as the npm package page).
4
4
 
5
- ## Features
5
+ ## Prerequisites
6
6
 
7
- - **Pull** existing Embeddables from the cloud and reverse-compile to TSX
8
- - **Build** TSX pages into the canonical Embeddable JSON format
9
- - **Dev** mode with hot reload and proxy to a live Engine instance
10
- - Full **TypeScript support** with autocomplete for all component primitives
7
+ - Node.js >= 18.0.0
11
8
 
12
- ## Installation
13
-
14
- Install the CLI globally:
15
-
16
- ```bash
17
- npm install -g @embeddables/cli
18
- ```
19
-
20
- ## Quick Start
9
+ ## Setup
21
10
 
22
11
  ```bash
23
- # Initialize a new project (will ask for your Embeddables project ID)
24
- embeddables init
25
-
26
- # Install dependencies
12
+ git clone <this-repo>
13
+ cd embeddables-cli
27
14
  npm install
28
-
29
- # Login to your Embeddables account
30
- embeddables login
31
-
32
- # Pull an embeddable (shows a list to choose from)
33
- embeddables pull
34
-
35
- # Start dev server with hot reload
36
- embeddables dev
37
15
  ```
38
16
 
39
- This creates the following structure in your repo:
40
-
41
- ```
42
- embeddables/
43
- <embeddable-id>/
44
- pages/ # TSX page files
45
- styles/ # CSS styles
46
- computed-fields/ # Custom computed field logic
47
- actions/ # Data output actions
48
- config.json # Embeddable configuration
49
- .generated/ # Compiled JSON output
50
- ```
51
-
52
- ## Commands
53
-
54
- ### `embeddables init`
55
- Initialize a new Embeddables project. Creates `package.json`, `embeddables.json`, `.gitignore`, and an `embeddables/` directory.
56
-
57
- If you're logged in, you'll see an interactive list of your projects to choose from. Otherwise, you can enter a project ID manually or skip.
58
-
59
- Options:
60
- - `--name <name>`: Project name (prompts if not provided)
61
- - `--project-id <id>`: Embeddables project ID (shows list if logged in)
62
- - `-y, --yes`: Skip prompts and use defaults
63
-
64
- The project ID is stored in `embeddables.json` and enables interactive embeddable selection when running `embeddables pull`.
65
-
66
- ### `embeddables login`
67
- Authenticate with your Embeddables account.
17
+ ## Scripts
68
18
 
69
- ### `embeddables logout`
70
- Clear stored authentication.
19
+ | Script | Description |
20
+ | ----------------------- | ----------------------------- |
21
+ | `npm run build` | Compile TypeScript to `dist/` |
22
+ | `npm run build:watch` | Watch and recompile on change |
23
+ | `npm test` | Run tests (Vitest) |
24
+ | `npm run test:watch` | Run tests in watch mode |
25
+ | `npm run test:coverage` | Coverage report |
26
+ | `npm run lint` | ESLint |
27
+ | `npm run format` | Prettier (write) |
28
+ | `npm run format:check` | Prettier (check only) |
71
29
 
72
- ### `embeddables pull`
73
- Fetch an embeddable from the cloud and reverse-compile it into local TSX files.
30
+ ## Local testing
74
31
 
75
- If you're logged in and run `embeddables pull` without arguments:
76
- 1. If no project is configured, you'll be prompted to select one (saved to `embeddables.json`)
77
- 2. Then you'll see an interactive list of embeddables to choose from
32
+ After building, link the CLI so you can run `embeddables` from another project:
78
33
 
79
- Options:
80
- - `--id <id>`: Embeddable ID to pull (skips interactive selection)
81
- - `--branch <branch_id>`: Pull a specific branch version
82
- - `--fix`: Remove components with missing required props instead of erroring
83
-
84
- ### `embeddables branch`
85
- Switch to a different branch of a local embeddable. Shows an interactive list of branches to choose from.
86
-
87
- Options:
88
- - `--id <id>`: Embeddable ID (will prompt from local embeddables if not provided)
89
-
90
- ### `embeddables build --id <id>`
91
- Compile TSX pages into the canonical JSON format.
92
-
93
- Options:
94
- - `--id <id>` (required): Embeddable ID
95
- - `--pages <glob>`: Custom pages glob pattern
96
- - `--out <path>`: Custom output path
97
-
98
- ### `embeddables dev`
99
- Start a dev server with hot reload.
100
-
101
- Options:
102
- - `--id <id>`: Embeddable ID (will prompt if not provided)
103
- - `--engine <url>`: Engine origin (default: `http://localhost:8787`)
104
- - `--remote`: Use production engine (`https://engine.embeddables.com`)
105
- - `--port <n>`: Dev proxy port (default: `3000`)
106
-
107
- ## Authoring with TypeScript
34
+ ```bash
35
+ npm run build
36
+ npm link
37
+ ```
108
38
 
109
- Import component primitives for full autocomplete:
39
+ Then in a project that uses the CLI (e.g. an embeddables app), run `embeddables <command>` — it will use your linked build. Unlink with `npm unlink -g @embeddables/cli` when done.
110
40
 
111
- ```tsx
112
- import { Container, InputBox, PlainText, CustomButton } from '@embeddables/cli/components'
41
+ You can also run the CLI without installing, from this repo:
113
42
 
114
- export default function MyPage() {
115
- return (
116
- <Container id="main" key="main">
117
- <PlainText id="title" key="title" text="Welcome!" />
118
- <InputBox
119
- id="email"
120
- key="email"
121
- input_type="email"
122
- label="Email"
123
- placeholder="you@example.com"
124
- isRequired
125
- />
126
- <CustomButton id="submit" key="submit" text="Continue" action="next-page" />
127
- </Container>
128
- )
129
- }
43
+ ```bash
44
+ npx tsx src/cli.ts dev
45
+ # or
46
+ npx tsx src/cli.ts build -i <embeddable-id>
130
47
  ```
131
48
 
132
- ## Development (Contributing)
49
+ ## Project structure
133
50
 
134
- ```bash
135
- # Install dependencies
136
- npm install
51
+ - **`src/cli.ts`** — Commander setup and command registration
52
+ - **`src/commands/`** — Implementations for `init`, `login`, `logout`, `pull`, `branch`, `build`, `save`, `dev`, `build-workbench`
53
+ - **`src/compiler/`** — TSX → JSON compile and JSON → TSX reverse-compile
54
+ - **`src/config/`** — `embeddables.json` and project config
55
+ - **`src/auth/`** — Login/logout and token storage
56
+ - **`src/prompts/`** — Interactive prompts (projects, embeddables, branches)
57
+ - **`src/proxy/`** — Dev server and engine proxy
58
+ - **`src/components/`** — React primitives exported as `@embeddables/cli/components`
59
+ - **`bin/embeddables.mjs`** — Entry script that runs `dist/cli.js`
60
+ - **`tests/`** — Unit and integration tests (Vitest)
137
61
 
138
- # Build the CLI
139
- npm run build
62
+ ## Publishing
140
63
 
141
- # Link for local testing
142
- npm link
64
+ The user-facing README is in **`package-readme.md`**. It is swapped in as `README.md` during `npm pack` / `npm publish` via `prepack`/`postpack` scripts so the npm package shows the right docs. Do not remove those scripts.
143
65
 
144
- # Run tests
145
- npm test
146
- ```
66
+ ## User documentation
67
+
68
+ Full command reference, installation, and usage for people who install the package: **[package-readme.md](./package-readme.md)**.
package/dist/cli.js CHANGED
@@ -23,27 +23,27 @@ program.name('embeddables').description('Embeddables CLI').version('0.1.0');
23
23
  program
24
24
  .command('init')
25
25
  .description('Initialize a new Embeddables project')
26
- .option('--project-id <id>', 'Embeddables project ID')
26
+ .option('-p, --project-id <id>', 'Embeddables project ID')
27
27
  .option('-y, --yes', 'Skip prompts and use defaults')
28
28
  .action(async (opts) => {
29
29
  await runInit({ projectId: opts.projectId, yes: opts.yes });
30
30
  });
31
31
  program
32
32
  .command('build')
33
- .requiredOption('--id <id>', 'Embeddable ID')
34
- .option('--pages <glob>', 'Pages glob')
35
- .option('--out <path>', 'Output json path')
33
+ .requiredOption('-i, --id <id>', 'Embeddable ID')
34
+ .option('-p, --pages <glob>', 'Pages glob')
35
+ .option('-o, --out <path>', 'Output json path')
36
36
  .option('--pageKeyFrom <mode>', 'filename|export', 'filename')
37
37
  .action(async (opts) => {
38
38
  await runBuild(opts);
39
39
  });
40
40
  program
41
41
  .command('dev')
42
- .option('--id <id>', 'Embeddable ID (will prompt if not provided)')
43
- .option('--pages <glob>', 'Pages glob')
44
- .option('--out <path>', 'Output json path')
45
- .option('--local', 'Use local engine (http://localhost:8787)')
46
- .option('--engine <url>', 'Engine origin', 'https://engine.embeddables.com')
42
+ .option('-i, --id <id>', 'Embeddable ID (will prompt if not provided)')
43
+ .option('-p, --pages <glob>', 'Pages glob')
44
+ .option('-o, --out <path>', 'Output json path')
45
+ .option('-L, --local', 'Use local engine (http://localhost:8787)')
46
+ .option('-e, --engine <url>', 'Engine origin', 'https://engine.embeddables.com')
47
47
  .option('--port <n>', 'Dev proxy port', '3000')
48
48
  .option('--overrideRoute <path>', 'Route to override in proxy (exact match, no wildcards yet)', '/init')
49
49
  .option('--pageKeyFrom <mode>', 'filename|export', 'filename')
@@ -69,20 +69,20 @@ program
69
69
  program
70
70
  .command('pull')
71
71
  .description('Pull an embeddable from the cloud')
72
- .option('--id <id>', 'Embeddable ID to pull (interactive selection if not provided)')
73
- .option('--out <path>', 'Output json path')
74
- .option('--branch <branch_id>', 'Embeddable branch ID')
75
- .option('--fix', 'Fix by removing components missing required props (warn instead of error)')
72
+ .option('-i, --id <id>', 'Embeddable ID to pull (interactive selection if not provided)')
73
+ .option('-o, --out <path>', 'Output json path')
74
+ .option('-b, --branch <branch_id>', 'Embeddable branch ID')
75
+ .option('-f, --fix', 'Fix by removing components missing required props (warn instead of error)')
76
76
  .action(async (opts) => {
77
77
  await runPull(opts);
78
78
  });
79
79
  program
80
80
  .command('save')
81
81
  .description('Build and save an embeddable to the cloud')
82
- .option('--id <id>', 'Embeddable ID (will prompt if not provided)')
83
- .option('--label <label>', 'Human-readable label for this version')
84
- .option('--branch <branch_id>', 'Branch ID to save to')
85
- .option('--skip-build', 'Skip the build step and use existing compiled JSON')
82
+ .option('-i, --id <id>', 'Embeddable ID (will prompt if not provided)')
83
+ .option('-l, --label <label>', 'Human-readable label for this version')
84
+ .option('-b, --branch <branch_id>', 'Branch ID to save to')
85
+ .option('-s, --skip-build', 'Skip the build step and use existing compiled JSON')
86
86
  .option('--from-version <number>', 'Base version number (auto-detected from local files if not provided)')
87
87
  .action(async (opts) => {
88
88
  await runSave({
@@ -96,14 +96,14 @@ program
96
96
  program
97
97
  .command('branch')
98
98
  .description('Switch to a different branch of an embeddable')
99
- .option('--id <id>', 'Embeddable ID (will prompt if not provided)')
99
+ .option('-i, --id <id>', 'Embeddable ID (will prompt if not provided)')
100
100
  .action(async (opts) => {
101
101
  await runBranch(opts);
102
102
  });
103
103
  program
104
104
  .command('build-workbench')
105
105
  .description('Build Workbench for CDN deployment')
106
- .option('--out <path>', 'Output directory', 'dist/workbench')
106
+ .option('-o, --out <path>', 'Output directory', 'dist/workbench')
107
107
  .option('--no-minify', 'Disable minification (for debugging)')
108
108
  .action(async (opts) => {
109
109
  await runBuildWorkbench(opts);
@@ -1 +1 @@
1
- {"version":3,"file":"save.d.ts","sourceRoot":"","sources":["../../src/commands/save.ts"],"names":[],"mappings":"AA+FA,wBAAsB,OAAO,CAAC,IAAI,EAAE;IAClC,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,iBAoPA"}
1
+ {"version":3,"file":"save.d.ts","sourceRoot":"","sources":["../../src/commands/save.ts"],"names":[],"mappings":"AAoJA,wBAAsB,OAAO,CAAC,IAAI,EAAE;IAClC,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,iBAoBA"}
@@ -8,6 +8,48 @@ import { compileAllPages } from '../compiler/index.js';
8
8
  import { formatError } from '../compiler/errors.js';
9
9
  import { promptForLocalEmbeddable, promptForProject } from '../prompts/index.js';
10
10
  import { WEB_APP_BASE_URL } from '../constants.js';
11
+ /** Error with optional gray detail line (hint/next step) for the user. */
12
+ class SaveError extends Error {
13
+ detail;
14
+ constructor(message, detail) {
15
+ super(message);
16
+ this.detail = detail;
17
+ this.name = 'SaveError';
18
+ }
19
+ }
20
+ /**
21
+ * Parse response body as JSON. Returns null if body is not valid JSON (e.g. HTML error page).
22
+ */
23
+ async function safeParseJson(response) {
24
+ try {
25
+ const data = await response.json();
26
+ return data;
27
+ }
28
+ catch {
29
+ return null;
30
+ }
31
+ }
32
+ function isSaveErrorResponse(value) {
33
+ return (typeof value === 'object' &&
34
+ value !== null &&
35
+ 'error' in value &&
36
+ typeof value.error === 'string');
37
+ }
38
+ function isSaveConflictResponse(value) {
39
+ return (typeof value === 'object' &&
40
+ value !== null &&
41
+ 'latestVersionNumber' in value &&
42
+ 'yourVersionNumber' in value &&
43
+ typeof value.latestVersionNumber === 'number' &&
44
+ typeof value.yourVersionNumber === 'number');
45
+ }
46
+ function isSaveResponse(value) {
47
+ return (typeof value === 'object' &&
48
+ value !== null &&
49
+ value.success === true &&
50
+ typeof value.data === 'object' &&
51
+ typeof value.data?.newVersionNumber === 'number');
52
+ }
11
53
  /**
12
54
  * Read `_version` from config.json for the given embeddable.
13
55
  */
@@ -67,6 +109,30 @@ function getLatestVersionFromFiles(generatedDir) {
67
109
  return maxVersion;
68
110
  }
69
111
  export async function runSave(opts) {
112
+ try {
113
+ await runSaveInner(opts);
114
+ }
115
+ catch (error) {
116
+ if (error instanceof SaveError) {
117
+ console.error(pc.red(`Save failed: ${error.message}`));
118
+ if (error.detail) {
119
+ console.log(pc.gray(error.detail));
120
+ }
121
+ }
122
+ else if (error instanceof Error) {
123
+ console.error(pc.red(`Save failed: ${error.message}`));
124
+ }
125
+ else {
126
+ console.error(pc.red('Save failed with an unexpected error.'));
127
+ }
128
+ process.exit(1);
129
+ // Rethrow only when exit was mocked to throw (e.g. in tests expecting rejection)
130
+ if (error instanceof Error && error.message === 'exit') {
131
+ throw error;
132
+ }
133
+ }
134
+ }
135
+ async function runSaveInner(opts) {
70
136
  // 1. Check login
71
137
  if (!isLoggedIn()) {
72
138
  console.error(pc.red('Not logged in.'));
@@ -195,75 +261,106 @@ export async function runSave(opts) {
195
261
  Authorization: `Bearer ${accessToken}`,
196
262
  'Content-Type': 'application/json',
197
263
  };
264
+ let response;
198
265
  try {
199
- const response = await fetch(url, {
266
+ response = await fetch(url, {
200
267
  method: 'POST',
201
268
  headers,
202
269
  body: JSON.stringify(body),
203
270
  });
204
- if (response.status === 409) {
205
- const conflictResult = (await response.json());
206
- console.log('');
207
- console.warn(pc.yellow(`⚠ Version conflict: the server has version ${conflictResult.latestVersionNumber}, but you are saving from version ${conflictResult.yourVersionNumber}.`));
208
- const { forceSave } = await prompts({
209
- type: 'confirm',
210
- name: 'forceSave',
211
- message: 'A newer version exists on the server. Save anyway?',
212
- initial: false,
213
- }, {
214
- onCancel: () => {
215
- process.exit(1);
216
- },
217
- });
218
- if (!forceSave) {
219
- console.log(pc.gray('Save cancelled.'));
220
- process.exit(0);
221
- }
222
- // Retry with force flag
223
- console.log(pc.cyan('Retrying save with force...'));
224
- const forceResponse = await fetch(url, {
271
+ }
272
+ catch (networkError) {
273
+ const message = networkError instanceof Error ? networkError.message : 'Network request failed';
274
+ if (message.includes('fetch') ||
275
+ message.includes('ECONNREFUSED') ||
276
+ message.includes('ETIMEDOUT') ||
277
+ message.includes('ENOTFOUND') ||
278
+ message.includes('network')) {
279
+ throw new SaveError(`Could not reach the server (${message}).`, `Check your connection and that the base URL is correct (currently ${WEB_APP_BASE_URL}).`);
280
+ }
281
+ throw new SaveError(`Network error: ${message}`, 'Check your network and firewall settings.');
282
+ }
283
+ if (response.status === 404) {
284
+ throw new SaveError('Save endpoint not found. The server may not support this feature or the URL may be incorrect.', `The request was sent to ${WEB_APP_BASE_URL}. If you use a custom deployment, ensure the save-version API is available there.`);
285
+ }
286
+ if (response.status === 401 || response.status === 403) {
287
+ throw new SaveError('Not authorized.', 'Run "embeddables login" to re-authenticate.');
288
+ }
289
+ if (response.status >= 500) {
290
+ throw new SaveError(`Server error (HTTP ${response.status}). Please try again later.`, 'If this keeps happening, try again later or contact support.');
291
+ }
292
+ if (response.status === 409) {
293
+ const conflictResult = await safeParseJson(response);
294
+ if (!conflictResult || !isSaveConflictResponse(conflictResult)) {
295
+ throw new SaveError(`Version conflict but invalid response (HTTP ${response.status}).`, 'Try saving again; if it persists, the server may be misconfigured.');
296
+ }
297
+ console.log('');
298
+ console.warn(pc.yellow(`⚠ Version conflict: the server has version ${conflictResult.latestVersionNumber}, but you are saving from version ${conflictResult.yourVersionNumber}.`));
299
+ const { forceSave } = await prompts({
300
+ type: 'confirm',
301
+ name: 'forceSave',
302
+ message: 'A newer version exists on the server. Save anyway?',
303
+ initial: false,
304
+ }, {
305
+ onCancel: () => {
306
+ process.exit(1);
307
+ },
308
+ });
309
+ if (!forceSave) {
310
+ console.log(pc.gray('Save cancelled.'));
311
+ process.exit(0);
312
+ }
313
+ // Retry with force flag
314
+ console.log(pc.cyan('Retrying save with force...'));
315
+ let forceResponse;
316
+ try {
317
+ forceResponse = await fetch(url, {
225
318
  method: 'POST',
226
319
  headers,
227
320
  body: JSON.stringify({ ...body, force: true }),
228
321
  });
229
- const forceResult = (await forceResponse.json());
230
- if (!forceResponse.ok) {
231
- const errorResult = forceResult;
232
- throw new Error(errorResult.error || `HTTP ${forceResponse.status}`);
233
- }
234
- const successResult = forceResult;
235
- const { newVersionNumber } = successResult.data;
236
- console.log(pc.green(`✓ Saved as version ${newVersionNumber}`));
237
- setVersionInConfig(embeddableId, newVersionNumber);
238
- const versionedPath = path.join(generatedDir, `embeddable-v${newVersionNumber}.json`);
239
- fs.mkdirSync(generatedDir, { recursive: true });
240
- fs.writeFileSync(versionedPath, jsonContent, 'utf8');
241
- console.log(pc.cyan(`✓ Saved version file to ${versionedPath}`));
242
- return;
243
322
  }
244
- const result = (await response.json());
245
- if (!response.ok) {
246
- const errorResult = result;
247
- throw new Error(errorResult.error || `HTTP ${response.status}`);
323
+ catch (forceNetworkError) {
324
+ const message = forceNetworkError instanceof Error ? forceNetworkError.message : 'Network request failed';
325
+ throw new SaveError(`Retry failed: ${message}`, 'The initial save hit a version conflict; the force-save retry could not reach the server.');
326
+ }
327
+ if (forceResponse.status === 404) {
328
+ throw new SaveError('Save endpoint not found. The server may not support this feature or the URL may be incorrect.', `The request was sent to ${WEB_APP_BASE_URL}. If you use a custom deployment, ensure the save-version API is available there.`);
329
+ }
330
+ const forceResult = await safeParseJson(forceResponse);
331
+ if (!forceResponse.ok) {
332
+ const errorMessage = forceResult && isSaveErrorResponse(forceResult)
333
+ ? forceResult.error
334
+ : `HTTP ${forceResponse.status}`;
335
+ throw new SaveError(errorMessage, 'If the problem persists, run "embeddables login" or try again later.');
336
+ }
337
+ if (!forceResult || !isSaveResponse(forceResult)) {
338
+ throw new SaveError(`Invalid response from server (HTTP ${forceResponse.status}).`, 'The server returned an unexpected format. Try again or contact support if it persists.');
248
339
  }
249
- const successResult = result;
250
- const { newVersionNumber } = successResult.data;
340
+ const { newVersionNumber } = forceResult.data;
251
341
  console.log(pc.green(`✓ Saved as version ${newVersionNumber}`));
252
- // Update _version in config.json so future saves know the base version
253
342
  setVersionInConfig(embeddableId, newVersionNumber);
254
- // Also save the versioned file to .generated/ as a snapshot
255
343
  const versionedPath = path.join(generatedDir, `embeddable-v${newVersionNumber}.json`);
256
344
  fs.mkdirSync(generatedDir, { recursive: true });
257
345
  fs.writeFileSync(versionedPath, jsonContent, 'utf8');
258
346
  console.log(pc.cyan(`✓ Saved version file to ${versionedPath}`));
347
+ return;
259
348
  }
260
- catch (error) {
261
- if (error instanceof Error) {
262
- console.error(pc.red(`Save failed: ${error.message}`));
263
- }
264
- else {
265
- console.error(pc.red('Save failed with an unexpected error.'));
266
- }
267
- process.exit(1);
349
+ const result = await safeParseJson(response);
350
+ if (!response.ok) {
351
+ const errorMessage = result && isSaveErrorResponse(result) ? result.error : `HTTP ${response.status}`;
352
+ throw new SaveError(errorMessage, 'If the problem persists, run "embeddables login" or try again later.');
353
+ }
354
+ if (!result || !isSaveResponse(result)) {
355
+ throw new SaveError(`Invalid response from server (HTTP ${response.status}).`, 'The server returned an unexpected format. Try again or contact support if it persists.');
268
356
  }
357
+ const { newVersionNumber } = result.data;
358
+ console.log(pc.green(`✓ Saved as version ${newVersionNumber}`));
359
+ // Update _version in config.json so future saves know the base version
360
+ setVersionInConfig(embeddableId, newVersionNumber);
361
+ // Also save the versioned file to .generated/ as a snapshot
362
+ const versionedPath = path.join(generatedDir, `embeddable-v${newVersionNumber}.json`);
363
+ fs.mkdirSync(generatedDir, { recursive: true });
364
+ fs.writeFileSync(versionedPath, jsonContent, 'utf8');
365
+ console.log(pc.cyan(`✓ Saved version file to ${versionedPath}`));
269
366
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@embeddables/cli",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "embeddables": "./bin/embeddables.mjs"