superslate 0.3.0 → 0.4.0

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/cli.js +133 -33
  2. package/package.json +4 -3
package/dist/cli.js CHANGED
@@ -1329,6 +1329,7 @@ function isUnicodeSupported() {
1329
1329
  }
1330
1330
  var unicode = isUnicodeSupported();
1331
1331
  var isCI = () => process.env.CI === "true";
1332
+ var isTTY = (o2) => o2.isTTY === true;
1332
1333
  var unicodeOr = (o2, e) => unicode ? o2 : e;
1333
1334
  var S_STEP_ACTIVE = unicodeOr("◆", "*");
1334
1335
  var S_STEP_CANCEL = unicodeOr("■", "x");
@@ -1591,6 +1592,46 @@ var u4 = {
1591
1592
  heavy: unicodeOr("━", "="),
1592
1593
  block: unicodeOr("█", "#")
1593
1594
  };
1595
+ function progress({
1596
+ style: o2 = "heavy",
1597
+ max: d = 100,
1598
+ size: v = 40,
1599
+ ...x
1600
+ } = {}) {
1601
+ const r2 = spinner(x);
1602
+ let a2 = 0, n3 = "";
1603
+ const c2 = Math.max(1, d), l2 = Math.max(1, v), S = (t2) => {
1604
+ switch (t2) {
1605
+ case "initial":
1606
+ case "active":
1607
+ return (e) => styleText2("magenta", e);
1608
+ case "error":
1609
+ case "cancel":
1610
+ return (e) => styleText2("red", e);
1611
+ case "submit":
1612
+ return (e) => styleText2("green", e);
1613
+ default:
1614
+ return (e) => styleText2("magenta", e);
1615
+ }
1616
+ }, p2 = (t2, e) => {
1617
+ const m2 = Math.floor(a2 / c2 * l2);
1618
+ return `${S(t2)(u4[o2].repeat(m2))}${styleText2("dim", u4[o2].repeat(l2 - m2))} ${e}`;
1619
+ }, h2 = (t2 = "") => {
1620
+ n3 = t2, r2.start(p2("initial", t2));
1621
+ }, g2 = (t2 = 1, e) => {
1622
+ a2 = Math.min(c2, t2 + a2), r2.message(p2("active", e ?? n3)), n3 = e ?? n3;
1623
+ };
1624
+ return {
1625
+ start: h2,
1626
+ stop: r2.stop,
1627
+ cancel: r2.cancel,
1628
+ error: r2.error,
1629
+ clear: r2.clear,
1630
+ advance: g2,
1631
+ isCancelled: r2.isCancelled,
1632
+ message: (t2) => g2(0, t2)
1633
+ };
1634
+ }
1594
1635
  var SELECT_INSTRUCTIONS = [
1595
1636
  `${styleText2("dim", "↑/↓")} to navigate`,
1596
1637
  `${styleText2("dim", "Enter:")} confirm`
@@ -2550,10 +2591,10 @@ ${dryRunDescription(client, root)}`);
2550
2591
  outro2("Dry run complete. No client configuration was changed.");
2551
2592
  return;
2552
2593
  }
2553
- const progress = spinner2();
2554
- progress.start("Testing the local MCP server");
2594
+ const progress2 = spinner2();
2595
+ progress2.start("Testing the local MCP server");
2555
2596
  await smokeServer(root);
2556
- progress.stop("Local MCP server passed its protocol self-check");
2597
+ progress2.stop("Local MCP server passed its protocol self-check");
2557
2598
  for (const client of clients) {
2558
2599
  const result = client === "codex" ? await configureCodex(root) : client === "cursor" ? await configureCursor(root) : await configureClaude(root);
2559
2600
  log2.success(`${client}: ${result}`);
@@ -3297,7 +3338,7 @@ import { promisify } from "node:util";
3297
3338
  import { createWriteStream } from "node:fs";
3298
3339
  import { rm as rm3 } from "node:fs/promises";
3299
3340
  import { join as join6 } from "node:path";
3300
- import { Readable } from "node:stream";
3341
+ import { Readable, Transform } from "node:stream";
3301
3342
  import { pipeline } from "node:stream/promises";
3302
3343
  var RELEASE_TAG_PATTERN2 = /^v\d+\.\d+\.\d+(?:-rc\.\d+)?$/;
3303
3344
  var INDEX_TIMEOUT_MS = 15000;
@@ -3357,7 +3398,24 @@ async function resolveRelease(licenseKey, baseUrl = deliveryBaseUrl()) {
3357
3398
  }
3358
3399
  return index;
3359
3400
  }
3360
- async function downloadAsset(licenseKey, releaseTag, assetName, directory, baseUrl = deliveryBaseUrl()) {
3401
+ function readContentLength(response) {
3402
+ const header = response.headers.get("content-length");
3403
+ if (!header)
3404
+ return;
3405
+ const total = Number(header);
3406
+ return Number.isSafeInteger(total) && total > 0 ? total : undefined;
3407
+ }
3408
+ function countBytes(total, onBytes) {
3409
+ let received = 0;
3410
+ return new Transform({
3411
+ transform(chunk, _encoding, done) {
3412
+ received += chunk.length;
3413
+ onBytes(received, total);
3414
+ done(null, chunk);
3415
+ }
3416
+ });
3417
+ }
3418
+ async function downloadAsset(licenseKey, releaseTag, assetName, directory, baseUrl = deliveryBaseUrl(), onBytes) {
3361
3419
  const response = await requestDelivery(`${baseUrl}/v1/source/${encodeURIComponent(releaseTag)}/${encodeURIComponent(assetName)}`, {
3362
3420
  headers: { authorization: `Bearer ${licenseKey}` },
3363
3421
  signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS)
@@ -3365,8 +3423,13 @@ async function downloadAsset(licenseKey, releaseTag, assetName, directory, baseU
3365
3423
  if (!response.body)
3366
3424
  throw new Error(`Source delivery returned an empty ${assetName}.`);
3367
3425
  const path = join6(directory, assetName);
3426
+ const source = Readable.fromWeb(response.body);
3368
3427
  try {
3369
- await pipeline(Readable.fromWeb(response.body), createWriteStream(path));
3428
+ if (onBytes) {
3429
+ await pipeline(source, countBytes(readContentLength(response), onBytes), createWriteStream(path));
3430
+ } else {
3431
+ await pipeline(source, createWriteStream(path));
3432
+ }
3370
3433
  } catch {
3371
3434
  await rm3(path, { force: true });
3372
3435
  throw new Error(`Downloading ${assetName} was interrupted. Try again in a few minutes.`);
@@ -3722,6 +3785,8 @@ function isTemplatePathExcluded(relativePath) {
3722
3785
  return true;
3723
3786
  if (parts.includes("dist") || parts.includes("out"))
3724
3787
  return true;
3788
+ if (parts.includes(".next") || parts.includes(".source") || parts.includes(".turbo"))
3789
+ return true;
3725
3790
  if (normalized === "bun.lock")
3726
3791
  return true;
3727
3792
  if (normalized === ".vercelignore")
@@ -3806,14 +3871,14 @@ function verifyReleaseMetadata(checksum, manifest, artifactName, digest, templat
3806
3871
  throw new Error(`${CLI_NAME2} release manifest does not match the downloaded source artifact.`);
3807
3872
  }
3808
3873
  }
3809
- async function downloadLicensedTemplate(target, licenseKey, sourceVersion) {
3874
+ async function downloadLicensedTemplate(target, licenseKey, sourceVersion, onDownloadProgress) {
3810
3875
  const baseUrl = deliveryBaseUrl();
3811
3876
  const releaseTag = sourceVersion ?? (await resolveRelease(licenseKey, baseUrl)).latest;
3812
3877
  const { artifact, checksum: checksumName, manifest: manifestName } = releaseAssetNames(releaseTag);
3813
3878
  const temporaryDirectory = await mkdtemp(join7(tmpdir(), `${CLI_NAME2}-`));
3814
3879
  const extractedPath = join7(temporaryDirectory, "template");
3815
3880
  try {
3816
- const archivePath = await downloadAsset(licenseKey, releaseTag, artifact, temporaryDirectory, baseUrl);
3881
+ const archivePath = await downloadAsset(licenseKey, releaseTag, artifact, temporaryDirectory, baseUrl, onDownloadProgress);
3817
3882
  await downloadAsset(licenseKey, releaseTag, checksumName, temporaryDirectory, baseUrl);
3818
3883
  await downloadAsset(licenseKey, releaseTag, manifestName, temporaryDirectory, baseUrl);
3819
3884
  const archive = await readFile5(archivePath);
@@ -3965,7 +4030,7 @@ async function scaffold(options) {
3965
4030
  if (options.from)
3966
4031
  await copyLocalTemplate(options.from, target);
3967
4032
  else if (options.licenseKey) {
3968
- await downloadLicensedTemplate(target, options.licenseKey, options.sourceVersion);
4033
+ await downloadLicensedTemplate(target, options.licenseKey, options.sourceVersion, options.onDownloadProgress);
3969
4034
  } else {
3970
4035
  throw new Error(`A local source path or a ${CLI_NAME2} licence key is required.`);
3971
4036
  }
@@ -4305,35 +4370,71 @@ async function installDependencies(target) {
4305
4370
  throw new Error(`Dependency installation failed.${commandFailureOutput(stdout2, stderr)}`);
4306
4371
  }
4307
4372
  }
4308
- async function runCapturedCommand(command, target) {
4309
- const { stdout: stdout2, stderr, exitCode } = await runCaptured2(command, { cwd: target });
4310
- if (exitCode !== 0) {
4311
- throw new Error(`${command.join(" ")} failed.${commandFailureOutput(stdout2, stderr)}`);
4312
- }
4313
- }
4314
4373
  async function occupiedDevelopmentPorts() {
4315
4374
  const results = await Promise.all([5173, 8000].map(async (port) => ({ port, available: await isPortAvailable(port) })));
4316
4375
  return results.filter((result) => !result.available).map((result) => result.port);
4317
4376
  }
4318
4377
  async function startDevelopment(target) {
4319
- await runStep("Starting local Postgres", "Local Postgres is ready", () => runCapturedCommand(["docker", "compose", "up", "-d", "--wait", "postgres"], target));
4320
4378
  log.info(`Web http://localhost:5173
4321
4379
  API http://localhost:8000`);
4322
4380
  outro("Development servers are starting. Ctrl+C stops app servers; Postgres stays running.");
4323
- const exitCode = await runInherited(["vp", "run", "-r", "--parallel", "dev"], { cwd: target });
4381
+ const exitCode = await runInherited(["pnpm", "run", "dev"], { cwd: target });
4324
4382
  if (exitCode !== 0 && exitCode !== 130) {
4325
4383
  throw new Error(`Development servers exited with code ${exitCode}.`);
4326
4384
  }
4327
4385
  }
