@embeddables/cli 0.6.11 → 0.7.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.
Files changed (40) hide show
  1. package/.prompts/custom/build-funnel.md +1 -1
  2. package/dist/cli.js +7 -10
  3. package/dist/commands/branch.d.ts.map +1 -1
  4. package/dist/commands/branch.js +20 -15
  5. package/dist/commands/build-workbench.d.ts.map +1 -1
  6. package/dist/commands/build-workbench.js +37 -31
  7. package/dist/commands/build.d.ts.map +1 -1
  8. package/dist/commands/build.js +12 -3
  9. package/dist/commands/dev.d.ts.map +1 -1
  10. package/dist/commands/dev.js +34 -21
  11. package/dist/commands/experiments-connect.d.ts.map +1 -1
  12. package/dist/commands/experiments-connect.js +43 -30
  13. package/dist/commands/init.d.ts.map +1 -1
  14. package/dist/commands/init.js +49 -48
  15. package/dist/commands/login.d.ts.map +1 -1
  16. package/dist/commands/login.js +35 -25
  17. package/dist/commands/logout.d.ts.map +1 -1
  18. package/dist/commands/logout.js +10 -6
  19. package/dist/commands/pull.d.ts.map +1 -1
  20. package/dist/commands/pull.js +49 -36
  21. package/dist/commands/save.d.ts.map +1 -1
  22. package/dist/commands/save.js +79 -53
  23. package/dist/compiler/index.d.ts.map +1 -1
  24. package/dist/compiler/index.js +10 -9
  25. package/dist/compiler/reverse.d.ts.map +1 -1
  26. package/dist/compiler/reverse.js +18 -17
  27. package/dist/logger.d.ts +1 -0
  28. package/dist/logger.d.ts.map +1 -1
  29. package/dist/logger.js +4 -0
  30. package/dist/prompts/branches.d.ts.map +1 -1
  31. package/dist/prompts/branches.js +3 -2
  32. package/dist/prompts/embeddables.d.ts.map +1 -1
  33. package/dist/prompts/embeddables.js +8 -7
  34. package/dist/prompts/experiments.d.ts.map +1 -1
  35. package/dist/prompts/experiments.js +5 -4
  36. package/dist/prompts/projects.d.ts.map +1 -1
  37. package/dist/prompts/projects.js +4 -3
  38. package/dist/proxy/server.d.ts.map +1 -1
  39. package/dist/proxy/server.js +33 -32
  40. package/package.json +2 -1
@@ -6,6 +6,7 @@ import prompts from 'prompts';
6
6
  import { writeProjectConfig, readProjectConfig } from '../config/index.js';
7
7
  import { isLoggedIn } from '../auth/index.js';
8
8
  import { promptForProject } from '../prompts/index.js';
9
+ import * as stdout from '../stdout.js';
9
10
  const __filename = fileURLToPath(import.meta.url);
10
11
  const __dirname = path.dirname(__filename);
11
12
  /** Recursively copy a directory, creating target dirs as needed. */
@@ -43,7 +44,7 @@ function writeTypeStubs(cwd) {
43
44
  fs.copyFileSync(typesBuilderDts, path.join(typesDir, 'types.d.ts'));
44
45
  }
45
46
  else {
46
- console.warn(pc.yellow(' ⚠ Could not find types-builder.d.ts — run `npm run build` first'));
47
+ stdout.warn(pc.yellow(' ⚠ Could not find types-builder.d.ts — run `npm run build` first'));
47
48
  }
48
49
  // React JSX runtime types (needed for "jsx": "react-jsx" in tsconfig)
