@embeddable.com/init 0.1.0 → 0.1.2

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 (2) hide show
  1. package/dist/index.js +142 -55
  2. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -7,45 +7,71 @@ import path from "path";
7
7
  import chalk from "chalk";
8
8
  import degit from "degit";
9
9
  import prompts from "prompts";
10
+ import terminalLink from "terminal-link";
10
11
  var REPO_URL = "embeddable-hq/remarkable-pro-boilerplate";
11
12
  var DEFAULT_FOLDER = "embeddable-repo";
12
- var SIGNUP_URLS = {
13
- US: "https://app.us.embeddable.com/auth/login?screen_hint=signup",
14
- EU: "https://app.eu.embeddable.com/auth/login?screen_hint=signup"
13
+ var WORKSPACE_URLS = {
14
+ US: "https://app.us.embeddable.com",
15
+ EU: "https://app.eu.embeddable.com"
15
16
  };
17
+ var INVALID_FOLDER_CHARS = /[<>:"/\\|?*\x00-\x1f]/;
16
18
  function exit(message) {
17
19
  if (message) console.log(message);
18
20
  process.exit(0);
19
21
  }
22
+ function printDocsHint() {
23
+ const docsUrl = "https://docs.embeddable.com";
24
+ const docsLink = terminalLink(docsUrl, docsUrl, { fallback: (text) => text });
25
+ console.log(`If you get stuck, please follow our 'Quick-start guide' at ${chalk.cyan(docsLink)}
26
+ `);
27
+ }
28
+ function validateFolderName(value, cwd) {
29
+ const trimmed = value.trim();
30
+ if (!trimmed) return "Folder name is required";
31
+ if (INVALID_FOLDER_CHARS.test(trimmed)) {
32
+ return "Folder name contains invalid characters";
33
+ }
34
+ if (trimmed.startsWith(".") || trimmed.startsWith("-")) {
35
+ return "Folder name should not start with . or -";
36
+ }
37
+ if (fs.existsSync(path.join(cwd, trimmed))) {
38
+ return `Folder "${trimmed}" already exists`;
39
+ }
40
+ return true;
41
+ }
42
+ function isValidUuid(value) {
43
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
44
+ return uuidRegex.test(value.trim());
45
+ }
20
46
  async function main() {
21
- prompts.override({ onCancel: () => exit("\nSetup cancelled.\n") });
47
+ const onCancel = () => exit("\nSetup cancelled.\n");
22
48
  console.log(chalk.bold("\nWelcome to Embeddable!\n"));
49
+ console.log(`Embeddable stores your ${chalk.bold("charting components")}, ${chalk.bold("data models")} and ${chalk.bold("themes")} in code, so you have ${chalk.bold("full control")} over the ${chalk.bold("look-and-feel")} and the ${chalk.bold("data")} powering your dashboards.
50
+ `);
51
+ console.log("This tool helps you get set up to create your first Embeddable dashboard.\n");
23
52
  const cwd = process.cwd();
24
- const { confirmDir } = await prompts({
25
- type: "confirm",
26
- name: "confirmDir",
27
- message: `Create a new project in ${chalk.cyan(cwd)}?`,
28
- initial: true
29
- });
30
- if (confirmDir === void 0) {
31
- exit("\nSetup cancelled.\n");
32
- }
53
+ const { confirmDir } = await prompts(
54
+ {
55
+ type: "confirm",
56
+ name: "confirmDir",
57
+ message: `Ok to create a new Project subfolder in your current directory ${chalk.cyan(cwd)}?`,
58
+ initial: true
59
+ },
60
+ { onCancel }
61
+ );
33
62
  if (!confirmDir) {
34
63
  exit("\nRun this command from the directory where you'd like to create your project.\n");
35
64
  }
36
- const { folderName } = await prompts({
37
- type: "text",
38
- name: "folderName",
39
- message: "Project folder name:",
40
- initial: DEFAULT_FOLDER,
41
- validate: (value) => {
42
- if (!value.trim()) return "Folder name is required";
43
- if (fs.existsSync(path.join(cwd, value))) {
44
- return `Folder "${value}" already exists`;
45
- }
46
- return true;
47
- }
48
- });
65
+ const { folderName } = await prompts(
66
+ {
67
+ type: "text",
68
+ name: "folderName",
69
+ message: "Project folder name:",
70
+ initial: DEFAULT_FOLDER,
71
+ validate: (value) => validateFolderName(value, cwd)
72
+ },
73
+ { onCancel }
74
+ );
49
75
  if (!folderName) {
50
76
  exit("\nSetup cancelled.\n");
51
77
  }
@@ -62,6 +88,7 @@ async function main() {
62
88
  console.error(chalk.red(`
63
89
  Failed to download boilerplate: ${message}
64
90
  `));
91
+ printDocsHint();
65
92
  process.exit(1);
66
93
  }
67
94
  try {
@@ -71,57 +98,117 @@ Failed to download boilerplate: ${message}
71
98
  chalk.yellow("\nWarning: Could not initialize git repository. Is git installed?")
72
99
  );
73
100
  }
74
- const { region: selectedRegion } = await prompts({
75
- type: "select",
76
- name: "region",
77
- message: "Select your region:",
78
- choices: [
79
- { title: "US", value: "US" },
80
- { title: "EU", value: "EU" }
81
- ],
82
- initial: 0
83
- });
84
- if (!selectedRegion) {
101
+ console.log(chalk.dim("Installing dependencies...\n"));
102
+ try {
103
+ execSync("npm install", { cwd: projectPath, stdio: "inherit" });
104
+ } catch {
105
+ console.error(chalk.red("\nnpm install failed.\n"));
106
+ printDocsHint();
107
+ process.exit(1);
108
+ }
109
+ console.log(`
110
+ `);
111
+ const { apiKey } = await prompts(
112
+ {
113
+ type: "password",
114
+ name: "apiKey",
115
+ message: `You'll need an API Key to push this code bundle to your workspace (to create an account, head to ${chalk.cyan("https://embeddable.com/start")}).
116
+ Paste your API Key here:`,
117
+ validate: (value) => {
118
+ const trimmed = value.trim();
119
+ if (!trimmed) return "API key is required";
120
+ if (!isValidUuid(trimmed)) return "API key must be a valid UUID";
121
+ return true;
122
+ }
123
+ },
124
+ { onCancel }
125
+ );
126
+ if (!apiKey) {
127
+ exit("\nSetup cancelled.\n");
128
+ }
129
+ const embeddableDir = path.join(projectPath, ".embeddable");
130
+ fs.mkdirSync(embeddableDir, { recursive: true });
131
+ fs.writeFileSync(path.join(embeddableDir, ".api-key"), apiKey.trim());
132
+ const regionResponse = await prompts(
133
+ {
134
+ type: "select",
135
+ name: "region",
136
+ message: "Confirm your workspace region:",
137
+ choices: [
138
+ { title: "US", value: "US" },
139
+ { title: "EU", value: "EU" }
140
+ ],
141
+ initial: 0
142
+ },
143
+ { onCancel }
144
+ );
145
+ const region = regionResponse.region;
146
+ if (!region) {
85
147
  exit("\nSetup cancelled.\n");
86
148
  }
87
- const region = selectedRegion;
88
149
  if (region === "EU") {
89
150
  const configPath = path.join(projectPath, "embeddable.config.ts");
90
- if (!fs.existsSync(configPath)) {
91
- console.log(
92
- chalk.yellow("\nWarning: Could not find embeddable.config.ts to set region.")
93
- );
94
- } else {
95
- let configContent = fs.readFileSync(configPath, "utf-8");
96
- const originalContent = configContent;
97
- configContent = configContent.replace(
151
+ try {
152
+ const configContent = fs.readFileSync(configPath, "utf-8");
153
+ let updatedContent = configContent.replace(
98
154
  /^(\s*)region:\s*["']US["'],?/m,
99
155
  "$1// region: 'US',"
100
156
  );
101
- configContent = configContent.replace(
157
+ updatedContent = updatedContent.replace(
102
158
  /^(\s*)\/\/\s*region:\s*["']EU["'],?/m,
103
159
  "$1region: 'EU',"
104
160
  );
105
- if (configContent === originalContent) {
161
+ if (updatedContent === configContent) {
106
162
  console.log(
107
163
  chalk.yellow("\nWarning: Could not update region in config file. Please set it manually.")
108
164
  );
109
165
  } else {
110
- fs.writeFileSync(configPath, configContent);
166
+ fs.writeFileSync(configPath, updatedContent);
111
167
  }
168
+ } catch {
169
+ console.log(
170
+ chalk.yellow("\nWarning: Could not find embeddable.config.ts to set region.")
171
+ );
112
172
  }
113
173
  }
174
+ console.log(chalk.dim("\nBuilding bundle...\n"));
175
+ try {
176
+ execSync("npm run embeddable:build", { cwd: projectPath, stdio: "inherit" });
177
+ } catch {
178
+ console.error(chalk.red("\nFailed to build bundle.\n"));
179
+ printDocsHint();
180
+ process.exit(1);
181
+ }
182
+ console.log(chalk.dim("\nPushing bundle to workspace...\n"));
183
+ try {
184
+ execSync(
185
+ `npm run embeddable:push -- --api-key ${apiKey.trim()} --email "no-reply@embeddable.com" --message "npx @embeddable.com/init"`,
186
+ { cwd: projectPath, stdio: "inherit" }
187
+ );
188
+ } catch {
189
+ console.error(chalk.red("\nFailed to push bundle to workspace.\n"));
190
+ printDocsHint();
191
+ process.exit(1);
192
+ }
193
+ const workspaceUrl = WORKSPACE_URLS[region];
194
+ const workspaceLink = terminalLink(workspaceUrl, workspaceUrl, {
195
+ fallback: (text) => text
196
+ });
197
+ const docsUrl = "https://docs.embeddable.com/getting-started/first-embeddable";
198
+ const docsLink = terminalLink(docsUrl, docsUrl, {
199
+ fallback: (text) => text
200
+ });
114
201
  console.log(chalk.green(`
115
- \u2713 Created ${chalk.bold(folderName)}!
202
+ Success!
116
203
  `));
117
- console.log("Next steps:\n");
118
- console.log(chalk.cyan(` cd ${folderName}`));
119
- console.log(chalk.cyan(" npm install"));
120
- console.log(chalk.cyan(" npm run dev\n"));
121
- console.log(`Sign up at: ${chalk.underline(SIGNUP_URLS[region])}
204
+ console.log(`You have pushed your ${chalk.bold("components")} and ${chalk.bold("data models")} to workspace.`);
205
+ console.log(`Head there now to build your first Embeddable: ${chalk.cyan(workspaceLink)}
206
+ `);
207
+ console.log(`Follow the guide here to create your first Embeddable dashboard: ${chalk.cyan(docsLink)}
122
208
  `);
123
209
  }
124
210
  main().catch((error) => {
125
211
  console.error(chalk.red("An unexpected error occurred:"), error);
212
+ printDocsHint();
126
213
  process.exit(1);
127
214
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@embeddable.com/init",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "CLI tool for Embeddable",
5
5
  "type": "module",
6
6
  "bin": {
@@ -25,7 +25,8 @@
25
25
  "dependencies": {
26
26
  "chalk": "^5.3.0",
27
27
  "degit": "^2.8.4",
28
- "prompts": "^2.4.2"
28
+ "prompts": "^2.4.2",
29
+ "terminal-link": "^5.0.0"
29
30
  },
30
31
  "devDependencies": {
31
32
  "@types/degit": "^2.8.6",