@embeddables/cli 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -8,6 +8,7 @@ import { runInit } from './commands/init.js';
8
8
  import { runLogin } from './commands/login.js';
9
9
  import { runLogout } from './commands/logout.js';
10
10
  import { runPull } from './commands/pull.js';
11
+ import { runSave } from './commands/save.js';
11
12
  // Make all console.warn output yellow
12
13
  const originalWarn = console.warn.bind(console);
13
14
  console.warn = (...args) => {
@@ -22,11 +23,10 @@ program.name('embeddables').description('Embeddables CLI').version('0.1.0');
22
23
  program
23
24
  .command('init')
24
25
  .description('Initialize a new Embeddables project')
25
- .option('--name <name>', 'Project name')
26
26
  .option('--project-id <id>', 'Embeddables project ID')
27
27
  .option('-y, --yes', 'Skip prompts and use defaults')
28
28
  .action(async (opts) => {
29
- await runInit({ name: opts.name, projectId: opts.projectId, yes: opts.yes });
29
+ await runInit({ projectId: opts.projectId, yes: opts.yes });
30
30
  });
31
31
  program
32
32
  .command('build')
@@ -42,15 +42,15 @@ program
42
42
  .option('--id <id>', 'Embeddable ID (will prompt if not provided)')
43
43
  .option('--pages <glob>', 'Pages glob')
44
44
  .option('--out <path>', 'Output json path')
45
- .option('--remote', 'Use remote engine (https://engine.embeddables.com)')
46
- .option('--engine <url>', 'Engine origin', 'http://localhost:8787')
45
+ .option('--local', 'Use local engine (http://localhost:8787)')
46
+ .option('--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')
50
50
  .action(async (opts) => {
51
- // --remote flag overrides --engine to use production engine
52
- if (opts.remote) {
53
- opts.engine = 'https://engine.embeddables.com';
51
+ // --local flag overrides --engine to use local engine
52
+ if (opts.local) {
53
+ opts.engine = 'http://localhost:8787';
54
54
  }
55
55
  await runDev(opts);
56
56
  });
@@ -76,6 +76,23 @@ program
76
76
  .action(async (opts) => {
77
77
  await runPull(opts);
78
78
  });
79
+ program
80
+ .command('save')
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')
86
+ .option('--from-version <number>', 'Base version number (auto-detected from local files if not provided)')
87
+ .action(async (opts) => {
88
+ await runSave({
89
+ id: opts.id,
90
+ label: opts.label,
91
+ branch: opts.branch,
92
+ skipBuild: opts.skipBuild,
93
+ fromVersion: opts.fromVersion,
94
+ });
95
+ });
79
96
  program
80
97
  .command('branch')
81
98
  .description('Switch to a different branch of an embeddable')
@@ -2,7 +2,7 @@ export declare function runDev(opts: {
2
2
  id?: string;
3
3
  pages?: string;
4
4
  out?: string;
5
- remote?: boolean;
5
+ local?: boolean;
6
6
  engine: string;
7
7
  port: string;
8
8
  overrideRoute: string;
@@ -1 +1 @@
1
- {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAoFA,wBAAsB,MAAM,CAAC,IAAI,EAAE;IACjC,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,UAAU,GAAG,QAAQ,CAAA;CACnC,iBAgGA"}
1
+ {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAmHA,wBAAsB,MAAM,CAAC,IAAI,EAAE;IACjC,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,UAAU,GAAG,QAAQ,CAAA;CACnC,iBA4GA"}
@@ -1,4 +1,5 @@
1
1
  import fs from 'node:fs';
2
+ import net from 'node:net';
2
3
  import path from 'node:path';
3
4
  import chokidar from 'chokidar';
4
5
  import pc from 'picocolors';
@@ -6,6 +7,32 @@ import prompts from 'prompts';
6
7
  import { compileAllPages } from '../compiler/index.js';
7
8
  import { startProxyServer } from '../proxy/server.js';
8
9
  import { formatError } from '../compiler/errors.js';
10
+ /**
11
+ * Check whether a port is available by attempting to listen on it.
12
+ */
13
+ function isPortAvailable(port) {
14
+ return new Promise((resolve) => {
15
+ const server = net.createServer();
16
+ server.once('error', () => resolve(false));
17
+ server.once('listening', () => {
18
+ server.close(() => resolve(true));
19
+ });
20
+ server.listen(port);
21
+ });
22
+ }
23
+ /**
24
+ * Starting from `startPort`, find the first available port.
25
+ * Tries up to `maxAttempts` consecutive ports.
26
+ */
27
+ async function getAvailablePort(startPort, maxAttempts = 20) {
28
+ for (let i = 0; i < maxAttempts; i++) {
29
+ const port = startPort + i;
30
+ if (await isPortAvailable(port)) {
31
+ return port;
32
+ }
33
+ }
34
+ throw new Error(`Could not find an available port (tried ${startPort}–${startPort + maxAttempts - 1})`);
35
+ }
9
36
  async function discoverEmbeddables() {
10
37
  const embeddablesDir = 'embeddables';
11
38
  if (!fs.existsSync(embeddablesDir)) {
@@ -96,8 +123,20 @@ export async function runDev(opts) {
96
123
  console.error(formatError(e));
97
124
  process.exit(1);
98
125
  }
99
- // Start proxy
100
- const port = Number(opts.port);
126
+ // Start proxy — find an available port if the requested one is taken
127
+ const requestedPort = Number(opts.port);
128
+ let port;
129
+ try {
130
+ port = await getAvailablePort(requestedPort);
131
+ }
132
+ catch (e) {
133
+ console.error(pc.red(e.message));
134
+ process.exit(1);
135
+ }
136
+ if (port !== requestedPort) {
137
+ console.warn(`Port ${requestedPort} is in use, using ${port} instead.`);
138
+ console.log('');
139
+ }
101
140
  const proxy = await startProxyServer({
102
141
  port,
103
142
  engineOrigin: opts.engine,
@@ -139,11 +178,11 @@ export async function runDev(opts) {
139
178
  const separator = '━'.repeat(terminalWidth);
140
179
  console.log(`${pc.bold(pc.cyan(separator))}`);
141
180
  console.log('');
142
- if (opts.remote) {
143
- console.log(`${pc.bold(pc.yellow('Mode:'))} ${pc.yellow('Remote')} (using ${opts.engine})`);
181
+ if (opts.local) {
182
+ console.log(`${pc.bold(pc.blue('Mode:'))} ${pc.blue('Local')} (using ${opts.engine})`);
144
183
  }
145
184
  else {
146
- console.log(`${pc.bold(pc.blue('Mode:'))} ${pc.blue('Local')} (using ${opts.engine})`);
185
+ console.log(`${pc.bold(pc.yellow('Mode:'))} ${pc.yellow('Remote')} (using ${opts.engine})`);
147
186
  }
148
187
  console.log('');
149
188
  console.log(`${pc.bold(pc.green('Preview URL:'))} ${pc.underline(pc.cyan(`http://localhost:${port}?id=${embeddableId}&version=latest`))}`);
@@ -1,5 +1,4 @@
1
1
  export declare function runInit(opts: {
2
- name?: string;
3
2
  projectId?: string;
4
3
  yes?: boolean;
5
4
  }): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAiBA,wBAAsB,OAAO,CAAC,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,iBAsMvF"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAuGA,wBAAsB,OAAO,CAAC,IAAI,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,iBA0KxE"}
@@ -1,37 +1,112 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
3
4
  import pc from 'picocolors';
4
5
  import prompts from 'prompts';
5
6
  import { writeProjectConfig, readProjectConfig } from '../config/index.js';
6
7
  import { isLoggedIn } from '../auth/index.js';
7
8
  import { promptForProject } from '../prompts/index.js';
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+ /** Recursively copy a directory, creating target dirs as needed. */
12
+ function copyDirSync(src, dest) {
13
+ fs.mkdirSync(dest, { recursive: true });
14
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
15
+ const srcPath = path.join(src, entry.name);
16
+ const destPath = path.join(dest, entry.name);
17
+ if (entry.isDirectory()) {
18
+ copyDirSync(srcPath, destPath);
19
+ }
20
+ else {
21
+ fs.copyFileSync(srcPath, destPath);
22
+ }
23
+ }
24
+ }
25
+ /**
26
+ * Generate type declaration stubs in embeddables/.types/ so the editor
27
+ * can resolve imports in generated TSX files without npm install.
28
+ */
29
+ function writeTypeStubs(embeddablesDir) {
30
+ const typesDir = path.join(embeddablesDir, '.types');
31
+ fs.mkdirSync(typesDir, { recursive: true });
32
+ // React JSX runtime types (needed for "jsx": "react-jsx" in tsconfig)
33
+ fs.writeFileSync(path.join(typesDir, 'react-jsx-runtime.d.ts'), `export namespace JSX {
34
+ type Element = any
35
+ interface IntrinsicElements {
36
+ [elemName: string]: any
37
+ }
38
+ }
39
+ export function jsx(type: any, props: any, key?: string): JSX.Element
40
+ export function jsxs(type: any, props: any, key?: string): JSX.Element
41
+ export const Fragment: unique symbol
42
+ `, 'utf8');
43
+ // Component primitives
44
+ const componentNames = [
45
+ 'BookMeeting',
46
+ 'Chart',
47
+ 'Container',
48
+ 'CustomButton',
49
+ 'CustomHTML',
50
+ 'FileUpload',
51
+ 'InputBox',
52
+ 'Lottie',
53
+ 'MediaEmbed',
54
+ 'MediaImage',
55
+ 'OptionSelector',
56
+ 'PaypalCheckout',
57
+ 'PlainText',
58
+ 'ProgressBar',
59
+ 'RichText',
60
+ 'RichTextMarkdown',
61
+ 'Rive',
62
+ 'StripeCheckout',
63
+ 'StripeCheckout2',
64
+ ];
65
+ const componentExports = componentNames
66
+ .map((name) => `export function ${name}(props: Record<string, any>): any`)
67
+ .join('\n');
68
+ fs.writeFileSync(path.join(typesDir, 'components.d.ts'), componentExports + '\n', 'utf8');
69
+ // Embeddables types (OptionSelectorButton, etc.)
70
+ fs.writeFileSync(path.join(typesDir, 'types.d.ts'), `export interface OptionSelectorButton {
71
+ id?: string
72
+ key: string
73
+ text?: string
74
+ description?: string
75
+ icon?: string
76
+ emojiIcon?: string
77
+ imageUrl?: string
78
+ imageAltText?: string
79
+ conditions?: Record<string, any>[]
80
+ triggerEvent?: 'no-action' | 'next-page' | 'open-url'
81
+ openUrlInNewTab?: boolean
82
+ url?: string
83
+ single_select?: boolean
84
+ hide?: boolean
85
+ is_repeatable_button?: boolean
86
+ [key: string]: any
87
+ }
88
+ `, 'utf8');
89
+ }
8
90
  export async function runInit(opts) {
9
91
  const cwd = process.cwd();
10
- const packageJsonPath = path.join(cwd, 'package.json');
11
92
  const gitignorePath = path.join(cwd, '.gitignore');
12
93
  const embeddablesDir = path.join(cwd, 'embeddables');
13
94
  console.log('');
14
95
  console.log(pc.bold(pc.cyan(' Embeddables Project Setup')));
15
96
  console.log('');
16
- // Check if already initialized
17
- let existingPackageJson = null;
18
- if (fs.existsSync(packageJsonPath)) {
19
- try {
20
- existingPackageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
21
- }
22
- catch {
23
- // Invalid JSON, we'll overwrite
24
- }
25
- }
26
97
  const existingConfig = readProjectConfig();
27
- // Step 1: Get Embeddables project (this determines the default name)
98
+ // Step 1: Get Embeddables project
28
99
  let projectId = opts.projectId;
29
100
  let selectedProjectTitle;
101
+ let selectedOrgId;
102
+ let selectedOrgTitle;
30
103
  if (!projectId && !opts.yes) {
31
104
  if (existingConfig?.project_id) {
32
105
  console.log(pc.gray(` Using existing project ID: ${existingConfig.project_id}`));
33
106
  projectId = existingConfig.project_id;
34
107
  selectedProjectTitle = existingConfig.project_name || undefined;
108
+ selectedOrgId = existingConfig.org_id || undefined;
109
+ selectedOrgTitle = existingConfig.org_title || undefined;
35
110
  }
36
111
  else if (isLoggedIn()) {
37
112
  // Fetch and show project list
@@ -43,6 +118,8 @@ export async function runInit(opts) {
43
118
  if (selectedProject) {
44
119
  projectId = selectedProject.id;
45
120
  selectedProjectTitle = selectedProject.title || undefined;
121
+ selectedOrgId = selectedProject.org_id || undefined;
122
+ selectedOrgTitle = selectedProject.org_title || undefined;
46
123
  }
47
124
  }
48
125
  else {
@@ -62,77 +139,18 @@ export async function runInit(opts) {
62
139
  projectId = response.projectId || undefined;
63
140
  }
64
141
  }
65
- // Step 2: Get project name (default to selected project title, or existing, or directory name)
66
- let projectName = opts.name;
67
- if (!projectName) {
68
- if (existingPackageJson?.name) {
69
- projectName = existingPackageJson.name;
70
- console.log(pc.gray(` Using existing project name: ${projectName}`));
71
- }
72
- else if (opts.yes) {
73
- // Use selected project title or directory name as default
74
- projectName = selectedProjectTitle || path.basename(cwd);
75
- }
76
- else {
77
- // Default to selected project title, then directory name
78
- const defaultName = selectedProjectTitle || path.basename(cwd);
79
- const response = await prompts({
80
- type: 'text',
81
- name: 'name',
82
- message: 'Project name:',
83
- initial: defaultName,
84
- }, {
85
- onCancel: () => {
86
- console.log(pc.gray('\n Cancelled.'));
87
- process.exit(0);
88
- },
89
- });
90
- projectName = response.name;
91
- }
92
- }
93
- if (!projectName) {
94
- console.error('Project name is required.');
95
- process.exit(1);
96
- }
97
- // Create or update package.json
98
- const packageJson = existingPackageJson || {
99
- name: projectName,
100
- version: '1.0.0',
101
- type: 'module',
102
- };
103
- // Ensure name is set
104
- if (!packageJson.name) {
105
- packageJson.name = projectName;
106
- }
107
- // Ensure type is module
108
- if (!packageJson.type) {
109
- packageJson.type = 'module';
110
- }
111
- // Add @embeddables/cli as a dependency
112
- if (!packageJson.dependencies) {
113
- packageJson.dependencies = {};
114
- }
115
- if (!packageJson.dependencies['@embeddables/cli']) {
116
- packageJson.dependencies['@embeddables/cli'] = '^0.1.0';
117
- }
118
- // Write package.json
119
- fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf8');
120
- if (existingPackageJson) {
121
- console.log(pc.green(' ✓ Updated package.json'));
122
- }
123
- else {
124
- console.log(pc.green(' ✓ Created package.json'));
125
- }
126
142
  // Write embeddables.json config
127
143
  if (projectId) {
128
144
  writeProjectConfig({
145
+ org_id: selectedOrgId,
146
+ org_title: selectedOrgTitle,
129
147
  project_id: projectId,
130
- project_name: selectedProjectTitle || projectName,
148
+ project_name: selectedProjectTitle,
131
149
  });
132
150
  console.log(pc.green(' ✓ Created embeddables.json'));
133
151
  }
134
152
  // Create or update .gitignore
135
- const gitignoreEntries = ['node_modules/', '**/.generated/', '.DS_Store'];
153
+ const gitignoreEntries = ['**/.generated/', '**/.types/', '.DS_Store'];
136
154
  let existingGitignore = '';
137
155
  if (fs.existsSync(gitignorePath)) {
138
156
  existingGitignore = fs.readFileSync(gitignorePath, 'utf8');
@@ -162,27 +180,66 @@ export async function runInit(opts) {
162
180
  else {
163
181
  console.log(pc.gray(' ✓ embeddables/ directory exists'));
164
182
  }
183
+ // Copy .cursor/ folder with Cursor rules
184
+ const packageRoot = path.resolve(__dirname, '..', '..');
185
+ const sourceCursorDir = path.join(packageRoot, '.cursor');
186
+ const targetCursorDir = path.join(cwd, '.cursor');
187
+ if (fs.existsSync(sourceCursorDir)) {
188
+ copyDirSync(sourceCursorDir, targetCursorDir);
189
+ console.log(pc.green(' ✓ Injected .cursor/ rules'));
190
+ }
191
+ // Create tsconfig.json for editor support (JSX, type checking)
192
+ const tsconfigPath = path.join(cwd, 'tsconfig.json');
193
+ if (!fs.existsSync(tsconfigPath)) {
194
+ const tsconfig = {
195
+ compilerOptions: {
196
+ target: 'esnext',
197
+ module: 'esnext',
198
+ moduleResolution: 'bundler',
199
+ jsx: 'react-jsx',
200
+ noEmit: true,
201
+ strict: false,
202
+ skipLibCheck: true,
203
+ esModuleInterop: true,
204
+ baseUrl: '.',
205
+ paths: {
206
+ 'react/jsx-runtime': ['./embeddables/.types/react-jsx-runtime'],
207
+ '@embeddables/cli/components': ['./embeddables/.types/components'],
208
+ '@embeddables/cli/types': ['./embeddables/.types/types'],
209
+ },
210
+ },
211
+ include: ['embeddables'],
212
+ };
213
+ fs.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2) + '\n', 'utf8');
214
+ console.log(pc.green(' ✓ Created tsconfig.json'));
215
+ }
216
+ else {
217
+ console.log(pc.gray(' ✓ tsconfig.json already exists'));
218
+ }
219
+ // Generate type declaration stubs for editor support (no npm install needed)
220
+ writeTypeStubs(embeddablesDir);
221
+ console.log(pc.green(' ✓ Generated type declarations'));
222
+ // Remind user to install dependencies
223
+ console.log('');
224
+ console.log(pc.yellow(' → Run `npm install` to install dependencies'));
165
225
  // Print next steps
166
226
  console.log('');
167
227
  console.log(pc.bold(' Next steps:'));
168
228
  console.log('');
169
- console.log(pc.cyan(' 1. Install dependencies:'));
170
- console.log(pc.white(' npm install'));
171
- console.log('');
172
- console.log(pc.cyan(' 2. Login to Embeddables:'));
173
- console.log(pc.white(' npx embeddables login'));
229
+ console.log(pc.cyan(' 1. Login to Embeddables:'));
230
+ console.log(pc.white(' embeddables login'));
174
231
  console.log('');
175
232
  if (projectId) {
176
- console.log(pc.cyan(' 3. Pull an embeddable:'));
177
- console.log(pc.white(' npx embeddables pull'));
233
+ console.log(pc.cyan(' 2. Pull an embeddable:'));
234
+ console.log(pc.white(' embeddables pull'));
178
235
  console.log(pc.gray(' (will show a list of embeddables in your project)'));
179
236
  }
180
237
  else {
181
- console.log(pc.cyan(' 3. Pull an existing embeddable:'));
182
- console.log(pc.white(' npx embeddables pull --id <embeddable-id>'));
238
+ console.log(pc.cyan(' 2. Pull an existing embeddable:'));
239
+ console.log(pc.white(' embeddables pull --id <embeddable-id>'));
183
240
  }
184
241
  console.log('');
185
- console.log(pc.cyan(' 4. Start developing:'));
186
- console.log(pc.white(' npx embeddables dev'));
242
+ console.log(pc.cyan(' 3. Start developing:'));
243
+ console.log(pc.white(' embeddables dev'));
187
244
  console.log('');
188
245
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../../src/commands/pull.ts"],"names":[],"mappings":"AAUA,wBAAsB,OAAO,CAAC,IAAI,EAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,iBAmMhG"}
1
+ {"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../../src/commands/pull.ts"],"names":[],"mappings":"AAUA,wBAAsB,OAAO,CAAC,IAAI,EAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,iBAwNhG"}
@@ -29,6 +29,8 @@ export async function runPull(opts) {
29
29
  projectId = selectedProject.id;
30
30
  // Save the selected project to config
31
31
  writeProjectConfig({
32
+ org_id: selectedProject.org_id || undefined,
33
+ org_title: selectedProject.org_title || undefined,
32
34
  project_id: projectId,
33
35
  project_name: selectedProject.title || undefined,
34
36
  });
@@ -170,6 +172,24 @@ export async function runPull(opts) {
170
172
  throw compileError;
171
173
  }
172
174
  }
175
+ // Store version number in config.json so save knows the base version
176
+ if (version != null) {
177
+ const versionNumber = typeof version === 'number' ? version : parseInt(String(version), 10);
178
+ if (!isNaN(versionNumber)) {
179
+ const configPath = path.join('embeddables', embeddableId, 'config.json');
180
+ if (fs.existsSync(configPath)) {
181
+ try {
182
+ const configContent = fs.readFileSync(configPath, 'utf8');
183
+ const config = JSON.parse(configContent);
184
+ config._version = versionNumber;
185
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
186
+ }
187
+ catch {
188
+ // Ignore errors updating config.json - versioned files are a fallback
189
+ }
190
+ }
191
+ }
192
+ }
173
193
  }
174
194
  catch (error) {
175
195
  console.error('Error pulling embeddable:', error);
@@ -0,0 +1,8 @@
1
+ export declare function runSave(opts: {
2
+ id?: string;
3
+ label?: string;
4
+ branch?: string;
5
+ skipBuild?: boolean;
6
+ fromVersion?: string;
7
+ }): Promise<void>;
8
+ //# sourceMappingURL=save.d.ts.map
@@ -0,0 +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"}