@embeddables/cli 0.6.7 → 0.6.9

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 CHANGED
@@ -51,6 +51,20 @@ embeddables/
51
51
  .generated/ # Compiled JSON output
52
52
  ```
53
53
 
54
+ ## Upgrading the CLI
55
+
56
+ When a new CLI version is released, update with:
57
+
58
+ ```bash
59
+ embeddables upgrade
60
+ ```
61
+
62
+ You can check your currently installed version with:
63
+
64
+ ```bash
65
+ embeddables -v
66
+ ```
67
+
54
68
  ## Commands
55
69
 
56
70
  ### `embeddables init`
@@ -1 +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,iBAoDpD"}
1
+ {"version":3,"file":"branch.d.ts","sourceRoot":"","sources":["../../src/commands/branch.ts"],"names":[],"mappings":"AAMA,wBAAsB,SAAS,CAAC,IAAI,EAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,iBAwDpD"}
@@ -2,6 +2,7 @@ import pc from 'picocolors';
2
2
  import { isLoggedIn } from '../auth/index.js';
3
3
  import { runPull } from './pull.js';
4
4
  import { promptForLocalEmbeddable, fetchBranches, promptForBranch } from '../prompts/index.js';
5
+ import { inferEmbeddableFromCwd } from '../helpers/utils.js';
5
6
  export async function runBranch(opts) {
6
7
  // Check login status
7
8
  if (!isLoggedIn()) {
@@ -10,7 +11,11 @@ export async function runBranch(opts) {
10
11
  process.exit(1);
11
12
  }
12
13
  // Get embeddable ID
13
- let embeddableId = opts.id;
14
+ const inferred = inferEmbeddableFromCwd();
15
+ let embeddableId = opts.id ?? inferred?.embeddableId;
16
+ if (inferred && !opts.id && embeddableId) {
17
+ process.chdir(inferred.projectRoot);
18
+ }
14
19
  if (!embeddableId) {
15
20
  const selected = await promptForLocalEmbeddable();
16
21
  if (!selected) {
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/commands/build.ts"],"names":[],"mappings":"AAKA,wBAAsB,QAAQ,CAAC,IAAI,EAAE;IACnC,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,WAAW,EAAE,UAAU,GAAG,QAAQ,CAAA;CACnC,iBA6BA"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/commands/build.ts"],"names":[],"mappings":"AAMA,wBAAsB,QAAQ,CAAC,IAAI,EAAE;IACnC,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,WAAW,EAAE,UAAU,GAAG,QAAQ,CAAA;CACnC,iBAiCA"}
@@ -2,8 +2,13 @@ import path from 'node:path';
2
2
  import { compileAllPages } from '../compiler/index.js';
3
3
  import { formatError } from '../compiler/errors.js';
4
4
  import { promptForLocalEmbeddable } from '../prompts/index.js';
5
+ import { inferEmbeddableFromCwd } from '../helpers/utils.js';
5
6
  export async function runBuild(opts) {
6
- let embeddableId = opts.id;
7
+ const inferred = inferEmbeddableFromCwd();
8
+ let embeddableId = opts.id ?? inferred?.embeddableId;
9
+ if (inferred && !opts.id && embeddableId) {
10
+ process.chdir(inferred.projectRoot);
11
+ }
7
12
  if (!embeddableId) {
8
13
  const selected = await promptForLocalEmbeddable({
9
14
  message: 'Select an embeddable to build:',
@@ -1 +1 @@
1
- {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAmHA,wBAAsB,MAAM,CAAC,IAAI,EAAE;IACjC,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,UAAU,GAAG,QAAQ,CAAA;CACnC,iBA+GA"}
1
+ {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAoHA,wBAAsB,MAAM,CAAC,IAAI,EAAE;IACjC,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,UAAU,GAAG,QAAQ,CAAA;CACnC,iBAkHA"}
@@ -7,6 +7,7 @@ import prompts from 'prompts';
7
7
  import { compileAllPages } from '../compiler/index.js';
8
8
  import { startProxyServer } from '../proxy/server.js';
9
9
  import { formatError } from '../compiler/errors.js';
10
+ import { inferEmbeddableFromCwd } from '../helpers/utils.js';
10
11
  /**
11
12
  * Check whether a port is available by attempting to listen on it.
12
13
  */
@@ -92,7 +93,11 @@ async function promptForEmbeddable() {
92
93
  return response.id || null;
93
94
  }
94
95
  export async function runDev(opts) {
95
- let embeddableId = opts.id;
96
+ const inferred = inferEmbeddableFromCwd();
97
+ let embeddableId = opts.id ?? inferred?.embeddableId;
98
+ if (inferred && !opts.id && embeddableId) {
99
+ process.chdir(inferred.projectRoot);
100
+ }
96
101
  if (!embeddableId) {
97
102
  const selected = await promptForEmbeddable();
98
103
  if (!selected) {
@@ -1 +1 @@
1
- {"version":3,"file":"experiments-connect.d.ts","sourceRoot":"","sources":["../../src/commands/experiments-connect.ts"],"names":[],"mappings":"AAYA,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,iBAcA"}
1
+ {"version":3,"file":"experiments-connect.d.ts","sourceRoot":"","sources":["../../src/commands/experiments-connect.ts"],"names":[],"mappings":"AAaA,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,iBAcA"}
@@ -4,6 +4,7 @@ import pc from 'picocolors';
4
4
  import { isLoggedIn } from '../auth/index.js';
5
5
  import { getProjectId, writeProjectConfig } from '../config/index.js';
6
6
  import { promptForProject, promptForLocalEmbeddable, promptForExperiment, } from '../prompts/index.js';
7
+ import { inferEmbeddableFromCwd } from '../helpers/utils.js';
7
8
  export async function runExperimentsConnect(opts) {
8
9
  try {
9
10
  await runExperimentsConnectInner(opts);
@@ -46,8 +47,12 @@ async function runExperimentsConnectInner(opts) {
46
47
  console.log(pc.green('✓ Saved project to embeddables.json'));
47
48
  console.log('');
48
49
  }
49
- // 4. Get embeddable ID
50
- let embeddableId = opts.id;
50
+ // 4. Get embeddable ID (from option, cwd inference, or interactive prompt)
51
+ const inferred = inferEmbeddableFromCwd();
52
+ let embeddableId = opts.id ?? inferred?.embeddableId;
53
+ if (inferred && !opts.id && embeddableId) {
54
+ process.chdir(inferred.projectRoot);
55
+ }
51
56
  if (!embeddableId) {
52
57
  const selected = await promptForLocalEmbeddable({
53
58
  message: 'Select an embeddable to connect the experiment to:',
@@ -1 +1 @@
1
- {"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../../src/commands/pull.ts"],"names":[],"mappings":"AA+GA,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,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,cAAc,iBA6PjD"}
1
+ {"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../../src/commands/pull.ts"],"names":[],"mappings":"AAgHA,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,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,cAAc,iBAiQjD"}
@@ -6,6 +6,7 @@ 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
8
  import { promptForProject, promptForEmbeddable, fetchEmbeddableMetadata } from '../prompts/index.js';
9
+ import { inferEmbeddableFromCwd } from '../helpers/utils.js';
9
10
  /**
10
11
  * Normalize custom_validation_function strings so literal "\\n" (backslash-n) becomes real newline.
11
12
  * The API sometimes returns these double-escaped; dataOutputs/computedFields code is written
@@ -108,7 +109,11 @@ function writePullMetadataToConfig(embeddableId, version, branch, branchName) {
108
109
  }
109
110
  }
110
111
  export async function runPull(opts) {
111
- let embeddableId = opts.id;
112
+ const inferred = inferEmbeddableFromCwd();
113
+ let embeddableId = opts.id ?? inferred?.embeddableId;
114
+ if (inferred && !opts.id && embeddableId) {
115
+ process.chdir(inferred.projectRoot);
116
+ }
112
117
  // If no ID provided, try to get it interactively
113
118
  if (!embeddableId) {
114
119
  if (!isLoggedIn()) {
@@ -1,3 +1,25 @@
1
+ import type { HistoryCommand } from '../types-builder.js';
2
+ export type EditHistoryDescription = {
3
+ origin: string;
4
+ description: string;
5
+ } | {
6
+ overflowed: boolean;
7
+ };
8
+ export type SaveEmbeddableVersionParams = {
9
+ embeddableId: string;
10
+ jsonString: string;
11
+ userId: string;
12
+ projectId: string;
13
+ label?: string;
14
+ fromVersionNumber: number;
15
+ editHistoryLength?: number;
16
+ editHistoryDescriptions?: EditHistoryDescription[];
17
+ workingDraftId?: string;
18
+ editHistoryStorageKey?: string;
19
+ editHistory?: HistoryCommand[];
20
+ branchId?: string | null;
21
+ branchMergedId?: string | null;
22
+ };
1
23
  export declare function runSave(opts: {
2
24
  id?: string;
3
25
  label?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"save.d.ts","sourceRoot":"","sources":["../../src/commands/save.ts"],"names":[],"mappings":"AA+OA,wBAAsB,OAAO,CAAC,IAAI,EAAE;IAClC,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,iBAoBA"}
1
+ {"version":3,"file":"save.d.ts","sourceRoot":"","sources":["../../src/commands/save.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAGV,cAAc,EACf,MAAM,qBAAqB,CAAA;AAgC5B,MAAM,MAAM,sBAAsB,GAC9B;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,UAAU,EAAE,OAAO,CAAA;CAAE,CAAA;AAE3B,MAAM,MAAM,2BAA2B,GAAG;IACxC,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,iBAAiB,EAAE,MAAM,CAAA;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,uBAAuB,CAAC,EAAE,sBAAsB,EAAE,CAAA;IAClD,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,WAAW,CAAC,EAAE,cAAc,EAAE,CAAA;IAC9B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC/B,CAAA;AAkND,wBAAsB,OAAO,CAAC,IAAI,EAAE;IAClC,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,iBAoBA"}
@@ -8,6 +8,8 @@ import { compileAllPages } from '../compiler/index.js';
8
8
  import { formatError } from '../compiler/errors.js';
9
9
  import { promptForLocalEmbeddable, promptForProject } from '../prompts/index.js';
10
10
  import { WEB_APP_BASE_URL } from '../constants.js';
11
+ import { translateJsonDiffToEditCommands } from '../helpers/json.js';
12
+ import { generateId, inferEmbeddableFromCwd } from '../helpers/utils.js';
11
13
  /** Error with optional gray detail line (hint/next step) for the user. */
12
14
  class SaveError extends Error {
13
15
  detail;
@@ -90,7 +92,9 @@ function getBranchFromConfig(embeddableId) {
90
92
  }
91
93
  /** Slug for branch name/id for versioned filenames (e.g. "my branch" -> "my_branch"). */
92
94
  function slugForBranch(nameOrId) {
93
- return String(nameOrId).replace(/[^a-zA-Z0-9_.-]/g, '_').replace(/_+/g, '_') || 'main';
95
+ return (String(nameOrId)
96
+ .replace(/[^a-zA-Z0-9_.-]/g, '_')
97
+ .replace(/_+/g, '_') || 'main');
94
98
  }
95
99
  /** Get branch slug from config (_branch_name preferred, else _branch_id, else main). */
96
100
  function getBranchSlugFromConfig(embeddableId) {
@@ -221,8 +225,12 @@ async function runSaveInner(opts) {
221
225
  console.log(pc.gray('Run "embeddables login" to re-authenticate.'));
222
226
  process.exit(1);
223
227
  }
224
- // 3. Get embeddable ID (from option or interactive prompt)
225
- let embeddableId = opts.id;
228
+ // 3. Get embeddable ID (from option, cwd inference, or interactive prompt)
229
+ const inferred = inferEmbeddableFromCwd();
230
+ let embeddableId = opts.id ?? inferred?.embeddableId;
231
+ if (inferred && !opts.id && embeddableId) {
232
+ process.chdir(inferred.projectRoot);
233
+ }
226
234
  if (!embeddableId) {
227
235
  const selected = await promptForLocalEmbeddable({
228
236
  message: 'Select an embeddable to save:',
@@ -320,10 +328,11 @@ async function runSaveInner(opts) {
320
328
  fromVersionNumber = detectedVersion;
321
329
  }
322
330
  // 7b. Check for other users' drafts on this version; warn and optionally abort
331
+ let currentUserId = null;
323
332
  const supabase = await getAuthenticatedSupabaseClient();
324
333
  if (supabase) {
325
334
  const { data: { user }, } = await supabase.auth.getUser();
326
- const currentUserId = user?.id;
335
+ currentUserId = user?.id ?? null;
327
336
  if (currentUserId) {
328
337
  const branchIdForDrafts = effectiveBranch ?? null;
329
338
  const otherDrafts = await fetchOtherUsersDrafts(supabase, embeddableId, fromVersionNumber, branchIdForDrafts, currentUserId);
@@ -331,7 +340,9 @@ async function runSaveInner(opts) {
331
340
  const names = otherDrafts
332
341
  .map((d) => d.author_name?.trim() || d.author_id || 'Someone')
333
342
  .filter((n, i, a) => a.indexOf(n) === i);
334
- const namesText = names.length === 1 ? names[0] : `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`;
343
+ const namesText = names.length === 1
344
+ ? names[0]
345
+ : `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`;
335
346
  console.log('');
336
347
  console.warn(pc.yellow(`⚠ ${namesText} ${names.length === 1 ? 'has' : 'have'} unsaved edits on version ${fromVersionNumber}. Saving may cause conflicts.`));
337
348
  const { proceed } = await prompts({
@@ -354,11 +365,44 @@ async function runSaveInner(opts) {
354
365
  }
355
366
  // 8. POST to save-version API
356
367
  console.log(pc.cyan(`Saving embeddable (based on v${fromVersionNumber})...`));
368
+ // Resolve current user id (required by API)
369
+ if (!currentUserId && supabase) {
370
+ const { data: { user }, } = await supabase.auth.getUser();
371
+ currentUserId = user?.id ?? null;
372
+ }
373
+ if (!currentUserId) {
374
+ throw new SaveError('You must be logged in to save.', 'Run "embeddables login" to authenticate.');
375
+ }
376
+ // Previous version JSON: from versioned snapshot file, or {} if not found (e.g. --from-version without pull)
377
+ const previousVersionPath = path.join(generatedDir, `embeddable-${getBranchSlugFromConfig(embeddableId)}@${fromVersionNumber}.json`);
378
+ const legacyVersionPath = path.join(generatedDir, `embeddable-v${fromVersionNumber}.json`);
379
+ const previousJsonContent = fs.existsSync(previousVersionPath)
380
+ ? fs.readFileSync(previousVersionPath, 'utf8')
381
+ : fs.existsSync(legacyVersionPath)
382
+ ? fs.readFileSync(legacyVersionPath, 'utf8')
383
+ : '{}';
384
+ const commands = translateJsonDiffToEditCommands({
385
+ previousObject: JSON.parse(previousJsonContent),
386
+ currentObject: JSON.parse(jsonContent),
387
+ basePath: [],
388
+ });
357
389
  const body = {
390
+ userId: currentUserId,
358
391
  embeddableId,
359
392
  jsonString: JSON.stringify(embeddableJson),
360
393
  projectId,
361
394
  fromVersionNumber,
395
+ editHistoryLength: 1,
396
+ editHistoryDescriptions: [{ origin: 'CLI', description: 'Saved version from CLI' }],
397
+ editHistory: [
398
+ setMultipleFlowUpdates({
399
+ commands,
400
+ metadata: {
401
+ description: 'Saved version from CLI',
402
+ trigger: { origin: 'CLI', editor: 'CLI' },
403
+ },
404
+ }),
405
+ ],
362
406
  };
363
407
  if (opts.label) {
364
408
  body.label = opts.label;
@@ -476,3 +520,25 @@ async function runSaveInner(opts) {
476
520
  fs.writeFileSync(versionedPath, jsonContent, 'utf8');
477
521
  console.log(pc.cyan(`✓ Saved version file to ${versionedPath}`));
478
522
  }
523
+ function setMultipleFlowUpdates({ commands, metadata, }) {
524
+ const type = 'set_multiple_embeddable_updates';
525
+ const simpleCommandTypes = {
526
+ add: 'add_embeddable_property',
527
+ remove: 'remove_embeddable_property',
528
+ set: 'update_embeddable_property',
529
+ move: 'move_embeddable_property',
530
+ };
531
+ //* Map the commands to the correct type and data
532
+ const mappedCommands = commands.map((command) => ({
533
+ type: Object.keys(simpleCommandTypes).includes(command.type)
534
+ ? simpleCommandTypes[command.type]
535
+ : command.type,
536
+ data: command.data,
537
+ }));
538
+ return {
539
+ id: generateId('edit'),
540
+ type,
541
+ data: { commands: mappedCommands },
542
+ metadata,
543
+ };
544
+ }
@@ -426,6 +426,10 @@ function attrsToProps(attrs, constEnv, filePath, validationFunctionSource) {
426
426
  }
427
427
  throw new CompileError(`Unsupported JSX attribute value for "${name}".`, loc(filePath, a));
428
428
  }
429
+ // When validation_formula is not "custom", custom_validation_function is ignored and omitted (removed on save).
430
+ if (out.validation_formula != null && out.validation_formula !== 'custom' && 'custom_validation_function' in out) {
431
+ delete out.custom_validation_function;
432
+ }
429
433
  return out;
430
434
  }
431
435
  function getJsxTagName(n) {
@@ -506,25 +510,42 @@ const HTML_VOID_ELEMENTS = new Set([
506
510
  'track',
507
511
  'wbr',
508
512
  ]);
513
+ /**
514
+ * Map React/JSX attribute names to HTML attribute names so serialized HTML
515
+ * uses valid attributes (e.g. class for CSS, not className).
516
+ */
517
+ function jsxAttrNameToHTMLAttrName(jsxName) {
518
+ switch (jsxName) {
519
+ case 'className':
520
+ return 'class';
521
+ case 'htmlFor':
522
+ return 'for';
523
+ case 'tabIndex':
524
+ return 'tabindex';
525
+ default:
526
+ return jsxName;
527
+ }
528
+ }
509
529
  /**
510
530
  * Converts a JSX element to HTML string.
511
531
  */
512
532
  function jsxElementToHTML(el, constEnv, filePath) {
513
533
  const opening = el.openingElement;
514
534
  const tagName = getJsxTagName(opening.name);
515
- // Build attributes string
535
+ // Build attributes string (use HTML attribute names so class selectors work)
516
536
  const attrs = [];
517
537
  for (const attr of opening.attributes) {
518
538
  if (attr.type === 'JSXAttribute') {
519
539
  const name = attr.name.type === 'JSXIdentifier'
520
540
  ? attr.name.name
521
541
  : `${attr.name.namespace.name}:${attr.name.name.name}`;
542
+ const htmlName = jsxAttrNameToHTMLAttrName(name);
522
543
  if (!attr.value) {
523
544
  // Boolean attribute
524
- attrs.push(name);
545
+ attrs.push(htmlName);
525
546
  }
526
547
  else if (attr.value.type === 'StringLiteral') {
527
- attrs.push(`${name}="${escapeHTMLAttribute(attr.value.value)}"`);
548
+ attrs.push(`${htmlName}="${escapeHTMLAttribute(attr.value.value)}"`);
528
549
  }
529
550
  else if (attr.value.type === 'JSXExpressionContainer') {
530
551
  try {
@@ -532,13 +553,13 @@ function jsxElementToHTML(el, constEnv, filePath) {
532
553
  if (name === 'style' && typeof value === 'object' && value !== null) {
533
554
  // Convert style object to CSS string
534
555
  const cssString = styleObjectToCSS(value);
535
- attrs.push(`${name}="${escapeHTMLAttribute(cssString)}"`);
556
+ attrs.push(`${htmlName}="${escapeHTMLAttribute(cssString)}"`);
536
557
  }
537
558
  else if (typeof value === 'string') {
538
- attrs.push(`${name}="${escapeHTMLAttribute(value)}"`);
559
+ attrs.push(`${htmlName}="${escapeHTMLAttribute(value)}"`);
539
560
  }
540
561
  else if (typeof value === 'number' || typeof value === 'boolean') {
541
- attrs.push(`${name}="${String(value)}"`);
562
+ attrs.push(`${htmlName}="${String(value)}"`);
542
563
  }
543
564
  }
544
565
  catch {
@@ -1 +1 @@
1
- {"version":3,"file":"reverse.d.ts","sourceRoot":"","sources":["../../src/compiler/reverse.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAiB,MAAM,YAAY,CAAA;AAqlBzD,wBAAsB,cAAc,CAClC,UAAU,EAAE;IACV,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,QAAQ,EAAE,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC5B,cAAc,CAAC,EAAE,GAAG,EAAE,CAAA;IACtB,WAAW,CAAC,EAAE,GAAG,EAAE,CAAA;IACnB,UAAU,CAAC,EAAE,GAAG,EAAE,CAAA;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CACnB,EACD,YAAY,EAAE,MAAM,EACpB,IAAI,CAAC,EAAE;IACL,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,YAAY,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAC5E,iBAoFF;AAyhDD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKpD"}
1
+ {"version":3,"file":"reverse.d.ts","sourceRoot":"","sources":["../../src/compiler/reverse.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAiB,MAAM,YAAY,CAAA;AAqlBzD,wBAAsB,cAAc,CAClC,UAAU,EAAE;IACV,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,QAAQ,EAAE,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC5B,cAAc,CAAC,EAAE,GAAG,EAAE,CAAA;IACtB,WAAW,CAAC,EAAE,GAAG,EAAE,CAAA;IACnB,UAAU,CAAC,EAAE,GAAG,EAAE,CAAA;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CACnB,EACD,YAAY,EAAE,MAAM,EACpB,IAAI,CAAC,EAAE;IACL,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,YAAY,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAC5E,iBAoFF;AAgiDD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKpD"}
@@ -770,6 +770,11 @@ function generateCustomValidationFunctions(node) {
770
770
  }
771
771
  function collect(n) {
772
772
  if (n.component.type === 'InputBox') {
773
+ if (n.component.validation_formula !== 'custom') {
774
+ for (const child of n.children)
775
+ collect(child);
776
+ return;
777
+ }
773
778
  const value = n.component.custom_validation_function;
774
779
  if (typeof value !== 'string' || !value)
775
780
  return;
@@ -968,8 +973,10 @@ function generateJSX(node, indent = 4, pageKey, componentId, nameMap, validation
968
973
  if (value === null || value === undefined)
969
974
  continue;
970
975
  if (typeof value === 'string') {
971
- // custom_validation_function: emit as function reference (validate, validate2, ...)
976
+ // custom_validation_function: only emit when validation_formula is "custom"; otherwise ignore (removed on save).
972
977
  if (key === 'custom_validation_function') {
978
+ if (comp.validation_formula !== 'custom')
979
+ continue;
973
980
  const fnName = validationFunctionNameMap?.get(comp.id) ?? identifierToGeneratedName?.get(value) ?? value;
974
981
  props.push(`custom_validation_function={${fnName}}`);
975
982
  continue;
@@ -1210,13 +1217,15 @@ function htmlToJSXChildren(html, indent) {
1210
1217
  if (!attrMatch[0].includes('=')) {
1211
1218
  continue;
1212
1219
  }
1213
- // Convert HTML attribute names to JSX (e.g., class -> className, for -> htmlFor)
1220
+ // Convert HTML attribute names to JSX (e.g., class -> className, for -> htmlFor).
1221
+ // Normalize classname (wrong serialization) to className so round-trip fixes bad HTML.
1214
1222
  let jsxAttrName = attrName;
1215
- if (attrName === 'class')
1223
+ const attrLower = attrName.toLowerCase();
1224
+ if (attrLower === 'class' || attrLower === 'classname')
1216
1225
  jsxAttrName = 'className';
1217
- else if (attrName === 'for')
1226
+ else if (attrLower === 'for')
1218
1227
  jsxAttrName = 'htmlFor';
1219
- else if (attrName === 'tabindex')
1228
+ else if (attrLower === 'tabindex')
1220
1229
  jsxAttrName = 'tabIndex';
1221
1230
  // Handle style attribute: convert CSS string to JSX object
1222
1231
  if (attrName === 'style' && attrValue) {
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=TEMP%20helpers%20file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TEMP helpers file.d.ts","sourceRoot":"","sources":["../../src/helpers/TEMP helpers file.ts"],"names":[],"mappings":""}
@@ -0,0 +1 @@
1
+ "use strict";
@@ -0,0 +1,47 @@
1
+ import { type Component, type GlobalComponentLocation, type InsertPosition, SetMultipleEmbeddableUpdateCommands } from '../types-builder';
2
+ export declare function translateJsonDiffToEditCommands({ previousObject, currentObject, path, objectMatchKeys, basePath, forceObjectKeyInFirstLevelIteration, extraAlternativePathObjectMatchKeys, }: {
3
+ previousObject: any;
4
+ currentObject: any;
5
+ path?: string[];
6
+ objectMatchKeys?: {
7
+ [k: string]: string;
8
+ };
9
+ basePath?: string[];
10
+ forceObjectKeyInFirstLevelIteration?: string;
11
+ extraAlternativePathObjectMatchKeys?: string[];
12
+ }): SetMultipleEmbeddableUpdateCommands;
13
+ export declare function translateArrayJsonDiffToEditCommands({ previousArray, currentArray, path, objectMatchKeys, basePath, extraAlternativePathObjectMatchKeys, }: {
14
+ previousArray: any[];
15
+ currentArray: any[];
16
+ path?: string[];
17
+ objectMatchKeys?: {
18
+ [k: string]: string;
19
+ };
20
+ basePath?: string[];
21
+ extraAlternativePathObjectMatchKeys?: string[];
22
+ }): SetMultipleEmbeddableUpdateCommands;
23
+ export declare function findAncestorsOfComponent({ components, componentId, }: {
24
+ components: Component[];
25
+ componentId: string;
26
+ }): Component[];
27
+ export declare function getInsertPositionForComponent({ afterComponentId, beforeComponentId, parentComponentId, globalLocation, components, isGlobalComponents, }: {
28
+ afterComponentId?: string;
29
+ beforeComponentId?: string;
30
+ parentComponentId?: string;
31
+ globalLocation?: GlobalComponentLocation;
32
+ components: Component[];
33
+ isGlobalComponents?: boolean;
34
+ }): InsertPosition | undefined;
35
+ export declare function deepEqual(obj1: any, obj2: any): boolean;
36
+ export declare function findUniqueKey(arr: {
37
+ [key: string]: any;
38
+ }[]): string[];
39
+ export declare function findDifferentPaths(obj1: any, obj2: any): Record<string, string[]>;
40
+ export declare function getValueByPath(obj: any, path: string[]): any;
41
+ export declare function getBreakLineStringFromArray(input: any): any;
42
+ export declare function getMultilanguageKey({ attributeKey, languageKey, }: {
43
+ attributeKey: string;
44
+ languageKey: string;
45
+ }): string;
46
+ export declare function getArrayFromBreakLineString(input: string): string[];
47
+ //# sourceMappingURL=json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../src/helpers/json.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,SAAS,EACd,KAAK,uBAAuB,EAC5B,KAAK,cAAc,EACnB,mCAAmC,EACpC,MAAM,kBAAkB,CAAA;AAEzB,wBAAgB,+BAA+B,CAAC,EAC9C,cAAmB,EACnB,aAAkB,EAClB,IAAS,EACT,eAAoB,EACpB,QAAa,EACb,mCAAmC,EACnC,mCAAwC,GACzC,EAAE;IACD,cAAc,EAAE,GAAG,CAAA;IACnB,aAAa,EAAE,GAAG,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,eAAe,CAAC,EAAE;QAAE,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAA;IACzC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IACnB,mCAAmC,CAAC,EAAE,MAAM,CAAA;IAC5C,mCAAmC,CAAC,EAAE,MAAM,EAAE,CAAA;CAC/C,GAAG,mCAAmC,CA6JtC;AAED,wBAAgB,oCAAoC,CAAC,EACnD,aAAkB,EAClB,YAAiB,EACjB,IAAS,EACT,eAAoB,EACpB,QAAa,EACb,mCAAwC,GACzC,EAAE;IACD,aAAa,EAAE,GAAG,EAAE,CAAA;IACpB,YAAY,EAAE,GAAG,EAAE,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,eAAe,CAAC,EAAE;QAAE,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAA;IACzC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IACnB,mCAAmC,CAAC,EAAE,MAAM,EAAE,CAAA;CAC/C,GAAG,mCAAmC,CA2MtC;AAGD,wBAAgB,wBAAwB,CAAC,EACvC,UAAU,EACV,WAAW,GACZ,EAAE;IACD,UAAU,EAAE,SAAS,EAAE,CAAA;IACvB,WAAW,EAAE,MAAM,CAAA;CACpB,GAAG,SAAS,EAAE,CAsBd;AAsDD,wBAAgB,6BAA6B,CAAC,EAC5C,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EACd,UAAU,EACV,kBAAkB,GACnB,EAAE;IACD,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,cAAc,CAAC,EAAE,uBAAuB,CAAA;IACxC,UAAU,EAAE,SAAS,EAAE,CAAA;IACvB,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B,GAAG,cAAc,GAAG,SAAS,CAgI7B;AAKD,wBAAgB,SAAS,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,WAkB7C;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,EAAE,GAAG,MAAM,EAAE,CA6BrE;AAGD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAqEjF;AAGD,wBAAgB,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,OAYtD;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,GAAG,OAGrD;AAMD,wBAAgB,mBAAmB,CAAC,EAClC,YAAY,EACZ,WAAW,GACZ,EAAE;IACD,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;CACpB,GAAG,MAAM,CAIT;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,MAAM,YAGxD"}