49
50
  fs.writeFileSync(path.join(typesDir, 'react-jsx-runtime.d.ts'), `export namespace JSX {
@@ -104,9 +105,9 @@ export async function runInit(opts) {
104
105
  const cwd = process.cwd();
105
106
  const gitignorePath = path.join(cwd, '.gitignore');
106
107
  const embeddablesDir = path.join(cwd, 'embeddables');
107
- console.log('');
108
- console.log(pc.bold(pc.cyan(' Embeddables Project Setup')));
109
- console.log('');
108
+ stdout.print('');
109
+ stdout.print(pc.bold(pc.cyan(' Embeddables Project Setup')));
110
+ stdout.print('');
110
111
  const existingConfig = readProjectConfig();
111
112
  // Step 1: Get Embeddables project
112
113
  let projectId = opts.projectId;
@@ -115,7 +116,7 @@ export async function runInit(opts) {
115
116
  let selectedOrgTitle;
116
117
  if (!projectId && !opts.yes) {
117
118
  if (existingConfig?.project_id) {
118
- console.log(pc.gray(` Using existing project ID: ${existingConfig.project_id}`));
119
+ stdout.print(pc.gray(` Using existing project ID: ${existingConfig.project_id}`));
119
120
  projectId = existingConfig.project_id;
120
121
  selectedProjectTitle = existingConfig.project_name || undefined;
121
122
  selectedOrgId = existingConfig.org_id || undefined;
@@ -123,7 +124,7 @@ export async function runInit(opts) {
123
124
  }
124
125
  else if (isLoggedIn()) {
125
126
  // Fetch and show project list
126
- console.log(pc.gray(' Fetching projects...'));
127
+ stdout.print(pc.gray(' Fetching projects...'));
127
128
  const selectedProject = await promptForProject({
128
129
  allowSkip: true,
129
130
  message: 'Select your Embeddables project:',
@@ -137,7 +138,7 @@ export async function runInit(opts) {
137
138
  }
138
139
  else {
139
140
  // Not logged in - offer manual entry or skip
140
- console.log(pc.gray(' (Login with "embeddables login" to select from your projects)'));
141
+ stdout.print(pc.gray(' (Login with "embeddables login" to select from your projects)'));
141
142
  const response = await prompts({
142
143
  type: 'text',
143
144
  name: 'projectId',
@@ -145,7 +146,7 @@ export async function runInit(opts) {
145
146
  hint: 'e.g., proj_abc123 - leave empty to skip',
146
147
  }, {
147
148
  onCancel: () => {
148
- console.log(pc.gray('\n Cancelled.'));
149
+ stdout.print(pc.gray('\n Cancelled.'));
149
150
  process.exit(0);
150
151
  },
151
152
  });
@@ -160,7 +161,7 @@ export async function runInit(opts) {
160
161
  project_id: projectId,
161
162
  project_name: selectedProjectTitle,
162
163
  });
163
- console.log(pc.green(' ✓ Created embeddables.json'));
164
+ stdout.print(pc.green(' ✓ Created embeddables.json'));
164
165
  }
165
166
  // Create or update .gitignore
166
167
  const gitignoreEntries = ['**/.generated/', '**/.types/', '.DS_Store'];
@@ -176,28 +177,28 @@ export async function runInit(opts) {
176
177
  : `# Embeddables\n${gitignoreEntries.join('\n')}\n`;
177
178
  fs.writeFileSync(gitignorePath, existingGitignore + embeddablesSection, 'utf8');
178
179
  if (existingGitignore) {
179
- console.log(pc.green(' ✓ Updated .gitignore'));
180
+ stdout.print(pc.green(' ✓ Updated .gitignore'));
180
181
  }
181
182
  else {
182
- console.log(pc.green(' ✓ Created .gitignore'));
183
+ stdout.print(pc.green(' ✓ Created .gitignore'));
183
184
  }
184
185
  }
185
186
  else {
186
- console.log(pc.gray(' ✓ .gitignore already configured'));
187
+ stdout.print(pc.gray(' ✓ .gitignore already configured'));
187
188
  }
188
189
  // Create embeddables directory
189
190
  if (!fs.existsSync(embeddablesDir)) {
190
191
  fs.mkdirSync(embeddablesDir, { recursive: true });
191
- console.log(pc.green(' ✓ Created embeddables/ directory'));
192
+ stdout.print(pc.green(' ✓ Created embeddables/ directory'));
192
193
  }
193
194
  else {
194
- console.log(pc.gray(' ✓ embeddables/ directory exists'));
195
+ stdout.print(pc.gray(' ✓ embeddables/ directory exists'));
195
196
  }
196
- // Inject .cursor/ and .claudefiles/ from source .prompts/embeddables-cli.md (or copy if pre-built dirs exist)
197
+ // Inject .cursor/ and .claude/ from source .prompts/embeddables-cli.md (or copy if pre-built dirs exist)
197
198
  const packageRoot = path.resolve(__dirname, '..', '..');
198
199
  const promptsSource = path.join(packageRoot, '.prompts', 'embeddables-cli.md');
199
200
  const sourceCursorDir = path.join(packageRoot, '.cursor');
