@embeddables/cli 0.1.0 → 0.2.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/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 +20 -1
- 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.js +4 -4
- package/dist/commands/init.d.ts +6 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +188 -0
- package/dist/commands/pull.d.ts +1 -1
- package/dist/commands/pull.d.ts.map +1 -1
- package/dist/commands/pull.js +84 -3
- package/dist/config/index.d.ts +21 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +42 -0
- package/dist/constants.d.ts +6 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +4 -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 +21 -0
- package/dist/prompts/projects.d.ts.map +1 -0
- package/dist/prompts/projects.js +84 -0
- package/package.json +5 -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,8 +1,10 @@
|
|
|
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';
|
|
@@ -17,6 +19,15 @@ console.error = (...args) => {
|
|
|
17
19
|
};
|
|
18
20
|
const program = new Command();
|
|
19
21
|
program.name('embeddables').description('Embeddables CLI').version('0.1.0');
|
|
22
|
+
program
|
|
23
|
+
.command('init')
|
|
24
|
+
.description('Initialize a new Embeddables project')
|
|
25
|
+
.option('--name <name>', 'Project name')
|
|
26
|
+
.option('--project-id <id>', 'Embeddables project ID')
|
|
27
|
+
.option('-y, --yes', 'Skip prompts and use defaults')
|
|
28
|
+
.action(async (opts) => {
|
|
29
|
+
await runInit({ name: opts.name, projectId: opts.projectId, yes: opts.yes });
|
|
30
|
+
});
|
|
20
31
|
program
|
|
21
32
|
.command('build')
|
|
22
33
|
.requiredOption('--id <id>', 'Embeddable ID')
|
|
@@ -57,13 +68,21 @@ 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('branch')
|
|
81
|
+
.description('Switch to a different branch of an embeddable')
|
|
82
|
+
.option('--id <id>', 'Embeddable ID (will prompt if not provided)')
|
|
83
|
+
.action(async (opts) => {
|
|
84
|
+
await runBranch(opts);
|
|
85
|
+
});
|
|
67
86
|
program
|
|
68
87
|
.command('build-workbench')
|
|
69
88
|
.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.js
CHANGED
|
@@ -17,12 +17,12 @@ async function discoverEmbeddables() {
|
|
|
17
17
|
if (!entry.isDirectory())
|
|
18
18
|
continue;
|
|
19
19
|
const id = entry.name;
|
|
20
|
-
const
|
|
20
|
+
const metadataPath = path.join(embeddablesDir, id, 'metadata.json');
|
|
21
21
|
let title = null;
|
|
22
|
-
if (fs.existsSync(
|
|
22
|
+
if (fs.existsSync(metadataPath)) {
|
|
23
23
|
try {
|
|
24
|
-
const
|
|
25
|
-
title =
|
|
24
|
+
const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf-8'));
|
|
25
|
+
title = metadata.title || null;
|
|
26
26
|
}
|
|
27
27
|
catch {
|
|
28
28
|
// Ignore JSON parse errors
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAiBA,wBAAsB,OAAO,CAAC,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,iBAsMvF"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import pc from 'picocolors';
|
|
4
|
+
import prompts from 'prompts';
|
|
5
|
+
import { writeProjectConfig, readProjectConfig } from '../config/index.js';
|
|
6
|
+
import { isLoggedIn } from '../auth/index.js';
|
|
7
|
+
import { promptForProject } from '../prompts/index.js';
|
|
8
|
+
export async function runInit(opts) {
|
|
9
|
+
const cwd = process.cwd();
|
|
10
|
+
const packageJsonPath = path.join(cwd, 'package.json');
|
|
11
|
+
const gitignorePath = path.join(cwd, '.gitignore');
|
|
12
|
+
const embeddablesDir = path.join(cwd, 'embeddables');
|
|
13
|
+
console.log('');
|
|
14
|
+
console.log(pc.bold(pc.cyan(' Embeddables Project Setup')));
|
|
15
|
+
console.log('');
|
|
16
|
+
// Check if already initialized
|
|
17
|
+
let existingPackageJson = null;
|
|
18
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
19
|
+
try {
|
|
20
|
+
existingPackageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// Invalid JSON, we'll overwrite
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const existingConfig = readProjectConfig();
|
|
27
|
+
// Step 1: Get Embeddables project (this determines the default name)
|
|
28
|
+
let projectId = opts.projectId;
|
|
29
|
+
let selectedProjectTitle;
|
|
30
|
+
if (!projectId && !opts.yes) {
|
|
31
|
+
if (existingConfig?.project_id) {
|
|
32
|
+
console.log(pc.gray(` Using existing project ID: ${existingConfig.project_id}`));
|
|
33
|
+
projectId = existingConfig.project_id;
|
|
34
|
+
selectedProjectTitle = existingConfig.project_name || undefined;
|
|
35
|
+
}
|
|
36
|
+
else if (isLoggedIn()) {
|
|
37
|
+
// Fetch and show project list
|
|
38
|
+
console.log(pc.gray(' Fetching projects...'));
|
|
39
|
+
const selectedProject = await promptForProject({
|
|
40
|
+
allowSkip: true,
|
|
41
|
+
message: 'Select your Embeddables project:',
|
|
42
|
+
});
|
|
43
|
+
if (selectedProject) {
|
|
44
|
+
projectId = selectedProject.id;
|
|
45
|
+
selectedProjectTitle = selectedProject.title || undefined;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
// Not logged in - offer manual entry or skip
|
|
50
|
+
console.log(pc.gray(' (Login with "embeddables login" to select from your projects)'));
|
|
51
|
+
const response = await prompts({
|
|
52
|
+
type: 'text',
|
|
53
|
+
name: 'projectId',
|
|
54
|
+
message: 'Embeddables project ID (optional):',
|
|
55
|
+
hint: 'e.g., proj_abc123 - leave empty to skip',
|
|
56
|
+
}, {
|
|
57
|
+
onCancel: () => {
|
|
58
|
+
console.log(pc.gray('\n Cancelled.'));
|
|
59
|
+
process.exit(0);
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
projectId = response.projectId || undefined;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Step 2: Get project name (default to selected project title, or existing, or directory name)
|
|
66
|
+
let projectName = opts.name;
|
|
67
|
+
if (!projectName) {
|
|
68
|
+
if (existingPackageJson?.name) {
|
|
69
|
+
projectName = existingPackageJson.name;
|
|
70
|
+
console.log(pc.gray(` Using existing project name: ${projectName}`));
|
|
71
|
+
}
|
|
72
|
+
else if (opts.yes) {
|
|
73
|
+
// Use selected project title or directory name as default
|
|
74
|
+
projectName = selectedProjectTitle || path.basename(cwd);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
// Default to selected project title, then directory name
|
|
78
|
+
const defaultName = selectedProjectTitle || path.basename(cwd);
|
|
79
|
+
const response = await prompts({
|
|
80
|
+
type: 'text',
|
|
81
|
+
name: 'name',
|
|
82
|
+
message: 'Project name:',
|
|
83
|
+
initial: defaultName,
|
|
84
|
+
}, {
|
|
85
|
+
onCancel: () => {
|
|
86
|
+
console.log(pc.gray('\n Cancelled.'));
|
|
87
|
+
process.exit(0);
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
projectName = response.name;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (!projectName) {
|
|
94
|
+
console.error('Project name is required.');
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
// Create or update package.json
|
|
98
|
+
const packageJson = existingPackageJson || {
|
|
99
|
+
name: projectName,
|
|
100
|
+
version: '1.0.0',
|
|
101
|
+
type: 'module',
|
|
102
|
+
};
|
|
103
|
+
// Ensure name is set
|
|
104
|
+
if (!packageJson.name) {
|
|
105
|
+
packageJson.name = projectName;
|
|
106
|
+
}
|
|
107
|
+
// Ensure type is module
|
|
108
|
+
if (!packageJson.type) {
|
|
109
|
+
packageJson.type = 'module';
|
|
110
|
+
}
|
|
111
|
+
// Add @embeddables/cli as a dependency
|
|
112
|
+
if (!packageJson.dependencies) {
|
|
113
|
+
packageJson.dependencies = {};
|
|
114
|
+
}
|
|
115
|
+
if (!packageJson.dependencies['@embeddables/cli']) {
|
|
116
|
+
packageJson.dependencies['@embeddables/cli'] = '^0.1.0';
|
|
117
|
+
}
|
|
118
|
+
// Write package.json
|
|
119
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf8');
|
|
120
|
+
if (existingPackageJson) {
|
|
121
|
+
console.log(pc.green(' ✓ Updated package.json'));
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
console.log(pc.green(' ✓ Created package.json'));
|
|
125
|
+
}
|
|
126
|
+
// Write embeddables.json config
|
|
127
|
+
if (projectId) {
|
|
128
|
+
writeProjectConfig({
|
|
129
|
+
project_id: projectId,
|
|
130
|
+
project_name: selectedProjectTitle || projectName,
|
|
131
|
+
});
|
|
132
|
+
console.log(pc.green(' ✓ Created embeddables.json'));
|
|
133
|
+
}
|
|
134
|
+
// Create or update .gitignore
|
|
135
|
+
const gitignoreEntries = ['node_modules/', '**/.generated/', '.DS_Store'];
|
|
136
|
+
let existingGitignore = '';
|
|
137
|
+
if (fs.existsSync(gitignorePath)) {
|
|
138
|
+
existingGitignore = fs.readFileSync(gitignorePath, 'utf8');
|
|
139
|
+
}
|
|
140
|
+
const newEntries = gitignoreEntries.filter((entry) => !existingGitignore.includes(entry.replace('/', '')));
|
|
141
|
+
if (newEntries.length > 0) {
|
|
142
|
+
const separator = existingGitignore && !existingGitignore.endsWith('\n') ? '\n' : '';
|
|
143
|
+
const embeddablesSection = existingGitignore
|
|
144
|
+
? `${separator}\n# Embeddables\n${newEntries.join('\n')}\n`
|
|
145
|
+
: `# Embeddables\n${gitignoreEntries.join('\n')}\n`;
|
|
146
|
+
fs.writeFileSync(gitignorePath, existingGitignore + embeddablesSection, 'utf8');
|
|
147
|
+
if (existingGitignore) {
|
|
148
|
+
console.log(pc.green(' ✓ Updated .gitignore'));
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
console.log(pc.green(' ✓ Created .gitignore'));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
console.log(pc.gray(' ✓ .gitignore already configured'));
|
|
156
|
+
}
|
|
157
|
+
// Create embeddables directory
|
|
158
|
+
if (!fs.existsSync(embeddablesDir)) {
|
|
159
|
+
fs.mkdirSync(embeddablesDir, { recursive: true });
|
|
160
|
+
console.log(pc.green(' ✓ Created embeddables/ directory'));
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
console.log(pc.gray(' ✓ embeddables/ directory exists'));
|
|
164
|
+
}
|
|
165
|
+
// Print next steps
|
|
166
|
+
console.log('');
|
|
167
|
+
console.log(pc.bold(' Next steps:'));
|
|
168
|
+
console.log('');
|
|
169
|
+
console.log(pc.cyan(' 1. Install dependencies:'));
|
|
170
|
+
console.log(pc.white(' npm install'));
|
|
171
|
+
console.log('');
|
|
172
|
+
console.log(pc.cyan(' 2. Login to Embeddables:'));
|
|
173
|
+
console.log(pc.white(' npx embeddables login'));
|
|
174
|
+
console.log('');
|
|
175
|
+
if (projectId) {
|
|
176
|
+
console.log(pc.cyan(' 3. Pull an embeddable:'));
|
|
177
|
+
console.log(pc.white(' npx embeddables pull'));
|
|
178
|
+
console.log(pc.gray(' (will show a list of embeddables in your project)'));
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
console.log(pc.cyan(' 3. Pull an existing embeddable:'));
|
|
182
|
+
console.log(pc.white(' npx embeddables pull --id <embeddable-id>'));
|
|
183
|
+
}
|
|
184
|
+
console.log('');
|
|
185
|
+
console.log(pc.cyan(' 4. Start developing:'));
|
|
186
|
+
console.log(pc.white(' npx embeddables dev'));
|
|
187
|
+
console.log('');
|
|
188
|
+
}
|
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,iBAmMhG"}
|
package/dist/commands/pull.js
CHANGED
|
@@ -1,10 +1,50 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import pc from 'picocolors';
|
|
4
|
+
import prompts from 'prompts';
|
|
4
5
|
import { reverseCompile } from '../compiler/reverse.js';
|
|
5
|
-
import { getAccessToken } from '../auth/index.js';
|
|
6
|
+
import { getAccessToken, isLoggedIn } from '../auth/index.js';
|
|
7
|
+
import { getProjectId, writeProjectConfig } from '../config/index.js';
|
|
8
|
+
import { promptForProject, promptForEmbeddable, fetchEmbeddableMetadata } from '../prompts/index.js';
|
|
6
9
|
export async function runPull(opts) {
|
|
7
|
-
|
|
10
|
+
let embeddableId = opts.id;
|
|
11
|
+
// If no ID provided, try to get it interactively
|
|
12
|
+
if (!embeddableId) {
|
|
13
|
+
if (!isLoggedIn()) {
|
|
14
|
+
console.error(pc.red('No embeddable ID provided and not logged in.'));
|
|
15
|
+
console.log('');
|
|
16
|
+
console.log(pc.gray('Either:'));
|
|
17
|
+
console.log(pc.gray(' 1. Use --id <embeddable-id> to specify an embeddable'));
|
|
18
|
+
console.log(pc.gray(' 2. Run "embeddables login" first for interactive selection'));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
let projectId = getProjectId();
|
|
22
|
+
// If no project ID configured, prompt for project selection
|
|
23
|
+
if (!projectId) {
|
|
24
|
+
console.log(pc.cyan('Fetching projects...'));
|
|
25
|
+
const selectedProject = await promptForProject();
|
|
26
|
+
if (!selectedProject) {
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
projectId = selectedProject.id;
|
|
30
|
+
// Save the selected project to config
|
|
31
|
+
writeProjectConfig({
|
|
32
|
+
project_id: projectId,
|
|
33
|
+
project_name: selectedProject.title || undefined,
|
|
34
|
+
});
|
|
35
|
+
console.log(pc.green(`✓ Saved project to embeddables.json`));
|
|
36
|
+
console.log('');
|
|
37
|
+
}
|
|
38
|
+
console.log(pc.cyan('Fetching embeddables from project...'));
|
|
39
|
+
const selected = await promptForEmbeddable(projectId, {
|
|
40
|
+
message: 'Select an embeddable to pull:',
|
|
41
|
+
});
|
|
42
|
+
if (!selected) {
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
embeddableId = selected;
|
|
46
|
+
console.log('');
|
|
47
|
+
}
|
|
8
48
|
let url = `https://engine.embeddables.com/${embeddableId}?version=latest`;
|
|
9
49
|
if (opts.branch) {
|
|
10
50
|
url += `&embeddable_branch=${opts.branch}`;
|
|
@@ -44,6 +84,14 @@ export async function runPull(opts) {
|
|
|
44
84
|
fs.writeFileSync(versionedPath, flowJson, 'utf8');
|
|
45
85
|
console.log(pc.cyan(`✓ Saved embeddable JSON to ${versionedPath}`));
|
|
46
86
|
}
|
|
87
|
+
// Fetch and save flow metadata from DB (title, archived, created_at)
|
|
88
|
+
const metadata = await fetchEmbeddableMetadata(embeddableId);
|
|
89
|
+
if (metadata) {
|
|
90
|
+
const metadataPath = path.join('embeddables', embeddableId, 'metadata.json');
|
|
91
|
+
fs.mkdirSync(path.dirname(metadataPath), { recursive: true });
|
|
92
|
+
fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2) + '\n', 'utf8');
|
|
93
|
+
console.log(pc.cyan(`✓ Saved flow metadata to ${metadataPath}`));
|
|
94
|
+
}
|
|
47
95
|
// Clear existing pages, styles, computed-fields, actions, and global-components before generating new ones
|
|
48
96
|
const pagesDir = path.join('embeddables', embeddableId, 'pages');
|
|
49
97
|
const stylesDir = path.join('embeddables', embeddableId, 'styles');
|
|
@@ -88,7 +136,40 @@ export async function runPull(opts) {
|
|
|
88
136
|
console.log(`${pc.gray(`Cleared ${existingGlobalComponents.length} existing global component(s)`)}`);
|
|
89
137
|
}
|
|
90
138
|
// Run reverse compiler
|
|
91
|
-
|
|
139
|
+
try {
|
|
140
|
+
await reverseCompile(flow, embeddableId, { fix: opts.fix });
|
|
141
|
+
}
|
|
142
|
+
catch (compileError) {
|
|
143
|
+
// If fix mode wasn't already enabled, offer to retry with fix mode
|
|
144
|
+
if (!opts.fix && compileError instanceof Error) {
|
|
145
|
+
console.log('');
|
|
146
|
+
console.error(pc.red('Error during reverse compile:'));
|
|
147
|
+
console.error(pc.yellow(` ${compileError.message}`));
|
|
148
|
+
console.log('');
|
|
149
|
+
const response = await prompts({
|
|
150
|
+
type: 'confirm',
|
|
151
|
+
name: 'fix',
|
|
152
|
+
message: 'Would you like to retry with auto-fix enabled? (removes problematic components)',
|
|
153
|
+
initial: true,
|
|
154
|
+
}, {
|
|
155
|
+
onCancel: () => {
|
|
156
|
+
process.exit(1);
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
if (response.fix) {
|
|
160
|
+
console.log('');
|
|
161
|
+
console.log(pc.cyan('Retrying with auto-fix enabled...'));
|
|
162
|
+
console.log('');
|
|
163
|
+
await reverseCompile(flow, embeddableId, { fix: true });
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
throw compileError;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
92
173
|
}
|
|
93
174
|
catch (error) {
|
|
94
175
|
console.error('Error pulling embeddable:', error);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface ProjectConfig {
|
|
2
|
+
project_id?: string;
|
|
3
|
+
project_name?: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Get the path to the project config file
|
|
7
|
+
*/
|
|
8
|
+
export declare function getConfigFilePath(): string;
|
|
9
|
+
/**
|
|
10
|
+
* Read project config from file
|
|
11
|
+
*/
|
|
12
|
+
export declare function readProjectConfig(): ProjectConfig | null;
|
|
13
|
+
/**
|
|
14
|
+
* Write project config to file
|
|
15
|
+
*/
|
|
16
|
+
export declare function writeProjectConfig(config: ProjectConfig): void;
|
|
17
|
+
/**
|
|
18
|
+
* Get the project ID from config
|
|
19
|
+
*/
|
|
20
|
+
export declare function getProjectId(): string | null;
|
|
21
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAID;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,aAAa,GAAG,IAAI,CAWxD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAQ9D;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,MAAM,GAAG,IAAI,CAG5C"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
const CONFIG_FILE = 'embeddables.json';
|
|
4
|
+
/**
|
|
5
|
+
* Get the path to the project config file
|
|
6
|
+
*/
|
|
7
|
+
export function getConfigFilePath() {
|
|
8
|
+
return path.join(process.cwd(), CONFIG_FILE);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Read project config from file
|
|
12
|
+
*/
|
|
13
|
+
export function readProjectConfig() {
|
|
14
|
+
const configPath = getConfigFilePath();
|
|
15
|
+
try {
|
|
16
|
+
if (!fs.existsSync(configPath)) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
20
|
+
return JSON.parse(content);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Write project config to file
|
|
28
|
+
*/
|
|
29
|
+
export function writeProjectConfig(config) {
|
|
30
|
+
const configPath = getConfigFilePath();
|
|
31
|
+
// Merge with existing config
|
|
32
|
+
const existing = readProjectConfig() || {};
|
|
33
|
+
const merged = { ...existing, ...config };
|
|
34
|
+
fs.writeFileSync(configPath, JSON.stringify(merged, null, 2) + '\n', 'utf8');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Get the project ID from config
|
|
38
|
+
*/
|
|
39
|
+
export function getProjectId() {
|
|
40
|
+
const config = readProjectConfig();
|
|
41
|
+
return config?.project_id || null;
|
|
42
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY;;;CAGf,CAAA;AAEV,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,OAAO,YAAY,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dates.d.ts","sourceRoot":"","sources":["../../src/helpers/dates.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAGtD"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { BranchStatus } from '../constants.js';
|
|
2
|
+
export interface BranchInfo {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
status: BranchStatus;
|
|
6
|
+
created_at: string;
|
|
7
|
+
origin_version: number | null;
|
|
8
|
+
origin_branch: string | null;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Fetch all branches for an embeddable from Supabase
|
|
12
|
+
*/
|
|
13
|
+
export declare function fetchBranches(flowId: string): Promise<BranchInfo[]>;
|
|
14
|
+
/**
|
|
15
|
+
* Prompt the user to select a branch
|
|
16
|
+
* Returns null if user selects "main" or cancels
|
|
17
|
+
* The "main" option is always included at the top
|
|
18
|
+
*/
|
|
19
|
+
export declare function promptForBranch(branches: BranchInfo[]): Promise<BranchInfo | null>;
|
|
20
|
+
//# sourceMappingURL=branches.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"branches.d.ts","sourceRoot":"","sources":["../../src/prompts/branches.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAG9C,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,YAAY,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CA8BzE;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CA8DxF"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
import prompts from 'prompts';
|
|
3
|
+
import { getAuthenticatedSupabaseClient } from '../auth/index.js';
|
|
4
|
+
import { BranchStatus } from '../constants.js';
|
|
5
|
+
import { formatDate } from '../helpers/dates.js';
|
|
6
|
+
/**
|
|
7
|
+
* Fetch all branches for an embeddable from Supabase
|
|
8
|
+
*/
|
|
9
|
+
export async function fetchBranches(flowId) {
|
|
10
|
+
const supabase = await getAuthenticatedSupabaseClient();
|
|
11
|
+
if (!supabase) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const { data, error } = await supabase
|
|
16
|
+
.from('branches')
|
|
17
|
+
.select('id, name, status, created_at, origin_version, origin_branch')
|
|
18
|
+
.eq('flow_id', flowId)
|
|
19
|
+
.order('created_at', { ascending: false });
|
|
20
|
+
if (error) {
|
|
21
|
+
console.warn(pc.yellow(`Could not fetch branches: ${error.message}`));
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
return (data || []).map((row) => ({
|
|
25
|
+
id: row.id,
|
|
26
|
+
name: row.name,
|
|
27
|
+
status: row.status,
|
|
28
|
+
created_at: row.created_at,
|
|
29
|
+
origin_version: row.origin_version,
|
|
30
|
+
origin_branch: row.origin_branch,
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
console.warn(pc.yellow(`Could not fetch branches: ${err}`));
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Prompt the user to select a branch
|
|
40
|
+
* Returns null if user selects "main" or cancels
|
|
41
|
+
* The "main" option is always included at the top
|
|
42
|
+
*/
|
|
43
|
+
export async function promptForBranch(branches) {
|
|
44
|
+
// Separate active and merged branches
|
|
45
|
+
const activeBranches = branches.filter((b) => b.status === BranchStatus.ACTIVE);
|
|
46
|
+
const mergedBranches = branches.filter((b) => b.status === BranchStatus.MERGED);
|
|
47
|
+
const choices = [];
|
|
48
|
+
// Add "main" option first
|
|
49
|
+
choices.push({
|
|
50
|
+
title: pc.bold('main'),
|
|
51
|
+
value: 'main',
|
|
52
|
+
description: 'Latest published version',
|
|
53
|
+
});
|
|
54
|
+
// Add active branches
|
|
55
|
+
for (const branch of activeBranches) {
|
|
56
|
+
const date = formatDate(branch.created_at);
|
|
57
|
+
choices.push({
|
|
58
|
+
title: branch.name,
|
|
59
|
+
value: branch.id,
|
|
60
|
+
description: `Active · Created ${date}`,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
// Add merged branches (dimmed)
|
|
64
|
+
for (const branch of mergedBranches) {
|
|
65
|
+
const date = formatDate(branch.created_at);
|
|
66
|
+
choices.push({
|
|
67
|
+
title: pc.dim(branch.name),
|
|
68
|
+
value: branch.id,
|
|
69
|
+
description: pc.dim(`Merged · Created ${date}`),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
const response = await prompts({
|
|
73
|
+
type: 'autocomplete',
|
|
74
|
+
name: 'branchId',
|
|
75
|
+
message: 'Select a branch:',
|
|
76
|
+
choices,
|
|
77
|
+
suggest: (input, choices) => Promise.resolve(choices.filter((c) => c.value === 'main' ||
|
|
78
|
+
(c.title?.toLowerCase().includes(input.toLowerCase()) ?? false) ||
|
|
79
|
+
String(c.value).toLowerCase().includes(input.toLowerCase()))),
|
|
80
|
+
}, {
|
|
81
|
+
onCancel: () => {
|
|
82
|
+
process.exit(0);
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
if (!response.branchId || response.branchId === 'main') {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
return branches.find((b) => b.id === response.branchId) || null;
|
|
89
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export interface EmbeddableInfo {
|
|
2
|
+
id: string;
|
|
3
|
+
title: string | null;
|
|
4
|
+
}
|
|
5
|
+
export interface EmbeddableMetadata {
|
|
6
|
+
title: string | null;
|
|
7
|
+
archived: boolean;
|
|
8
|
+
created_at: string | null;
|
|
9
|
+
}
|
|
10
|
+
export interface LocalEmbeddable {
|
|
11
|
+
id: string;
|
|
12
|
+
title: string | null;
|
|
13
|
+
}
|
|
14
|
+
export interface PromptForEmbeddableOptions {
|
|
15
|
+
/** Custom message for the prompt */
|
|
16
|
+
message?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Fetch all embeddables for a project from Supabase
|
|
20
|
+
*/
|
|
21
|
+
export declare function fetchProjectEmbeddables(projectId: string): Promise<EmbeddableInfo[]>;
|
|
22
|
+
/**
|
|
23
|
+
* Fetch metadata for a single embeddable from the flows table.
|
|
24
|
+
* Returns title, archived, and created_at from the DB (not from the version-specific embeddable.json).
|
|
25
|
+
*/
|
|
26
|
+
export declare function fetchEmbeddableMetadata(embeddableId: string): Promise<EmbeddableMetadata | null>;
|
|
27
|
+
/**
|
|
28
|
+
* Prompt the user to select an embeddable from a project
|
|
29
|
+
* Returns null if no embeddables found or user cancels
|
|
30
|
+
*/
|
|
31
|
+
export declare function promptForEmbeddable(projectId: string, options?: PromptForEmbeddableOptions): Promise<string | null>;
|
|
32
|
+
/**
|
|
33
|
+
* Discover embeddables in the local embeddables/ directory
|
|
34
|
+
*/
|
|
35
|
+
export declare function discoverLocalEmbeddables(): Promise<LocalEmbeddable[]>;
|
|
36
|
+
/**
|
|
37
|
+
* Prompt the user to select a local embeddable
|
|
38
|
+
* Returns null if no embeddables found or user cancels
|
|
39
|
+
*/
|
|
40
|
+
export declare function promptForLocalEmbeddable(options?: PromptForEmbeddableOptions): Promise<string | null>;
|
|
41
|
+
//# sourceMappingURL=embeddables.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embeddables.d.ts","sourceRoot":"","sources":["../../src/prompts/embeddables.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,QAAQ,EAAE,OAAO,CAAA;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB;AAED,MAAM,WAAW,0BAA0B;IACzC,oCAAoC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CA0B1F;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CA2BpC;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAuCxB;AAED;;GAEG;AACH,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAqC3E;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAuCxB"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import pc from 'picocolors';
|
|
4
|
+
import prompts from 'prompts';
|
|
5
|
+
import { getAuthenticatedSupabaseClient } from '../auth/index.js';
|
|
6
|
+
/**
|
|
7
|
+
* Fetch all embeddables for a project from Supabase
|
|
8
|
+
*/
|
|
9
|
+
export async function fetchProjectEmbeddables(projectId) {
|
|
10
|
+
const supabase = await getAuthenticatedSupabaseClient();
|
|
11
|
+
if (!supabase) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const { data, error } = await supabase
|
|
16
|
+
.from('flows')
|
|
17
|
+
.select('id, title')
|
|
18
|
+
.eq('project_id', projectId)
|
|
19
|
+
.order('title', { ascending: true });
|
|
20
|
+
if (error) {
|
|
21
|
+
console.warn(pc.yellow(`Could not fetch embeddables: ${error.message}`));
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
return (data || []).map((row) => ({
|
|
25
|
+
id: row.id,
|
|
26
|
+
title: row.title || null,
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
console.warn(pc.yellow(`Could not fetch embeddables: ${err}`));
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Fetch metadata for a single embeddable from the flows table.
|
|
36
|
+
* Returns title, archived, and created_at from the DB (not from the version-specific embeddable.json).
|
|
37
|
+
*/
|
|
38
|
+
export async function fetchEmbeddableMetadata(embeddableId) {
|
|
39
|
+
const supabase = await getAuthenticatedSupabaseClient();
|
|
40
|
+
if (!supabase) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const { data, error } = await supabase
|
|
45
|
+
.from('flows')
|
|
46
|
+
.select('title, archived, created_at')
|
|
47
|
+
.eq('id', embeddableId)
|
|
48
|
+
.single();
|
|
49
|
+
if (error) {
|
|
50
|
+
console.warn(pc.yellow(`Could not fetch embeddable metadata: ${error.message}`));
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
title: data.title || null,
|
|
55
|
+
archived: data.archived ?? false,
|
|
56
|
+
created_at: data.created_at || null,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
console.warn(pc.yellow(`Could not fetch embeddable metadata: ${err}`));
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Prompt the user to select an embeddable from a project
|
|
66
|
+
* Returns null if no embeddables found or user cancels
|
|
67
|
+
*/
|
|
68
|
+
export async function promptForEmbeddable(projectId, options = {}) {
|
|
69
|
+
const { message = 'Select an embeddable:' } = options;
|
|
70
|
+
const embeddables = await fetchProjectEmbeddables(projectId);
|
|
71
|
+
if (embeddables.length === 0) {
|
|
72
|
+
console.log(pc.yellow('No embeddables found in this project.'));
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const choices = embeddables.map((e) => ({
|
|
76
|
+
title: e.title || e.id,
|
|
77
|
+
description: e.id,
|
|
78
|
+
value: e.id,
|
|
79
|
+
}));
|
|
80
|
+
const response = await prompts({
|
|
81
|
+
type: 'autocomplete',
|
|
82
|
+
name: 'id',
|
|
83
|
+
message,
|
|
84
|
+
choices,
|
|
85
|
+
suggest: (input, choices) => Promise.resolve(choices.filter((c) => (c.title?.toLowerCase().includes(input.toLowerCase()) ?? false) ||
|
|
86
|
+
String(c.value).toLowerCase().includes(input.toLowerCase()))),
|
|
87
|
+
}, {
|
|
88
|
+
onCancel: () => {
|
|
89
|
+
process.exit(0);
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
return response.id || null;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Discover embeddables in the local embeddables/ directory
|
|
96
|
+
*/
|
|
97
|
+
export async function discoverLocalEmbeddables() {
|
|
98
|
+
const embeddablesDir = 'embeddables';
|
|
99
|
+
if (!fs.existsSync(embeddablesDir)) {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
const entries = fs.readdirSync(embeddablesDir, { withFileTypes: true });
|
|
103
|
+
const embeddables = [];
|
|
104
|
+
for (const entry of entries) {
|
|
105
|
+
if (!entry.isDirectory())
|
|
106
|
+
continue;
|
|
107
|
+
const id = entry.name;
|
|
108
|
+
const metadataPath = path.join(embeddablesDir, id, 'metadata.json');
|
|
109
|
+
let title = null;
|
|
110
|
+
if (fs.existsSync(metadataPath)) {
|
|
111
|
+
try {
|
|
112
|
+
const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf-8'));
|
|
113
|
+
title = metadata.title || null;
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// Ignore JSON parse errors
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
embeddables.push({ id, title });
|
|
120
|
+
}
|
|
121
|
+
// Sort by title first, then by id
|
|
122
|
+
return embeddables.sort((a, b) => {
|
|
123
|
+
if (a.title && !b.title)
|
|
124
|
+
return -1;
|
|
125
|
+
if (!a.title && b.title)
|
|
126
|
+
return 1;
|
|
127
|
+
const aLabel = a.title || a.id;
|
|
128
|
+
const bLabel = b.title || b.id;
|
|
129
|
+
return aLabel.localeCompare(bLabel);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Prompt the user to select a local embeddable
|
|
134
|
+
* Returns null if no embeddables found or user cancels
|
|
135
|
+
*/
|
|
136
|
+
export async function promptForLocalEmbeddable(options = {}) {
|
|
137
|
+
const { message = 'Select an embeddable:' } = options;
|
|
138
|
+
const embeddables = await discoverLocalEmbeddables();
|
|
139
|
+
if (embeddables.length === 0) {
|
|
140
|
+
console.error(pc.red('No embeddables found in the embeddables/ directory.'));
|
|
141
|
+
console.log(pc.gray('Run `embeddables pull` to pull an embeddable first.'));
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
const choices = embeddables.map((e) => ({
|
|
145
|
+
title: e.title ? `${e.title} (${e.id})` : e.id,
|
|
146
|
+
value: e.id,
|
|
147
|
+
}));
|
|
148
|
+
const response = await prompts({
|
|
149
|
+
type: 'autocomplete',
|
|
150
|
+
name: 'id',
|
|
151
|
+
message,
|
|
152
|
+
choices,
|
|
153
|
+
suggest: (input, choices) => Promise.resolve(choices.filter((c) => (c.title?.toLowerCase().includes(input.toLowerCase()) ?? false) ||
|
|
154
|
+
String(c.value).toLowerCase().includes(input.toLowerCase()))),
|
|
155
|
+
}, {
|
|
156
|
+
onCancel: () => {
|
|
157
|
+
process.exit(0);
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
return response.id || null;
|
|
161
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { fetchProjects, promptForProject } from './projects.js';
|
|
2
|
+
export type { ProjectInfo, PromptForProjectOptions } from './projects.js';
|
|
3
|
+
export { fetchProjectEmbeddables, fetchEmbeddableMetadata, promptForEmbeddable, discoverLocalEmbeddables, promptForLocalEmbeddable, } from './embeddables.js';
|
|
4
|
+
export type { EmbeddableInfo, EmbeddableMetadata, LocalEmbeddable, PromptForEmbeddableOptions, } from './embeddables.js';
|
|
5
|
+
export { fetchBranches, promptForBranch } from './branches.js';
|
|
6
|
+
export type { BranchInfo } from './branches.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/prompts/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAC/D,YAAY,EAAE,WAAW,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAA;AAEzE,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,mBAAmB,EACnB,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,kBAAkB,CAAA;AACzB,YAAY,EACV,cAAc,EACd,kBAAkB,EAClB,eAAe,EACf,0BAA0B,GAC3B,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAC9D,YAAY,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Centralized prompts for CLI commands
|
|
2
|
+
export { fetchProjects, promptForProject } from './projects.js';
|
|
3
|
+
export { fetchProjectEmbeddables, fetchEmbeddableMetadata, promptForEmbeddable, discoverLocalEmbeddables, promptForLocalEmbeddable, } from './embeddables.js';
|
|
4
|
+
export { fetchBranches, promptForBranch } from './branches.js';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface ProjectInfo {
|
|
2
|
+
id: string;
|
|
3
|
+
title: string | null;
|
|
4
|
+
org_title: string | null;
|
|
5
|
+
}
|
|
6
|
+
export interface PromptForProjectOptions {
|
|
7
|
+
/** Include a "Skip for now" option (default: false) */
|
|
8
|
+
allowSkip?: boolean;
|
|
9
|
+
/** Custom message for the prompt (default: "Select a project:") */
|
|
10
|
+
message?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Fetch all non-archived projects from Supabase
|
|
14
|
+
*/
|
|
15
|
+
export declare function fetchProjects(): Promise<ProjectInfo[]>;
|
|
16
|
+
/**
|
|
17
|
+
* Prompt the user to select a project from the list
|
|
18
|
+
* Returns null if no projects found, user cancels, or user selects "skip"
|
|
19
|
+
*/
|
|
20
|
+
export declare function promptForProject(options?: PromptForProjectOptions): Promise<ProjectInfo | null>;
|
|
21
|
+
//# sourceMappingURL=projects.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../src/prompts/projects.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,MAAM,WAAW,uBAAuB;IACtC,uDAAuD;IACvD,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;GAEG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAuC5D;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAqD7B"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
import prompts from 'prompts';
|
|
3
|
+
import { getAuthenticatedSupabaseClient } from '../auth/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* Fetch all non-archived projects from Supabase
|
|
6
|
+
*/
|
|
7
|
+
export async function fetchProjects() {
|
|
8
|
+
const supabase = await getAuthenticatedSupabaseClient();
|
|
9
|
+
if (!supabase) {
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
const { data, error } = await supabase
|
|
14
|
+
.from('projects')
|
|
15
|
+
.select(`
|
|
16
|
+
id,
|
|
17
|
+
title,
|
|
18
|
+
group_id,
|
|
19
|
+
groups (
|
|
20
|
+
title
|
|
21
|
+
)
|
|
22
|
+
`)
|
|
23
|
+
.not('archived', 'is', 'true')
|
|
24
|
+
.order('title', { ascending: true });
|
|
25
|
+
if (error) {
|
|
26
|
+
console.warn(pc.yellow(`Could not fetch projects: ${error.message}`));
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
return (data || []).map((row) => ({
|
|
30
|
+
id: row.id,
|
|
31
|
+
title: row.title || null,
|
|
32
|
+
org_title:
|
|
33
|
+
// For some reason, I can't get Supabase typings to return this as an object
|
|
34
|
+
// (not an array of objects) so I have to convert it manually
|
|
35
|
+
row.groups?.title || null,
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
console.warn(pc.yellow(`Could not fetch projects: ${err}`));
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Prompt the user to select a project from the list
|
|
45
|
+
* Returns null if no projects found, user cancels, or user selects "skip"
|
|
46
|
+
*/
|
|
47
|
+
export async function promptForProject(options = {}) {
|
|
48
|
+
const { allowSkip = false, message = 'Select a project:' } = options;
|
|
49
|
+
const projects = await fetchProjects();
|
|
50
|
+
if (projects.length === 0) {
|
|
51
|
+
console.log(pc.yellow('No projects found.'));
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const choices = projects.map((p) => ({
|
|
55
|
+
title: p.title || p.id,
|
|
56
|
+
description: p.org_title ? `${p.org_title} (${p.id})` : p.id,
|
|
57
|
+
value: p.id,
|
|
58
|
+
}));
|
|
59
|
+
if (allowSkip) {
|
|
60
|
+
choices.push({
|
|
61
|
+
title: pc.dim('Skip for now'),
|
|
62
|
+
description: 'You can set this later',
|
|
63
|
+
value: '__skip__',
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
const response = await prompts({
|
|
67
|
+
type: 'autocomplete',
|
|
68
|
+
name: 'id',
|
|
69
|
+
message,
|
|
70
|
+
choices,
|
|
71
|
+
suggest: (input, choices) => Promise.resolve(choices.filter((c) => c.value === '__skip__' ||
|
|
72
|
+
(c.title?.toLowerCase().includes(input.toLowerCase()) ?? false) ||
|
|
73
|
+
String(c.value).toLowerCase().includes(input.toLowerCase()) ||
|
|
74
|
+
c.description?.toLowerCase().includes(input.toLowerCase()))),
|
|
75
|
+
}, {
|
|
76
|
+
onCancel: () => {
|
|
77
|
+
process.exit(0);
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
if (!response.id || response.id === '__skip__') {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
return projects.find((p) => p.id === response.id) || null;
|
|
84
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@embeddables/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"embeddables": "./bin/embeddables.mjs"
|
|
@@ -47,19 +47,22 @@
|
|
|
47
47
|
"@babel/parser": "^7.26.0",
|
|
48
48
|
"@babel/traverse": "^7.26.0",
|
|
49
49
|
"@supabase/supabase-js": "^2.39.0",
|
|
50
|
+
"@tailwindcss/postcss": "^4.1.18",
|
|
51
|
+
"autoprefixer": "^10.4.23",
|
|
50
52
|
"chokidar": "^3.6.0",
|
|
51
53
|
"commander": "^12.1.0",
|
|
52
54
|
"cssjson": "^2.1.3",
|
|
55
|
+
"esbuild": "^0.25.0",
|
|
53
56
|
"express": "^4.19.2",
|
|
54
57
|
"fast-glob": "^3.3.2",
|
|
55
58
|
"http-proxy-middleware": "^3.0.3",
|
|
56
59
|
"picocolors": "^1.1.0",
|
|
60
|
+
"postcss": "^8.5.6",
|
|
57
61
|
"prompts": "^2.4.2",
|
|
58
62
|
"react": "^19.2.3",
|
|
59
63
|
"react-dom": "^19.2.3"
|
|
60
64
|
},
|
|
61
65
|
"devDependencies": {
|
|
62
|
-
"@tailwindcss/postcss": "^4.1.18",
|
|
63
66
|
"@types/babel__generator": "^7.27.0",
|
|
64
67
|
"@types/babel__traverse": "^7.28.0",
|
|
65
68
|
"@types/express": "^4.17.21",
|
|
@@ -67,9 +70,6 @@
|
|
|
67
70
|
"@types/prompts": "^2.4.9",
|
|
68
71
|
"@types/react": "^19.2.8",
|
|
69
72
|
"@vitest/coverage-v8": "^2.1.8",
|
|
70
|
-
"autoprefixer": "^10.4.23",
|
|
71
|
-
"esbuild": "^0.25.0",
|
|
72
|
-
"postcss": "^8.5.6",
|
|
73
73
|
"prettier": "^3.8.1",
|
|
74
74
|
"tailwindcss": "^4.1.18",
|
|
75
75
|
"tsx": "^4.19.2",
|