@embeddable.com/init 0.1.21 → 0.1.23

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 (3) hide show
  1. package/README.md +15 -0
  2. package/dist/index.js +81 -7
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -43,6 +43,19 @@ npm run build
43
43
  npm run dev
44
44
  ```
45
45
 
46
+ ### Run tests
47
+
48
+ ```bash
49
+ npm test
50
+ ```
51
+
52
+ The test suite covers:
53
+ - User cancellation at each prompt stage
54
+ - Error handling (network failures, npm install failures, build/push failures)
55
+ - Happy path flows for both US and EU regions
56
+ - Non-fatal warnings (git init failure, config update issues)
57
+ - Cleanup behavior on failure
58
+
46
59
  ### Test locally
47
60
 
48
61
  After building, you can test the CLI locally:
@@ -77,6 +90,8 @@ npm view @embeddable.com/init
77
90
  ```
78
91
  ├── src/
79
92
  │ └── index.ts # Main CLI application
93
+ ├── test/
94
+ │ └── index.test.ts # Test suite
80
95
  ├── dist/ # Compiled output (generated)
81
96
  ├── package.json # Package configuration
82
97
  ├── tsconfig.json # TypeScript configuration
package/dist/index.js CHANGED
@@ -9,6 +9,42 @@ import degit from "degit";
9
9
  import prompts from "prompts";
10
10
  import terminalLink from "terminal-link";
11
11
  import open from "open";
12
+
13
+ // src/analytics.ts
14
+ import * as amplitude from "@amplitude/analytics-node";
15
+ import crypto from "crypto";
16
+ var sessionUserId = crypto.randomUUID();
17
+ function initAnalytics() {
18
+ try {
19
+ amplitude.init("9ceb0671ac9beaa2bb773d4ed7238d3d", {
20
+ serverZone: "EU",
21
+ flushIntervalMillis: 0,
22
+ flushQueueSize: 1
23
+ });
24
+ } catch {
25
+ }
26
+ }
27
+ function trackEvent(name, properties) {
28
+ try {
29
+ amplitude.track(name, properties, {
30
+ user_id: sessionUserId,
31
+ platform: "Web",
32
+ ip: "$remote"
33
+ });
34
+ } catch {
35
+ }
36
+ }
37
+ async function flushAndWait(timeoutMs = 1e3) {
38
+ try {
39
+ await Promise.race([
40
+ amplitude.flush(),
41
+ new Promise((resolve) => setTimeout(resolve, timeoutMs))
42
+ ]);
43
+ } catch {
44
+ }
45
+ }
46
+
47
+ // src/index.ts
12
48
  var REPO_URL = "embeddable-hq/remarkable-pro-boilerplate";
13
49
  var DEFAULT_FOLDER = "embeddable-repo";
14
50
  var TYPEWRITER_SPEED = 10;
@@ -17,8 +53,14 @@ var WORKSPACE_URLS = {
17
53
  EU: "https://app.eu.embeddable.com"
18
54
  };