200
- const sourceClaudefilesDir = path.join(packageRoot, '.claudefiles');
201
+ const sourceClaudeDir = path.join(packageRoot, '.claude');
201
202
  if (fs.existsSync(promptsSource)) {
202
203
  const content = fs.readFileSync(promptsSource, 'utf8');
203
204
  const cursorFrontmatter = `---
@@ -212,28 +213,28 @@ This is an Embeddables project managed by the Embeddables CLI. The CLI transform
212
213
 
213
214
  For full context when editing embeddables (pages, components, styles, actions, computed fields), see:
214
215
 
215
- - **.claudefiles/embeddables-cli.md** — Embeddables CLI structure, types, file layout, and conventions.
216
+ - **.claude/embeddables-cli.md** — Embeddables CLI structure, types, file layout, and conventions.
216
217
 
217
218
  All TypeScript types are in \`.types/\` (generated by \`embeddables init\`). Use \`embeddables dev\` to run the dev server and \`embeddables pull\` / \`embeddables save\` to sync with the project.
218
219
  `;
219
220
  const cursorRulesDir = path.join(cwd, '.cursor', 'rules');
220
- const claudefilesDir = path.join(cwd, '.claudefiles');
221
+ const claudeDir = path.join(cwd, '.claude');
221
222
  fs.mkdirSync(cursorRulesDir, { recursive: true });
222
- fs.mkdirSync(claudefilesDir, { recursive: true });
223
+ fs.mkdirSync(claudeDir, { recursive: true });
223
224
  fs.writeFileSync(path.join(cursorRulesDir, 'embeddables-cli.md'), cursorFrontmatter + content, 'utf8');
224
- fs.writeFileSync(path.join(claudefilesDir, 'CLAUDE.md'), claudeIntro, 'utf8');
225
- fs.writeFileSync(path.join(claudefilesDir, 'embeddables-cli.md'), content, 'utf8');
226
- console.log(pc.green(' ✓ Injected .cursor/ rules'));
227
- console.log(pc.green(' ✓ Injected .claudefiles/'));
225
+ fs.writeFileSync(path.join(claudeDir, 'CLAUDE.md'), claudeIntro, 'utf8');
226
+ fs.writeFileSync(path.join(claudeDir, 'embeddables-cli.md'), content, 'utf8');
227
+ stdout.print(pc.green(' ✓ Injected .cursor/ rules'));
228
+ stdout.print(pc.green(' ✓ Injected .claude/'));
228
229
  }
