@embeddables/cli 0.1.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.
Files changed (49) hide show
  1. package/.cursor/rules/embeddables-cli.md +679 -0
  2. package/README.md +37 -7
  3. package/dist/auth/index.d.ts +1 -1
  4. package/dist/auth/index.d.ts.map +1 -1
  5. package/dist/auth/index.js +5 -3
  6. package/dist/cli.js +42 -6
  7. package/dist/commands/branch.d.ts +4 -0
  8. package/dist/commands/branch.d.ts.map +1 -0
  9. package/dist/commands/branch.js +46 -0
  10. package/dist/commands/dev.d.ts +1 -1
  11. package/dist/commands/dev.d.ts.map +1 -1
  12. package/dist/commands/dev.js +48 -9
  13. package/dist/commands/init.d.ts +5 -0
  14. package/dist/commands/init.d.ts.map +1 -0
  15. package/dist/commands/init.js +245 -0
  16. package/dist/commands/pull.d.ts +1 -1
  17. package/dist/commands/pull.d.ts.map +1 -1
  18. package/dist/commands/pull.js +104 -3
  19. package/dist/commands/save.d.ts +8 -0
  20. package/dist/commands/save.d.ts.map +1 -0
  21. package/dist/commands/save.js +269 -0
  22. package/dist/compiler/index.d.ts.map +1 -1
  23. package/dist/compiler/index.js +4 -0
  24. package/dist/compiler/reverse.d.ts.map +1 -1
  25. package/dist/compiler/reverse.js +4 -10
  26. package/dist/config/index.d.ts +23 -0
  27. package/dist/config/index.d.ts.map +1 -0
  28. package/dist/config/index.js +42 -0
  29. package/dist/constants.d.ts +7 -0
  30. package/dist/constants.d.ts.map +1 -0
  31. package/dist/constants.js +6 -0
  32. package/dist/helpers/dates.d.ts +5 -0
  33. package/dist/helpers/dates.d.ts.map +1 -0
  34. package/dist/helpers/dates.js +7 -0
  35. package/dist/prompts/branches.d.ts +20 -0
  36. package/dist/prompts/branches.d.ts.map +1 -0
  37. package/dist/prompts/branches.js +89 -0
  38. package/dist/prompts/embeddables.d.ts +41 -0
  39. package/dist/prompts/embeddables.d.ts.map +1 -0
  40. package/dist/prompts/embeddables.js +161 -0
  41. package/dist/prompts/index.d.ts +7 -0
  42. package/dist/prompts/index.d.ts.map +1 -0
  43. package/dist/prompts/index.js +4 -0
  44. package/dist/prompts/projects.d.ts +22 -0
  45. package/dist/prompts/projects.d.ts.map +1 -0
  46. package/dist/prompts/projects.js +85 -0
  47. package/dist/proxy/server.d.ts.map +1 -1
  48. package/dist/proxy/server.js +10 -4
  49. package/package.json +6 -5
package/README.md CHANGED
@@ -11,21 +11,29 @@ A CLI for authoring and managing Embeddables locally using TypeScript/TSX.
11
11
 
12
12
  ## Installation
13
13
 
14
+ Install the CLI globally:
15
+
14
16
  ```bash
15
- npm install @embeddables/cli
17
+ npm install -g @embeddables/cli
16
18
  ```
17
19
 
18
20
  ## Quick Start
19
21
 
20
22
  ```bash
23
+ # Initialize a new project (will ask for your Embeddables project ID)
24
+ embeddables init
25
+
26
+ # Install dependencies
27
+ npm install
28
+
21
29
  # Login to your Embeddables account
22
- npx embeddables login
30
+ embeddables login
23
31
 
24
- # Pull an existing embeddable
25
- npx embeddables pull --id <embeddable-id>
32
+ # Pull an embeddable (shows a list to choose from)
33
+ embeddables pull
26
34
 
27
35
  # Start dev server with hot reload
28
- npx embeddables dev --id <embeddable-id>
36
+ embeddables dev
29
37
  ```
30
38
 
31
39
  This creates the following structure in your repo:
@@ -43,20 +51,42 @@ embeddables/
43
51
 
44
52
  ## Commands
45
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
+
46
66
  ### `embeddables login`