4328
- async function runStep(pending, complete, action, indicator = "dots") {
4329
- const progress = spinner({ indicator });
4330
- progress.start(pending);
4386
+ var BYTE_UNITS = ["B", "KB", "MB", "GB"];
4387
+ var BYTE_SCALE = 1024;
4388
+ var PROGRESS_MAX = 100;
4389
+ var PROGRESS_BAR_WIDTH = 40;
4390
+ var PROGRESS_MIN_INTERVAL_MS = 80;
4391
+ var PROGRESS_MIN_DELTA = 0.5;
4392
+ function formatBytes(bytes) {
4393
+ let value = bytes;
4394
+ let unit = 0;
4395
+ while (value >= BYTE_SCALE && unit < BYTE_UNITS.length - 1) {
4396
+ value /= BYTE_SCALE;
4397
+ unit += 1;
4398
+ }
4399
+ return `${unit === 0 ? value : value.toFixed(1)} ${BYTE_UNITS[unit]}`;
4400
+ }
4401
+ function supportsAnimation() {
4402
+ return isTTY(process.stdout) && !isCI();
4403
+ }
4404
+ function createScaffoldReporter(pending, expectsDownload) {
4405
+ if (!expectsDownload || !supportsAnimation())
4406
+ return { handle: spinner() };
4407
+ const bar = progress({
4408
+ style: "heavy",
4409
+ size: PROGRESS_BAR_WIDTH,
4410
+ max: PROGRESS_MAX
4411
+ });
4412
+ let shown = 0;
4413
+ let emittedAt = 0;
4414
+ return {
4415
+ handle: bar,
4416
+ onDownloadProgress: (received, total) => {
4417
+ const percent = total ? Math.min(PROGRESS_MAX, received / total * PROGRESS_MAX) : shown;
4418
+ const finished = total !== undefined && received >= total;
4419
+ const now = performance.now();
4420
+ if (!finished && now - emittedAt < PROGRESS_MIN_INTERVAL_MS && percent - shown < PROGRESS_MIN_DELTA) {
4421
+ return;
4422
+ }
4423
+ emittedAt = now;
4424
+ const label = finished ? pending : total === undefined ? `Downloading source ${formatBytes(received)}` : `Downloading source ${formatBytes(received)} / ${formatBytes(total)}`;
4425
+ bar.advance(percent - shown, label);
4426
+ shown = percent;
4427
+ }
4428
+ };
4429
+ }
4430
+ async function runStep(pending, complete, action, handle) {
4431
+ handle.start(pending);
4331
4432
  try {
4332
4433
  const result = await action();
4333
- progress.stop(complete);
4434
+ handle.stop(complete);
4334
4435
  return result;
4335
4436
  } catch (error) {
4336
- progress.error(`${pending} failed`);
4437
+ handle.error(`${pending} failed`);
4337
4438
  throw error;
4338
4439
  }
4339
4440
  }
@@ -4344,8 +4445,10 @@ async function createApplication(options) {
4344
4445
  const databasePort = await findAvailablePort();
4345
4446
  await mkdir5(dirname2(target), { recursive: true });
4346
4447
  const staging = await mkdtemp2(join8(dirname2(target), `.${options.appName}-${CLI_NAME}-`));
4448
+ const preparing = "Preparing your application";
4449
+ const reporter = createScaffoldReporter(preparing, Boolean(options.licenseKey));
4347
4450
  try {
4348
- await runStep("Preparing your application", "Application source prepared", () => scaffold({
4451
+ await runStep(preparing, "Application source prepared", () => scaffold({
4349
4452
  appName: options.appName,
4350
4453
  scope: options.scope,
4351
4454
  brandName: options.brandName,
@@ -4354,13 +4457,14 @@ async function createApplication(options) {
4354
4457
  licenseKey: options.licenseKey,
4355
4458
  sourceVersion: options.sourceVersion,
4356
4459
  initializeGit: options.initializeGit,
4357
- databasePort
4358
- }));
4460
+ databasePort,
4461
+ onDownloadProgress: reporter.onDownloadProgress
4462
+ }), reporter.handle);
4359
4463
  if (options.deployment) {
4360
- await runStep("Configuring deployment", "Deployment target locked", () => applyDeploymentSelection2(staging, options.appName, options.deployment));
4464
+ await runStep("Configuring deployment", "Deployment target locked", () => applyDeploymentSelection2(staging, options.appName, options.deployment), spinner());
4361
4465
  }
4362
4466
  if (options.installDependencies) {
4363
- await runStep("Installing dependencies with pnpm", "Dependencies installed with pnpm", () => installDependencies(staging), "timer");
4467
+ await runStep("Installing dependencies with pnpm", "Dependencies installed with pnpm", () => installDependencies(staging), spinner({ indicator: "timer" }));
4364
4468
  }
4365
4469
  if (await stat5(target).catch(() => {
4366
4470
  return;
@@ -4378,12 +4482,7 @@ function completionDetails(target, options, databasePort) {
4378
4482
  const install = options.installDependencies ? [] : ["vp install --frozen-lockfile"];
4379
4483
  return {
4380
4484
  directory: `./${targetName}`,
4381
- commands: [
4382
- `cd ${targetName}`,
4383
- ...install,
4384
- "docker compose up -d postgres",
4385
- "vp run -r --parallel dev"
4386
- ].join(`
4485
+ commands: [`cd ${targetName}`, ...install, "pnpm dev"].join(`
4387
4486
  `),
4388
4487
  database: `Postgres localhost:${databasePort}`
4389
4488
  };
@@ -4470,6 +4569,7 @@ export {
4470
4569
  printRunnableCommands,
4471
4570
  parseArguments,
4472
4571
  occupiedDevelopmentPorts,
4572
+ formatBytes,
4473
4573
  findAvailablePort,
4474
4574
  describeTargetConflict,
4475
4575
  collectLicenseKey
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "superslate",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Create a production-oriented SaaS application from superslate.",
5
5
  "keywords": [
6
6
  "bun",
@@ -39,8 +39,9 @@
39
39
  },
40
40
  "devDependencies": {
41
41
  "@clack/prompts": "^1.7.0",
42
- "@types/bun": "^1.3.14",
43
- "typescript": "^7.0.2"
42
+ "@types/node": "^26.1.2",
43
+ "typescript": "^7.0.2",
44
+ "vitest": "^4.1.10"
44
45
  },
45
46
  "engines": {
46
47
  "node": ">=20"