@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.
- package/.cursor/rules/embeddables-cli.md +679 -0
- package/README.md +37 -7
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +5 -3
- package/dist/cli.js +42 -6
- package/dist/commands/branch.d.ts +4 -0
- package/dist/commands/branch.d.ts.map +1 -0
- package/dist/commands/branch.js +46 -0
- package/dist/commands/dev.d.ts +1 -1
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +48 -9
- package/dist/commands/init.d.ts +5 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +245 -0
- package/dist/commands/pull.d.ts +1 -1
- package/dist/commands/pull.d.ts.map +1 -1
- package/dist/commands/pull.js +104 -3
- package/dist/commands/save.d.ts +8 -0
- package/dist/commands/save.d.ts.map +1 -0
- package/dist/commands/save.js +269 -0
- package/dist/compiler/index.d.ts.map +1 -1
- package/dist/compiler/index.js +4 -0
- package/dist/compiler/reverse.d.ts.map +1 -1
- package/dist/compiler/reverse.js +4 -10
- package/dist/config/index.d.ts +23 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +42 -0
- package/dist/constants.d.ts +7 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +6 -0
- package/dist/helpers/dates.d.ts +5 -0
- package/dist/helpers/dates.d.ts.map +1 -0
- package/dist/helpers/dates.js +7 -0
- package/dist/prompts/branches.d.ts +20 -0
- package/dist/prompts/branches.d.ts.map +1 -0
- package/dist/prompts/branches.js +89 -0
- package/dist/prompts/embeddables.d.ts +41 -0
- package/dist/prompts/embeddables.d.ts.map +1 -0
- package/dist/prompts/embeddables.js +161 -0
- package/dist/prompts/index.d.ts +7 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +4 -0
- package/dist/prompts/projects.d.ts +22 -0
- package/dist/prompts/projects.d.ts.map +1 -0
- package/dist/prompts/projects.js +85 -0
- package/dist/proxy/server.d.ts.map +1 -1
- package/dist/proxy/server.js +10 -4
- 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
|
-
|
|
30
|
+
embeddables login
|
|
23
31
|
|
|
24
|
-
# Pull an
|
|
25
|
-
|
|
32
|
+
# Pull an embeddable (shows a list to choose from)
|
|
33
|
+
embeddables pull
|
|
26
34
|
|
|
27
35
|
# Start dev server with hot reload
|
|
28
|
-
|
|
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
|
|
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
|
|
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
|
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -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
|
*/
|
package/dist/auth/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"
|
|
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"}
|
package/dist/auth/index.js
CHANGED
|
@@ -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
|
-
|
|
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('--
|
|
35
|
-
.option('--engine <url>', 'Engine origin', '
|
|
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
|
-
// --
|
|
41
|
-
if (opts.
|
|
42
|
-
opts.engine = '
|
|
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
|
-
.
|
|
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 @@
|
|
|
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
|
+
}
|
package/dist/commands/dev.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"
|
|
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"}
|
package/dist/commands/dev.js
CHANGED
|
@@ -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
|
|
47
|
+
const metadataPath = path.join(embeddablesDir, id, 'metadata.json');
|
|
21
48
|
let title = null;
|
|
22
|
-
if (fs.existsSync(
|
|
49
|
+
if (fs.existsSync(metadataPath)) {
|
|
23
50
|
try {
|
|
24
|
-
const
|
|
25
|
-
title =
|
|
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
|
|
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.
|
|
143
|
-
console.log(`${pc.bold(pc.
|
|
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.
|
|
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 @@
|
|
|
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
|
+
}
|
package/dist/commands/pull.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../../src/commands/pull.ts"],"names":[],"mappings":"
|
|
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"}
|