229
- else if (fs.existsSync(sourceCursorDir) || fs.existsSync(sourceClaudefilesDir)) {
230
+ else if (fs.existsSync(sourceCursorDir) || fs.existsSync(sourceClaudeDir)) {
230
231
  if (fs.existsSync(sourceCursorDir)) {
231
232
  copyDirSync(sourceCursorDir, path.join(cwd, '.cursor'));
232
- console.log(pc.green(' ✓ Injected .cursor/ rules'));
233
+ stdout.print(pc.green(' ✓ Injected .cursor/ rules'));
233
234
  }
234
- if (fs.existsSync(sourceClaudefilesDir)) {
235
- copyDirSync(sourceClaudefilesDir, path.join(cwd, '.claudefiles'));
236
- console.log(pc.green(' ✓ Injected .claudefiles/'));
235
+ if (fs.existsSync(sourceClaudeDir)) {
236
+ copyDirSync(sourceClaudeDir, path.join(cwd, '.claude'));
237
+ stdout.print(pc.green(' ✓ Injected .claude/'));
237
238
  }
238
239
  }
239
240
  // Create tsconfig.json for editor support (JSX, type checking)
@@ -259,16 +260,16 @@ All TypeScript types are in \`.types/\` (generated by \`embeddables init\`). Use
259
260
  include: ['embeddables'],
260
261
  };
261
262
  fs.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2) + '\n', 'utf8');
262
- console.log(pc.green(' ✓ Created tsconfig.json'));
263
+ stdout.print(pc.green(' ✓ Created tsconfig.json'));
263
264
  }
264
265
  else {
265
- console.log(pc.gray(' ✓ tsconfig.json already exists'));
266
+ stdout.print(pc.gray(' ✓ tsconfig.json already exists'));
266
267
  }
267
268
  // Migrate .types from old location (embeddables/.types/) to project root (.types/)
268
269
  const oldTypesDir = path.join(embeddablesDir, '.types');
269
270
  if (fs.existsSync(oldTypesDir)) {
270
271
  fs.rmSync(oldTypesDir, { recursive: true });
271
- console.log(pc.green(' ✓ Removed old embeddables/.types/'));
272
+ stdout.print(pc.green(' ✓ Removed old embeddables/.types/'));
272
273
  }
273
274
  // Fix tsconfig paths from older versions
274
275
  if (fs.existsSync(tsconfigPath)) {
@@ -286,7 +287,7 @@ All TypeScript types are in \`.types/\` (generated by \`embeddables init\`). Use
286
287
  }
287
288
  if (updated) {
288
289
  fs.writeFileSync(tsconfigPath, tsconfigContent, 'utf8');
289
- console.log(pc.green(' ✓ Updated tsconfig.json paths'));
290
+ stdout.print(pc.green(' ✓ Updated tsconfig.json paths'));
290
291
  }
291
292
  }
292
293
  // Remove old .types/types-builder.d.ts (now named .types/types.d.ts)
@@ -296,25 +297,25 @@ All TypeScript types are in \`.types/\` (generated by \`embeddables init\`). Use
296
297
  }
297
298
  // Generate type declaration stubs for editor support (no npm install needed)
298
299
  writeTypeStubs(cwd);
299
- console.log(pc.green(' ✓ Generated type declarations'));
300
+ stdout.print(pc.green(' ✓ Generated type declarations'));
300
301
  // Print next steps
301
- console.log('');
302
- console.log(pc.bold(' Next steps:'));
303
- console.log('');
304
- console.log(pc.cyan(' 1. Login to Embeddables:'));
305
- console.log(pc.white(' embeddables login'));
306
- console.log('');
302
+ stdout.print('');
303
+ stdout.print(pc.bold(' Next steps:'));
304
+ stdout.print('');
305
+ stdout.print(pc.cyan(' 1. Login to Embeddables:'));
306
+ stdout.print(pc.white(' embeddables login'));
307
+ stdout.print('');
307
308
  if (projectId) {
308
- console.log(pc.cyan(' 2. Pull an embeddable:'));
309
- console.log(pc.white(' embeddables pull'));
310
- console.log(pc.gray(' (will show a list of embeddables in your project)'));
309
+ stdout.print(pc.cyan(' 2. Pull an embeddable:'));
310
+ stdout.print(pc.white(' embeddables pull'));
311
+ stdout.print(pc.gray(' (will show a list of embeddables in your project)'));
311
312
  }
312
313
  else {
313
- console.log(pc.cyan(' 2. Pull an existing embeddable:'));
314
- console.log(pc.white(' embeddables pull --id <embeddable-id>'));
314
+ stdout.print(pc.cyan(' 2. Pull an existing embeddable:'));
315
+ stdout.print(pc.white(' embeddables pull --id <embeddable-id>'));
315
316
  }
316
- console.log('');
317
- console.log(pc.cyan(' 3. Start developing:'));
318
- console.log(pc.white(' embeddables dev'));
319
- console.log('');
317
+ stdout.print('');
318
+ stdout.print(pc.cyan(' 3. Start developing:'));
319
+ stdout.print(pc.white(' embeddables dev'));
320
+ stdout.print('');
320
321
  }
@@ -1 +1 @@
1
- {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAUA,wBAAsB,QAAQ,kBAyH7B"}
1
+ {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAYA,wBAAsB,QAAQ,kBAiI7B"}
@@ -1,7 +1,10 @@
1
1
  import prompts from 'prompts';
2
2
  import pc from 'picocolors';
3
3
  import { getSupabaseClient, writeAuthConfig, readAuthConfig, SUPABASE_URL, SUPABASE_ANON_KEY, } from '../auth/index.js';
4
+ import { captureException, createLogger, exit } from '../logger.js';
5
+ import * as stdout from '../stdout.js';
4
6
  export async function runLogin() {
7
+ const logger = createLogger('runLogin');
5
8
  try {
6
9
  const supabase = getSupabaseClient();
7
10
  // Check if already logged in
@@ -14,7 +17,7 @@ export async function runLogin() {
14
17
  initial: false,
15
18
  });
16
19
  if (!overwrite) {
17
- console.log(pc.green('Login cancelled.'));
20
+ stdout.print(pc.green('Login cancelled.'));
18
21
  return;
19
22
  }
20
23
  }
@@ -33,23 +36,24 @@ export async function runLogin() {
33
36
  },
34
37
  });
35
38
  if (!email) {
36
- console.log(pc.red('Login cancelled.'));
39
+ stdout.print(pc.red('Login cancelled.'));
37
40
  return;
38
41
  }
39
- console.log('');
40
- console.log(pc.cyan('Sending verification code to your email...'));
42
+ stdout.print('');
43
+ stdout.print(pc.cyan('Sending verification code to your email...'));
41
44
  // Send OTP code
42
45
  const { error: sendError } = await supabase.auth.signInWithOtp({
43
46
  email,
44
47
  });
45
48
  if (sendError) {
46
- console.error(pc.red(`Error sending verification code: ${sendError.message}`));
47
- process.exit(1);
49
+ stdout.error(pc.red(`Error sending verification code: ${sendError.message}`));
50
+ logger.error('failed to send OTP', { message: sendError.message });
51
+ await exit(1);
48
52
  }
49
- console.log('');
50
- console.log(pc.green('✓ Verification code sent!'));
51
- console.log(pc.dim('Please check your email for the code.'));
52
- console.log('');
53
+ stdout.print('');
54
+ stdout.print(pc.green('✓ Verification code sent!'));
55
+ stdout.print(pc.dim('Please check your email for the code.'));
56
+ stdout.print('');
53
57
  // Prompt for OTP code
54
58
  const { code } = await prompts({
55
59
  type: 'text',
@@ -65,11 +69,11 @@ export async function runLogin() {
65
69
  },
66
70
  });
67
71
  if (!code) {
68
- console.log(pc.red('Login cancelled.'));
72
+ stdout.print(pc.red('Login cancelled.'));
69
73
  return;
70
74
  }
71
- console.log('');
72
- console.log(pc.cyan('Verifying code...'));
75
+ stdout.print('');
76
+ stdout.print(pc.cyan('Verifying code...'));
73
77
  // Verify OTP code
74
78
  const { data, error: verifyError } = await supabase.auth.verifyOtp({
75
79
  email,
@@ -77,12 +81,15 @@ export async function runLogin() {
77
81
  type: 'email',
78
82
  });
79
83
  if (verifyError) {
80
- console.error(pc.red(`Error verifying code: ${verifyError.message}`));
81
- process.exit(1);
84
+ stdout.error(pc.red(`Error verifying code: ${verifyError.message}`));
85
+ logger.error('failed to verify OTP', { message: verifyError.message });
86
+ await exit(1);
82
87
  }
83
88
  if (!data.session) {
84
- console.error(pc.red('No session returned from verification'));
85
- process.exit(1);
89
+ stdout.error(pc.red('No session returned from verification'));
90
+ logger.error('no session returned from verification');
91
+ await exit(1);
92
+ return;
86
93
  }
87
94
  // Store auth config
88
95
  const config = {
@@ -93,20 +100,23 @@ export async function runLogin() {
93
100
  supabase_anon_key: SUPABASE_ANON_KEY,
94
101
  };
95
102
  writeAuthConfig(config);
96
- console.log('');
97
- console.log(pc.green('✓ Successfully logged in!'));
98
- console.log('');
103
+ stdout.print('');
104
+ stdout.print(pc.green('✓ Successfully logged in!'));
105
+ logger.info('login complete');
106
+ stdout.print('');
99
107
  }
100
108
  catch (error) {
101
109
  if (error.name === 'ExitPrompt') {
102
110
  // User cancelled prompt (Ctrl+C)
103
- console.log('');
104
- console.log(pc.red('Login cancelled.'));
105
- process.exit(0);
111
+ stdout.print('');
112
+ stdout.print(pc.red('Login cancelled.'));
113
+ await exit(0);
106
114
  }
107
115
  else {
108
- console.error(pc.red(`Error during login: ${error.message}`));
109
- process.exit(1);
116
+ captureException(error);
117
+ stdout.error(pc.red(`Error during login: ${error.message}`));
118
+ logger.error('login failed', { message: error.message });
119
+ await exit(1);
110
120
  }
111
121
  }
112
122
  }
@@ -1 +1 @@
1
- {"version":3,"file":"logout.d.ts","sourceRoot":"","sources":["../../src/commands/logout.ts"],"names":[],"mappings":"AAGA,wBAAsB,SAAS,kBAe9B"}
1
+ {"version":3,"file":"logout.d.ts","sourceRoot":"","sources":["../../src/commands/logout.ts"],"names":[],"mappings":"AAKA,wBAAsB,SAAS,kBAiB9B"}
@@ -1,18 +1,22 @@
1
1
  import pc from 'picocolors';
2
2
  import { deleteAuthConfig, isLoggedIn } from '../auth/index.js';
3
+ import { createLogger, exit } from '../logger.js';
4
+ import * as stdout from '../stdout.js';
3
5
  export async function runLogout() {
6
+ const logger = createLogger('runLogout');
4
7
  try {
5
8
  if (!isLoggedIn()) {
6
- console.log(pc.yellow('You are not currently logged in.'));
9
+ stdout.print(pc.yellow('You are not currently logged in.'));
7
10
  return;
8
11
  }
9
12
  deleteAuthConfig();
10
- console.log('');
11
- console.log(pc.green('✓ Successfully logged out!'));
12
- console.log('');
13
+ stdout.print('');
14
+ stdout.print(pc.green('✓ Successfully logged out!'));
15
+ stdout.print('');
13
16
  }
14
17
  catch (error) {
15
- console.error(pc.red(`Error during logout: ${error.message}`));
16
- process.exit(1);
18
+ stdout.error(pc.red(`Error during logout: ${error.message}`));
19
+ logger.error('logout failed', { message: error.message });
20
+ await exit(1);
17
21
  }
18
22
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../../src/commands/pull.ts"],"names":[],"mappings":"AAgHA,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,iHAAiH;IACjH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,cAAc,iBAkQjD"}
1
+ {"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../../src/commands/pull.ts"],"names":[],"mappings":"AAkHA,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,iHAAiH;IACjH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,cAAc,iBA8QjD"}
@@ -6,6 +6,8 @@ import { reverseCompile } from '../compiler/reverse.js';
6
6
  import { getAccessToken, isLoggedIn } from '../auth/index.js';
7
7
  import { getProjectId, writeProjectConfig } from '../config/index.js';
8
8
  import { promptForProject, promptForEmbeddable, fetchEmbeddableMetadata } from '../prompts/index.js';
9
+ import { captureException, createLogger, exit } from '../logger.js';
10
+ import * as stdout from '../stdout.js';
9
11
  import { inferEmbeddableFromCwd } from '../helpers/utils.js';
10
12
  /**
11
13
  * Normalize custom_validation_function strings so literal "\\n" (backslash-n) becomes real newline.
@@ -105,10 +107,11 @@ function writePullMetadataToConfig(embeddableId, version, branch, branchName) {
105
107
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
106
108
  }
107
109
  catch (err) {
108
- console.warn(pc.yellow(`Could not write config.json: ${err instanceof Error ? err.message : err}`));
110
+ stdout.warn(pc.yellow(`Could not write config.json: ${err instanceof Error ? err.message : err}`));
109
111
  }
110
112
  }
111
113
  export async function runPull(opts) {
114
+ const logger = createLogger('runPull');
112
115
  const inferred = inferEmbeddableFromCwd();
113
116
  let embeddableId = opts.id ?? inferred?.embeddableId;
114
117
  if (inferred && !opts.id && embeddableId) {
@@ -117,20 +120,24 @@ export async function runPull(opts) {
117
120
  // If no ID provided, try to get it interactively
118
121
  if (!embeddableId) {
119
122
  if (!isLoggedIn()) {
120
- console.error(pc.red('No embeddable ID provided and not logged in.'));
121
- console.log('');
122
- console.log(pc.gray('Either:'));
123
- console.log(pc.gray(' 1. Use --id <embeddable-id> to specify an embeddable'));
124
- console.log(pc.gray(' 2. Run "embeddables login" first for interactive selection'));
125
- process.exit(1);
123
+ stdout.error(pc.red('No embeddable ID provided and not logged in.'));
124
+ stdout.print('');
125
+ stdout.print(pc.gray('Either:'));
126
+ stdout.print(pc.gray(' 1. Use --id <embeddable-id> to specify an embeddable'));
127
+ stdout.print(pc.gray(' 2. Run "embeddables login" first for interactive selection'));
128
+ logger.error('not logged in and no embeddable id');
129
+ await exit(1);
130
+ return;
126
131
  }
127
132
  let projectId = getProjectId();
128
133
  // If no project ID configured, prompt for project selection
129
134
  if (!projectId) {
130
- console.log(pc.cyan('Fetching projects...'));
135
+ stdout.print(pc.cyan('Fetching projects...'));
131
136
  const selectedProject = await promptForProject();
132
137
  if (!selectedProject) {
133
- process.exit(1);
138
+ logger.error('project selection failed');
139
+ await exit(1);
140
+ return;
134
141
  }
135
142
  projectId = selectedProject.id;
136
143
  // Save the selected project to config
@@ -140,18 +147,20 @@ export async function runPull(opts) {
140
147
  project_id: projectId,
141
148
  project_name: selectedProject.title || undefined,
142
149
  });
143
- console.log(pc.green(`✓ Wrote project config to embeddables.json`));
144
- console.log('');
150
+ stdout.print(pc.green(`✓ Wrote project config to embeddables.json`));
151
+ stdout.print('');
145
152
  }
146
- console.log(pc.cyan('Fetching embeddables from project...'));
153
+ stdout.print(pc.cyan('Fetching embeddables from project...'));
147
154
  const selected = await promptForEmbeddable(projectId, {
148
155
  message: 'Select an embeddable to pull:',
149
156
  });
150
157
  if (!selected) {
151
- process.exit(1);
158
+ logger.error('embeddable selection failed');
159
+ await exit(1);
160
+ return;
152
161
  }
153
162
  embeddableId = selected;
154
- console.log('');
163
+ stdout.print('');
155
164
  }
156
165
  // When useMain, pull main and ignore/clear saved branch. Otherwise stay on current branch when no --branch.
157
166
  const currentFromConfig = opts.useMain || opts.branch != null ? null : getCurrentBranchFromConfig(embeddableId);
@@ -167,8 +176,9 @@ export async function runPull(opts) {
167
176
  ? `${effectiveBranchName} (${effectiveBranch})`
168
177
  : effectiveBranch
169
178
  : 'main';
170
- console.log(pc.cyan(`Pulling branch: ${pc.bold(branchLabel)}`));
171
- console.log(`Fetching embeddable from ${url}...`);
179
+ logger.info('pull started', { embeddableId, branch: effectiveBranch ?? 'main' });
180
+ stdout.print(pc.cyan(`Pulling branch: ${pc.bold(branchLabel)}`));
181
+ stdout.print(`Fetching embeddable from ${url}...`);
172
182
  let pullVersion;
173
183
  try {
174
184
  // Add authentication header if available
@@ -194,7 +204,7 @@ export async function runPull(opts) {
194
204
  fs.mkdirSync(path.dirname(outPath), { recursive: true });
195
205
  const flowJson = JSON.stringify(flow, null, 2);
196
206
  fs.writeFileSync(outPath, flowJson, 'utf8');
197
- console.log(pc.cyan(`✓ Wrote embeddable JSON to ${outPath}`));
207
+ stdout.print(pc.cyan(`✓ Wrote embeddable JSON to ${outPath}`));
198
208
  // Also save version-specific file (e.g. embeddable-main@135.json or embeddable-my_branch@135.json)
199
209
  pullVersion =
200
210
  data.version ?? data.embeddable_version ?? flow.version;
@@ -205,7 +215,7 @@ export async function runPull(opts) {
205
215
  const versionedBasename = getVersionedBasename(versionStr, branchSlug);
206
216
  const versionedPath = path.join(path.dirname(outPath), versionedBasename);
207
217
  fs.writeFileSync(versionedPath, flowJson, 'utf8');
208
- console.log(pc.cyan(`✓ Wrote versioned embeddable JSON to ${versionedPath}`));
218
+ stdout.print(pc.cyan(`✓ Wrote versioned embeddable JSON to ${versionedPath}`));
209
219
  }
210
220
  // Persist _version and _branch_id in config.json immediately so they survive reverseCompile
211
221
  writePullMetadataToConfig(embeddableId, version, effectiveBranch, effectiveBranchName);
@@ -215,7 +225,7 @@ export async function runPull(opts) {
215
225
  const metadataPath = path.join('embeddables', embeddableId, 'metadata.json');
216
226
  fs.mkdirSync(path.dirname(metadataPath), { recursive: true });
217
227
  fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2) + '\n', 'utf8');
218
- console.log(pc.cyan(`✓ Wrote flow metadata to ${metadataPath}`));
228
+ stdout.print(pc.cyan(`✓ Wrote flow metadata to ${metadataPath}`));
219
229
  }
220
230
  // Clear existing pages, styles, computed-fields, actions, and global-components before generating new ones
221
231
  const pagesDir = path.join('embeddables', embeddableId, 'pages');
@@ -228,28 +238,28 @@ export async function runPull(opts) {
228
238
  for (const page of existingPages) {
229
239
  fs.unlinkSync(path.join(pagesDir, page));
230
240
  }
231
- console.log(`${pc.gray(`Cleared ${existingPages.length} existing page(s)`)}`);
241
+ stdout.print(`${pc.gray(`Cleared ${existingPages.length} existing page(s)`)}`);
232
242
  }
233
243
  if (fs.existsSync(stylesDir)) {
234
244
  const existingStyles = fs.readdirSync(stylesDir).filter((f) => f.endsWith('.css'));
235
245
  for (const style of existingStyles) {
236
246
  fs.unlinkSync(path.join(stylesDir, style));
237
247
  }
238
- console.log(`${pc.gray(`Cleared ${existingStyles.length} existing style file(s)`)}`);
248
+ stdout.print(`${pc.gray(`Cleared ${existingStyles.length} existing style file(s)`)}`);
239
249
  }
240
250
  if (fs.existsSync(computedFieldsDir)) {
241
251
  const existingFields = fs.readdirSync(computedFieldsDir).filter((f) => f.endsWith('.js'));
242
252
  for (const field of existingFields) {
243
253
  fs.unlinkSync(path.join(computedFieldsDir, field));
244
254
  }
245
- console.log(`${pc.gray(`Cleared ${existingFields.length} existing computed field(s)`)}`);
255
+ stdout.print(`${pc.gray(`Cleared ${existingFields.length} existing computed field(s)`)}`);
246
256
  }
247
257
  if (fs.existsSync(actionsDir)) {
248
258
  const existingActions = fs.readdirSync(actionsDir).filter((f) => f.endsWith('.js'));
249
259
  for (const action of existingActions) {
250
260
  fs.unlinkSync(path.join(actionsDir, action));
251
261
  }
252
- console.log(`${pc.gray(`Cleared ${existingActions.length} existing action(s)`)}`);
262
+ stdout.print(`${pc.gray(`Cleared ${existingActions.length} existing action(s)`)}`);
253
263
  }
254
264
  if (fs.existsSync(globalComponentsDir)) {
255
265
  const existingGlobalComponents = fs
@@ -258,7 +268,7 @@ export async function runPull(opts) {
258
268
  for (const component of existingGlobalComponents) {
259
269
  fs.unlinkSync(path.join(globalComponentsDir, component));
260
270
  }
261
- console.log(`${pc.gray(`Cleared ${existingGlobalComponents.length} existing global component(s)`)}`);
271
+ stdout.print(`${pc.gray(`Cleared ${existingGlobalComponents.length} existing global component(s)`)}`);
262
272
  }
263
273
  // Run reverse compiler (pass pullMetadata so config.json gets _version and _branch_id even on fix retry)
264
274
  const versionNum = version != null && !isNaN(Number(version)) ? Number(version) : undefined;
@@ -278,10 +288,10 @@ export async function runPull(opts) {
278
288
  catch (compileError) {
279
289
  // If fix mode wasn't already enabled, offer to retry with fix mode
280
290
  if (!opts.fix && compileError instanceof Error) {
281
- console.log('');
282
- console.error(pc.red('Error during reverse compile:'));
283
- console.error(pc.yellow(` ${compileError.message}`));
284
- console.log('');
291
+ stdout.print('');
292
+ stdout.error(pc.red('Error during reverse compile:'));
293
+ stdout.error(pc.yellow(` ${compileError.message}`));
294
+ stdout.print('');
285
295
  const response = await prompts({
286
296
  type: 'confirm',
287
297
  name: 'fix',
@@ -294,9 +304,9 @@ export async function runPull(opts) {
294
304
  });
295
305
  if (response.fix) {
296
306
  usedFix = true;
297
- console.log('');
298
- console.log(pc.cyan('Retrying with auto-fix enabled...'));
299
- console.log('');
307
+ stdout.print('');
308
+ stdout.print(pc.cyan('Retrying with auto-fix enabled...'));
309
+ stdout.print('');
300
310
  await reverseCompile(flow, embeddableId, {
301
311
  fix: true,
302
312
  preserve: opts.preserve,
@@ -304,7 +314,7 @@ export async function runPull(opts) {
304
314
  });
305
315
  }
306
316
  else {
307
- process.exit(1);
317
+ await exit(1);
308
318
  }
309
319
  }
310
320
  else {
@@ -315,19 +325,22 @@ export async function runPull(opts) {
315
325
  if (usedFix) {
316
326
  const flowJson = JSON.stringify(flow, null, 2);
317
327
  fs.writeFileSync(outPath, flowJson, 'utf8');
318
- console.log(pc.cyan(`✓ Wrote embeddable JSON to ${outPath} (with fixes applied)`));
328
+ stdout.print(pc.cyan(`✓ Wrote embeddable JSON to ${outPath} (with fixes applied)`));
319
329
  if (version != null) {
320
330
  const versionStr = typeof version === 'string' ? version : String(version);
321
331
  const versionedBasename = getVersionedBasename(versionStr, branchSlug);
322
332
  const versionedPath = path.join(path.dirname(outPath), versionedBasename);
323
333
  fs.writeFileSync(versionedPath, flowJson, 'utf8');
324
- console.log(pc.cyan(`✓ Wrote versioned embeddable JSON to ${versionedPath} (with fixes applied)`));
334
+ stdout.print(pc.cyan(`✓ Wrote versioned embeddable JSON to ${versionedPath} (with fixes applied)`));
325
335
  }
326
336
  }
337
+ logger.info('pull complete', { embeddableId, version: String(pullVersion ?? 'unknown') });
327
338
  }
328
339
  catch (error) {
329
- console.error('Error pulling embeddable:', error);
330
- process.exit(1);
340
+ captureException(error);
341
+ stdout.error(`Error pulling embeddable: ${error instanceof Error ? error.message : String(error)}`);
342
+ logger.error('pull failed', { message: error instanceof Error ? error.message : String(error) });
343
+ await exit(1);
331
344
  }
332
345
  finally {
333
346
  // Always persist _version and _branch_id once we've fetched (so config is correct even if reverseCompile failed or was retried with fix)
@@ -1 +1 @@
1
- {"version":3,"file":"save.d.ts","sourceRoot":"","sources":["../../src/commands/save.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAGV,cAAc,EACf,MAAM,qBAAqB,CAAA;AAgC5B,MAAM,MAAM,sBAAsB,GAC9B;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,UAAU,EAAE,OAAO,CAAA;CAAE,CAAA;AAE3B,MAAM,MAAM,2BAA2B,GAAG;IACxC,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,iBAAiB,EAAE,MAAM,CAAA;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,uBAAuB,CAAC,EAAE,sBAAsB,EAAE,CAAA;IAClD,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,WAAW,CAAC,EAAE,cAAc,EAAE,CAAA;IAC9B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC/B,CAAA;AAkND,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"}
1
+ {"version":3,"file":"save.d.ts","sourceRoot":"","sources":["../../src/commands/save.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAGV,cAAc,EACf,MAAM,qBAAqB,CAAA;AAkC5B,MAAM,MAAM,sBAAsB,GAC9B;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,UAAU,EAAE,OAAO,CAAA;CAAE,CAAA;AAE3B,MAAM,MAAM,2BAA2B,GAAG;IACxC,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,iBAAiB,EAAE,MAAM,CAAA;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,uBAAuB,CAAC,EAAE,sBAAsB,EAAE,CAAA;IAClD,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,WAAW,CAAC,EAAE,cAAc,EAAE,CAAA;IAC9B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC/B,CAAA;AAkND,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,iBAqBA"}