@embeddables/cli 0.7.19 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (256) hide show
  1. package/.prompts/embeddables-cli.md +7 -4
  2. package/dist/auth/index.d.ts +43 -0
  3. package/dist/auth/index.d.ts.map +1 -0
  4. package/dist/auth/index.js +102 -0
  5. package/dist/cli.d.ts +2 -0
  6. package/dist/cli.d.ts.map +1 -0
  7. package/dist/cli.js +210 -0
  8. package/dist/command-history.d.ts +13 -0
  9. package/dist/command-history.d.ts.map +1 -0
  10. package/dist/command-history.js +34 -0
  11. package/dist/commands/branch.d.ts +4 -0
  12. package/dist/commands/branch.d.ts.map +1 -0
  13. package/dist/commands/branch.js +67 -0
  14. package/dist/commands/build-workbench.d.ts +5 -0
  15. package/dist/commands/build-workbench.d.ts.map +1 -0
  16. package/dist/commands/build-workbench.js +116 -0
  17. package/dist/commands/build.d.ts +8 -0
  18. package/dist/commands/build.d.ts.map +1 -0
  19. package/dist/commands/build.js +60 -0
  20. package/dist/commands/builder-open.d.ts +4 -0
  21. package/dist/commands/builder-open.d.ts.map +1 -0
  22. package/dist/commands/builder-open.js +74 -0
  23. package/dist/commands/dev.d.ts +12 -0
  24. package/dist/commands/dev.d.ts.map +1 -0
  25. package/dist/commands/dev.js +226 -0
  26. package/dist/commands/diff.d.ts +76 -0
  27. package/dist/commands/diff.d.ts.map +1 -0
  28. package/dist/commands/diff.js +653 -0
  29. package/dist/commands/experiments-connect.d.ts +6 -0
  30. package/dist/commands/experiments-connect.d.ts.map +1 -0
  31. package/dist/commands/experiments-connect.js +140 -0
  32. package/dist/commands/feedback.d.ts +29 -0
  33. package/dist/commands/feedback.d.ts.map +1 -0
  34. package/dist/commands/feedback.js +267 -0
  35. package/dist/commands/init.d.ts +5 -0
  36. package/dist/commands/init.d.ts.map +1 -0
  37. package/dist/commands/init.js +384 -0
  38. package/dist/commands/inspect.d.ts +9 -0
  39. package/dist/commands/inspect.d.ts.map +1 -0
  40. package/dist/commands/inspect.js +293 -0
  41. package/dist/commands/login.d.ts +2 -0
  42. package/dist/commands/login.d.ts.map +1 -0
  43. package/dist/commands/login.js +117 -0
  44. package/dist/commands/logout.d.ts +2 -0
  45. package/dist/commands/logout.d.ts.map +1 -0
  46. package/dist/commands/logout.js +19 -0
  47. package/dist/commands/pull.d.ts +16 -0
  48. package/dist/commands/pull.d.ts.map +1 -0
  49. package/dist/commands/pull.js +395 -0
  50. package/dist/commands/save.d.ts +30 -0
  51. package/dist/commands/save.d.ts.map +1 -0
  52. package/dist/commands/save.js +597 -0
  53. package/dist/commands/upgrade.d.ts +2 -0
  54. package/dist/commands/upgrade.d.ts.map +1 -0
  55. package/dist/commands/upgrade.js +50 -0
  56. package/dist/compiler/errors.d.ts +20 -0
  57. package/dist/compiler/errors.d.ts.map +1 -0
  58. package/dist/compiler/errors.js +35 -0
  59. package/dist/compiler/evalStatic.d.ts +3 -0
  60. package/dist/compiler/evalStatic.d.ts.map +1 -0
  61. package/dist/compiler/evalStatic.js +57 -0
  62. package/dist/compiler/flatten.js +1 -0
  63. package/dist/compiler/helpers/duplicateIds.d.ts +9 -0
  64. package/dist/compiler/helpers/duplicateIds.d.ts.map +1 -0
  65. package/dist/compiler/helpers/duplicateIds.js +71 -0
  66. package/dist/compiler/helpers/numericLeadingKeys.d.ts +8 -0
  67. package/dist/compiler/helpers/numericLeadingKeys.d.ts.map +1 -0
  68. package/dist/compiler/helpers/numericLeadingKeys.js +17 -0
  69. package/dist/compiler/index.d.ts +18 -0
  70. package/dist/compiler/index.d.ts.map +1 -0
  71. package/dist/compiler/index.js +1272 -0
  72. package/dist/compiler/parsePage.d.ts +15 -0
  73. package/dist/compiler/parsePage.d.ts.map +1 -0
  74. package/dist/compiler/parsePage.js +654 -0
  75. package/dist/compiler/registry.d.ts +4 -0
  76. package/dist/compiler/registry.d.ts.map +1 -0
  77. package/dist/compiler/registry.js +44 -0
  78. package/dist/compiler/reverse.d.ts +23 -0
  79. package/dist/compiler/reverse.d.ts.map +1 -0
  80. package/dist/compiler/reverse.js +1920 -0
  81. package/dist/compiler/types.d.ts +21 -0
  82. package/dist/compiler/types.d.ts.map +1 -0
  83. package/dist/compiler/types.js +1 -0
  84. package/dist/components/index.d.ts +21 -0
  85. package/dist/components/index.d.ts.map +1 -0
  86. package/dist/components/index.js +21 -0
  87. package/dist/components/primitives/BaseComponent.d.ts +33 -0
  88. package/dist/components/primitives/BaseComponent.d.ts.map +1 -0
  89. package/dist/components/primitives/BaseComponent.js +26 -0
  90. package/dist/components/primitives/BookMeeting.d.ts +18 -0
  91. package/dist/components/primitives/BookMeeting.d.ts.map +1 -0
  92. package/dist/components/primitives/BookMeeting.js +5 -0
  93. package/dist/components/primitives/Chart.d.ts +41 -0
  94. package/dist/components/primitives/Chart.d.ts.map +1 -0
  95. package/dist/components/primitives/Chart.js +5 -0
  96. package/dist/components/primitives/Container.d.ts +8 -0
  97. package/dist/components/primitives/Container.d.ts.map +1 -0
  98. package/dist/components/primitives/Container.js +5 -0
  99. package/dist/components/primitives/CustomButton.d.ts +37 -0
  100. package/dist/components/primitives/CustomButton.d.ts.map +1 -0
  101. package/dist/components/primitives/CustomButton.js +10 -0
  102. package/dist/components/primitives/CustomHTML.d.ts +8 -0
  103. package/dist/components/primitives/CustomHTML.d.ts.map +1 -0
  104. package/dist/components/primitives/CustomHTML.js +5 -0
  105. package/dist/components/primitives/FileUpload.d.ts +18 -0
  106. package/dist/components/primitives/FileUpload.d.ts.map +1 -0
  107. package/dist/components/primitives/FileUpload.js +16 -0
  108. package/dist/components/primitives/InputBox.d.ts +34 -0
  109. package/dist/components/primitives/InputBox.d.ts.map +1 -0
  110. package/dist/components/primitives/InputBox.js +25 -0
  111. package/dist/components/primitives/Lottie.d.ts +11 -0
  112. package/dist/components/primitives/Lottie.d.ts.map +1 -0
  113. package/dist/components/primitives/Lottie.js +5 -0
  114. package/dist/components/primitives/MediaEmbed.d.ts +13 -0
  115. package/dist/components/primitives/MediaEmbed.d.ts.map +1 -0
  116. package/dist/components/primitives/MediaEmbed.js +6 -0
  117. package/dist/components/primitives/MediaImage.d.ts +8 -0
  118. package/dist/components/primitives/MediaImage.d.ts.map +1 -0
  119. package/dist/components/primitives/MediaImage.js +5 -0
  120. package/dist/components/primitives/OptionSelector.d.ts +38 -0
  121. package/dist/components/primitives/OptionSelector.d.ts.map +1 -0
  122. package/dist/components/primitives/OptionSelector.js +8 -0
  123. package/dist/components/primitives/PaypalCheckout.d.ts +25 -0
  124. package/dist/components/primitives/PaypalCheckout.d.ts.map +1 -0
  125. package/dist/components/primitives/PaypalCheckout.js +5 -0
  126. package/dist/components/primitives/PlainText.d.ts +6 -0
  127. package/dist/components/primitives/PlainText.d.ts.map +1 -0
  128. package/dist/components/primitives/PlainText.js +5 -0
  129. package/dist/components/primitives/ProgressBar.d.ts +15 -0
  130. package/dist/components/primitives/ProgressBar.d.ts.map +1 -0
  131. package/dist/components/primitives/ProgressBar.js +5 -0
  132. package/dist/components/primitives/RichText.d.ts +6 -0
  133. package/dist/components/primitives/RichText.d.ts.map +1 -0
  134. package/dist/components/primitives/RichText.js +5 -0
  135. package/dist/components/primitives/RichTextMarkdown.d.ts +6 -0
  136. package/dist/components/primitives/RichTextMarkdown.d.ts.map +1 -0
  137. package/dist/components/primitives/RichTextMarkdown.js +5 -0
  138. package/dist/components/primitives/Rive.d.ts +16 -0
  139. package/dist/components/primitives/Rive.d.ts.map +1 -0
  140. package/dist/components/primitives/Rive.js +8 -0
  141. package/dist/components/primitives/StripeCheckout.d.ts +52 -0
  142. package/dist/components/primitives/StripeCheckout.d.ts.map +1 -0
  143. package/dist/components/primitives/StripeCheckout.js +5 -0
  144. package/dist/components/primitives/StripeCheckout2.d.ts +30 -0
  145. package/dist/components/primitives/StripeCheckout2.d.ts.map +1 -0
  146. package/dist/components/primitives/StripeCheckout2.js +7 -0
  147. package/dist/config/index.d.ts +23 -0
  148. package/dist/config/index.d.ts.map +1 -0
  149. package/dist/config/index.js +42 -0
  150. package/dist/constants.d.ts +9 -0
  151. package/dist/constants.d.ts.map +1 -0
  152. package/dist/constants.js +9 -0
  153. package/dist/helpers/TEMP helpers file.d.ts +1 -0
  154. package/dist/helpers/TEMP helpers file.d.ts.map +1 -0
  155. package/dist/helpers/TEMP helpers file.js +1 -0
  156. package/dist/helpers/dates.d.ts +5 -0
  157. package/dist/helpers/dates.d.ts.map +1 -0
  158. package/dist/helpers/dates.js +7 -0
  159. package/dist/helpers/json.d.ts +47 -0
  160. package/dist/helpers/json.d.ts.map +1 -0
  161. package/dist/helpers/json.js +622 -0
  162. package/dist/helpers/prompt.d.ts +15 -0
  163. package/dist/helpers/prompt.d.ts.map +1 -0
  164. package/dist/helpers/prompt.js +35 -0
  165. package/dist/helpers/utils.d.ts +13 -0
  166. package/dist/helpers/utils.d.ts.map +1 -0
  167. package/dist/helpers/utils.js +28 -0
  168. package/dist/logger.d.ts +11 -0
  169. package/dist/logger.d.ts.map +1 -0
  170. package/dist/logger.js +21 -0
  171. package/dist/patches/prompts-escape.d.ts +14 -0
  172. package/dist/patches/prompts-escape.d.ts.map +1 -0
  173. package/dist/patches/prompts-escape.js +23 -0
  174. package/dist/prompts/branches.d.ts +20 -0
  175. package/dist/prompts/branches.d.ts.map +1 -0
  176. package/dist/prompts/branches.js +86 -0
  177. package/dist/prompts/embeddables.d.ts +43 -0
  178. package/dist/prompts/embeddables.d.ts.map +1 -0
  179. package/dist/prompts/embeddables.js +200 -0
  180. package/dist/prompts/experiments.d.ts +28 -0
  181. package/dist/prompts/experiments.d.ts.map +1 -0
  182. package/dist/prompts/experiments.js +89 -0
  183. package/dist/prompts/index.d.ts +11 -0
  184. package/dist/prompts/index.d.ts.map +1 -0
  185. package/dist/prompts/index.js +6 -0
  186. package/dist/prompts/projects.d.ts +22 -0
  187. package/dist/prompts/projects.d.ts.map +1 -0
  188. package/dist/prompts/projects.js +92 -0
  189. package/dist/prompts/versions.d.ts +18 -0
  190. package/dist/prompts/versions.d.ts.map +1 -0
  191. package/dist/prompts/versions.js +95 -0
  192. package/dist/proxy/injectApiInterceptor.d.ts +6 -0
  193. package/dist/proxy/injectApiInterceptor.d.ts.map +1 -0
  194. package/dist/proxy/injectApiInterceptor.js +66 -0
  195. package/dist/proxy/injectReload.d.ts +2 -0
  196. package/dist/proxy/injectReload.d.ts.map +1 -0
  197. package/dist/proxy/injectReload.js +14 -0
  198. package/dist/proxy/injectWorkbench.d.ts +5 -0
  199. package/dist/proxy/injectWorkbench.d.ts.map +1 -0
  200. package/dist/proxy/injectWorkbench.js +22 -0
  201. package/dist/proxy/server.d.ts +11 -0
  202. package/dist/proxy/server.d.ts.map +1 -0
  203. package/dist/proxy/server.js +304 -0
  204. package/dist/proxy/sse.d.ts +5 -0
  205. package/dist/proxy/sse.d.ts.map +1 -0
  206. package/dist/proxy/sse.js +17 -0
  207. package/dist/sentry-context.d.ts +48 -0
  208. package/dist/sentry-context.d.ts.map +1 -0
  209. package/dist/sentry-context.js +156 -0
  210. package/dist/stdout.d.ts +61 -0
  211. package/dist/stdout.d.ts.map +1 -0
  212. package/dist/stdout.js +163 -0
  213. package/dist/types-builder.d.ts +800 -0
  214. package/dist/types-builder.d.ts.map +1 -0
  215. package/dist/types-builder.js +20 -0
  216. package/dist/workbench/ActionsPanel.d.ts +6 -0
  217. package/dist/workbench/ActionsPanel.d.ts.map +1 -0
  218. package/dist/workbench/ActionsPanel.js +47 -0
  219. package/dist/workbench/AutofillPanel.d.ts +6 -0
  220. package/dist/workbench/AutofillPanel.d.ts.map +1 -0
  221. package/dist/workbench/AutofillPanel.js +543 -0
  222. package/dist/workbench/ComputedFieldsPanel.d.ts +6 -0
  223. package/dist/workbench/ComputedFieldsPanel.d.ts.map +1 -0
  224. package/dist/workbench/ComputedFieldsPanel.js +31 -0
  225. package/dist/workbench/ExperimentsPanel.d.ts +6 -0
  226. package/dist/workbench/ExperimentsPanel.d.ts.map +1 -0
  227. package/dist/workbench/ExperimentsPanel.js +182 -0
  228. package/dist/workbench/FieldEditorPanel.d.ts +9 -0
  229. package/dist/workbench/FieldEditorPanel.d.ts.map +1 -0
  230. package/dist/workbench/FieldEditorPanel.js +650 -0
  231. package/dist/workbench/InspectorPanel.d.ts +6 -0
  232. package/dist/workbench/InspectorPanel.d.ts.map +1 -0
  233. package/dist/workbench/InspectorPanel.js +341 -0
  234. package/dist/workbench/PageNavigator.d.ts +6 -0
  235. package/dist/workbench/PageNavigator.d.ts.map +1 -0
  236. package/dist/workbench/PageNavigator.js +123 -0
  237. package/dist/workbench/SchemaPanel.d.ts +6 -0
  238. package/dist/workbench/SchemaPanel.d.ts.map +1 -0
  239. package/dist/workbench/SchemaPanel.js +222 -0
  240. package/dist/workbench/UserDataPanel.d.ts +6 -0
  241. package/dist/workbench/UserDataPanel.d.ts.map +1 -0
  242. package/dist/workbench/UserDataPanel.js +350 -0
  243. package/dist/workbench/WorkbenchApp.d.ts +7 -0
  244. package/dist/workbench/WorkbenchApp.d.ts.map +1 -0
  245. package/dist/workbench/WorkbenchApp.js +193 -0
  246. package/dist/workbench/cloudflare-worker/README.md +31 -0
  247. package/dist/workbench/cloudflare-worker/public/workbench.css +1614 -0
  248. package/dist/workbench/cloudflare-worker/public/workbench.js +77 -0
  249. package/dist/workbench/cloudflare-worker/worker.js +40 -0
  250. package/dist/workbench/cloudflare-worker/wrangler.toml +10 -0
  251. package/dist/workbench/index.d.ts +10 -0
  252. package/dist/workbench/index.d.ts.map +1 -0
  253. package/dist/workbench/index.js +44 -0
  254. package/dist/workbench/workbench.css +1614 -0
  255. package/dist/workbench/workbench.js +77 -0
  256. package/package.json +4 -1
