@embeddables/cli 0.7.8 → 0.7.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +5 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +6 -0
- package/dist/commands/pull.d.ts +2 -0
- package/dist/commands/pull.d.ts.map +1 -1
- package/dist/commands/pull.js +20 -5
- package/dist/prompts/embeddables.d.ts +2 -0
- package/dist/prompts/embeddables.d.ts.map +1 -1
- package/dist/prompts/embeddables.js +39 -4
- package/dist/prompts/index.d.ts +2 -0
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +1 -0
- package/dist/prompts/versions.d.ts +18 -0
- package/dist/prompts/versions.d.ts.map +1 -0
- package/dist/prompts/versions.js +99 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -109,13 +109,17 @@ program
|
|
|
109
109
|
.description('Pull an embeddable from the cloud')
|
|
110
110
|
.option('-i, --id <id>', 'Embeddable ID to pull (interactive selection if not provided)')
|
|
111
111
|
.option('-o, --out <path>', 'Output json path')
|
|
112
|
+
.option('--version <version>', 'Version to pull (number or "latest"); if omitted, choose from 100 most recent')
|
|
112
113
|
.option('-b, --branch <branch_id>', 'Embeddable branch ID')
|
|
113
114
|
.option('-f, --fix', 'Fix by removing components missing required props (warn instead of error)')
|
|
114
115
|
.option('-p, --preserve', 'Preserve component order in config for forward compile')
|
|
115
116
|
.action(async (opts) => {
|
|
116
117
|
await requireLogin('pull');
|
|
117
118
|
await setSentryContextForCommand();
|
|
118
|
-
await runPull(
|
|
119
|
+
await runPull({
|
|
120
|
+
...opts,
|
|
121
|
+
version: opts.version,
|
|
122
|
+
});
|
|
119
123
|
});
|
|
120
124
|
program
|
|
121
125
|
.command('save')
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAwHA,wBAAsB,OAAO,CAAC,IAAI,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAwHA,wBAAsB,OAAO,CAAC,IAAI,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,iBAgPxE"}
|
package/dist/commands/init.js
CHANGED
|
@@ -194,6 +194,12 @@ export async function runInit(opts) {
|
|
|
194
194
|
else {
|
|
195
195
|
stdout.print(pc.gray(' ✓ embeddables/ directory exists'));
|
|
196
196
|
}
|
|
197
|
+
// Remove old .claudefiles folder (replaced by .cursor/ and .claude/)
|
|
198
|
+
const claudefilesDir = path.join(cwd, '.claudefiles');
|
|
199
|
+
if (fs.existsSync(claudefilesDir)) {
|
|
200
|
+
fs.rmSync(claudefilesDir, { recursive: true });
|
|
201
|
+
stdout.print(pc.green(' ✓ Removed old .claudefiles/'));
|
|
202
|
+
}
|
|
197
203
|
// Inject .cursor/ and .claude/ from source .prompts/embeddables-cli.md (or copy if pre-built dirs exist)
|
|
198
204
|
const packageRoot = path.resolve(__dirname, '..', '..');
|
|
199
205
|
const promptsSource = path.join(packageRoot, '.prompts', 'embeddables-cli.md');
|
package/dist/commands/pull.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export type RunPullOptions = {
|
|
|
3
3
|
out?: string;
|
|
4
4
|
branch?: string;
|
|
5
5
|
branchName?: string;
|
|
6
|
+
/** Version to pull (number or "latest"). If omitted and embeddable is known, prompt from 100 most recent. */
|
|
7
|
+
version?: string | number;
|
|
6
8
|
/** When true, pull main and clear saved branch (e.g. when user explicitly selects "main" in branch switcher). */
|
|
7
9
|
useMain?: boolean;
|
|
8
10
|
fix?: boolean;
|
|
@@ -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":"AA6HA,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,6GAA6G;IAC7G,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACzB,iHAAiH;IACjH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,cAAc,iBAgTjD"}
|
package/dist/commands/pull.js
CHANGED
|
@@ -5,7 +5,7 @@ import prompts from 'prompts';
|
|
|
5
5
|
import { reverseCompile } from '../compiler/reverse.js';
|
|
6
6
|
import { getAccessToken, isLoggedIn } from '../auth/index.js';
|
|
7
7
|
import { getProjectId, writeProjectConfig } from '../config/index.js';
|
|
8
|
-
import { promptForProject, promptForEmbeddable, fetchEmbeddableMetadata } from '../prompts/index.js';
|
|
8
|
+
import { promptForProject, promptForEmbeddable, fetchEmbeddableMetadata, fetchRecentVersions, promptForVersion, } from '../prompts/index.js';
|
|
9
9
|
import { captureException, createLogger, exit } from '../logger.js';
|
|
10
10
|
import { getSentryContextFromEmbeddableConfig, getSentryContextFromProjectConfig, setSentryContext, } from '../sentry-context.js';
|
|
11
11
|
import * as stdout from '../stdout.js';
|
|
@@ -156,6 +156,7 @@ export async function runPull(opts) {
|
|
|
156
156
|
stdout.print(pc.cyan('Fetching embeddables from project...'));
|
|
157
157
|
const selected = await promptForEmbeddable(projectId, {
|
|
158
158
|
message: 'Select an embeddable to pull:',
|
|
159
|
+
prioritizeLocal: true,
|
|
159
160
|
});
|
|
160
161
|
if (!selected) {
|
|
161
162
|
logger.error('embeddable selection failed');
|
|
@@ -175,10 +176,6 @@ export async function runPull(opts) {
|
|
|
175
176
|
const currentFromConfig = opts.useMain || opts.branch != null ? null : getCurrentBranchFromConfig(embeddableId);
|
|
176
177
|
const effectiveBranch = opts.useMain ? undefined : (opts.branch ?? currentFromConfig?.branchId);
|
|
177
178
|
const effectiveBranchName = opts.useMain ? undefined : (opts.branchName ?? currentFromConfig?.branchName);
|
|
178
|
-
let url = `https://engine.embeddables.com/${embeddableId}?version=latest`;
|
|
179
|
-
if (effectiveBranch) {
|
|
180
|
-
url += `&embeddable_branch=${effectiveBranch}`;
|
|
181
|
-
}
|
|
182
179
|
const outPath = opts.out || path.join('embeddables', embeddableId, '.generated', 'embeddable.json');
|
|
183
180
|
const branchLabel = effectiveBranch
|
|
184
181
|
? effectiveBranchName
|
|
@@ -188,6 +185,24 @@ export async function runPull(opts) {
|
|
|
188
185
|
if (effectiveBranch) {
|
|
189
186
|
setSentryContext({ branch: { id: effectiveBranch, name: effectiveBranchName ?? null } });
|
|
190
187
|
}
|
|
188
|
+
// Resolve version: explicit --version, or interactive choice from 100 most recent
|
|
189
|
+
let versionParam;
|
|
190
|
+
if (opts.version !== undefined && opts.version !== '') {
|
|
191
|
+
const v = String(opts.version).toLowerCase();
|
|
192
|
+
versionParam = v === 'latest' ? 'latest' : v;
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
stdout.print(pc.cyan('Fetching recent versions...'));
|
|
196
|
+
const recentVersions = await fetchRecentVersions(embeddableId, effectiveBranch ?? null);
|
|
197
|
+
const selected = await promptForVersion(recentVersions, {
|
|
198
|
+
message: 'Select a version to pull:',
|
|
199
|
+
});
|
|
200
|
+
versionParam = selected === null ? 'latest' : String(selected);
|
|
201
|
+
stdout.print('');
|
|
202
|
+
}
|
|
203
|
+
let url = `https://engine.embeddables.com/${embeddableId}?version=${versionParam}`;
|
|
204
|
+
if (effectiveBranch)
|
|
205
|
+
url += `&embeddable_branch=${effectiveBranch}`;
|
|
191
206
|
logger.info('pull started');
|
|
192
207
|
stdout.print(pc.cyan(`Pulling branch: ${pc.bold(branchLabel)}`));
|
|
193
208
|
stdout.print(`Fetching embeddable from ${url}...`);
|
|
@@ -14,6 +14,8 @@ export interface LocalEmbeddable {
|
|
|
14
14
|
export interface PromptForEmbeddableOptions {
|
|
15
15
|
/** Custom message for the prompt */
|
|
16
16
|
message?: string;
|
|
17
|
+
/** When true, sort so embeddables already present in embeddables/ appear first */
|
|
18
|
+
prioritizeLocal?: boolean;
|
|
17
19
|
}
|
|
18
20
|
/**
|
|
19
21
|
* Fetch all embeddables for a project from Supabase
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embeddables.d.ts","sourceRoot":"","sources":["../../src/prompts/embeddables.ts"],"names":[],"mappings":"AAOA,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;
|
|
1
|
+
{"version":3,"file":"embeddables.d.ts","sourceRoot":"","sources":["../../src/prompts/embeddables.ts"],"names":[],"mappings":"AAOA,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;IAChB,kFAAkF;IAClF,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B;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,CA4ExB;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"}
|
|
@@ -67,29 +67,64 @@ export async function fetchEmbeddableMetadata(embeddableId) {
|
|
|
67
67
|
* Returns null if no embeddables found or user cancels
|
|
68
68
|
*/
|
|
69
69
|
export async function promptForEmbeddable(projectId, options = {}) {
|
|
70
|
-
const { message = 'Select an embeddable:' } = options;
|
|
71
|
-
|
|
70
|
+
const { message = 'Select an embeddable:', prioritizeLocal = false } = options;
|
|
71
|
+
let embeddables = await fetchProjectEmbeddables(projectId);
|
|
72
|
+
const SEPARATOR_VALUE = '__separator__';
|
|
73
|
+
let inRepo = [];
|
|
74
|
+
let notInRepo = [];
|
|
75
|
+
if (prioritizeLocal && embeddables.length > 0) {
|
|
76
|
+
const local = await discoverLocalEmbeddables();
|
|
77
|
+
const localIds = new Set(local.map((e) => e.id));
|
|
78
|
+
inRepo = embeddables.filter((e) => localIds.has(e.id));
|
|
79
|
+
notInRepo = embeddables.filter((e) => !localIds.has(e.id));
|
|
80
|
+
embeddables = [...inRepo, ...notInRepo];
|
|
81
|
+
}
|
|
72
82
|
if (embeddables.length === 0) {
|
|
73
83
|
stdout.print(pc.yellow('No embeddables found in this project.'));
|
|
74
84
|
return null;
|
|
75
85
|
}
|
|
86
|
+
const hasTwoSections = prioritizeLocal && inRepo.length > 0 && notInRepo.length > 0;
|
|
87
|
+
const separatorChoice = {
|
|
88
|
+
title: pc.dim('── Other embeddables in project ──'),
|
|
89
|
+
value: SEPARATOR_VALUE,
|
|
90
|
+
disabled: true,
|
|
91
|
+
};
|
|
76
92
|
const choices = embeddables.map((e) => ({
|
|
77
93
|
title: e.title || e.id,
|
|
78
94
|
description: e.id,
|
|
79
95
|
value: e.id,
|
|
80
96
|
}));
|
|
97
|
+
if (hasTwoSections) {
|
|
98
|
+
choices.splice(inRepo.length, 0, separatorChoice);
|
|
99
|
+
}
|
|
81
100
|
const response = await prompts({
|
|
82
101
|
type: 'autocomplete',
|
|
83
102
|
name: 'id',
|
|
84
103
|
message,
|
|
85
104
|
choices,
|
|
86
|
-
suggest: (input,
|
|
87
|
-
|
|
105
|
+
suggest: (input, list) => {
|
|
106
|
+
const realChoices = list.filter((c) => c.value !== SEPARATOR_VALUE);
|
|
107
|
+
const filtered = input
|
|
108
|
+
? realChoices.filter((c) => (c.title?.toLowerCase().includes(input.toLowerCase()) ?? false) ||
|
|
109
|
+
String(c.value).toLowerCase().includes(input.toLowerCase()))
|
|
110
|
+
: realChoices;
|
|
111
|
+
if (hasTwoSections && !input) {
|
|
112
|
+
return Promise.resolve([
|
|
113
|
+
...filtered.slice(0, inRepo.length),
|
|
114
|
+
separatorChoice,
|
|
115
|
+
...filtered.slice(inRepo.length),
|
|
116
|
+
]);
|
|
117
|
+
}
|
|
118
|
+
return Promise.resolve(filtered);
|
|
119
|
+
},
|
|
88
120
|
}, {
|
|
89
121
|
onCancel: () => {
|
|
90
122
|
process.exit(0);
|
|
91
123
|
},
|
|
92
124
|
});
|
|
125
|
+
if (response.id === SEPARATOR_VALUE) {
|
|
126
|
+
return promptForEmbeddable(projectId, options);
|
|
127
|
+
}
|
|
93
128
|
return response.id || null;
|
|
94
129
|
}
|
|
95
130
|
/**
|
package/dist/prompts/index.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export { fetchProjectEmbeddables, fetchEmbeddableMetadata, promptForEmbeddable,
|
|
|
4
4
|
export type { EmbeddableInfo, EmbeddableMetadata, LocalEmbeddable, PromptForEmbeddableOptions, } from './embeddables.js';
|
|
5
5
|
export { fetchBranches, promptForBranch } from './branches.js';
|
|
6
6
|
export type { BranchInfo } from './branches.js';
|
|
7
|
+
export { fetchRecentVersions, promptForVersion } from './versions.js';
|
|
8
|
+
export type { VersionInfo } from './versions.js';
|
|
7
9
|
export { fetchProjectExperiments, promptForExperiment, } from './experiments.js';
|
|
8
10
|
export type { ExperimentInfo, PromptForExperimentOptions } from './experiments.js';
|
|
9
11
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +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;AAE/C,OAAO,EACL,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,kBAAkB,CAAA;AACzB,YAAY,EAAE,cAAc,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAA"}
|
|
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;AAE/C,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrE,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAEhD,OAAO,EACL,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,kBAAkB,CAAA;AACzB,YAAY,EAAE,cAAc,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAA"}
|
package/dist/prompts/index.js
CHANGED
|
@@ -2,4 +2,5 @@
|
|
|
2
2
|
export { fetchProjects, promptForProject } from './projects.js';
|
|
3
3
|
export { fetchProjectEmbeddables, fetchEmbeddableMetadata, promptForEmbeddable, discoverLocalEmbeddables, promptForLocalEmbeddable, } from './embeddables.js';
|
|
4
4
|
export { fetchBranches, promptForBranch } from './branches.js';
|
|
5
|
+
export { fetchRecentVersions, promptForVersion } from './versions.js';
|
|
5
6
|
export { fetchProjectExperiments, promptForExperiment, } from './experiments.js';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface VersionInfo {
|
|
2
|
+
version_number: number;
|
|
3
|
+
created_at: string | null;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Fetch up to 100 most recent version numbers for an embeddable (flow).
|
|
7
|
+
* Uses flow_versions for the given flow_id and branch (null = main).
|
|
8
|
+
* Returns unique version numbers in descending order.
|
|
9
|
+
*/
|
|
10
|
+
export declare function fetchRecentVersions(flowId: string, branchId: string | null): Promise<VersionInfo[]>;
|
|
11
|
+
/**
|
|
12
|
+
* Prompt the user to select a version from the list.
|
|
13
|
+
* Returns the selected version number, or null for "Latest".
|
|
14
|
+
*/
|
|
15
|
+
export declare function promptForVersion(versions: VersionInfo[], options?: {
|
|
16
|
+
message?: string;
|
|
17
|
+
}): Promise<number | null>;
|
|
18
|
+
//# sourceMappingURL=versions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"versions.d.ts","sourceRoot":"","sources":["../../src/prompts/versions.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,WAAW;IAC1B,cAAc,EAAE,MAAM,CAAA;IACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B;AAID;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GAAG,IAAI,GACtB,OAAO,CAAC,WAAW,EAAE,CAAC,CA8CxB;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,WAAW,EAAE,EACvB,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GACjC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAkDxB"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
import prompts from 'prompts';
|
|
3
|
+
import { getAuthenticatedSupabaseClient } from '../auth/index.js';
|
|
4
|
+
import * as stdout from '../stdout.js';
|
|
5
|
+
import { formatDate } from '../helpers/dates.js';
|
|
6
|
+
const RECENT_VERSIONS_LIMIT = 100;
|
|
7
|
+
/**
|
|
8
|
+
* Fetch up to 100 most recent version numbers for an embeddable (flow).
|
|
9
|
+
* Uses flow_versions for the given flow_id and branch (null = main).
|
|
10
|
+
* Returns unique version numbers in descending order.
|
|
11
|
+
*/
|
|
12
|
+
export async function fetchRecentVersions(flowId, branchId) {
|
|
13
|
+
const supabase = await getAuthenticatedSupabaseClient();
|
|
14
|
+
if (!supabase) {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
let query = supabase
|
|
19
|
+
.from('flow_versions')
|
|
20
|
+
.select('version_number, created_at')
|
|
21
|
+
.eq('flow_id', flowId)
|
|
22
|
+
.order('version_number', { ascending: false })
|
|
23
|
+
.limit(RECENT_VERSIONS_LIMIT * 2); // fetch extra then dedupe
|
|
24
|
+
if (branchId === null) {
|
|
25
|
+
query = query.is('branch_id', null);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
query = query.eq('branch_id', branchId);
|
|
29
|
+
}
|
|
30
|
+
const { data, error } = await query;
|
|
31
|
+
if (error) {
|
|
32
|
+
stdout.warn(pc.yellow(`Could not fetch versions: ${error.message}`));
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
// Dedupe by version_number, preserving order (most recent first)
|
|
36
|
+
const seen = new Set();
|
|
37
|
+
const versions = [];
|
|
38
|
+
for (const row of data || []) {
|
|
39
|
+
const v = row.version_number;
|
|
40
|
+
if (typeof v !== 'number' || seen.has(v))
|
|
41
|
+
continue;
|
|
42
|
+
seen.add(v);
|
|
43
|
+
versions.push({
|
|
44
|
+
version_number: v,
|
|
45
|
+
created_at: row.created_at ?? null,
|
|
46
|
+
});
|
|
47
|
+
if (versions.length >= RECENT_VERSIONS_LIMIT)
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
return versions;
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
stdout.warn(pc.yellow(`Could not fetch versions: ${err}`));
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Prompt the user to select a version from the list.
|
|
59
|
+
* Returns the selected version number, or null for "Latest".
|
|
60
|
+
*/
|
|
61
|
+
export async function promptForVersion(versions, options = {}) {
|
|
62
|
+
const choices = [];
|
|
63
|
+
if (versions.length === 0) {
|
|
64
|
+
choices.push({
|
|
65
|
+
title: pc.bold('Latest'),
|
|
66
|
+
value: 'latest',
|
|
67
|
+
description: 'Current published version',
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
for (let i = 0; i < versions.length; i++) {
|
|
72
|
+
const v = versions[i];
|
|
73
|
+
const date = v.created_at ? formatDate(v.created_at) : '';
|
|
74
|
+
const isLatest = i === 0;
|
|
75
|
+
choices.push({
|
|
76
|
+
title: isLatest ? `Version ${v.version_number} (Latest)` : `Version ${v.version_number}`,
|
|
77
|
+
value: isLatest ? 'latest' : v.version_number,
|
|
78
|
+
description: date || undefined,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const response = await prompts({
|
|
83
|
+
type: 'autocomplete',
|
|
84
|
+
name: 'version',
|
|
85
|
+
message: options.message ?? 'Select a version to pull:',
|
|
86
|
+
choices,
|
|
87
|
+
suggest: (input, choicesList) => Promise.resolve(choicesList.filter((c) => c.value === 'latest' ||
|
|
88
|
+
String(c.value).includes(input) ||
|
|
89
|
+
(typeof c.title === 'string' && c.title.toLowerCase().includes(input.toLowerCase())))),
|
|
90
|
+
}, {
|
|
91
|
+
onCancel: () => {
|
|
92
|
+
process.exit(0);
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
if (response.version === 'latest' || response.version === undefined) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
return typeof response.version === 'number' ? response.version : null;
|
|
99
|
+
}
|