@electric-sql/start 1.0.2 → 1.0.3

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/dist/bin.js CHANGED
@@ -2,39 +2,140 @@
2
2
  import {
3
3
  provisionElectricResources,
4
4
  setupTemplate
5
- } from "./chunk-J5QG5B4S.js";
5
+ } from "./chunk-RTAT4KWN.js";
6
6
 
7
7
  // src/cli.ts
8
8
  import { execSync } from "child_process";
9
+ import { createInterface } from "readline";
9
10
  import { join } from "path";
10
- function printNextSteps(appName, fullSetup = false) {
11
+ function parseArgs(args) {
12
+ const positionalArgs = [];
13
+ let sourceId;
14
+ let secret;
15
+ let databaseUrl;
16
+ for (let i = 0; i < args.length; i++) {
17
+ if (args[i] === `--source`) {
18
+ sourceId = args[i + 1];
19
+ if (!sourceId || sourceId.startsWith(`-`)) {
20
+ console.error(`Error: --source requires a source ID value`);
21
+ process.exit(1);
22
+ }
23
+ i++;
24
+ } else if (args[i] === `--secret`) {
25
+ secret = args[i + 1];
26
+ if (!secret || secret.startsWith(`-`)) {
27
+ console.error(`Error: --secret requires a value`);
28
+ process.exit(1);
29
+ }
30
+ i++;
31
+ } else if (args[i] === `--database-url`) {
32
+ databaseUrl = args[i + 1];
33
+ if (!databaseUrl || databaseUrl.startsWith(`-`)) {
34
+ console.error(`Error: --database-url requires a value`);
35
+ process.exit(1);
36
+ }
37
+ i++;
38
+ } else if (!args[i].startsWith(`-`)) {
39
+ positionalArgs.push(args[i]);
40
+ }
41
+ }
42
+ if (positionalArgs.length === 0) {
43
+ console.error(
44
+ `Usage: npx @electric-sql/start <app-name> [--source <source-id>] [--secret <secret>] [--database-url <url>]`
45
+ );
46
+ console.error(
47
+ ` npx @electric-sql/start . (configure current directory)`
48
+ );
49
+ process.exit(1);
50
+ }
51
+ if (positionalArgs.length > 1) {
52
+ console.error(
53
+ `Error: Expected only one app name, but received multiple: ${positionalArgs.join(`, `)}`
54
+ );
55
+ console.error(
56
+ `Usage: npx @electric-sql/start <app-name> [--source <source-id>] [--secret <secret>] [--database-url <url>]`
57
+ );
58
+ process.exit(1);
59
+ }
60
+ return { appName: positionalArgs[0], sourceId, secret, databaseUrl };
61
+ }
62
+ function prompt(question) {
63
+ const rl = createInterface({
64
+ input: process.stdin,
65
+ output: process.stdout
66
+ });
67
+ return new Promise((resolve) => {
68
+ rl.question(question, (answer) => {
69
+ rl.close();
70
+ resolve(answer);
71
+ });
72
+ });
73
+ }
74
+ function promptPassword(question) {
75
+ const stdin = process.stdin;
76
+ if (!stdin.isTTY) {
77
+ return prompt(question);
78
+ }
79
+ return new Promise((resolve) => {
80
+ process.stdout.write(question);
81
+ const wasRaw = stdin.isRaw;
82
+ stdin.setRawMode(true);
83
+ stdin.resume();
84
+ stdin.setEncoding(`utf8`);
85
+ let password = ``;
86
+ const onData = (char) => {
87
+ const code = char.charCodeAt(0);
88
+ if (char === `\r` || char === `
89
+ ` || code === 13) {
90
+ stdin.setRawMode(wasRaw ?? false);
91
+ stdin.removeListener(`data`, onData);
92
+ stdin.pause();
93
+ process.stdout.write(`
94
+ `);
95
+ resolve(password);
96
+ } else if (code === 127 || code === 8) {
97
+ if (password.length > 0) {
98
+ password = password.slice(0, -1);
99
+ process.stdout.write(`\b \b`);
100
+ }
101
+ } else if (code === 3) {
102
+ process.stdout.write(`
103
+ `);
104
+ process.exit(1);
105
+ } else if (code >= 32) {
106
+ password += char;
107
+ process.stdout.write(`*`);
108
+ }
109
+ };
110
+ stdin.on(`data`, onData);
111
+ });
112
+ }
113
+ function printNextSteps(appName, options = {}) {
114
+ const { showInstall = false, showMigrate = false, showClaim = true } = options;
11
115
  console.log(`Next steps:`);
12
116
  if (appName !== `.`) {
13
117
  console.log(` cd ${appName}`);
14
118
  }
15
- if (fullSetup) {
119
+ if (showInstall) {
16
120
  console.log(` pnpm install`);
121
+ }
122
+ if (showMigrate) {
17
123
  console.log(` pnpm migrate`);
18
124
  }
19
125
  console.log(` pnpm dev`);
20
126
  console.log(``);
21
127
  console.log(`Commands:`);
22
128
  console.log(` pnpm psql # Connect to database`);
23
- console.log(` pnpm claim # Claim cloud resources`);
129
+ if (showClaim) {
130
+ console.log(` pnpm claim # Claim cloud resources`);
131
+ }
24
132
  console.log(` pnpm deploy:netlify # Deploy to Netlify`);
25
133
  console.log(``);
26
134
  console.log(`Tutorial: https://electric-sql.com/docs`);
27
135
  }
28
136
  async function main() {
29
137
  const args = process.argv.slice(2);
30
- if (args.length === 0) {
31
- console.error(`Usage: npx @electric-sql/start <app-name>`);
32
- console.error(
33
- ` npx @electric-sql/start . (configure current directory)`
34
- );
35
- process.exit(1);
36
- }
37
- const appName = args[0];
138
+ const { appName, sourceId, secret, databaseUrl } = parseArgs(args);
38
139
  if (appName !== `.` && !/^[a-zA-Z0-9-_]+$/.test(appName)) {
39
140
  console.error(
40
141
  `App name must contain only letters, numbers, hyphens, and underscores`
@@ -47,7 +148,37 @@ async function main() {
47
148
  console.log(`Creating app: ${appName}`);
48
149
  }
49
150
  try {
50
- const credentials = await provisionElectricResources();
151
+ let credentials;
152
+ let userProvidedCredentials = false;
153
+ if (sourceId) {
154
+ let finalSecret = secret;
155
+ if (!finalSecret) {
156
+ finalSecret = await promptPassword(
157
+ `Enter secret for source ${sourceId}: `
158
+ );
159
+ }
160
+ if (!finalSecret.trim()) {
161
+ console.error(`Error: Secret cannot be empty`);
162
+ process.exit(1);
163
+ }
164
+ let finalDatabaseUrl = databaseUrl;
165
+ if (!finalDatabaseUrl) {
166
+ finalDatabaseUrl = await prompt(`Enter DATABASE_URL: `);
167
+ }
168
+ if (!finalDatabaseUrl.trim()) {
169
+ console.error(`Error: DATABASE_URL cannot be empty`);
170
+ process.exit(1);
171
+ }
172
+ credentials = {
173
+ source_id: sourceId,
174
+ secret: finalSecret.trim(),
175
+ DATABASE_URL: finalDatabaseUrl.trim()
176
+ };
177
+ userProvidedCredentials = true;
178
+ console.log(`Using provided credentials...`);
179
+ } else {
180
+ credentials = await provisionElectricResources();
181
+ }
51
182
  console.log(`Setting up template...`);
52
183
  await setupTemplate(appName, credentials);
53
184
  console.log(`Installing dependencies...`);
@@ -58,22 +189,34 @@ async function main() {
58
189
  });
59
190
  } catch (_error) {
60
191
  console.log(`Failed to install dependencies`);
61
- printNextSteps(appName, true);
62
- process.exit(1);
63
- }
64
- console.log(`Running migrations...`);
65
- try {
66
- execSync(`pnpm migrate`, {
67
- stdio: `inherit`,
68
- cwd: appName === `.` ? process.cwd() : join(process.cwd(), appName)
192
+ printNextSteps(appName, {
193
+ showInstall: true,
194
+ showMigrate: true,
195
+ showClaim: !userProvidedCredentials
69
196
  });
70
- } catch (_error) {
71
- console.log(`Failed to apply migrations`);
72
- printNextSteps(appName, true);
73
197
  process.exit(1);
74
198
  }
199
+ if (!userProvidedCredentials) {
200
+ console.log(`Running migrations...`);
201
+ try {
202
+ execSync(`pnpm migrate`, {
203
+ stdio: `inherit`,
204
+ cwd: appName === `.` ? process.cwd() : join(process.cwd(), appName)
205
+ });
206
+ } catch (_error) {
207
+ console.log(`Failed to apply migrations`);
208
+ printNextSteps(appName, {
209
+ showMigrate: true,
210
+ showClaim: true
211
+ });
212
+ process.exit(1);
213
+ }
214
+ }
75
215
  console.log(`Setup complete`);
76
- printNextSteps(appName);
216
+ printNextSteps(appName, {
217
+ showMigrate: userProvidedCredentials,
218
+ showClaim: !userProvidedCredentials
219
+ });
77
220
  } catch (error) {
78
221
  console.error(
79
222
  `Setup failed:`,
package/dist/bin.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/bin.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { execSync } from 'child_process'\nimport { provisionElectricResources } from './electric-api.js'\nimport { setupTemplate } from './template-setup.js'\nimport { join } from 'path'\n\nfunction printNextSteps(appName: string, fullSetup: boolean = false) {\n console.log(`Next steps:`)\n if (appName !== `.`) {\n console.log(` cd ${appName}`)\n }\n\n if (fullSetup) {\n console.log(` pnpm install`)\n console.log(` pnpm migrate`)\n }\n\n console.log(` pnpm dev`)\n console.log(``)\n console.log(`Commands:`)\n console.log(` pnpm psql # Connect to database`)\n console.log(` pnpm claim # Claim cloud resources`)\n console.log(` pnpm deploy:netlify # Deploy to Netlify`)\n console.log(``)\n console.log(`Tutorial: https://electric-sql.com/docs`)\n}\n\nasync function main() {\n const args = process.argv.slice(2)\n\n if (args.length === 0) {\n console.error(`Usage: npx @electric-sql/start <app-name>`)\n console.error(\n ` npx @electric-sql/start . (configure current directory)`\n )\n\n process.exit(1)\n }\n\n const appName = args[0]\n\n // Validate app name (skip validation for \".\" which means current directory)\n if (appName !== `.` && !/^[a-zA-Z0-9-_]+$/.test(appName)) {\n console.error(\n `App name must contain only letters, numbers, hyphens, and underscores`\n )\n\n process.exit(1)\n }\n\n if (appName === `.`) {\n console.log(`Configuring current directory...`)\n } else {\n console.log(`Creating app: ${appName}`)\n }\n\n try {\n const credentials = await provisionElectricResources()\n\n // Step 2: Setup TanStack Start template\n console.log(`Setting up template...`)\n await setupTemplate(appName, credentials)\n\n console.log(`Installing dependencies...`)\n try {\n execSync(`pnpm install`, {\n stdio: `inherit`,\n cwd: appName === `.` ? process.cwd() : join(process.cwd(), appName),\n })\n } catch (_error) {\n console.log(`Failed to install dependencies`)\n printNextSteps(appName, true)\n process.exit(1)\n }\n\n console.log(`Running migrations...`)\n try {\n execSync(`pnpm migrate`, {\n stdio: `inherit`,\n cwd: appName === `.` ? process.cwd() : join(process.cwd(), appName),\n })\n } catch (_error) {\n console.log(`Failed to apply migrations`)\n printNextSteps(appName, true)\n process.exit(1)\n }\n\n // Step 3: Display completion message\n console.log(`Setup complete`)\n printNextSteps(appName)\n } catch (error) {\n console.error(\n `Setup failed:`,\n error instanceof Error ? error.message : error\n )\n process.exit(1)\n }\n}\n\nexport { main }\n","import { main } from './cli.js'\n\nmain().catch((error) => {\n console.error(`Unexpected error:`, error)\n process.exit(1)\n})\n"],"mappings":";;;;;;;AAEA,SAAS,gBAAgB;AAGzB,SAAS,YAAY;AAErB,SAAS,eAAe,SAAiB,YAAqB,OAAO;AACnE,UAAQ,IAAI,aAAa;AACzB,MAAI,YAAY,KAAK;AACnB,YAAQ,IAAI,QAAQ,OAAO,EAAE;AAAA,EAC/B;AAEA,MAAI,WAAW;AACb,YAAQ,IAAI,gBAAgB;AAC5B,YAAQ,IAAI,gBAAgB;AAAA,EAC9B;AAEA,UAAQ,IAAI,YAAY;AACxB,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,WAAW;AACvB,UAAQ,IAAI,+CAA+C;AAC3D,UAAQ,IAAI,iDAAiD;AAC7D,UAAQ,IAAI,6CAA6C;AACzD,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,yCAAyC;AACvD;AAEA,eAAe,OAAO;AACpB,QAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AAEjC,MAAI,KAAK,WAAW,GAAG;AACrB,YAAQ,MAAM,2CAA2C;AACzD,YAAQ;AAAA,MACN;AAAA,IACF;AAEA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,UAAU,KAAK,CAAC;AAGtB,MAAI,YAAY,OAAO,CAAC,mBAAmB,KAAK,OAAO,GAAG;AACxD,YAAQ;AAAA,MACN;AAAA,IACF;AAEA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,YAAY,KAAK;AACnB,YAAQ,IAAI,kCAAkC;AAAA,EAChD,OAAO;AACL,YAAQ,IAAI,iBAAiB,OAAO,EAAE;AAAA,EACxC;AAEA,MAAI;AACF,UAAM,cAAc,MAAM,2BAA2B;AAGrD,YAAQ,IAAI,wBAAwB;AACpC,UAAM,cAAc,SAAS,WAAW;AAExC,YAAQ,IAAI,4BAA4B;AACxC,QAAI;AACF,eAAS,gBAAgB;AAAA,QACvB,OAAO;AAAA,QACP,KAAK,YAAY,MAAM,QAAQ,IAAI,IAAI,KAAK,QAAQ,IAAI,GAAG,OAAO;AAAA,MACpE,CAAC;AAAA,IACH,SAAS,QAAQ;AACf,cAAQ,IAAI,gCAAgC;AAC5C,qBAAe,SAAS,IAAI;AAC5B,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,YAAQ,IAAI,uBAAuB;AACnC,QAAI;AACF,eAAS,gBAAgB;AAAA,QACvB,OAAO;AAAA,QACP,KAAK,YAAY,MAAM,QAAQ,IAAI,IAAI,KAAK,QAAQ,IAAI,GAAG,OAAO;AAAA,MACpE,CAAC;AAAA,IACH,SAAS,QAAQ;AACf,cAAQ,IAAI,4BAA4B;AACxC,qBAAe,SAAS,IAAI;AAC5B,cAAQ,KAAK,CAAC;AAAA,IAChB;AAGA,YAAQ,IAAI,gBAAgB;AAC5B,mBAAe,OAAO;AAAA,EACxB,SAAS,OAAO;AACd,YAAQ;AAAA,MACN;AAAA,MACA,iBAAiB,QAAQ,MAAM,UAAU;AAAA,IAC3C;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;;;AChGA,KAAK,EAAE,MAAM,CAAC,UAAU;AACtB,UAAQ,MAAM,qBAAqB,KAAK;AACxC,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":[]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/bin.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { execSync } from 'child_process'\nimport { createInterface } from 'readline'\nimport { provisionElectricResources } from './electric-api.js'\nimport { setupTemplate } from './template-setup.js'\nimport { join } from 'path'\n\ninterface ParsedArgs {\n appName: string\n sourceId?: string\n secret?: string\n databaseUrl?: string\n}\n\nfunction parseArgs(args: string[]): ParsedArgs {\n const positionalArgs: string[] = []\n let sourceId: string | undefined\n let secret: string | undefined\n let databaseUrl: string | undefined\n\n for (let i = 0; i < args.length; i++) {\n if (args[i] === `--source`) {\n sourceId = args[i + 1]\n if (!sourceId || sourceId.startsWith(`-`)) {\n console.error(`Error: --source requires a source ID value`)\n process.exit(1)\n }\n i++ // Skip the value\n } else if (args[i] === `--secret`) {\n secret = args[i + 1]\n if (!secret || secret.startsWith(`-`)) {\n console.error(`Error: --secret requires a value`)\n process.exit(1)\n }\n i++ // Skip the value\n } else if (args[i] === `--database-url`) {\n databaseUrl = args[i + 1]\n if (!databaseUrl || databaseUrl.startsWith(`-`)) {\n console.error(`Error: --database-url requires a value`)\n process.exit(1)\n }\n i++ // Skip the value\n } else if (!args[i].startsWith(`-`)) {\n positionalArgs.push(args[i])\n }\n }\n\n if (positionalArgs.length === 0) {\n console.error(\n `Usage: npx @electric-sql/start <app-name> [--source <source-id>] [--secret <secret>] [--database-url <url>]`\n )\n console.error(\n ` npx @electric-sql/start . (configure current directory)`\n )\n process.exit(1)\n }\n\n if (positionalArgs.length > 1) {\n console.error(\n `Error: Expected only one app name, but received multiple: ${positionalArgs.join(`, `)}`\n )\n console.error(\n `Usage: npx @electric-sql/start <app-name> [--source <source-id>] [--secret <secret>] [--database-url <url>]`\n )\n process.exit(1)\n }\n\n return { appName: positionalArgs[0], sourceId, secret, databaseUrl }\n}\n\nfunction prompt(question: string): Promise<string> {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n })\n\n return new Promise((resolve) => {\n rl.question(question, (answer) => {\n rl.close()\n resolve(answer)\n })\n })\n}\n\nfunction promptPassword(question: string): Promise<string> {\n const stdin = process.stdin\n\n // Fall back to regular readline-based prompt if not a TTY (e.g., piped input, CI)\n if (!stdin.isTTY) {\n return prompt(question)\n }\n\n return new Promise((resolve) => {\n process.stdout.write(question)\n\n const wasRaw = stdin.isRaw\n stdin.setRawMode(true)\n stdin.resume()\n stdin.setEncoding(`utf8`)\n\n let password = ``\n\n const onData = (char: string) => {\n const code = char.charCodeAt(0)\n\n if (char === `\\r` || char === `\\n` || code === 13) {\n // Enter pressed\n stdin.setRawMode(wasRaw ?? false)\n stdin.removeListener(`data`, onData)\n stdin.pause()\n process.stdout.write(`\\n`)\n resolve(password)\n } else if (code === 127 || code === 8) {\n // Backspace\n if (password.length > 0) {\n password = password.slice(0, -1)\n process.stdout.write(`\\b \\b`)\n }\n } else if (code === 3) {\n // Ctrl+C\n process.stdout.write(`\\n`)\n process.exit(1)\n } else if (code >= 32) {\n // Printable character\n password += char\n process.stdout.write(`*`)\n }\n }\n\n stdin.on(`data`, onData)\n })\n}\n\ninterface NextStepsOptions {\n showInstall?: boolean\n showMigrate?: boolean\n showClaim?: boolean\n}\n\nfunction printNextSteps(appName: string, options: NextStepsOptions = {}) {\n const { showInstall = false, showMigrate = false, showClaim = true } = options\n\n console.log(`Next steps:`)\n if (appName !== `.`) {\n console.log(` cd ${appName}`)\n }\n\n if (showInstall) {\n console.log(` pnpm install`)\n }\n\n if (showMigrate) {\n console.log(` pnpm migrate`)\n }\n\n console.log(` pnpm dev`)\n console.log(``)\n console.log(`Commands:`)\n console.log(` pnpm psql # Connect to database`)\n if (showClaim) {\n console.log(` pnpm claim # Claim cloud resources`)\n }\n console.log(` pnpm deploy:netlify # Deploy to Netlify`)\n console.log(``)\n console.log(`Tutorial: https://electric-sql.com/docs`)\n}\n\nasync function main() {\n const args = process.argv.slice(2)\n const { appName, sourceId, secret, databaseUrl } = parseArgs(args)\n\n // Validate app name (skip validation for \".\" which means current directory)\n if (appName !== `.` && !/^[a-zA-Z0-9-_]+$/.test(appName)) {\n console.error(\n `App name must contain only letters, numbers, hyphens, and underscores`\n )\n\n process.exit(1)\n }\n\n if (appName === `.`) {\n console.log(`Configuring current directory...`)\n } else {\n console.log(`Creating app: ${appName}`)\n }\n\n try {\n let credentials: {\n source_id: string\n secret: string\n DATABASE_URL: string\n claimId?: string\n }\n let userProvidedCredentials = false\n\n if (sourceId) {\n // User provided source ID, get secret and DATABASE_URL from CLI params or prompt\n let finalSecret = secret\n if (!finalSecret) {\n finalSecret = await promptPassword(\n `Enter secret for source ${sourceId}: `\n )\n }\n if (!finalSecret.trim()) {\n console.error(`Error: Secret cannot be empty`)\n process.exit(1)\n }\n\n let finalDatabaseUrl = databaseUrl\n if (!finalDatabaseUrl) {\n finalDatabaseUrl = await prompt(`Enter DATABASE_URL: `)\n }\n if (!finalDatabaseUrl.trim()) {\n console.error(`Error: DATABASE_URL cannot be empty`)\n process.exit(1)\n }\n\n credentials = {\n source_id: sourceId,\n secret: finalSecret.trim(),\n DATABASE_URL: finalDatabaseUrl.trim(),\n }\n userProvidedCredentials = true\n console.log(`Using provided credentials...`)\n } else {\n credentials = await provisionElectricResources()\n }\n\n // Step 2: Setup TanStack Start template\n console.log(`Setting up template...`)\n await setupTemplate(appName, credentials)\n\n console.log(`Installing dependencies...`)\n try {\n execSync(`pnpm install`, {\n stdio: `inherit`,\n cwd: appName === `.` ? process.cwd() : join(process.cwd(), appName),\n })\n } catch (_error) {\n console.log(`Failed to install dependencies`)\n printNextSteps(appName, {\n showInstall: true,\n showMigrate: true,\n showClaim: !userProvidedCredentials,\n })\n process.exit(1)\n }\n\n // Skip migrations if user provided credentials (they may have their own DB setup)\n if (!userProvidedCredentials) {\n console.log(`Running migrations...`)\n try {\n execSync(`pnpm migrate`, {\n stdio: `inherit`,\n cwd: appName === `.` ? process.cwd() : join(process.cwd(), appName),\n })\n } catch (_error) {\n console.log(`Failed to apply migrations`)\n printNextSteps(appName, {\n showMigrate: true,\n showClaim: true,\n })\n process.exit(1)\n }\n }\n\n // Step 3: Display completion message\n console.log(`Setup complete`)\n printNextSteps(appName, {\n showMigrate: userProvidedCredentials,\n showClaim: !userProvidedCredentials,\n })\n } catch (error) {\n console.error(\n `Setup failed:`,\n error instanceof Error ? error.message : error\n )\n process.exit(1)\n }\n}\n\nexport { main }\n","import { main } from './cli.js'\n\nmain().catch((error) => {\n console.error(`Unexpected error:`, error)\n process.exit(1)\n})\n"],"mappings":"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","names":[]}
@@ -181,11 +181,14 @@ BETTER_AUTH_SECRET=${betterAuthSecret}
181
181
  const packageJsonPath = join(appPath, `package.json`);
182
182
  if (existsSync(packageJsonPath)) {
183
183
  const packageJson = JSON.parse(readFileSync(packageJsonPath, `utf8`));
184
- packageJson.scripts = {
184
+ const scripts = {
185
185
  ...packageJson.scripts,
186
- claim: `npx open-cli "${getElectricDashboardUrl()}/claim?uuid=${credentials.claimId}"`,
187
186
  "deploy:netlify": `NODE_ENV=production NITRO_PRESET=netlify pnpm build && NODE_ENV=production npx netlify deploy --no-build --prod --dir=dist --functions=.netlify/functions-internal && npx netlify env:import .env`
188
187
  };
188
+ if (credentials.claimId) {
189
+ scripts.claim = `npx open-cli "${getElectricDashboardUrl()}/claim?uuid=${credentials.claimId}"`;
190
+ }
191
+ packageJson.scripts = scripts;
189
192
  writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
190
193
  }
191
194
  console.log(`Template setup complete`);
@@ -207,4 +210,4 @@ export {
207
210
  claimResources,
208
211
  setupTemplate
209
212
  };
210
- //# sourceMappingURL=chunk-J5QG5B4S.js.map
213
+ //# sourceMappingURL=chunk-RTAT4KWN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/electric-api.ts","../src/template-setup.ts"],"sourcesContent":["// Using native fetch (Node.js 18+)\n\nexport interface ElectricCredentials {\n source_id: string\n secret: string\n DATABASE_URL: string\n}\n\nexport interface ClaimableSourceResponse {\n claimId: string\n}\n\ninterface ClaimableSourceStatus {\n state: `pending` | `ready` | `failed`\n source: {\n source_id: string\n secret: string\n }\n connection_uri: string\n claim_link?: string\n project_id?: string\n error: string | null\n}\n\nexport const DEFAULT_ELECTRIC_API_BASE = `https://dashboard.electric-sql.cloud/api`\nexport const DEFAULT_ELECTRIC_URL = `https://api.electric-sql.cloud`\nexport const DEFAULT_ELECTRIC_DASHBOARD_URL = `https://dashboard.electric-sql.cloud`\n\nexport function getElectricApiBase(): string {\n return process.env.ELECTRIC_API_BASE_URL ?? DEFAULT_ELECTRIC_API_BASE\n}\n\nexport function getElectricUrl(): string {\n return process.env.ELECTRIC_URL ?? DEFAULT_ELECTRIC_URL\n}\n\nexport function getElectricDashboardUrl(): string {\n return process.env.ELECTRIC_DASHBOARD_URL ?? DEFAULT_ELECTRIC_DASHBOARD_URL\n}\n\nconst POLL_INTERVAL_MS = 1000 // Poll every 1 second\nconst MAX_POLL_ATTEMPTS = 60 // Max 60 seconds\n\nasync function pollClaimableSource(\n claimId: string,\n maxAttempts: number = MAX_POLL_ATTEMPTS\n): Promise<ClaimableSourceStatus> {\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n const response = await fetch(\n `${getElectricApiBase()}/public/v1/claimable-sources/${claimId}`,\n {\n method: `GET`,\n headers: {\n 'User-Agent': `@electric-sql/start`,\n },\n }\n )\n\n // Handle 404 as \"still being provisioned\" - continue polling\n if (response.status === 404) {\n await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS))\n continue\n }\n\n // For other non-OK responses, throw an error\n if (!response.ok) {\n throw new Error(\n `Electric API error: ${response.status} ${response.statusText}`\n )\n }\n\n const status = (await response.json()) as ClaimableSourceStatus\n\n if (status.state === `ready`) {\n return status\n }\n\n if (status.state === `failed` || status.error) {\n throw new Error(\n `Resource provisioning failed${status.error ? `: ${status.error}` : ``}`\n )\n }\n\n // Wait before polling again\n await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS))\n }\n\n throw new Error(\n `Timeout waiting for resources to be provisioned after ${maxAttempts} attempts`\n )\n}\n\nexport async function provisionElectricResources(): Promise<\n ElectricCredentials & ClaimableSourceResponse\n> {\n console.log(`Provisioning resources...`)\n try {\n // Step 1: POST to create claimable source and get claimId\n const response = await fetch(\n `${getElectricApiBase()}/public/v1/claimable-sources`,\n {\n method: `POST`,\n headers: {\n 'Content-Type': `application/json`,\n 'User-Agent': `@electric-sql/start`,\n },\n body: JSON.stringify({}),\n }\n )\n\n if (!response.ok) {\n throw new Error(\n `Electric API error: ${response.status} ${response.statusText}`\n )\n }\n\n const { claimId } = (await response.json()) as ClaimableSourceResponse\n\n if (!claimId) {\n throw new Error(`Invalid response from Electric API - missing claimId`)\n }\n\n // Step 2: Poll until state === 'ready'\n const status = await pollClaimableSource(claimId)\n\n // Step 3: Extract and validate credentials\n if (\n !status.source?.source_id ||\n !status.source?.secret ||\n !status.connection_uri\n ) {\n throw new Error(\n `Invalid response from Electric API - missing required credentials`\n )\n }\n\n return {\n source_id: status.source.source_id,\n secret: status.source.secret,\n DATABASE_URL: status.connection_uri,\n claimId,\n }\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(\n `Failed to provision Electric resources: ${error.message}`\n )\n }\n throw new Error(`Failed to provision Electric resources: Unknown error`)\n }\n}\n\nexport async function claimResources(\n sourceId: string,\n secret: string\n): Promise<{ claimUrl: string }> {\n try {\n const response = await fetch(`${getElectricApiBase()}/v1/claim`, {\n method: `POST`,\n headers: {\n 'Content-Type': `application/json`,\n Authorization: `Bearer ${secret}`,\n 'User-Agent': `@electric-sql/start`,\n },\n body: JSON.stringify({\n source_id: sourceId,\n }),\n })\n\n if (!response.ok) {\n throw new Error(\n `Electric API error: ${response.status} ${response.statusText}`\n )\n }\n\n const result = (await response.json()) as { claimUrl: string }\n\n if (!result.claimUrl) {\n throw new Error(`Invalid response from Electric API - missing claim URL`)\n }\n\n return result\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Failed to initiate resource claim: ${error.message}`)\n }\n throw new Error(`Failed to initiate resource claim: Unknown error`)\n }\n}\n","import { execSync } from 'child_process'\nimport { randomBytes } from 'crypto'\nimport { writeFileSync, readFileSync, existsSync } from 'fs'\nimport { join } from 'path'\nimport {\n ElectricCredentials,\n getElectricUrl,\n getElectricDashboardUrl,\n} from './electric-api'\n\ninterface SetupCredentials extends ElectricCredentials {\n claimId?: string\n}\n\n/**\n * Generates a cryptographically secure random string for use as a secret\n * @param length - The length of the secret in bytes (will be hex encoded, so output is 2x length)\n * @returns A random hex string\n */\nfunction generateSecret(length: number = 32): string {\n return randomBytes(length).toString(`hex`)\n}\n\nexport async function setupTemplate(\n appName: string,\n credentials: SetupCredentials\n): Promise<void> {\n const appPath = appName === `.` ? process.cwd() : join(process.cwd(), appName)\n\n try {\n // Step 1: Pull TanStack Start template using gitpick (skip for current directory)\n if (appName !== `.`) {\n console.log(`Pulling template...`)\n execSync(\n `npx gitpick electric-sql/electric/tree/main/examples/tanstack-db-web-starter ${appName}`,\n { stdio: `inherit` }\n )\n }\n\n // Step 2: Generate .env file with credentials\n console.log(`Configuring environment...`)\n const betterAuthSecret = generateSecret(32)\n const electricUrl = getElectricUrl()\n const envContent = `# Electric SQL Configuration\n# Generated by @electric-sql/start\n# DO NOT COMMIT THIS FILE\n\n# Database\nDATABASE_URL=${credentials.DATABASE_URL}\n\n# Electric Cloud\nELECTRIC_URL=${electricUrl}\nELECTRIC_SOURCE_ID=${credentials.source_id}\nELECTRIC_SECRET=${credentials.secret}\n\n# Authentication\nBETTER_AUTH_SECRET=${betterAuthSecret}\n`\n\n writeFileSync(join(appPath, `.env`), envContent)\n\n // Step 3: Ensure .gitignore includes .env\n console.log(`Updating .gitignore...`)\n const gitignorePath = join(appPath, `.gitignore`)\n let gitignoreContent = ``\n\n if (existsSync(gitignorePath)) {\n gitignoreContent = readFileSync(gitignorePath, `utf8`)\n }\n\n if (!gitignoreContent.includes(`.env`)) {\n gitignoreContent += `\\n# Environment variables\\n.env\\n.env.local\\n.env.*.local\\n`\n writeFileSync(gitignorePath, gitignoreContent)\n }\n\n console.log(`Adding Electric commands...`)\n const packageJsonPath = join(appPath, `package.json`)\n\n if (existsSync(packageJsonPath)) {\n const packageJson = JSON.parse(readFileSync(packageJsonPath, `utf8`))\n\n // Add/update scripts for cloud mode and Electric commands\n const scripts: Record<string, string> = {\n ...packageJson.scripts,\n 'deploy:netlify': `NODE_ENV=production NITRO_PRESET=netlify pnpm build && NODE_ENV=production npx netlify deploy --no-build --prod --dir=dist --functions=.netlify/functions-internal && npx netlify env:import .env`,\n }\n\n // Only add claim script if claimId is present (i.e., resources were provisioned)\n if (credentials.claimId) {\n scripts.claim = `npx open-cli \"${getElectricDashboardUrl()}/claim?uuid=${credentials.claimId}\"`\n }\n\n packageJson.scripts = scripts\n\n writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2))\n }\n\n console.log(`Template setup complete`)\n } catch (error) {\n throw new Error(\n `Template setup failed: ${error instanceof Error ? error.message : error}`\n )\n }\n}\n"],"mappings":";;;AAwBO,IAAM,4BAA4B;AAClC,IAAM,uBAAuB;AAC7B,IAAM,iCAAiC;AAEvC,SAAS,qBAA6B;AAC3C,SAAO,QAAQ,IAAI,yBAAyB;AAC9C;AAEO,SAAS,iBAAyB;AACvC,SAAO,QAAQ,IAAI,gBAAgB;AACrC;AAEO,SAAS,0BAAkC;AAChD,SAAO,QAAQ,IAAI,0BAA0B;AAC/C;AAEA,IAAM,mBAAmB;AACzB,IAAM,oBAAoB;AAE1B,eAAe,oBACb,SACA,cAAsB,mBACU;AAChC,WAAS,UAAU,GAAG,UAAU,aAAa,WAAW;AACtD,UAAM,WAAW,MAAM;AAAA,MACrB,GAAG,mBAAmB,CAAC,gCAAgC,OAAO;AAAA,MAC9D;AAAA,QACE,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,cAAc;AAAA,QAChB;AAAA,MACF;AAAA,IACF;AAGA,QAAI,SAAS,WAAW,KAAK;AAC3B,YAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,gBAAgB,CAAC;AACpE;AAAA,IACF;AAGA,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI;AAAA,QACR,uBAAuB,SAAS,MAAM,IAAI,SAAS,UAAU;AAAA,MAC/D;AAAA,IACF;AAEA,UAAM,SAAU,MAAM,SAAS,KAAK;AAEpC,QAAI,OAAO,UAAU,SAAS;AAC5B,aAAO;AAAA,IACT;AAEA,QAAI,OAAO,UAAU,YAAY,OAAO,OAAO;AAC7C,YAAM,IAAI;AAAA,QACR,+BAA+B,OAAO,QAAQ,KAAK,OAAO,KAAK,KAAK,EAAE;AAAA,MACxE;AAAA,IACF;AAGA,UAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,gBAAgB,CAAC;AAAA,EACtE;AAEA,QAAM,IAAI;AAAA,IACR,yDAAyD,WAAW;AAAA,EACtE;AACF;AAEA,eAAsB,6BAEpB;AACA,UAAQ,IAAI,2BAA2B;AACvC,MAAI;AAEF,UAAM,WAAW,MAAM;AAAA,MACrB,GAAG,mBAAmB,CAAC;AAAA,MACvB;AAAA,QACE,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,cAAc;AAAA,QAChB;AAAA,QACA,MAAM,KAAK,UAAU,CAAC,CAAC;AAAA,MACzB;AAAA,IACF;AAEA,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI;AAAA,QACR,uBAAuB,SAAS,MAAM,IAAI,SAAS,UAAU;AAAA,MAC/D;AAAA,IACF;AAEA,UAAM,EAAE,QAAQ,IAAK,MAAM,SAAS,KAAK;AAEzC,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI,MAAM,sDAAsD;AAAA,IACxE;AAGA,UAAM,SAAS,MAAM,oBAAoB,OAAO;AAGhD,QACE,CAAC,OAAO,QAAQ,aAChB,CAAC,OAAO,QAAQ,UAChB,CAAC,OAAO,gBACR;AACA,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,MACL,WAAW,OAAO,OAAO;AAAA,MACzB,QAAQ,OAAO,OAAO;AAAA,MACtB,cAAc,OAAO;AAAA,MACrB;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,QAAI,iBAAiB,OAAO;AAC1B,YAAM,IAAI;AAAA,QACR,2CAA2C,MAAM,OAAO;AAAA,MAC1D;AAAA,IACF;AACA,UAAM,IAAI,MAAM,uDAAuD;AAAA,EACzE;AACF;AAEA,eAAsB,eACpB,UACA,QAC+B;AAC/B,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,GAAG,mBAAmB,CAAC,aAAa;AAAA,MAC/D,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,eAAe,UAAU,MAAM;AAAA,QAC/B,cAAc;AAAA,MAChB;AAAA,MACA,MAAM,KAAK,UAAU;AAAA,QACnB,WAAW;AAAA,MACb,CAAC;AAAA,IACH,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI;AAAA,QACR,uBAAuB,SAAS,MAAM,IAAI,SAAS,UAAU;AAAA,MAC/D;AAAA,IACF;AAEA,UAAM,SAAU,MAAM,SAAS,KAAK;AAEpC,QAAI,CAAC,OAAO,UAAU;AACpB,YAAM,IAAI,MAAM,wDAAwD;AAAA,IAC1E;AAEA,WAAO;AAAA,EACT,SAAS,OAAO;AACd,QAAI,iBAAiB,OAAO;AAC1B,YAAM,IAAI,MAAM,sCAAsC,MAAM,OAAO,EAAE;AAAA,IACvE;AACA,UAAM,IAAI,MAAM,kDAAkD;AAAA,EACpE;AACF;;;AC5LA,SAAS,gBAAgB;AACzB,SAAS,mBAAmB;AAC5B,SAAS,eAAe,cAAc,kBAAkB;AACxD,SAAS,YAAY;AAgBrB,SAAS,eAAe,SAAiB,IAAY;AACnD,SAAO,YAAY,MAAM,EAAE,SAAS,KAAK;AAC3C;AAEA,eAAsB,cACpB,SACA,aACe;AACf,QAAM,UAAU,YAAY,MAAM,QAAQ,IAAI,IAAI,KAAK,QAAQ,IAAI,GAAG,OAAO;AAE7E,MAAI;AAEF,QAAI,YAAY,KAAK;AACnB,cAAQ,IAAI,qBAAqB;AACjC;AAAA,QACE,gFAAgF,OAAO;AAAA,QACvF,EAAE,OAAO,UAAU;AAAA,MACrB;AAAA,IACF;AAGA,YAAQ,IAAI,4BAA4B;AACxC,UAAM,mBAAmB,eAAe,EAAE;AAC1C,UAAM,cAAc,eAAe;AACnC,UAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA,eAKR,YAAY,YAAY;AAAA;AAAA;AAAA,eAGxB,WAAW;AAAA,qBACL,YAAY,SAAS;AAAA,kBACxB,YAAY,MAAM;AAAA;AAAA;AAAA,qBAGf,gBAAgB;AAAA;AAGjC,kBAAc,KAAK,SAAS,MAAM,GAAG,UAAU;AAG/C,YAAQ,IAAI,wBAAwB;AACpC,UAAM,gBAAgB,KAAK,SAAS,YAAY;AAChD,QAAI,mBAAmB;AAEvB,QAAI,WAAW,aAAa,GAAG;AAC7B,yBAAmB,aAAa,eAAe,MAAM;AAAA,IACvD;AAEA,QAAI,CAAC,iBAAiB,SAAS,MAAM,GAAG;AACtC,0BAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AACpB,oBAAc,eAAe,gBAAgB;AAAA,IAC/C;AAEA,YAAQ,IAAI,6BAA6B;AACzC,UAAM,kBAAkB,KAAK,SAAS,cAAc;AAEpD,QAAI,WAAW,eAAe,GAAG;AAC/B,YAAM,cAAc,KAAK,MAAM,aAAa,iBAAiB,MAAM,CAAC;AAGpE,YAAM,UAAkC;AAAA,QACtC,GAAG,YAAY;AAAA,QACf,kBAAkB;AAAA,MACpB;AAGA,UAAI,YAAY,SAAS;AACvB,gBAAQ,QAAQ,iBAAiB,wBAAwB,CAAC,eAAe,YAAY,OAAO;AAAA,MAC9F;AAEA,kBAAY,UAAU;AAEtB,oBAAc,iBAAiB,KAAK,UAAU,aAAa,MAAM,CAAC,CAAC;AAAA,IACrE;AAEA,YAAQ,IAAI,yBAAyB;AAAA,EACvC,SAAS,OAAO;AACd,UAAM,IAAI;AAAA,MACR,0BAA0B,iBAAiB,QAAQ,MAAM,UAAU,KAAK;AAAA,IAC1E;AAAA,EACF;AACF;","names":[]}
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  getElectricUrl,
10
10
  provisionElectricResources,
11
11
  setupTemplate
12
- } from "./chunk-J5QG5B4S.js";
12
+ } from "./chunk-RTAT4KWN.js";
13
13
  export {
14
14
  DEFAULT_ELECTRIC_API_BASE,
15
15
  DEFAULT_ELECTRIC_DASHBOARD_URL,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electric-sql/start",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "CLI package for the ElectricSQL Quickstart.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/cli.ts CHANGED
@@ -1,18 +1,156 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { execSync } from 'child_process'
4
+ import { createInterface } from 'readline'
4
5
  import { provisionElectricResources } from './electric-api.js'
5
6
  import { setupTemplate } from './template-setup.js'
6
7
  import { join } from 'path'
7
8
 
8
- function printNextSteps(appName: string, fullSetup: boolean = false) {
9
+ interface ParsedArgs {
10
+ appName: string
11
+ sourceId?: string
12
+ secret?: string
13
+ databaseUrl?: string
14
+ }
15
+
16
+ function parseArgs(args: string[]): ParsedArgs {
17
+ const positionalArgs: string[] = []
18
+ let sourceId: string | undefined
19
+ let secret: string | undefined
20
+ let databaseUrl: string | undefined
21
+
22
+ for (let i = 0; i < args.length; i++) {
23
+ if (args[i] === `--source`) {
24
+ sourceId = args[i + 1]
25
+ if (!sourceId || sourceId.startsWith(`-`)) {
26
+ console.error(`Error: --source requires a source ID value`)
27
+ process.exit(1)
28
+ }
29
+ i++ // Skip the value
30
+ } else if (args[i] === `--secret`) {
31
+ secret = args[i + 1]
32
+ if (!secret || secret.startsWith(`-`)) {
33
+ console.error(`Error: --secret requires a value`)
34
+ process.exit(1)
35
+ }
36
+ i++ // Skip the value
37
+ } else if (args[i] === `--database-url`) {
38
+ databaseUrl = args[i + 1]
39
+ if (!databaseUrl || databaseUrl.startsWith(`-`)) {
40
+ console.error(`Error: --database-url requires a value`)
41
+ process.exit(1)
42
+ }
43
+ i++ // Skip the value
44
+ } else if (!args[i].startsWith(`-`)) {
45
+ positionalArgs.push(args[i])
46
+ }
47
+ }
48
+
49
+ if (positionalArgs.length === 0) {
50
+ console.error(
51
+ `Usage: npx @electric-sql/start <app-name> [--source <source-id>] [--secret <secret>] [--database-url <url>]`
52
+ )
53
+ console.error(
54
+ ` npx @electric-sql/start . (configure current directory)`
55
+ )
56
+ process.exit(1)
57
+ }
58
+
59
+ if (positionalArgs.length > 1) {
60
+ console.error(
61
+ `Error: Expected only one app name, but received multiple: ${positionalArgs.join(`, `)}`
62
+ )
63
+ console.error(
64
+ `Usage: npx @electric-sql/start <app-name> [--source <source-id>] [--secret <secret>] [--database-url <url>]`
65
+ )
66
+ process.exit(1)
67
+ }
68
+
69
+ return { appName: positionalArgs[0], sourceId, secret, databaseUrl }
70
+ }
71
+
72
+ function prompt(question: string): Promise<string> {
73
+ const rl = createInterface({
74
+ input: process.stdin,
75
+ output: process.stdout,
76
+ })
77
+
78
+ return new Promise((resolve) => {
79
+ rl.question(question, (answer) => {
80
+ rl.close()
81
+ resolve(answer)
82
+ })
83
+ })
84
+ }
85
+
86
+ function promptPassword(question: string): Promise<string> {
87
+ const stdin = process.stdin
88
+
89
+ // Fall back to regular readline-based prompt if not a TTY (e.g., piped input, CI)
90
+ if (!stdin.isTTY) {
91
+ return prompt(question)
92
+ }
93
+
94
+ return new Promise((resolve) => {
95
+ process.stdout.write(question)
96
+
97
+ const wasRaw = stdin.isRaw
98
+ stdin.setRawMode(true)
99
+ stdin.resume()
100
+ stdin.setEncoding(`utf8`)
101
+
102
+ let password = ``
103
+
104
+ const onData = (char: string) => {
105
+ const code = char.charCodeAt(0)
106
+
107
+ if (char === `\r` || char === `\n` || code === 13) {
108
+ // Enter pressed
109
+ stdin.setRawMode(wasRaw ?? false)
110
+ stdin.removeListener(`data`, onData)
111
+ stdin.pause()
112
+ process.stdout.write(`\n`)
113
+ resolve(password)
114
+ } else if (code === 127 || code === 8) {
115
+ // Backspace
116
+ if (password.length > 0) {
117
+ password = password.slice(0, -1)
118
+ process.stdout.write(`\b \b`)
119
+ }
120
+ } else if (code === 3) {
121
+ // Ctrl+C
122
+ process.stdout.write(`\n`)
123
+ process.exit(1)
124
+ } else if (code >= 32) {
125
+ // Printable character
126
+ password += char
127
+ process.stdout.write(`*`)
128
+ }
129
+ }
130
+
131
+ stdin.on(`data`, onData)
132
+ })
133
+ }
134
+
135
+ interface NextStepsOptions {
136
+ showInstall?: boolean
137
+ showMigrate?: boolean
138
+ showClaim?: boolean
139
+ }
140
+
141
+ function printNextSteps(appName: string, options: NextStepsOptions = {}) {
142
+ const { showInstall = false, showMigrate = false, showClaim = true } = options
143
+
9
144
  console.log(`Next steps:`)
10
145
  if (appName !== `.`) {
11
146
  console.log(` cd ${appName}`)
12
147
  }
13
148
 
14
- if (fullSetup) {
149
+ if (showInstall) {
15
150
  console.log(` pnpm install`)
151
+ }
152
+
153
+ if (showMigrate) {
16
154
  console.log(` pnpm migrate`)
17
155
  }
18
156
 
@@ -20,7 +158,9 @@ function printNextSteps(appName: string, fullSetup: boolean = false) {
20
158
  console.log(``)
21
159
  console.log(`Commands:`)
22
160
  console.log(` pnpm psql # Connect to database`)
23
- console.log(` pnpm claim # Claim cloud resources`)
161
+ if (showClaim) {
162
+ console.log(` pnpm claim # Claim cloud resources`)
163
+ }
24
164
  console.log(` pnpm deploy:netlify # Deploy to Netlify`)
25
165
  console.log(``)
26
166
  console.log(`Tutorial: https://electric-sql.com/docs`)
@@ -28,17 +168,7 @@ function printNextSteps(appName: string, fullSetup: boolean = false) {
28
168
 
29
169
  async function main() {
30
170
  const args = process.argv.slice(2)
31
-
32
- if (args.length === 0) {
33
- console.error(`Usage: npx @electric-sql/start <app-name>`)
34
- console.error(
35
- ` npx @electric-sql/start . (configure current directory)`
36
- )
37
-
38
- process.exit(1)
39
- }
40
-
41
- const appName = args[0]
171
+ const { appName, sourceId, secret, databaseUrl } = parseArgs(args)
42
172
 
43
173
  // Validate app name (skip validation for "." which means current directory)
44
174
  if (appName !== `.` && !/^[a-zA-Z0-9-_]+$/.test(appName)) {
@@ -56,7 +186,46 @@ async function main() {
56
186
  }
57
187
 
58
188
  try {
59
- const credentials = await provisionElectricResources()
189
+ let credentials: {
190
+ source_id: string
191
+ secret: string
192
+ DATABASE_URL: string
193
+ claimId?: string
194
+ }
195
+ let userProvidedCredentials = false
196
+
197
+ if (sourceId) {
198
+ // User provided source ID, get secret and DATABASE_URL from CLI params or prompt
199
+ let finalSecret = secret
200
+ if (!finalSecret) {
201
+ finalSecret = await promptPassword(
202
+ `Enter secret for source ${sourceId}: `
203
+ )
204
+ }
205
+ if (!finalSecret.trim()) {
206
+ console.error(`Error: Secret cannot be empty`)
207
+ process.exit(1)
208
+ }
209
+
210
+ let finalDatabaseUrl = databaseUrl
211
+ if (!finalDatabaseUrl) {
212
+ finalDatabaseUrl = await prompt(`Enter DATABASE_URL: `)
213
+ }
214
+ if (!finalDatabaseUrl.trim()) {
215
+ console.error(`Error: DATABASE_URL cannot be empty`)
216
+ process.exit(1)
217
+ }
218
+
219
+ credentials = {
220
+ source_id: sourceId,
221
+ secret: finalSecret.trim(),
222
+ DATABASE_URL: finalDatabaseUrl.trim(),
223
+ }
224
+ userProvidedCredentials = true
225
+ console.log(`Using provided credentials...`)
226
+ } else {
227
+ credentials = await provisionElectricResources()
228
+ }
60
229
 
61
230
  // Step 2: Setup TanStack Start template
62
231
  console.log(`Setting up template...`)
@@ -70,25 +239,38 @@ async function main() {
70
239
  })
71
240
  } catch (_error) {
72
241
  console.log(`Failed to install dependencies`)
73
- printNextSteps(appName, true)
242
+ printNextSteps(appName, {
243
+ showInstall: true,
244
+ showMigrate: true,
245
+ showClaim: !userProvidedCredentials,
246
+ })
74
247
  process.exit(1)
75
248
  }
76
249
 
77
- console.log(`Running migrations...`)
78
- try {
79
- execSync(`pnpm migrate`, {
80
- stdio: `inherit`,
81
- cwd: appName === `.` ? process.cwd() : join(process.cwd(), appName),
82
- })
83
- } catch (_error) {
84
- console.log(`Failed to apply migrations`)
85
- printNextSteps(appName, true)
86
- process.exit(1)
250
+ // Skip migrations if user provided credentials (they may have their own DB setup)
251
+ if (!userProvidedCredentials) {
252
+ console.log(`Running migrations...`)
253
+ try {
254
+ execSync(`pnpm migrate`, {
255
+ stdio: `inherit`,
256
+ cwd: appName === `.` ? process.cwd() : join(process.cwd(), appName),
257
+ })
258
+ } catch (_error) {
259
+ console.log(`Failed to apply migrations`)
260
+ printNextSteps(appName, {
261
+ showMigrate: true,
262
+ showClaim: true,
263
+ })
264
+ process.exit(1)
265
+ }
87
266
  }
88
267
 
89
268
  // Step 3: Display completion message
90
269
  console.log(`Setup complete`)
91
- printNextSteps(appName)
270
+ printNextSteps(appName, {
271
+ showMigrate: userProvidedCredentials,
272
+ showClaim: !userProvidedCredentials,
273
+ })
92
274
  } catch (error) {
93
275
  console.error(
94
276
  `Setup failed:`,
@@ -4,11 +4,14 @@ import { writeFileSync, readFileSync, existsSync } from 'fs'
4
4
  import { join } from 'path'
5
5
  import {
6
6
  ElectricCredentials,
7
- ClaimableSourceResponse,
8
7
  getElectricUrl,
9
8
  getElectricDashboardUrl,
10
9
  } from './electric-api'
11
10
 
11
+ interface SetupCredentials extends ElectricCredentials {
12
+ claimId?: string
13
+ }
14
+
12
15
  /**
13
16
  * Generates a cryptographically secure random string for use as a secret
14
17
  * @param length - The length of the secret in bytes (will be hex encoded, so output is 2x length)
@@ -20,7 +23,7 @@ function generateSecret(length: number = 32): string {
20
23
 
21
24
  export async function setupTemplate(
22
25
  appName: string,
23
- credentials: ElectricCredentials & ClaimableSourceResponse
26
+ credentials: SetupCredentials
24
27
  ): Promise<void> {
25
28
  const appPath = appName === `.` ? process.cwd() : join(process.cwd(), appName)
26
29
 
@@ -77,12 +80,18 @@ BETTER_AUTH_SECRET=${betterAuthSecret}
77
80
  const packageJson = JSON.parse(readFileSync(packageJsonPath, `utf8`))
78
81
 
79
82
  // Add/update scripts for cloud mode and Electric commands
80
- packageJson.scripts = {
83
+ const scripts: Record<string, string> = {
81
84
  ...packageJson.scripts,
82
- claim: `npx open-cli "${getElectricDashboardUrl()}/claim?uuid=${credentials.claimId}"`,
83
85
  'deploy:netlify': `NODE_ENV=production NITRO_PRESET=netlify pnpm build && NODE_ENV=production npx netlify deploy --no-build --prod --dir=dist --functions=.netlify/functions-internal && npx netlify env:import .env`,
84
86
  }
85
87
 
88
+ // Only add claim script if claimId is present (i.e., resources were provisioned)
89
+ if (credentials.claimId) {
90
+ scripts.claim = `npx open-cli "${getElectricDashboardUrl()}/claim?uuid=${credentials.claimId}"`
91
+ }
92
+
93
+ packageJson.scripts = scripts
94
+
86
95
  writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2))
87
96
  }
88
97
 
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/electric-api.ts","../src/template-setup.ts"],"sourcesContent":["// Using native fetch (Node.js 18+)\n\nexport interface ElectricCredentials {\n source_id: string\n secret: string\n DATABASE_URL: string\n}\n\nexport interface ClaimableSourceResponse {\n claimId: string\n}\n\ninterface ClaimableSourceStatus {\n state: `pending` | `ready` | `failed`\n source: {\n source_id: string\n secret: string\n }\n connection_uri: string\n claim_link?: string\n project_id?: string\n error: string | null\n}\n\nexport const DEFAULT_ELECTRIC_API_BASE = `https://dashboard.electric-sql.cloud/api`\nexport const DEFAULT_ELECTRIC_URL = `https://api.electric-sql.cloud`\nexport const DEFAULT_ELECTRIC_DASHBOARD_URL = `https://dashboard.electric-sql.cloud`\n\nexport function getElectricApiBase(): string {\n return process.env.ELECTRIC_API_BASE_URL ?? DEFAULT_ELECTRIC_API_BASE\n}\n\nexport function getElectricUrl(): string {\n return process.env.ELECTRIC_URL ?? DEFAULT_ELECTRIC_URL\n}\n\nexport function getElectricDashboardUrl(): string {\n return process.env.ELECTRIC_DASHBOARD_URL ?? DEFAULT_ELECTRIC_DASHBOARD_URL\n}\n\nconst POLL_INTERVAL_MS = 1000 // Poll every 1 second\nconst MAX_POLL_ATTEMPTS = 60 // Max 60 seconds\n\nasync function pollClaimableSource(\n claimId: string,\n maxAttempts: number = MAX_POLL_ATTEMPTS\n): Promise<ClaimableSourceStatus> {\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n const response = await fetch(\n `${getElectricApiBase()}/public/v1/claimable-sources/${claimId}`,\n {\n method: `GET`,\n headers: {\n 'User-Agent': `@electric-sql/start`,\n },\n }\n )\n\n // Handle 404 as \"still being provisioned\" - continue polling\n if (response.status === 404) {\n await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS))\n continue\n }\n\n // For other non-OK responses, throw an error\n if (!response.ok) {\n throw new Error(\n `Electric API error: ${response.status} ${response.statusText}`\n )\n }\n\n const status = (await response.json()) as ClaimableSourceStatus\n\n if (status.state === `ready`) {\n return status\n }\n\n if (status.state === `failed` || status.error) {\n throw new Error(\n `Resource provisioning failed${status.error ? `: ${status.error}` : ``}`\n )\n }\n\n // Wait before polling again\n await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS))\n }\n\n throw new Error(\n `Timeout waiting for resources to be provisioned after ${maxAttempts} attempts`\n )\n}\n\nexport async function provisionElectricResources(): Promise<\n ElectricCredentials & ClaimableSourceResponse\n> {\n console.log(`Provisioning resources...`)\n try {\n // Step 1: POST to create claimable source and get claimId\n const response = await fetch(\n `${getElectricApiBase()}/public/v1/claimable-sources`,\n {\n method: `POST`,\n headers: {\n 'Content-Type': `application/json`,\n 'User-Agent': `@electric-sql/start`,\n },\n body: JSON.stringify({}),\n }\n )\n\n if (!response.ok) {\n throw new Error(\n `Electric API error: ${response.status} ${response.statusText}`\n )\n }\n\n const { claimId } = (await response.json()) as ClaimableSourceResponse\n\n if (!claimId) {\n throw new Error(`Invalid response from Electric API - missing claimId`)\n }\n\n // Step 2: Poll until state === 'ready'\n const status = await pollClaimableSource(claimId)\n\n // Step 3: Extract and validate credentials\n if (\n !status.source?.source_id ||\n !status.source?.secret ||\n !status.connection_uri\n ) {\n throw new Error(\n `Invalid response from Electric API - missing required credentials`\n )\n }\n\n return {\n source_id: status.source.source_id,\n secret: status.source.secret,\n DATABASE_URL: status.connection_uri,\n claimId,\n }\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(\n `Failed to provision Electric resources: ${error.message}`\n )\n }\n throw new Error(`Failed to provision Electric resources: Unknown error`)\n }\n}\n\nexport async function claimResources(\n sourceId: string,\n secret: string\n): Promise<{ claimUrl: string }> {\n try {\n const response = await fetch(`${getElectricApiBase()}/v1/claim`, {\n method: `POST`,\n headers: {\n 'Content-Type': `application/json`,\n Authorization: `Bearer ${secret}`,\n 'User-Agent': `@electric-sql/start`,\n },\n body: JSON.stringify({\n source_id: sourceId,\n }),\n })\n\n if (!response.ok) {\n throw new Error(\n `Electric API error: ${response.status} ${response.statusText}`\n )\n }\n\n const result = (await response.json()) as { claimUrl: string }\n\n if (!result.claimUrl) {\n throw new Error(`Invalid response from Electric API - missing claim URL`)\n }\n\n return result\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Failed to initiate resource claim: ${error.message}`)\n }\n throw new Error(`Failed to initiate resource claim: Unknown error`)\n }\n}\n","import { execSync } from 'child_process'\nimport { randomBytes } from 'crypto'\nimport { writeFileSync, readFileSync, existsSync } from 'fs'\nimport { join } from 'path'\nimport {\n ElectricCredentials,\n ClaimableSourceResponse,\n getElectricUrl,\n getElectricDashboardUrl,\n} from './electric-api'\n\n/**\n * Generates a cryptographically secure random string for use as a secret\n * @param length - The length of the secret in bytes (will be hex encoded, so output is 2x length)\n * @returns A random hex string\n */\nfunction generateSecret(length: number = 32): string {\n return randomBytes(length).toString(`hex`)\n}\n\nexport async function setupTemplate(\n appName: string,\n credentials: ElectricCredentials & ClaimableSourceResponse\n): Promise<void> {\n const appPath = appName === `.` ? process.cwd() : join(process.cwd(), appName)\n\n try {\n // Step 1: Pull TanStack Start template using gitpick (skip for current directory)\n if (appName !== `.`) {\n console.log(`Pulling template...`)\n execSync(\n `npx gitpick electric-sql/electric/tree/main/examples/tanstack-db-web-starter ${appName}`,\n { stdio: `inherit` }\n )\n }\n\n // Step 2: Generate .env file with credentials\n console.log(`Configuring environment...`)\n const betterAuthSecret = generateSecret(32)\n const electricUrl = getElectricUrl()\n const envContent = `# Electric SQL Configuration\n# Generated by @electric-sql/start\n# DO NOT COMMIT THIS FILE\n\n# Database\nDATABASE_URL=${credentials.DATABASE_URL}\n\n# Electric Cloud\nELECTRIC_URL=${electricUrl}\nELECTRIC_SOURCE_ID=${credentials.source_id}\nELECTRIC_SECRET=${credentials.secret}\n\n# Authentication\nBETTER_AUTH_SECRET=${betterAuthSecret}\n`\n\n writeFileSync(join(appPath, `.env`), envContent)\n\n // Step 3: Ensure .gitignore includes .env\n console.log(`Updating .gitignore...`)\n const gitignorePath = join(appPath, `.gitignore`)\n let gitignoreContent = ``\n\n if (existsSync(gitignorePath)) {\n gitignoreContent = readFileSync(gitignorePath, `utf8`)\n }\n\n if (!gitignoreContent.includes(`.env`)) {\n gitignoreContent += `\\n# Environment variables\\n.env\\n.env.local\\n.env.*.local\\n`\n writeFileSync(gitignorePath, gitignoreContent)\n }\n\n console.log(`Adding Electric commands...`)\n const packageJsonPath = join(appPath, `package.json`)\n\n if (existsSync(packageJsonPath)) {\n const packageJson = JSON.parse(readFileSync(packageJsonPath, `utf8`))\n\n // Add/update scripts for cloud mode and Electric commands\n packageJson.scripts = {\n ...packageJson.scripts,\n claim: `npx open-cli \"${getElectricDashboardUrl()}/claim?uuid=${credentials.claimId}\"`,\n 'deploy:netlify': `NODE_ENV=production NITRO_PRESET=netlify pnpm build && NODE_ENV=production npx netlify deploy --no-build --prod --dir=dist --functions=.netlify/functions-internal && npx netlify env:import .env`,\n }\n\n writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2))\n }\n\n console.log(`Template setup complete`)\n } catch (error) {\n throw new Error(\n `Template setup failed: ${error instanceof Error ? error.message : error}`\n )\n }\n}\n"],"mappings":"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","names":[]}