47
67
  Authenticate with your Embeddables account.
48
68
 
49
69
  ### `embeddables logout`
50
70
  Clear stored authentication.
51
71
 
52
- ### `embeddables pull --id <id>`
72
+ ### `embeddables pull`
53
73
  Fetch an embeddable from the cloud and reverse-compile it into local TSX files.
54
74
 
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
78
+
55
79
  Options:
56
- - `--id <id>` (required): Embeddable ID to pull
80
+ - `--id <id>`: Embeddable ID to pull (skips interactive selection)
57
81
  - `--branch <branch_id>`: Pull a specific branch version
58
82
  - `--fix`: Remove components with missing required props instead of erroring
59
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
+
60
90
  ### `embeddables build --id <id>`
61
91
  Compile TSX pages into the canonical JSON format.
62
92
 
@@ -35,7 +35,7 @@ export declare function getSupabaseClient(): SupabaseClient;
35
35
  /**
36
36
  * Get authenticated Supabase client (with stored session)
37
37
  */
38
- export declare function getAuthenticatedSupabaseClient(): SupabaseClient | null;
38
+ export declare function getAuthenticatedSupabaseClient(): Promise<SupabaseClient | null>;
39
39
  /**
40
40
  * Get access token for authenticated requests
41
41
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAGpE,eAAO,MAAM,YAAY,6CAA6C,CAAA;AACtE,eAAO,MAAM,iBAAiB,mDAAmD,CAAA;AAEjF,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,iBAAiB,EAAE,MAAM,CAAA;CAC1B;AAKD;;GAEG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,UAAU,GAAG,IAAI,CAUlD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAGxD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAIvC;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,OAAO,CAcpC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,cAAc,CAWlD;AAED;;GAEG;AACH,wBAAgB,8BAA8B,IAAI,cAAc,GAAG,IAAI,CActE;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,GAAG,IAAI,CAM9C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgB,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAGpE,eAAO,MAAM,YAAY,6CAA6C,CAAA;AACtE,eAAO,MAAM,iBAAiB,mDAAmD,CAAA;AAEjF,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,iBAAiB,EAAE,MAAM,CAAA;CAC1B;AAMD;;GAEG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,UAAU,GAAG,IAAI,CAUlD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAGxD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAIvC;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,OAAO,CAcpC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,cAAc,CAWlD;AAED;;GAEG;AACH,wBAAsB,8BAA8B,IAAI,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAcrF;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,GAAG,IAAI,CAM9C"}
@@ -1,10 +1,12 @@
1
1
  import fs from 'node:fs';
2
+ import os from 'node:os';
2
3
  import path from 'node:path';
3
4
  import { createClient } from '@supabase/supabase-js';
4
5
  // TODO: Move to environment variables
5
6
  export const SUPABASE_URL = 'https://ierxexdtyashuotcsjyo.supabase.co'; // Replace with actual Supabase URL
6
7
  export const SUPABASE_ANON_KEY = 'sb_publishable_Vt8-msWNtn2hhOY3AA6RsQ_ueTx79Vo'; // Replace with actual Supabase anon key
7
- const AUTH_DIR = path.join(process.cwd(), '.auth');
8
+ // Store auth globally in user's home directory
9
+ const AUTH_DIR = path.join(os.homedir(), '.embeddables');
8
10
  const AUTH_FILE = path.join(AUTH_DIR, 'auth.json');
9
11
  /**
10
12
  * Get the path to the auth config file
@@ -75,14 +77,14 @@ export function getSupabaseClient() {
75
77
  /**
76
78
  * Get authenticated Supabase client (with stored session)
77
79
  */