@@ -0,0 +1,597 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import pc from 'picocolors';
4
+ import { getAccessToken, getAuthenticatedSupabaseClient, isLoggedIn } from '../auth/index.js';
5
+ import { getProjectId, writeProjectConfig } from '../config/index.js';
6
+ import { compileAllPages } from '../compiler/index.js';
7
+ import { formatError } from '../compiler/errors.js';
8
+ import { promptForLocalEmbeddable, promptForProject } from '../prompts/index.js';
9
+ import { WEB_APP_BASE_URL } from '../constants.js';
10
+ import { captureException, createLogger, exit } from '../logger.js';
11
+ import { getSentryContextFromEmbeddableConfig, getSentryContextFromProjectConfig, setSentryContext, } from '../sentry-context.js';
12
+ import * as stdout from '../stdout.js';
13
+ import { translateJsonDiffToEditCommands } from '../helpers/json.js';
14
+ import { generateId, inferEmbeddableFromCwd } from '../helpers/utils.js';
15
+ /** Error with optional gray detail line (hint/next step) for the user. */
16
+ class SaveError extends Error {
17
+ detail;
18
+ constructor(message, detail) {
19
+ super(message);
20
+ this.detail = detail;
21
+ this.name = 'SaveError';
22
+ }
23
+ }
24
+ /**
25
+ * Parse response body as JSON. Returns null if body is not valid JSON (e.g. HTML error page).
26
+ */
27
+ async function safeParseJson(response) {
28
+ try {
29
+ const data = await response.json();
30
+ return data;
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ }
36
+ function isSaveErrorResponse(value) {
37
+ return (typeof value === 'object' &&
38
+ value !== null &&
39
+ 'error' in value &&
40
+ typeof value.error === 'string');
41
+ }
42
+ function isSaveConflictResponse(value) {
43
+ return (typeof value === 'object' &&
44
+ value !== null &&
45
+ 'latestVersionNumber' in value &&
46
+ 'yourVersionNumber' in value &&
47
+ typeof value.latestVersionNumber === 'number' &&
48
+ typeof value.yourVersionNumber === 'number');
49
+ }
50
+ function isSaveResponse(value) {
51
+ return (typeof value === 'object' &&
52
+ value !== null &&
53
+ value.success === true &&
54
+ typeof value.data === 'object' &&
55
+ typeof value.data?.newVersionNumber === 'number');
56
+ }
57
+ /**
58
+ * Read `_version` from config.json for the given embeddable.
59
+ */
60
+ function getVersionFromConfig(embeddableId) {
61
+ const configPath = path.join('embeddables', embeddableId, 'config.json');
62
+ if (!fs.existsSync(configPath)) {
63
+ return null;
64
+ }
65
+ try {
66
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
67
+ if (typeof config._version === 'number') {
68
+ return config._version;
69
+ }
70
+ }
71
+ catch {
72
+ // Ignore parse errors
73
+ }
74
+ return null;
75
+ }
76
+ /**
77
+ * Read `_branch_id` and `_branch_name` from config.json for the given embeddable.
78
+ */
79
+ function getBranchFromConfig(embeddableId) {
80
+ const configPath = path.join('embeddables', embeddableId, 'config.json');
81
+ if (!fs.existsSync(configPath))
82
+ return null;
83
+ try {
84
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
85
+ const branchId = config._branch_id;
86
+ if (typeof branchId !== 'string' || !branchId)
87
+ return null;
88
+ const branchName = config._branch_name;
89
+ return { branchId, branchName: typeof branchName === 'string' ? branchName : undefined };
90
+ }
91
+ catch {
92
+ return null;
93
+ }
94
+ }
95
+ /** Slug for branch name/id for versioned filenames (e.g. "my branch" -> "my_branch"). */
96
+ function slugForBranch(nameOrId) {
97
+ return (String(nameOrId)
98
+ .replace(/[^a-zA-Z0-9_.-]/g, '_')
99
+ .replace(/_+/g, '_') || 'main');
100
+ }
101
+ /** Get branch slug from config (_branch_name preferred, else _branch_id, else main). */
102
+ function getBranchSlugFromConfig(embeddableId) {
103
+ const configPath = path.join('embeddables', embeddableId, 'config.json');
104
+ if (!fs.existsSync(configPath))
105
+ return 'main';
106
+ try {
107
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
108
+ const name = config._branch_name ?? config._branch_id;
109
+ if (typeof name === 'string' && name)
110
+ return slugForBranch(name);
111
+ }
112
+ catch {
113
+ /* ignore */
114
+ }
115
+ return 'main';
116
+ }
117
+ /**
118
+ * Update `_version` in config.json for the given embeddable.
119
+ */
120
+ function setVersionInConfig(embeddableId, version) {
121
+ const configPath = path.join('embeddables', embeddableId, 'config.json');
122
+ if (!fs.existsSync(configPath)) {
123
+ return;
124
+ }
125
+ try {
126
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
127
+ config._version = version;
128
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
129
+ }
130
+ catch {
131
+ // Ignore errors - versioned files are a fallback
132
+ }
133
+ }
134
+ /**
135
+ * Scan the .generated/ directory for versioned files (embeddable-v*.json or embeddable-*@*.json)
136
+ * and return the highest version number found.
137
+ */
138
+ function getLatestVersionFromFiles(generatedDir) {
139
+ if (!fs.existsSync(generatedDir)) {
140
+ return null;
141
+ }
142
+ const files = fs.readdirSync(generatedDir);
143
+ const legacyPattern = /^embeddable-v(\d+)\.json$/;
144
+ const branchPattern = /^embeddable-[^@]+@(\d+)\.json$/;
145
+ let maxVersion = null;
146
+ for (const file of files) {
147
+ const match = file.match(legacyPattern) ?? file.match(branchPattern);
148
+ if (match) {
149
+ const version = parseInt(match[1], 10);
150
+ if (maxVersion === null || version > maxVersion) {
151
+ maxVersion = version;
152
+ }
153
+ }
154
+ }
155
+ return maxVersion;
156
+ }
157
+ /**
158
+ * Fetch active drafts from other users on the same version (status=DRAFT, not saved/discarded).
159
+ * Used to warn before saving that others may have unsaved edits.
160
+ */
161
+ async function fetchOtherUsersDrafts(supabase, flowId, versionNumber, branchId, currentUserId) {
162
+ try {
163
+ let query = supabase
164
+ .from('flow_versions')
165
+ .select('id, author_id, author_name')
166
+ .eq('flow_id', flowId)
167
+ .eq('status', 'DRAFT')
168
+ .eq('version_number', versionNumber)
169
+ .not('draft_saved', 'is', true)
170
+ .not('draft_discarded', 'is', true)
171
+ .neq('author_id', currentUserId);
172
+ if (branchId === null) {
173
+ query = query.is('branch_id', null);
174
+ }
175
+ else {
176
+ query = query.eq('branch_id', branchId);
177
+ }
178
+ const { data, error } = await query;
179
+ if (error) {
180
+ return [];
181
+ }
182
+ return (data || []).map((row) => ({
183
+ id: row.id,
184
+ author_id: row.author_id,
185
+ author_name: row.author_name ?? null,
186
+ }));
187
+ }
188
+ catch {
189
+ return [];
190
+ }
191
+ }
192
+ export async function runSave(opts) {
193
+ const logger = createLogger('runSave');
194
+ try {
195
+ await runSaveInner(opts);
196
+ }
197
+ catch (error) {
198
+ captureException(error);
199
+ if (error instanceof SaveError) {
200
+ stdout.error(`Save failed: ${error.message}`);
201
+ if (error.detail) {
202
+ stdout.dim(error.detail);
203
+ }
204
+ }
205
+ else if (error instanceof Error) {
206
+ stdout.error(`Save failed: ${error.message}`);
207
+ }
208
+ else {
209
+ stdout.error('Save failed with an unexpected error.');
210
+ }
211
+ logger.error('save failed', {
212
+ message: error instanceof Error ? error.message : 'unexpected error',
213
+ });
214
+ await exit(1);
215
+ }
216
+ }
217
+ async function runSaveInner(opts) {
218
+ const logger = createLogger('runSave');
219
+ // 1. Check login
220
+ if (!isLoggedIn()) {
221
+ stdout.warn('Not logged in.');
222
+ stdout.dim('Run "embeddables login" first.');
223
+ logger.error('not logged in');
224
+ await exit(1);
225
+ return;
226
+ }
227
+ // 2. Get access token
228
+ const accessToken = getAccessToken();
229
+ if (!accessToken) {
230
+ stdout.warn('Could not retrieve access token.');
231
+ stdout.dim('Run "embeddables login" to re-authenticate.');
232
+ logger.error('no access token');
233
+ await exit(1);
234
+ return;
235
+ }
236
+ // 3. Get embeddable ID (from option, cwd inference, or interactive prompt)
237
+ const inferred = inferEmbeddableFromCwd();
238
+ let embeddableId = opts.id ?? inferred?.embeddableId;
239
+ if (inferred && !opts.id && embeddableId) {
240
+ process.chdir(inferred.projectRoot);
241
+ }
242
+ setSentryContext(getSentryContextFromProjectConfig());
243
+ if (!embeddableId) {
244
+ const selected = await promptForLocalEmbeddable({
245
+ message: 'Select an embeddable to save:',
246
+ });
247
+ if (!selected) {
248
+ await exit(1);
249
+ return;
250
+ }
251
+ embeddableId = selected;
252
+ stdout.gap();
253
+ }
254
+ if (embeddableId) {
255
+ const embeddableCtx = getSentryContextFromEmbeddableConfig(embeddableId);
256
+ setSentryContext({
257
+ embeddable: { id: embeddableId },
258
+ ...embeddableCtx,
259
+ });
260
+ }
261
+ // Resolve branch: explicit -b flag wins, otherwise use current branch from config (set by `embeddables branch`)
262
+ const currentBranch = opts.branch ? null : getBranchFromConfig(embeddableId);
263
+ const effectiveBranch = opts.branch ?? currentBranch?.branchId ?? undefined;
264
+ const effectiveBranchName = currentBranch?.branchName;
265
+ const branchLabel = effectiveBranch
266
+ ? effectiveBranchName
267
+ ? `${effectiveBranchName} (${effectiveBranch})`
268
+ : effectiveBranch
269
+ : 'main';
270
+ logger.info('save started', { fromVersion: opts.fromVersion });
271
+ // 4. Get project ID (from config or interactive prompt)
272
+ let projectId = getProjectId();
273
+ if (!projectId) {
274
+ stdout.step('No project configured. Fetching projects…');
275
+ const selectedProject = await promptForProject();
276
+ if (!selectedProject) {
277
+ await exit(1);
278
+ return;
279
+ }
280
+ projectId = selectedProject.id;
281
+ writeProjectConfig({
282
+ org_id: selectedProject.org_id || undefined,
283
+ org_title: selectedProject.org_title || undefined,
284
+ project_id: projectId,
285
+ project_name: selectedProject.title || undefined,
286
+ });
287
+ stdout.success('Saved project to embeddables.json');
288
+ stdout.gap();
289
+ }
290
+ // 5. Build (compile TSX → JSON) unless --skip-build is set
291
+ const generatedDir = path.join('embeddables', embeddableId, '.generated');
292
+ const outPath = path.join(generatedDir, 'embeddable.json');
293
+ if (!opts.skipBuild) {
294
+ const pagesGlob = `embeddables/${embeddableId}/pages/**/*.page.tsx`;
295
+ const stylesDir = path.join('embeddables', embeddableId, 'styles');
296
+ const configPath = path.join('embeddables', embeddableId, 'config.json');
297
+ try {
298
+ await stdout.withSpinner('Building embeddable…', async () => {
299
+ await compileAllPages({
300
+ pagesGlob,
301
+ outPath,
302
+ pageKeyFrom: 'filename',
303
+ stylesDir,
304
+ embeddableId: embeddableId,
305
+ configPath,
306
+ });
307
+ }, {
308
+ successText: 'Build successful',
309
+ });
310
+ }
311
+ catch (e) {
312
+ captureException(e);
313
+ stdout.error(formatError(e));
314
+ logger.error('build failed');
315
+ await exit(1);
316
+ return;
317
+ }
318
+ }
319
+ // 6. Read the compiled JSON
320
+ if (!fs.existsSync(outPath)) {
321
+ stdout.warn(`No compiled embeddable found at ${outPath}`);
322
+ stdout.dim('Run "embeddables build" or "embeddables pull" first.');
323
+ logger.error('compiled json not found', { outPath });
324
+ await exit(1);
325
+ return;
326
+ }
327
+ const jsonContent = fs.readFileSync(outPath, 'utf8');
328
+ let embeddableJson;
329
+ try {
330
+ embeddableJson = JSON.parse(jsonContent);
331
+ }
332
+ catch {
333
+ stdout.error('Failed to parse embeddable JSON.');
334
+ logger.error('failed to parse compiled json');
335
+ await exit(1);
336
+ return;
337
+ }
338
+ // Validate that pages array exists and is non-empty
339
+ if (!embeddableJson.pages ||
340
+ !Array.isArray(embeddableJson.pages) ||
341
+ embeddableJson.pages.length === 0) {
342
+ stdout.error('Embeddable JSON must contain a non-empty pages array.');
343
+ logger.error('empty pages array');
344
+ await exit(1);
345
+ return;
346
+ }
347
+ // 7. Determine fromVersionNumber
348
+ let fromVersionNumber;
349
+ if (opts.fromVersion) {
350
+ fromVersionNumber = parseInt(opts.fromVersion, 10);
351
+ if (isNaN(fromVersionNumber)) {
352
+ stdout.error(`Invalid --from-version value: ${opts.fromVersion}`);
353
+ logger.error('invalid from-version', { fromVersion: opts.fromVersion });
354
+ await exit(1);
355
+ return;
356
+ }
357
+ }
358
+ else {
359
+ // Primary: read _version from config.json; fallback: scan .generated/ for versioned files
360
+ const detectedVersion = getVersionFromConfig(embeddableId) ?? getLatestVersionFromFiles(generatedDir);
361
+ if (detectedVersion === null) {
362
+ stdout.error('Could not determine the current version number.');
363
+ stdout.dim('Make sure you have pulled the embeddable first (embeddables pull), or specify --from-version <number>.');
364
+ logger.error('could not determine version');
365
+ await exit(1);
366
+ return;
367
+ }
368
+ fromVersionNumber = detectedVersion;
369
+ }
370
+ setSentryContext({
371
+ versionNumber: fromVersionNumber,
372
+ branch: effectiveBranch
373
+ ? { id: effectiveBranch, name: effectiveBranchName ?? null }
374
+ : undefined,
375
+ });
376
+ stdout.infoBox([
377
+ ['ID', embeddableId],
378
+ ['Branch', branchLabel],
379
+ ['Version', `v${fromVersionNumber}`],
380
+ ]
381
+ .map(([k, v]) => `${k}: ${v}`)
382
+ .join(' · '));
383
+ // 7b. Check for other users' drafts on this version; warn and optionally abort
384
+ let currentUserId = null;
385
+ const supabase = await getAuthenticatedSupabaseClient();
386
+ if (supabase) {
387
+ const { data: { user }, } = await supabase.auth.getUser();
388
+ currentUserId = user?.id ?? null;
389
+ if (currentUserId) {
390
+ const branchIdForDrafts = effectiveBranch ?? null;
391
+ const otherDrafts = await fetchOtherUsersDrafts(supabase, embeddableId, fromVersionNumber, branchIdForDrafts, currentUserId);
392
+ if (otherDrafts.length > 0) {
393
+ const names = otherDrafts
394
+ .map((d) => d.author_name?.trim() || d.author_id || 'Someone')
395
+ .filter((n, i, a) => a.indexOf(n) === i);
396
+ const namesText = names.length === 1
397
+ ? names[0]
398
+ : `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`;
399
+ stdout.gap();
400
+ stdout.warn(`${namesText} ${names.length === 1 ? 'has' : 'have'} unsaved edits on version ${fromVersionNumber}. Saving may cause conflicts.`);
401
+ const { prompt: promptWithCancel } = await import('../helpers/prompt.js');
402
+ const { proceed } = await promptWithCancel({
403
+ type: 'confirm',
404
+ name: 'proceed',
405
+ message: 'Save anyway?',
406
+ initial: false,
407
+ }, 1);
408
+ if (!proceed) {
409
+ stdout.dim('Save cancelled.');
410
+ await exit(0);
411
+ }
412
+ stdout.gap();
413
+ }
414
+ }
415
+ }
416
+ // 8. POST to save-version API
417
+ // Resolve current user id (required by API)
418
+ if (!currentUserId && supabase) {
419
+ const { data: { user }, } = await supabase.auth.getUser();
420
+ currentUserId = user?.id ?? null;
421
+ }
422
+ if (!currentUserId) {
423
+ throw new SaveError('You must be logged in to save.', 'Run "embeddables login" to authenticate.');
424
+ }
425
+ // Previous version JSON: from versioned snapshot file, or {} if not found (e.g. --from-version without pull)
426
+ const previousVersionPath = path.join(generatedDir, `embeddable-${getBranchSlugFromConfig(embeddableId)}@${fromVersionNumber}.json`);
427
+ const legacyVersionPath = path.join(generatedDir, `embeddable-v${fromVersionNumber}.json`);
428
+ const previousJsonContent = fs.existsSync(previousVersionPath)
429
+ ? fs.readFileSync(previousVersionPath, 'utf8')
430
+ : fs.existsSync(legacyVersionPath)
431
+ ? fs.readFileSync(legacyVersionPath, 'utf8')
432
+ : '{}';
433
+ const commands = translateJsonDiffToEditCommands({
434
+ previousObject: JSON.parse(previousJsonContent),
435
+ currentObject: JSON.parse(jsonContent),
436
+ basePath: [],
437
+ });
438
+ const body = {
439
+ userId: currentUserId,
440
+ embeddableId,
441
+ jsonString: JSON.stringify(embeddableJson),
442
+ projectId,
443
+ fromVersionNumber,
444
+ editHistoryLength: 1,
445
+ editHistoryDescriptions: [{ origin: 'CLI', description: 'Saved version from CLI' }],
446
+ editHistory: [
447
+ setMultipleFlowUpdates({
448
+ commands,
449
+ metadata: {
450
+ description: 'Saved version from CLI',
451
+ trigger: { origin: 'CLI', editor: 'CLI' },
452
+ },
453
+ }),
454
+ ],
455
+ };
456
+ if (opts.label) {
457
+ body.label = opts.label;
458
+ }
459
+ if (effectiveBranch) {
460
+ body.branchId = effectiveBranch;
461
+ }
462
+ const apiUrl = `${WEB_APP_BASE_URL}/api/embeddables/save-version`;
463
+ const headers = {
464
+ Authorization: `Bearer ${accessToken}`,
465
+ 'Content-Type': 'application/json',
466
+ };
467
+ let response;
468
+ try {
469
+ response = await stdout.withSpinner(`Saving embeddable (v${fromVersionNumber})…`, async () => {
470
+ return fetch(apiUrl, {
471
+ method: 'POST',
472
+ headers,
473
+ body: JSON.stringify(body),
474
+ });
475
+ }, { successText: 'Uploaded to server' });
476
+ }
477
+ catch (networkError) {
478
+ const message = networkError instanceof Error ? networkError.message : 'Network request failed';
479
+ if (message.includes('fetch') ||
480
+ message.includes('ECONNREFUSED') ||
481
+ message.includes('ETIMEDOUT') ||
482
+ message.includes('ENOTFOUND') ||
483
+ message.includes('network')) {
484
+ throw new SaveError(`Could not reach the server (${message}).`, `Check your connection and that the base URL is correct (currently ${WEB_APP_BASE_URL}).`);
485
+ }
486
+ throw new SaveError(`Network error: ${message}`, 'Check your network and firewall settings.');
487
+ }
488
+ if (response.status === 404) {
489
+ throw new SaveError('Save endpoint not found. The server may not support this feature or the URL may be incorrect.', `The request was sent to ${WEB_APP_BASE_URL}. If you use a custom deployment, ensure the save-version API is available there.`);
490
+ }
491
+ if (response.status === 401 || response.status === 403) {
492
+ throw new SaveError('Not authorized.', 'Run "embeddables login" to re-authenticate.');
493
+ }
494
+ if (response.status >= 500) {
495
+ throw new SaveError(`Server error (HTTP ${response.status}). Please try again later.`, 'If this keeps happening, try again later or contact support.');
496
+ }
497
+ if (response.status === 409) {
498
+ const conflictResult = await safeParseJson(response);
499
+ if (!conflictResult || !isSaveConflictResponse(conflictResult)) {
500
+ throw new SaveError(`Version conflict but invalid response (HTTP ${response.status}).`, 'Try saving again; if it persists, the server may be misconfigured.');
501
+ }
502
+ stdout.gap();
503
+ stdout.warn(`Version conflict: the server has version ${conflictResult.latestVersionNumber}, but you are saving from version ${conflictResult.yourVersionNumber}.`);
504
+ const { prompt: promptWithCancel } = await import('../helpers/prompt.js');
505
+ const { forceSave } = await promptWithCancel({
506
+ type: 'confirm',
507
+ name: 'forceSave',
508
+ message: 'A newer version exists on the server. Save anyway?',
509
+ initial: false,
510
+ }, 1);
511
+ if (!forceSave) {
512
+ stdout.dim('Save cancelled.');
513
+ await exit(0);
514
+ }
515
+ // Retry with force flag
516
+ let forceResponse;
517
+ try {
518
+ forceResponse = await stdout.withSpinner('Force-saving…', async () => {
519
+ return fetch(apiUrl, {
520
+ method: 'POST',
521
+ headers,
522
+ body: JSON.stringify({ ...body, force: true }),
523
+ });
524
+ }, {
525
+ successText: 'Force save uploaded',
526
+ });
527
+ }
528
+ catch (forceNetworkError) {
529
+ const message = forceNetworkError instanceof Error ? forceNetworkError.message : 'Network request failed';
530
+ throw new SaveError(`Retry failed: ${message}`, 'The initial save hit a version conflict; the force-save retry could not reach the server.');
531
+ }
532
+ if (forceResponse.status === 404) {
533
+ throw new SaveError('Save endpoint not found. The server may not support this feature or the URL may be incorrect.', `The request was sent to ${WEB_APP_BASE_URL}. If you use a custom deployment, ensure the save-version API is available there.`);
534
+ }
535
+ const forceResult = await safeParseJson(forceResponse);
536
+ if (!forceResponse.ok) {
537
+ const errorMessage = forceResult && isSaveErrorResponse(forceResult)
538
+ ? forceResult.error
539
+ : `HTTP ${forceResponse.status}`;
540
+ throw new SaveError(errorMessage, 'If the problem persists, run "embeddables login" or try again later.');
541
+ }
542
+ if (!forceResult || !isSaveResponse(forceResult)) {
543
+ throw new SaveError(`Invalid response from server (HTTP ${forceResponse.status}).`, 'The server returned an unexpected format. Try again or contact support if it persists.');
544
+ }
545
+ const { newVersionNumber } = forceResult.data;
546
+ setVersionInConfig(embeddableId, newVersionNumber);
547
+ const branchSlug = getBranchSlugFromConfig(embeddableId);
548
+ const versionedPath = path.join(generatedDir, `embeddable-${branchSlug}@${newVersionNumber}.json`);
549
+ fs.mkdirSync(generatedDir, { recursive: true });
550
+ fs.writeFileSync(versionedPath, jsonContent, 'utf8');
551
+ stdout.gap();
552
+ stdout.successBox(`${pc.bold(`Version ${newVersionNumber}`)} ${stdout.symbols.rocket}\n${stdout.randomMessage('save')}`, { title: 'Saved' });
553
+ logger.info('save complete', { newVersionNumber, forced: true });
554
+ return;
555
+ }
556
+ const result = await safeParseJson(response);
557
+ if (!response.ok) {
558
+ const errorMessage = result && isSaveErrorResponse(result) ? result.error : `HTTP ${response.status}`;
559
+ throw new SaveError(errorMessage, 'If the problem persists, run "embeddables login" or try again later.');
560
+ }
561
+ if (!result || !isSaveResponse(result)) {
562
+ throw new SaveError(`Invalid response from server (HTTP ${response.status}).`, 'The server returned an unexpected format. Try again or contact support if it persists.');
563
+ }
564
+ const { newVersionNumber } = result.data;
565
+ // Update _version in config.json so future saves know the base version
566
+ setVersionInConfig(embeddableId, newVersionNumber);
567
+ // Also save the versioned file to .generated/ as a snapshot (embeddable-{branch}@{version}.json)
568
+ const branchSlug = getBranchSlugFromConfig(embeddableId);
569
+ const versionedPath = path.join(generatedDir, `embeddable-${branchSlug}@${newVersionNumber}.json`);
570
+ fs.mkdirSync(generatedDir, { recursive: true });
571
+ fs.writeFileSync(versionedPath, jsonContent, 'utf8');
572
+ stdout.gap();
573
+ stdout.successBox(`${pc.bold(`Version ${newVersionNumber}`)} ${stdout.symbols.rocket}\n${stdout.randomMessage('save')}`, { title: 'Saved' });
574
+ logger.info('save complete', { newVersionNumber });
575
+ }
576
+ function setMultipleFlowUpdates({ commands, metadata, }) {
577
+ const type = 'set_multiple_embeddable_updates';
578
+ const simpleCommandTypes = {
579
+ add: 'add_embeddable_property',
580
+ remove: 'remove_embeddable_property',
581
+ set: 'update_embeddable_property',
582
+ move: 'move_embeddable_property',
583
+ };
584
+ //* Map the commands to the correct type and data
585
+ const mappedCommands = commands.map((command) => ({
586
+ type: Object.keys(simpleCommandTypes).includes(command.type)
587
+ ? simpleCommandTypes[command.type]
588
+ : command.type,
589
+ data: command.data,
590
+ }));
591
+ return {
592
+ id: generateId('edit'),
593
+ type,
594
+ data: { commands: mappedCommands },
595
+ metadata,
596
+ };
597
+ }
@@ -0,0 +1,2 @@
1
+ export declare function runUpgrade(): Promise<void>;
2
+ //# sourceMappingURL=upgrade.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upgrade.d.ts","sourceRoot":"","sources":["../../src/commands/upgrade.ts"],"names":[],"mappings":"AAwBA,wBAAsB,UAAU,kBA4C/B"}
@@ -0,0 +1,50 @@
1
+ import { createRequire } from 'node:module';
2
+ import { execSync } from 'node:child_process';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { dirname, join } from 'node:path';
5
+ import * as stdout from '../stdout.js';
6
+ const require = createRequire(import.meta.url);
7
+ function getCurrentVersion() {
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ const pkgPath = join(__dirname, '..', '..', 'package.json');
10
+ const pkg = require(pkgPath);
11
+ return pkg.version;
12
+ }
13
+ async function getLatestVersion() {
14
+ const res = await fetch('https://registry.npmjs.org/@embeddables/cli/latest');
15
+ if (!res.ok) {
16
+ throw new Error(`Failed to fetch latest version: ${res.status}`);
17
+ }
18
+ const data = (await res.json());
19
+ return data.version;
20
+ }
21
+ export async function runUpgrade() {
22
+ try {
23
+ const currentVersion = getCurrentVersion();
24
+ const latestVersion = await stdout.withSpinner('Checking for updates…', async () => getLatestVersion(), { successText: (v) => `Checked registry (latest: ${v})` });
25
+ if (currentVersion === latestVersion) {
26
+ stdout.gap();
27
+ stdout.successBox(`Already on the latest version ${stdout.symbols.sparkles}\n\nv${latestVersion}`, { title: 'Up to date' });
28
+ stdout.gap();
29
+ return;
30
+ }
31
+ await stdout.withSpinner(`Upgrading ${currentVersion} → ${latestVersion}…`, async () => {
32
+ execSync(`npm install -g @embeddables/cli@latest`, { stdio: 'pipe' });
33
+ }, { successText: `Upgraded to @embeddables/cli@${latestVersion}` });
34
+ stdout.gap();
35
+ stdout.successBox(`v${currentVersion} → v${latestVersion} ${stdout.symbols.rocket}`, {
36
+ title: 'Upgrade complete',
37
+ });
38
+ stdout.gap();
39
+ }
40
+ catch (error) {
41
+ const message = error instanceof Error ? error.message : String(error);
42
+ if (message.includes('EACCES') || message.includes('permission')) {
43
+ stdout.error('Permission denied. Try running with sudo: sudo npm install -g @embeddables/cli@latest');
44
+ }
45
+ else {
46
+ stdout.error(`Error during upgrade: ${message}`);
47
+ }
48
+ process.exit(1);
49
+ }
50
+ }
@@ -0,0 +1,20 @@
1
+ export declare class CompileError extends Error {
2
+ info?: {
3
+ file?: string;
4
+ line?: number;
5
+ column?: number;
6
+ pageKey?: string;
7
+ componentId?: string;
8
+ componentKey?: string;
9
+ } | undefined;
10
+ constructor(message: string, info?: {
11
+ file?: string;
12
+ line?: number;
13
+ column?: number;
14
+ pageKey?: string;
15
+ componentId?: string;
16
+ componentKey?: string;
17
+ } | undefined);
18
+ }
19
+ export declare function formatError(e: unknown): string;
20
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/compiler/errors.ts"],"names":[],"mappings":"AAEA,qBAAa,YAAa,SAAQ,KAAK;IAG5B,IAAI,CAAC,EAAE;QACZ,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,YAAY,CAAC,EAAE,MAAM,CAAA;KACtB;gBARD,OAAO,EAAE,MAAM,EACR,IAAI,CAAC,EAAE;QACZ,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,YAAY,CAAC,EAAE,MAAM,CAAA;KACtB,YAAA;CAIJ;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,OAAO,UAkCrC"}