create-snappy 1.0.0 → 1.1.1
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/package.json +2 -1
- package/scripts/create-snappy/cli.js +105 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-snappy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "The official installer for the SNAPPY stack.",
|
|
5
5
|
"main": "scripts/create-snappy/cli.js",
|
|
6
6
|
"bin": {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"commander": "^14.0.3",
|
|
15
|
+
"ora": "^5.4.1",
|
|
15
16
|
"picocolors": "^1.1.1",
|
|
16
17
|
"prompts": "^2.4.2"
|
|
17
18
|
},
|
|
@@ -14,6 +14,16 @@ import os from 'os'
|
|
|
14
14
|
import { Command } from 'commander'
|
|
15
15
|
import prompts from 'prompts'
|
|
16
16
|
import pc from 'picocolors'
|
|
17
|
+
import ora from 'ora'
|
|
18
|
+
|
|
19
|
+
const SNAPPY_LOGO = `
|
|
20
|
+
███████╗███╗ ██╗ █████╗ ██████╗ ██████╗ ██╗ ██╗
|
|
21
|
+
██╔════╝████╗ ██║██╔══██╗██╔══██╗██╔══██╗╚██╗ ██╔╝
|
|
22
|
+
███████╗██╔██╗ ██║███████║██████╔╝██████╔╝ ╚████╔╝
|
|
23
|
+
╚════██║██║╚██╗██║██╔══██║██╔═══╝ ██╔═══╝ ╚██╔╝
|
|
24
|
+
███████║██║ ╚████║██║ ██║██║ ██║ ██║
|
|
25
|
+
╚══════╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝
|
|
26
|
+
`
|
|
17
27
|
|
|
18
28
|
const __filename = fileURLToPath(import.meta.url)
|
|
19
29
|
const __dirname = path.dirname(__filename)
|
|
@@ -144,8 +154,13 @@ async function githubLogin() {
|
|
|
144
154
|
|
|
145
155
|
// --- Main CLI ---
|
|
146
156
|
|
|
157
|
+
function getPackageManager() {
|
|
158
|
+
return 'pnpm'
|
|
159
|
+
}
|
|
160
|
+
|
|
147
161
|
async function main() {
|
|
148
|
-
console.log(pc.cyan(
|
|
162
|
+
console.log(pc.cyan(SNAPPY_LOGO))
|
|
163
|
+
console.log(pc.cyan('🚀 Welcome to the SNAPPY Stack Installer!'))
|
|
149
164
|
console.log('------------------------------------------')
|
|
150
165
|
|
|
151
166
|
const program = new Command()
|
|
@@ -216,6 +231,13 @@ async function main() {
|
|
|
216
231
|
})
|
|
217
232
|
}
|
|
218
233
|
|
|
234
|
+
questions.push({
|
|
235
|
+
type: 'password',
|
|
236
|
+
name: 'snappyLicenseKey',
|
|
237
|
+
message: 'SNAPPY License Key (leave blank to skip)?',
|
|
238
|
+
initial: savedConfig.snappyLicenseKey || '',
|
|
239
|
+
})
|
|
240
|
+
|
|
219
241
|
// Guided Setup Questions
|
|
220
242
|
const needsGuided = options.guided || !hasSavedConfig
|
|
221
243
|
|
|
@@ -286,6 +308,7 @@ async function main() {
|
|
|
286
308
|
const config = {
|
|
287
309
|
...savedConfig,
|
|
288
310
|
authorName: response.authorName || savedConfig.authorName,
|
|
311
|
+
snappyLicenseKey: response.snappyLicenseKey || savedConfig.snappyLicenseKey,
|
|
289
312
|
supabaseUrl: response.supabaseUrl || savedConfig.supabaseUrl,
|
|
290
313
|
supabaseAnonKey: response.supabaseAnonKey || savedConfig.supabaseAnonKey,
|
|
291
314
|
supabaseServiceRole: response.supabaseServiceRole || savedConfig.supabaseServiceRole,
|
|
@@ -326,6 +349,53 @@ async function main() {
|
|
|
326
349
|
// Create target dir
|
|
327
350
|
fs.mkdirSync(targetDir, { recursive: true })
|
|
328
351
|
|
|
352
|
+
let finalLicenseToken = config.snappyLicenseKey;
|
|
353
|
+
let isTrial = false;
|
|
354
|
+
let machineIdToSave = null;
|
|
355
|
+
let skippedLicense = false;
|
|
356
|
+
|
|
357
|
+
if (!finalLicenseToken || finalLicenseToken.trim() === '') {
|
|
358
|
+
const trialResponse = await prompts({
|
|
359
|
+
type: 'confirm',
|
|
360
|
+
name: 'startTrial',
|
|
361
|
+
message: '🎯 Start 2-hour free trial?',
|
|
362
|
+
initial: true
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
if (trialResponse.startTrial) {
|
|
366
|
+
console.log(pc.cyan('\n⏳ Setting up 2-hour free trial...'));
|
|
367
|
+
const machineId = Math.random().toString(36).substring(2) + Date.now().toString(36);
|
|
368
|
+
|
|
369
|
+
try {
|
|
370
|
+
const res = await fetch('https://snappycore.wicky.id/api/trial', {
|
|
371
|
+
method: 'POST',
|
|
372
|
+
headers: { 'Content-Type': 'application/json' },
|
|
373
|
+
body: JSON.stringify({ machineId })
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
const data = await res.json();
|
|
377
|
+
if (res.ok && data.token) {
|
|
378
|
+
finalLicenseToken = data.token;
|
|
379
|
+
isTrial = true;
|
|
380
|
+
machineIdToSave = machineId;
|
|
381
|
+
} else {
|
|
382
|
+
console.warn(pc.yellow(`⚠️ Trial generation failed: ${data.error || 'Unknown error'}. Continuing without license.`));
|
|
383
|
+
skippedLicense = true;
|
|
384
|
+
}
|
|
385
|
+
} catch (err) {
|
|
386
|
+
console.warn(pc.yellow(`⚠️ Could not reach licensing server: ${err.message}`));
|
|
387
|
+
skippedLicense = true;
|
|
388
|
+
}
|
|
389
|
+
} else {
|
|
390
|
+
skippedLicense = true;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Save machine ID if it's a trial
|
|
395
|
+
if (machineIdToSave) {
|
|
396
|
+
fs.writeFileSync(path.join(targetDir, '.snappy-machine-id'), machineIdToSave);
|
|
397
|
+
}
|
|
398
|
+
|
|
329
399
|
try {
|
|
330
400
|
// 1. Initialize from repository
|
|
331
401
|
const token = getSavedToken() || (await githubLogin())
|
|
@@ -337,20 +407,24 @@ async function main() {
|
|
|
337
407
|
authenticatedUrl = repoUrl.replace('https://', `https://x-access-token:${token}@`)
|
|
338
408
|
}
|
|
339
409
|
|
|
340
|
-
|
|
410
|
+
const cloneSpinner = ora(`Cloning secure template (${selectedTemplate})...`).start()
|
|
341
411
|
try {
|
|
342
412
|
// Use branch as the template name
|
|
343
413
|
execSync(`git clone --depth 1 -b ${selectedTemplate} ${authenticatedUrl} "${targetDir}"`, {
|
|
344
|
-
stdio: '
|
|
414
|
+
stdio: 'ignore',
|
|
345
415
|
})
|
|
416
|
+
cloneSpinner.succeed('Project cloned successfully.')
|
|
346
417
|
} catch (cloneErr) {
|
|
418
|
+
cloneSpinner.stop()
|
|
347
419
|
if (!token) {
|
|
348
420
|
console.log(pc.yellow('\n🔐 This template might be private. Attempting login...'))
|
|
349
421
|
token = await githubLogin()
|
|
350
422
|
authenticatedUrl = repoUrl.replace('https://', `https://x-access-token:${token}@`)
|
|
423
|
+
const retrySpinner = ora(`Cloning secure template (${selectedTemplate})...`).start()
|
|
351
424
|
execSync(`git clone --depth 1 -b ${selectedTemplate} ${authenticatedUrl} "${targetDir}"`, {
|
|
352
|
-
stdio: '
|
|
425
|
+
stdio: 'ignore',
|
|
353
426
|
})
|
|
427
|
+
retrySpinner.succeed('Project cloned successfully.')
|
|
354
428
|
} else {
|
|
355
429
|
console.error(
|
|
356
430
|
pc.red("Clone failed. The branch might not exist, or you don't have access."),
|
|
@@ -389,6 +463,7 @@ async function main() {
|
|
|
389
463
|
NEXT_PUBLIC_SUPABASE_ANON_KEY="${config.supabaseAnonKey}"
|
|
390
464
|
SUPABASE_SERVICE_ROLE_KEY="${config.supabaseServiceRole}"
|
|
391
465
|
SUPABASE_URL="${config.supabaseUrl}"
|
|
466
|
+
SNAPPY_LICENSE_TOKEN="${finalLicenseToken || ''}"
|
|
392
467
|
|
|
393
468
|
# Database Configuration
|
|
394
469
|
POSTGRES_URL="postgres://postgres:${config.supabaseServiceRole}@${config.supabaseUrl.replace('https://', '').split('.')[0]}.supabase.co:5432/postgres"
|
|
@@ -405,19 +480,39 @@ PUBLIC_FRONTEND_URL="http://localhost:3000"
|
|
|
405
480
|
REQUIRE_LOGIN="yes"
|
|
406
481
|
`
|
|
407
482
|
fs.writeFileSync(path.join(targetDir, '.env'), envContent)
|
|
408
|
-
console.log(pc.green('✅ .env generated
|
|
483
|
+
console.log(pc.green('✅ .env generated.\n'))
|
|
409
484
|
|
|
410
485
|
if (process.env.SKIP_INSTALL !== 'true') {
|
|
411
|
-
|
|
486
|
+
const pm = getPackageManager()
|
|
487
|
+
const installCmd = pm === 'npm' ? 'npm install' : `${pm} install`
|
|
488
|
+
const installSpinner = ora(`Installing dependencies using ${pm}...`).start()
|
|
412
489
|
try {
|
|
413
|
-
execSync(
|
|
490
|
+
execSync(installCmd, { cwd: targetDir, stdio: 'ignore' })
|
|
491
|
+
installSpinner.succeed('Dependencies installed successfully.')
|
|
414
492
|
} catch (e) {
|
|
415
|
-
|
|
493
|
+
installSpinner.fail('Failed to install dependencies.')
|
|
494
|
+
console.warn(pc.yellow(`Warning: ${installCmd} failed.`))
|
|
416
495
|
}
|
|
417
496
|
}
|
|
418
497
|
|
|
419
|
-
|
|
420
|
-
|
|
498
|
+
const pmRun = getPackageManager() === 'npm' ? 'npm run dev' : `${getPackageManager()} run dev`
|
|
499
|
+
|
|
500
|
+
console.log(pc.green('\n✅ SNAPPY Stack is perfectly prepared and ready to launch!'))
|
|
501
|
+
console.log(`\nNext steps:\n cd ${pc.bold(projectName)}\n ${pmRun}\n`)
|
|
502
|
+
|
|
503
|
+
if (skippedLicense) {
|
|
504
|
+
console.log(pc.red('\n ──────────────────────────────────'));
|
|
505
|
+
console.log(` 🔑 ${pc.bold(pc.white('Add your license key to .env'))}`);
|
|
506
|
+
console.log(pc.gray(' SNAPPY_LICENSE_TOKEN=sk_snappy_...'));
|
|
507
|
+
console.log('');
|
|
508
|
+
console.log(` 📡 ${pc.cyan('Get your key: wicky.id')}`);
|
|
509
|
+
console.log(` 📖 ${pc.cyan('Docs: snappycore.wicky.id')}`);
|
|
510
|
+
console.log(pc.red(' ──────────────────────────────────'));
|
|
511
|
+
console.log(pc.bold(pc.red('\n THERE IS NO MERCY IN PRODUCTION 👹 \n')));
|
|
512
|
+
} else if (isTrial) {
|
|
513
|
+
console.log(pc.green('\n✅ 2-hour free trial active! Your trial license has been added to .env.'));
|
|
514
|
+
}
|
|
515
|
+
|
|
421
516
|
} catch (err) {
|
|
422
517
|
console.error(pc.red(`Installation failed: ${err.message || err}`))
|
|
423
518
|
} finally {
|