78
- export function getAuthenticatedSupabaseClient() {
80
+ export async function getAuthenticatedSupabaseClient() {
79
81
  const config = readAuthConfig();
80
82
  if (!config || !isLoggedIn()) {
81
83
  return null;
82
84
  }
83
85
  const client = getSupabaseClient();
84
86
  // Set the session manually
85
- client.auth.setSession({
87
+ await client.auth.setSession({
86
88
  access_token: config.access_token,
87
89
  refresh_token: config.refresh_token,
88
90
  });
package/dist/cli.js CHANGED
@@ -1,11 +1,14 @@
1
1
  import { Command } from 'commander';
2
2
  import pc from 'picocolors';
3
+ import { runBranch } from './commands/branch.js';
3
4
  import { runBuild } from './commands/build.js';
4
5
  import { runBuildWorkbench } from './commands/build-workbench.js';
5
6
  import { runDev } from './commands/dev.js';
7
+ import { runInit } from './commands/init.js';
6
8
  import { runLogin } from './commands/login.js';
7
9
  import { runLogout } from './commands/logout.js';
8
10
  import { runPull } from './commands/pull.js';
11
+ import { runSave } from './commands/save.js';
9
12
  // Make all console.warn output yellow
10
13
  const originalWarn = console.warn.bind(console);
11
14
  console.warn = (...args) => {
@@ -17,6 +20,14 @@ console.error = (...args) => {
17
20
  };
18
21
  const program = new Command();
19
22
  program.name('embeddables').description('Embeddables CLI').version('0.1.0');
23
+ program
24
+ .command('init')
25
+ .description('Initialize a new Embeddables project')
26
+ .option('--project-id <id>', 'Embeddables project ID')
27
+ .option('-y, --yes', 'Skip prompts and use defaults')
28
+ .action(async (opts) => {
29
+ await runInit({ projectId: opts.projectId, yes: opts.yes });
30
+ });
20
31
  program
21
32
  .command('build')
22
33
  .requiredOption('--id <id>', 'Embeddable ID')
@@ -31,15 +42,15 @@ program
31
42
  .option('--id <id>', 'Embeddable ID (will prompt if not provided)')
32
43
  .option('--pages <glob>', 'Pages glob')
33
44
  .option('--out <path>', 'Output json path')
34
- .option('--remote', 'Use remote engine (https://engine.embeddables.com)')
35
- .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')
36
47
  .option('--port <n>', 'Dev proxy port', '3000')
37
48
  .option('--overrideRoute <path>', 'Route to override in proxy (exact match, no wildcards yet)', '/init')
38
49
  .option('--pageKeyFrom <mode>', 'filename|export', 'filename')
39
50
  .action(async (opts) => {
40
- // --remote flag overrides --engine to use production engine
41
- if (opts.remote) {
42
- 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';
43
54
  }
44
55
  await runDev(opts);
45
56
  });
@@ -57,13 +68,38 @@ program
57
68
  });
58
69
  program
59
70
  .command('pull')
60
- .requiredOption('--id <id>', 'Embeddable ID to pull')
71
+ .description('Pull an embeddable from the cloud')
72
+ .option('--id <id>', 'Embeddable ID to pull (interactive selection if not provided)')
61
73
  .option('--out <path>', 'Output json path')
62
74
  .option('--branch <branch_id>', 'Embeddable branch ID')
63
75
  .option('--fix', 'Fix by removing components missing required props (warn instead of error)')
64
76
  .action(async (opts) => {
65
77
  await runPull(opts);
66
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
+ });
96
+ program
97
+ .command('branch')
98
+ .description('Switch to a different branch of an embeddable')
99
+ .option('--id <id>', 'Embeddable ID (will prompt if not provided)')
100
+ .action(async (opts) => {
101
+ await runBranch(opts);
102
+ });
67
103
  program
68
104
  .command('build-workbench')
69
105
  .description('Build Workbench for CDN deployment')
@@ -0,0 +1,4 @@
1
+ export declare function runBranch(opts: {
2
+ id?: string;
3
+ }): Promise<void>;
4
+ //# sourceMappingURL=branch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"branch.d.ts","sourceRoot":"","sources":["../../src/commands/branch.ts"],"names":[],"mappings":"AAKA,wBAAsB,SAAS,CAAC,IAAI,EAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,iBAgDpD"}
@@ -0,0 +1,46 @@
1
+ import pc from 'picocolors';
2
+ import { isLoggedIn } from '../auth/index.js';
3
+ import { runPull } from './pull.js';
4
+ import { promptForLocalEmbeddable, fetchBranches, promptForBranch } from '../prompts/index.js';
5
+ export async function runBranch(opts) {
6
+ // Check login status
7
+ if (!isLoggedIn()) {
8
+ console.error(pc.red('Not logged in.'));
9
+ console.log(pc.gray('Run "embeddables login" first.'));
10
+ process.exit(1);
11
+ }
12
+ // Get embeddable ID
13
+ let embeddableId = opts.id;
14
+ if (!embeddableId) {
15
+ const selected = await promptForLocalEmbeddable();
16
+ if (!selected) {
17
+ process.exit(1);
18
+ }
19
+ embeddableId = selected;
20
+ }
21
+ console.log('');
22
+ console.log(pc.cyan('Fetching branches...'));
23
+ // Fetch branches for this embeddable
24
+ const branches = await fetchBranches(embeddableId);
25
+ if (branches.length === 0) {
26
+ console.log(pc.yellow('No branches found for this embeddable.'));
27
+ console.log(pc.gray('Branches are created in the Embeddables Builder.'));
28
+ return;
29
+ }
30
+ console.log('');
31
+ // Prompt for branch selection
32
+ const selectedBranch = await promptForBranch(branches);
33
+ console.log('');
34
+ // Pull the selected branch
35
+ if (selectedBranch === null) {
36
+ // User selected "main" - pull without branch
37
+ console.log(pc.cyan('Switching to main (latest published version)...'));
38
+ console.log('');
39
+ await runPull({ id: embeddableId });
40
+ }
41
+ else {
42
+ console.log(pc.cyan(`Switching to branch: ${selectedBranch.name}...`));
43
+ console.log('');
44
+ await runPull({ id: embeddableId, branch: selectedBranch.id });
45
+ }
46
+ }
@@ -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)) {
@@ -17,12 +44,12 @@ async function discoverEmbeddables() {
17
44
  if (!entry.isDirectory())
18
45
  continue;
19
46
  const id = entry.name;
20
- const configPath = path.join(embeddablesDir, id, 'config.json');
47
+ const metadataPath = path.join(embeddablesDir, id, 'metadata.json');
21
48
  let title = null;
22
- if (fs.existsSync(configPath)) {
49
+ if (fs.existsSync(metadataPath)) {
23
50
  try {
24
- const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
25
- title = config.title || null;
51
+ const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf-8'));
52
+ title = metadata.title || null;
26
53
  }
27
54
  catch {
28
55
  // Ignore JSON parse errors
@@ -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`))}`);
@@ -0,0 +1,5 @@
1
+ export declare function runInit(opts: {
2
+ projectId?: string;
3
+ yes?: boolean;
4
+ }): Promise<void>;
5
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,245 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import pc from 'picocolors';
5
+ import prompts from 'prompts';
6
+ import { writeProjectConfig, readProjectConfig } from '../config/index.js';
7
+ import { isLoggedIn } from '../auth/index.js';
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
+ }
90
+ export async function runInit(opts) {
91
+ const cwd = process.cwd();
92
+ const gitignorePath = path.join(cwd, '.gitignore');
93
+ const embeddablesDir = path.join(cwd, 'embeddables');
94
+ console.log('');
95
+ console.log(pc.bold(pc.cyan(' Embeddables Project Setup')));
96
+ console.log('');
97
+ const existingConfig = readProjectConfig();
98
+ // Step 1: Get Embeddables project
99
+ let projectId = opts.projectId;
100
+ let selectedProjectTitle;
101
+ let selectedOrgId;
102
+ let selectedOrgTitle;
103
+ if (!projectId && !opts.yes) {
104
+ if (existingConfig?.project_id) {
105
+ console.log(pc.gray(` Using existing project ID: ${existingConfig.project_id}`));
106
+ projectId = existingConfig.project_id;
107
+ selectedProjectTitle = existingConfig.project_name || undefined;
108
+ selectedOrgId = existingConfig.org_id || undefined;
109
+ selectedOrgTitle = existingConfig.org_title || undefined;
110
+ }
111
+ else if (isLoggedIn()) {
112
+ // Fetch and show project list
113
+ console.log(pc.gray(' Fetching projects...'));
114
+ const selectedProject = await promptForProject({
115
+ allowSkip: true,
116
+ message: 'Select your Embeddables project:',
117
+ });
118
+ if (selectedProject) {
119
+ projectId = selectedProject.id;
120
+ selectedProjectTitle = selectedProject.title || undefined;
121
+ selectedOrgId = selectedProject.org_id || undefined;
122
+ selectedOrgTitle = selectedProject.org_title || undefined;
123
+ }
124
+ }
125
+ else {
126
+ // Not logged in - offer manual entry or skip
127
+ console.log(pc.gray(' (Login with "embeddables login" to select from your projects)'));
128
+ const response = await prompts({
129
+ type: 'text',
130
+ name: 'projectId',
131
+ message: 'Embeddables project ID (optional):',
132
+ hint: 'e.g., proj_abc123 - leave empty to skip',
133
+ }, {
134
+ onCancel: () => {
135
+ console.log(pc.gray('\n Cancelled.'));
136
+ process.exit(0);
137
+ },
138
+ });
139
+ projectId = response.projectId || undefined;
140
+ }
141
+ }
142
+ // Write embeddables.json config
143
+ if (projectId) {
144
+ writeProjectConfig({
145
+ org_id: selectedOrgId,
146
+ org_title: selectedOrgTitle,
147
+ project_id: projectId,
148
+ project_name: selectedProjectTitle,
149
+ });
150
+ console.log(pc.green(' ✓ Created embeddables.json'));
151
+ }
152
+ // Create or update .gitignore
153
+ const gitignoreEntries = ['**/.generated/', '**/.types/', '.DS_Store'];
154
+ let existingGitignore = '';
155
+ if (fs.existsSync(gitignorePath)) {
156
+ existingGitignore = fs.readFileSync(gitignorePath, 'utf8');
157
+ }
158
+ const newEntries = gitignoreEntries.filter((entry) => !existingGitignore.includes(entry.replace('/', '')));
159
+ if (newEntries.length > 0) {
160
+ const separator = existingGitignore && !existingGitignore.endsWith('\n') ? '\n' : '';
161
+ const embeddablesSection = existingGitignore
162
+ ? `${separator}\n# Embeddables\n${newEntries.join('\n')}\n`
163
+ : `# Embeddables\n${gitignoreEntries.join('\n')}\n`;
164
+ fs.writeFileSync(gitignorePath, existingGitignore + embeddablesSection, 'utf8');
165
+ if (existingGitignore) {
166
+ console.log(pc.green(' ✓ Updated .gitignore'));
167
+ }
168
+ else {
169
+ console.log(pc.green(' ✓ Created .gitignore'));
170
+ }
171
+ }
172
+ else {
173
+ console.log(pc.gray(' ✓ .gitignore already configured'));
174
+ }
175
+ // Create embeddables directory
176
+ if (!fs.existsSync(embeddablesDir)) {
177
+ fs.mkdirSync(embeddablesDir, { recursive: true });
178
+ console.log(pc.green(' ✓ Created embeddables/ directory'));
179
+ }
180
+ else {
181
+ console.log(pc.gray(' ✓ embeddables/ directory exists'));
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'));
225
+ // Print next steps
226
+ console.log('');
227
+ console.log(pc.bold(' Next steps:'));
228
+ console.log('');
229
+ console.log(pc.cyan(' 1. Login to Embeddables:'));
230
+ console.log(pc.white(' embeddables login'));
231
+ console.log('');
232
+ if (projectId) {
233
+ console.log(pc.cyan(' 2. Pull an embeddable:'));
234
+ console.log(pc.white(' embeddables pull'));
235
+ console.log(pc.gray(' (will show a list of embeddables in your project)'));
236
+ }
237
+ else {
238
+ console.log(pc.cyan(' 2. Pull an existing embeddable:'));
239
+ console.log(pc.white(' embeddables pull --id <embeddable-id>'));
240
+ }
241
+ console.log('');
242
+ console.log(pc.cyan(' 3. Start developing:'));
243
+ console.log(pc.white(' embeddables dev'));
244
+ console.log('');
245
+ }
@@ -1,5 +1,5 @@
1
1
  export declare function runPull(opts: {
2
- id: string;
2
+ id?: string;
3
3
  out?: string;
4
4
  branch?: string;
5
5
  fix?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../../src/commands/pull.ts"],"names":[],"mappings":"AAOA,wBAAsB,OAAO,CAAC,IAAI,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,iBA0G/F"}
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"}