19
55
  var INVALID_FOLDER_CHARS = /[<>:"/\\|?*\x00-\x1f]/;
20
- function exit(message) {
56
+ var exiting = false;
57
+ var currentStage = "init";
58
+ async function exit(message) {
59
+ if (exiting) return void 0;
60
+ exiting = true;
21
61
  if (message) console.log(message);
62
+ trackEvent("F1_Cli_Cancelled", { stage: currentStage });
63
+ await flushAndWait();
22
64
  printDocsHint();
23
65
  process.exit(0);
24
66
  }
@@ -62,7 +104,9 @@ var LOGO = `
62
104
  \u2580\u2580\u2588\u2588\u2588\u2588\u2580\u2580 \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D
63
105
  `;
64
106
  async function main() {
65
- const onCancel = () => exit("\nSetup cancelled.\n");
107
+ const onCancel = () => {
108
+ exit("\nSetup cancelled.\n");
109
+ };
66
110
  console.log(chalk.white(LOGO));
67
111
  await typewriter("Welcome to Embeddable!\n\n", chalk.bold, TYPEWRITER_SPEED);
68
112
  await typewriter("Embeddable stores your ", void 0, TYPEWRITER_SPEED);
@@ -85,7 +129,10 @@ async function main() {
85
129
  await typewriter(" and ", void 0, TYPEWRITER_SPEED);
86
130
  await typewriter("themes", chalk.bold, TYPEWRITER_SPEED);
87
131
  await typewriter(" (in your current directory) to help you create your first Embeddable dashboard.\n\n", void 0, TYPEWRITER_SPEED);
132
+ initAnalytics();
133
+ trackEvent("F1_Cli_Started");
88
134
  const cwd = process.cwd();
135
+ currentStage = "dir_confirm";
89
136
  const { confirmDir } = await prompts(
90
137
  {
91
138
  type: "confirm",
@@ -95,9 +142,11 @@ async function main() {
95
142
  },
96
143
  { onCancel }
97
144
  );
145
+ trackEvent("F1_Dir_Confirmed", { accepted: !!confirmDir });
98
146
  if (!confirmDir) {
99
- exit("\nRun this command from the directory where you'd like to create your project.\n");
147
+ await exit("\nRun this command from the directory where you'd like to create your project.\n");
100
148
  }
149
+ currentStage = "folder_name";
101
150
  const { folderName } = await prompts(
102
151
  {
103
152
  type: "text",
@@ -109,18 +158,23 @@ async function main() {
109
158
  { onCancel }
110
159
  );
111
160
  if (!folderName) {
112
- exit("\nSetup cancelled.\n");
161
+ await exit("\nSetup cancelled.\n");
113
162
  }
163
+ trackEvent("F1_Folder_Named");
114
164
  const projectPath = path.join(cwd, folderName);
115
165
  console.log(chalk.dim("\nDownloading boilerplate..."));
166
+ const startRepo = Date.now();
116
167
  try {
117
168
  const emitter = degit(REPO_URL, { cache: false, force: true });
118
169
  await emitter.clone(projectPath);
170
+ trackEvent("F1_Repo_Downloaded", { duration_ms: Date.now() - startRepo });
119
171
  } catch (error) {
120
172
  if (fs.existsSync(projectPath)) {
121
173
  fs.rmSync(projectPath, { recursive: true });
122
174
  }
123
175
  const message = error instanceof Error ? error.message : "Unknown error";
176
+ trackEvent("F1_Cli_Error", { stage: "repo_download", error: message });
177
+ await flushAndWait();
124
178
  console.error(chalk.red(`
125
179
  Failed to download boilerplate: ${message}
126
180
  `));
@@ -135,13 +189,18 @@ Failed to download boilerplate: ${message}
135
189
  );
136
190
  }
137
191
  console.log(chalk.dim("Installing dependencies...\n"));
192
+ const startDeps = Date.now();
138
193
  try {
139
194
  execSync("npm install", { cwd: projectPath, stdio: "inherit" });
195
+ trackEvent("F1_Deps_Installed", { duration_ms: Date.now() - startDeps });
140
196
  } catch {
197
+ trackEvent("F1_Cli_Error", { stage: "deps_install", error: "npm install failed" });
198
+ await flushAndWait();
141
199
  console.error(chalk.red("\nnpm install failed.\n"));
142
200
  printDocsHint();
143
201
  process.exit(1);
144
202
  }
203
+ currentStage = "api_key";
145
204
  console.log(`
146
205
  `);
147
206
  const { apiKey } = await prompts(
@@ -162,11 +221,13 @@ Paste your API Key here:`,
162
221
  { onCancel }
163
222
  );
164
223
  if (!apiKey) {
165
- exit("\nSetup cancelled.\n");
224
+ await exit("\nSetup cancelled.\n");
166
225
  }
226
+ trackEvent("F1_Api_Key_Entered");
167
227
  const embeddableDir = path.join(projectPath, ".embeddable");
168
228
  fs.mkdirSync(embeddableDir, { recursive: true });
169
229
  fs.writeFileSync(path.join(embeddableDir, ".api-key"), apiKey.trim());
230
+ currentStage = "region";
170
231
  console.log("");
171
232
  const regionResponse = await prompts(
172
233
  {
@@ -183,8 +244,9 @@ Paste your API Key here:`,
183
244
  );
184
245
  const region = regionResponse.region;
185
246
  if (!region) {
186
- exit("\nSetup cancelled.\n");
247
+ await exit("\nSetup cancelled.\n");
187
248
  }
249
+ trackEvent("F1_Region_Selected", { region });
188
250
  if (region === "EU") {
189
251
  const configPath = path.join(projectPath, "embeddable.config.ts");
190
252
  try {
@@ -211,20 +273,28 @@ Paste your API Key here:`,
211
273
  }
212
274
  }
213
275
  console.log(chalk.dim("\nBuilding bundle..."));
276
+ const startBuild = Date.now();
214
277
  try {
215
278
  execSync("npm run embeddable:build", { cwd: projectPath, stdio: "inherit" });
279
+ trackEvent("F1_Bundle_Built", { duration_ms: Date.now() - startBuild });
216
280
  } catch {
281
+ trackEvent("F1_Cli_Error", { stage: "bundle_build", error: "build failed" });
282
+ await flushAndWait();
217
283
  console.error(chalk.red("\nFailed to build bundle.\n"));
218
284
  printDocsHint();
219
285
  process.exit(1);
220
286
  }
221
287
  console.log(chalk.dim("Pushing bundle to workspace..."));
288
+ const startPush = Date.now();
222
289
  try {
223
290
  execSync(
224
291
  `npm run embeddable:push -- --api-key ${apiKey.trim()} --email "no-reply@embeddable.com" --message "npx @embeddable.com/init"`,
225
292
  { cwd: projectPath, stdio: "inherit" }
226
293
  );
294
+ trackEvent("F1_Bundle_Pushed", { duration_ms: Date.now() - startPush });
227
295
  } catch {
296
+ trackEvent("F1_Cli_Error", { stage: "bundle_push", error: "push failed" });
297
+ await flushAndWait();
228
298
  console.error(chalk.red("\nFailed to push bundle to workspace.\n"));
229
299
  printDocsHint();
230
300
  process.exit(1);
@@ -245,9 +315,13 @@ Success!
245
315
  `);
246
316
  console.log(`Follow the guide here to create your first Embeddable dashboard: ${chalk.cyan(docsLink)}
247
317
  `);
318
+ trackEvent("F1_Cli_Completed", { region });
319
+ await flushAndWait();
248
320
  await open(docsUrl);
249
321
  }
250
- main().catch((error) => {
322
+ main().catch(async (error) => {
323
+ trackEvent("F1_Cli_Error", { stage: currentStage, error: error instanceof Error ? error.message : "Unknown error" });
324
+ await flushAndWait();
251
325
  console.error(chalk.red("An unexpected error occurred:"), error);
252
326
  printDocsHint();
253
327
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@embeddable.com/init",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
4
4
  "description": "CLI tool for Embeddable",
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,6 +27,7 @@
27
27
  "node": ">=20"
28
28
  },
29
29
  "dependencies": {
30
+ "@amplitude/analytics-node": "^1.3.6",
30
31
  "chalk": "^5.3.0",
31
32
  "degit": "^2.8.4",
32
33
  "open": "^11.0.0",