@embeddables/cli 0.6.8 → 0.6.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.
@@ -510,6 +510,18 @@ Conditions control visibility of components and pages based on User Data. The `C
510
510
 
511
511
  **Storage**: Conditions are stored in the component/page JSON and preserved in config.json (not in React/CSS/JS files).
512
512
 
513
+ ### Experiments and conditions
514
+
515
+ When visibility is gated by an experiment (a key in `connected_experiments`, otherwise the experiment is not connected and will not work, and which controls the variants as well), conditions that use that experiment key must cover both:
516
+
517
+ 1. **Control variant** – Use the control variant value (e.g. `"control"`) in condition values so the control experience is shown when that variant is assigned.
518
+ 2. **No value** – Use the special value `_no_value` in condition values so users who are not yet assigned a variant (user data has no value for that experiment key) see the intended default experience—typically the same as control.
519
+ 3. Other variant keys.
520
+
521
+ #1 and #2 should be on the same conditions as each other, whereas all other variants (#3) can be on other conditions.
522
+
523
+ Example: for experiment key `hero_test` with variants `control` and `variant_b`, the control experience might use a condition like `key: "hero_test", operator: "==", values: ["control", "_no_value"]` so both control users and users with no assignment see the control. The variant experience would use `values: ["variant_b"]`.
524
+
513
525
  ## Config.json Structure
514
526
 
515
527
  The `config.json` file contains the reduced Embeddable JSON with:
@@ -613,7 +625,7 @@ The `config.json` file contains the reduced Embeddable JSON with:
613
625
 
614
626
  5. **Global Components**: Must use the filenames to replace the `_location` property.
615
627
 
616
- 6. **Conditions**: Stored in config.json, applied at runtime. Multiple values in one condition = OR, multiple conditions = AND.
628
+ 6. **Conditions**: Stored in config.json, applied at runtime. Multiple values in one condition = OR, multiple conditions = AND. For experiment-gated conditions, when setting the control variant's conditions always include both the control variant value and `_no_value` so both control and unassigned users see the intended default.
617
629
 
618
630
  7. **CSS Selectors**: Must follow exact structure with proper spacing (trailing space on breakpoints, leading space on sub-elements).
619
631
 
@@ -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()) {
@@ -9,7 +9,7 @@ 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
11
  import { translateJsonDiffToEditCommands } from '../helpers/json.js';
12
- import { generateId } from '../helpers/utils.js';
12
+ import { generateId, inferEmbeddableFromCwd } from '../helpers/utils.js';
13
13
  /** Error with optional gray detail line (hint/next step) for the user. */
14
14
  class SaveError extends Error {
15
15
  detail;
@@ -225,8 +225,12 @@ async function runSaveInner(opts) {
225
225
  console.log(pc.gray('Run "embeddables login" to re-authenticate.'));
226
226
  process.exit(1);
227
227
  }
228
- // 3. Get embeddable ID (from option or interactive prompt)
229
- 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
+ }
230
234
  if (!embeddableId) {
231
235
  const selected = await promptForLocalEmbeddable({
232
236
  message: 'Select an embeddable to save:',
@@ -526,7 +530,7 @@ function setMultipleFlowUpdates({ commands, metadata, }) {
526
530
  };
527
531
  //* Map the commands to the correct type and data
528
532
  const mappedCommands = commands.map((command) => ({
529
- type: command.type in Object.keys(simpleCommandTypes)
533
+ type: Object.keys(simpleCommandTypes).includes(command.type)
530
534
  ? simpleCommandTypes[command.type]
531
535
  : command.type,
532
536
  data: command.data,
@@ -1,2 +1,13 @@
1
1
  export declare function generateId(prefix: string): string;
2
+ /**
3
+ * If the current working directory is inside embeddables/<id>/ (or a subfolder),
4
+ * returns that embeddable id and the project root (the directory containing the
5
+ * embeddables/ folder). Otherwise returns null.
6
+ * When using the inferred id, callers should chdir(projectRoot) so relative
7
+ * paths like embeddables/<id>/... resolve correctly.
8
+ */
9
+ export declare function inferEmbeddableFromCwd(): {
10
+ embeddableId: string;
11
+ projectRoot: string;
12
+ } | null;
2
13
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/helpers/utils.ts"],"names":[],"mappings":"AAAA,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,UAIxC"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/helpers/utils.ts"],"names":[],"mappings":"AAEA,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,UAIxC;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,IAAI;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAc7F"}
@@ -1,4 +1,28 @@
1
+ import path from 'node:path';
1
2
  export function generateId(prefix) {
2
3
  const randomString = Math.random().toString(20).slice(2) + Math.random().toString(20).slice(2);
3
4
  return prefix ? `${prefix}_${randomString}` : `_${randomString}`;
4
5
  }
6
+ /**
7
+ * If the current working directory is inside embeddables/<id>/ (or a subfolder),
8
+ * returns that embeddable id and the project root (the directory containing the
9
+ * embeddables/ folder). Otherwise returns null.
10
+ * When using the inferred id, callers should chdir(projectRoot) so relative
11
+ * paths like embeddables/<id>/... resolve correctly.
12
+ */
13
+ export function inferEmbeddableFromCwd() {
14
+ let current = path.resolve(process.cwd());
15
+ for (;;) {
16
+ const parent = path.dirname(current);
17
+ if (parent === current)
18
+ break;
19
+ if (path.basename(parent) === 'embeddables') {
20
+ return {
21
+ embeddableId: path.basename(current),
22
+ projectRoot: path.dirname(parent),
23
+ };
24
+ }
25
+ current = parent;
26
+ }
27
+ return null;
28
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@embeddables/cli",
3
- "version": "0.6.8",
3
+ "version": "0.6.10",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "embeddables": "./bin/embeddables.mjs"