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.
- package/dist/cli.js +133 -33
- 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
|
|
2554
|
-
|
|
2594
|
+
const progress2 = spinner2();
|
|
2595
|
+
progress2.start("Testing the local MCP server");
|
|
2555
2596
|
await smokeServer(root);
|
|
2556
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(["
|
|
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
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
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
|
-
|
|
4434
|
+
handle.stop(complete);
|
|
4334
4435
|
return result;
|
|
4335
4436
|
} catch (error) {
|
|
4336
|
-
|
|
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(
|
|
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
|
+
"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/
|
|
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"
|