superslate 0.3.0 → 0.8.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 +246 -71
- 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`
|
|
@@ -1640,7 +1681,7 @@ import { existsSync, readdirSync, statSync } from "fs";
|
|
|
1640
1681
|
import { mkdir as mkdir5, mkdtemp as mkdtemp2, readFile as readFile6, readdir as readdir2, rename as rename2, rm as rm5, rmdir, stat as stat5 } from "fs/promises";
|
|
1641
1682
|
import { createConnection, createServer } from "net";
|
|
1642
1683
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1643
|
-
import { dirname as
|
|
1684
|
+
import { dirname as dirname3, join as join8, relative as relative2, resolve as resolve4, sep } from "path";
|
|
1644
1685
|
|
|
1645
1686
|
// src/constants.ts
|
|
1646
1687
|
var CLI_NAME = "superslate";
|
|
@@ -1653,6 +1694,7 @@ var BUYER_CLI_NAME = "create-app";
|
|
|
1653
1694
|
var BUYER_CLI_PACKAGE_NAME = "create-app";
|
|
1654
1695
|
var DEFAULT_SCOPE = "@app";
|
|
1655
1696
|
var SOURCE_BRAND_NAME = "superslate";
|
|
1697
|
+
var DOCS_BASE_URL = "https://docs.superslate.dev";
|
|
1656
1698
|
var DELIVERY_BASE_URL = "https://dl.superslate.dev";
|
|
1657
1699
|
var DELIVERY_URL_VARIABLE = "SUPERSLATE_DELIVERY_URL";
|
|
1658
1700
|
var LICENSE_KEY_VARIABLE2 = "SUPERSLATE_LICENSE_KEY";
|
|
@@ -2278,9 +2320,17 @@ function isExecutable(path) {
|
|
|
2278
2320
|
|
|
2279
2321
|
// src/agent-setup.ts
|
|
2280
2322
|
var CLIENTS = ["codex", "claude", "cursor"];
|
|
2281
|
-
var SERVER_NAME = "
|
|
2282
|
-
var CODEX_START = "#
|
|
2283
|
-
var CODEX_END = "#
|
|
2323
|
+
var SERVER_NAME = "agent-docs";
|
|
2324
|
+
var CODEX_START = "# agent-context:start";
|
|
2325
|
+
var CODEX_END = "# agent-context:end";
|
|
2326
|
+
var AGENT_CONTEXT_TOOLS = [
|
|
2327
|
+
"search_docs",
|
|
2328
|
+
"search_implementation",
|
|
2329
|
+
"get_repository_map",
|
|
2330
|
+
"plan_change",
|
|
2331
|
+
"get_invariants",
|
|
2332
|
+
"get_verification"
|
|
2333
|
+
];
|
|
2284
2334
|
function takeValue(args, index, flag) {
|
|
2285
2335
|
const value = args[index + 1];
|
|
2286
2336
|
if (!value || value.startsWith("-"))
|
|
@@ -2311,7 +2361,7 @@ function parseAgentSetupArguments(argv) {
|
|
|
2311
2361
|
function agentSetupUsage(cliName) {
|
|
2312
2362
|
return `Usage: ${cliName} agent setup [options]
|
|
2313
2363
|
|
|
2314
|
-
Connect local coding clients to this checkout's read-only
|
|
2364
|
+
Connect local coding clients to this checkout's read-only project context server.
|
|
2315
2365
|
|
|
2316
2366
|
Options:
|
|
2317
2367
|
--clients <list> Comma-separated clients: codex,claude,cursor
|
|
@@ -2332,12 +2382,12 @@ async function findAgentRepositoryRoot(start = process.cwd()) {
|
|
|
2332
2382
|
}
|
|
2333
2383
|
const parent = dirname(current);
|
|
2334
2384
|
if (parent === current)
|
|
2335
|
-
throw new Error("Run agent setup inside a
|
|
2385
|
+
throw new Error("Run agent setup inside a project repository.");
|
|
2336
2386
|
current = parent;
|
|
2337
2387
|
}
|
|
2338
2388
|
}
|
|
2339
2389
|
function serverCommand(root) {
|
|
2340
|
-
return { command: "pnpm", args: ["--dir", resolve(root), "
|
|
2390
|
+
return { command: "pnpm", args: ["--dir", resolve(root), "agent:mcp"] };
|
|
2341
2391
|
}
|
|
2342
2392
|
function codexManagedBlock(root) {
|
|
2343
2393
|
const server = serverCommand(root);
|
|
@@ -2347,7 +2397,7 @@ function codexManagedBlock(root) {
|
|
|
2347
2397
|
command = ${JSON.stringify(server.command)}
|
|
2348
2398
|
args = [${args}]
|
|
2349
2399
|
required = false
|
|
2350
|
-
enabled_tools =
|
|
2400
|
+
enabled_tools = ${JSON.stringify(AGENT_CONTEXT_TOOLS)}
|
|
2351
2401
|
${CODEX_END}`;
|
|
2352
2402
|
}
|
|
2353
2403
|
function mergeCodexConfig(existing, root) {
|
|
@@ -2356,14 +2406,14 @@ function mergeCodexConfig(existing, root) {
|
|
|
2356
2406
|
const end = existing.indexOf(CODEX_END);
|
|
2357
2407
|
if (start >= 0 || end >= 0) {
|
|
2358
2408
|
if (start < 0 || end < start)
|
|
2359
|
-
throw new Error("Codex config has an incomplete
|
|
2409
|
+
throw new Error("Codex config has an incomplete agent-context block.");
|
|
2360
2410
|
const after = end + CODEX_END.length;
|
|
2361
2411
|
return `${existing.slice(0, start).trimEnd()}
|
|
2362
2412
|
|
|
2363
2413
|
${managed}${existing.slice(after)}`.trimStart();
|
|
2364
2414
|
}
|
|
2365
|
-
if (/^\[mcp_servers\.(?:
|
|
2366
|
-
throw new Error("Codex already has an unmanaged
|
|
2415
|
+
if (/^\[mcp_servers\.(?:agent-docs|agent_docs|"agent-docs")\]$/m.test(existing)) {
|
|
2416
|
+
throw new Error("Codex already has an unmanaged agent-docs server; remove or rename it first.");
|
|
2367
2417
|
}
|
|
2368
2418
|
return `${existing.trimEnd()}${existing.trim() ? `
|
|
2369
2419
|
|
|
@@ -2389,7 +2439,7 @@ function mergeCursorConfig(existing, root) {
|
|
|
2389
2439
|
const current = servers[SERVER_NAME];
|
|
2390
2440
|
const desired = serverCommand(root);
|
|
2391
2441
|
if (current !== undefined && JSON.stringify(current) !== JSON.stringify(desired)) {
|
|
2392
|
-
throw new Error("Cursor already has a different
|
|
2442
|
+
throw new Error("Cursor already has a different agent-docs server; remove or rename it first.");
|
|
2393
2443
|
}
|
|
2394
2444
|
config.mcpServers = { ...servers, [SERVER_NAME]: desired };
|
|
2395
2445
|
return `${JSON.stringify(config, null, 2)}
|
|
@@ -2419,7 +2469,7 @@ async function addGitInfoExcludes(root, paths) {
|
|
|
2419
2469
|
const prefix = existing && !existing.endsWith(`
|
|
2420
2470
|
`) ? `
|
|
2421
2471
|
` : "";
|
|
2422
|
-
const marker = "#
|
|
2472
|
+
const marker = "# Local agent configuration";
|
|
2423
2473
|
const heading = existing.split(/\r?\n/).includes(marker) ? "" : `${marker}
|
|
2424
2474
|
`;
|
|
2425
2475
|
await atomicWrite(excludePath, `${existing}${prefix}${heading}${missing.join(`
|
|
@@ -2450,10 +2500,10 @@ async function configureClaude(root) {
|
|
|
2450
2500
|
if (existing.exitCode === 0) {
|
|
2451
2501
|
const combined = `${existing.stdout}
|
|
2452
2502
|
${existing.stderr}`;
|
|
2453
|
-
if (combined.includes("
|
|
2503
|
+
if (combined.includes("agent:mcp") && combined.includes(root)) {
|
|
2454
2504
|
return "Claude Code already has the matching local server.";
|
|
2455
2505
|
}
|
|
2456
|
-
throw new Error("Claude Code already has a different
|
|
2506
|
+
throw new Error("Claude Code already has a different agent-docs server; remove it first.");
|
|
2457
2507
|
}
|
|
2458
2508
|
const server = serverCommand(root);
|
|
2459
2509
|
const result = await capture(["claude", "mcp", "add", SERVER_NAME, "--scope", "local", "--", server.command, ...server.args], root);
|
|
@@ -2464,14 +2514,15 @@ ${existing.stderr}`;
|
|
|
2464
2514
|
return "Configured Claude Code local project scope.";
|
|
2465
2515
|
}
|
|
2466
2516
|
async function smokeServer(root) {
|
|
2467
|
-
const result = await capture(["pnpm", "--dir", root, "
|
|
2517
|
+
const result = await capture(["pnpm", "--dir", root, "agent:mcp", "--check", "--root", root], root);
|
|
2468
2518
|
if (result.exitCode !== 0) {
|
|
2469
2519
|
const output = result.stderr.trim() || result.stdout.trim();
|
|
2470
|
-
throw new Error(`
|
|
2520
|
+
throw new Error(`Agent context MCP self-check failed${output ? `: ${output}` : "."}`);
|
|
2471
2521
|
}
|
|
2472
2522
|
const report = JSON.parse(result.stdout);
|
|
2473
|
-
if (!report.ok || report.tools !==
|
|
2474
|
-
throw new Error("
|
|
2523
|
+
if (!report.ok || report.tools !== AGENT_CONTEXT_TOOLS.length) {
|
|
2524
|
+
throw new Error("Agent context MCP self-check was incomplete.");
|
|
2525
|
+
}
|
|
2475
2526
|
}
|
|
2476
2527
|
function detectedClients() {
|
|
2477
2528
|
return CLIENTS.filter((client) => {
|
|
@@ -2541,7 +2592,7 @@ async function runAgentSetup(argv, cliName) {
|
|
|
2541
2592
|
const clients = await chooseClients(parsed);
|
|
2542
2593
|
if (!clients)
|
|
2543
2594
|
return;
|
|
2544
|
-
intro2("
|
|
2595
|
+
intro2("Local agent context");
|
|
2545
2596
|
if (parsed.dryRun) {
|
|
2546
2597
|
for (const client of clients) {
|
|
2547
2598
|
log2.info(`${client}
|
|
@@ -2550,16 +2601,16 @@ ${dryRunDescription(client, root)}`);
|
|
|
2550
2601
|
outro2("Dry run complete. No client configuration was changed.");
|
|
2551
2602
|
return;
|
|
2552
2603
|
}
|
|
2553
|
-
const
|
|
2554
|
-
|
|
2604
|
+
const progress2 = spinner2();
|
|
2605
|
+
progress2.start("Testing the local MCP server");
|
|
2555
2606
|
await smokeServer(root);
|
|
2556
|
-
|
|
2607
|
+
progress2.stop("Local MCP server passed its protocol self-check");
|
|
2557
2608
|
for (const client of clients) {
|
|
2558
2609
|
const result = client === "codex" ? await configureCodex(root) : client === "cursor" ? await configureCursor(root) : await configureClaude(root);
|
|
2559
2610
|
log2.success(`${client}: ${result}`);
|
|
2560
2611
|
}
|
|
2561
2612
|
log2.info("Restart configured clients, then ask: “Use plan_change for my next feature.”");
|
|
2562
|
-
outro2("
|
|
2613
|
+
outro2("Agent context is ready.");
|
|
2563
2614
|
}
|
|
2564
2615
|
|
|
2565
2616
|
// src/deployment-cli.ts
|
|
@@ -2694,7 +2745,8 @@ Create a GitHub environment named \`${config.environment}\` and configure:
|
|
|
2694
2745
|
- a \`BACKEND_HCL\` secret containing the selected remote-state backend configuration;
|
|
2695
2746
|
- a \`TFVARS_JSON\` secret containing only:
|
|
2696
2747
|
- ${providerInputs};
|
|
2697
|
-
- \`VITE_TERMS_URL\` and \`VITE_PRIVACY_URL
|
|
2748
|
+
- \`VITE_TERMS_URL\` and \`VITE_PRIVACY_URL\`, plus \`VITE_MARKETING_URL\` when the product has a
|
|
2749
|
+
public marketing site.
|
|
2698
2750
|
|
|
2699
2751
|
## Cloud preparation
|
|
2700
2752
|
|
|
@@ -3203,7 +3255,8 @@ Create a GitHub environment named \`${config.environment}\` and configure:
|
|
|
3203
3255
|
- a \`BACKEND_HCL\` secret containing the selected remote-state backend configuration;
|
|
3204
3256
|
- a \`TFVARS_JSON\` secret containing only:
|
|
3205
3257
|
- ${providerInputs};
|
|
3206
|
-
- \`VITE_TERMS_URL\` and \`VITE_PRIVACY_URL
|
|
3258
|
+
- \`VITE_TERMS_URL\` and \`VITE_PRIVACY_URL\`, plus \`VITE_MARKETING_URL\` when the product has a
|
|
3259
|
+
public marketing site.
|
|
3207
3260
|
|
|
3208
3261
|
## Cloud preparation
|
|
3209
3262
|
|
|
@@ -3290,14 +3343,14 @@ import { execFile } from "node:child_process";
|
|
|
3290
3343
|
import { createHash } from "node:crypto";
|
|
3291
3344
|
import { cp, lstat, mkdir as mkdir4, mkdtemp, readFile as readFile5, readdir, rm as rm4, stat as stat4, writeFile as writeFile4 } from "node:fs/promises";
|
|
3292
3345
|
import { tmpdir } from "node:os";
|
|
3293
|
-
import { basename, join as join7, relative, resolve as resolve3 } from "node:path";
|
|
3346
|
+
import { basename, dirname as dirname2, join as join7, relative, resolve as resolve3 } from "node:path";
|
|
3294
3347
|
import { promisify } from "node:util";
|
|
3295
3348
|
|
|
3296
3349
|
// src/delivery.ts
|
|
3297
3350
|
import { createWriteStream } from "node:fs";
|
|
3298
3351
|
import { rm as rm3 } from "node:fs/promises";
|
|
3299
3352
|
import { join as join6 } from "node:path";
|
|
3300
|
-
import { Readable } from "node:stream";
|
|
3353
|
+
import { Readable, Transform } from "node:stream";
|
|
3301
3354
|
import { pipeline } from "node:stream/promises";
|
|
3302
3355
|
var RELEASE_TAG_PATTERN2 = /^v\d+\.\d+\.\d+(?:-rc\.\d+)?$/;
|
|
3303
3356
|
var INDEX_TIMEOUT_MS = 15000;
|
|
@@ -3357,7 +3410,24 @@ async function resolveRelease(licenseKey, baseUrl = deliveryBaseUrl()) {
|
|
|
3357
3410
|
}
|
|
3358
3411
|
return index;
|
|
3359
3412
|
}
|
|
3360
|
-
|
|
3413
|
+
function readContentLength(response) {
|
|
3414
|
+
const header = response.headers.get("content-length");
|
|
3415
|
+
if (!header)
|
|
3416
|
+
return;
|
|
3417
|
+
const total = Number(header);
|
|
3418
|
+
return Number.isSafeInteger(total) && total > 0 ? total : undefined;
|
|
3419
|
+
}
|
|
3420
|
+
function countBytes(total, onBytes) {
|
|
3421
|
+
let received = 0;
|
|
3422
|
+
return new Transform({
|
|
3423
|
+
transform(chunk, _encoding, done) {
|
|
3424
|
+
received += chunk.length;
|
|
3425
|
+
onBytes(received, total);
|
|
3426
|
+
done(null, chunk);
|
|
3427
|
+
}
|
|
3428
|
+
});
|
|
3429
|
+
}
|
|
3430
|
+
async function downloadAsset(licenseKey, releaseTag, assetName, directory, baseUrl = deliveryBaseUrl(), onBytes) {
|
|
3361
3431
|
const response = await requestDelivery(`${baseUrl}/v1/source/${encodeURIComponent(releaseTag)}/${encodeURIComponent(assetName)}`, {
|
|
3362
3432
|
headers: { authorization: `Bearer ${licenseKey}` },
|
|
3363
3433
|
signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS)
|
|
@@ -3365,8 +3435,13 @@ async function downloadAsset(licenseKey, releaseTag, assetName, directory, baseU
|
|
|
3365
3435
|
if (!response.body)
|
|
3366
3436
|
throw new Error(`Source delivery returned an empty ${assetName}.`);
|
|
3367
3437
|
const path = join6(directory, assetName);
|
|
3438
|
+
const source = Readable.fromWeb(response.body);
|
|
3368
3439
|
try {
|
|
3369
|
-
|
|
3440
|
+
if (onBytes) {
|
|
3441
|
+
await pipeline(source, countBytes(readContentLength(response), onBytes), createWriteStream(path));
|
|
3442
|
+
} else {
|
|
3443
|
+
await pipeline(source, createWriteStream(path));
|
|
3444
|
+
}
|
|
3370
3445
|
} catch {
|
|
3371
3446
|
await rm3(path, { force: true });
|
|
3372
3447
|
throw new Error(`Downloading ${assetName} was interrupted. Try again in a few minutes.`);
|
|
@@ -3383,6 +3458,9 @@ var BRAND_MANIFEST = [
|
|
|
3383
3458
|
{ path: "apps/web/public/favicon/site.webmanifest", occurrences: 2 },
|
|
3384
3459
|
{ path: "apps/web/public/offline.html", occurrences: 6 },
|
|
3385
3460
|
{ path: "apps/docs/lib/shared.ts", occurrences: 1 },
|
|
3461
|
+
{ path: "apps/docs/README.md", occurrences: 1 },
|
|
3462
|
+
{ path: "README.md", occurrences: 1 },
|
|
3463
|
+
{ path: "docs/meta.json", occurrences: 1 },
|
|
3386
3464
|
{ path: "apps/server/.env.example", occurrences: 1 },
|
|
3387
3465
|
{ path: "apps/server/src/config/env.ts", occurrences: 2 },
|
|
3388
3466
|
{ path: "apps/server/src/config/env.test.ts", occurrences: 1 },
|
|
@@ -3406,6 +3484,7 @@ var BRAND_MANIFEST = [
|
|
|
3406
3484
|
{ path: "apps/server/email-templates/security-alert.html", occurrences: 4 },
|
|
3407
3485
|
{ path: "apps/server/email-templates/verify-email.html", occurrences: 5 },
|
|
3408
3486
|
{ path: "apps/server/email-templates/reset-password.html", occurrences: 5 },
|
|
3487
|
+
{ path: "apps/server/email-templates/organization-invitation.html", occurrences: 3 },
|
|
3409
3488
|
{ path: "apps/server/email-templates/welcome-email.html", occurrences: 8 }
|
|
3410
3489
|
];
|
|
3411
3490
|
function validateAppName2(value) {
|
|
@@ -3454,7 +3533,7 @@ function rewriteBrandContent(content, brandName) {
|
|
|
3454
3533
|
if (error)
|
|
3455
3534
|
throw new Error(error);
|
|
3456
3535
|
const escapedSourceBrand = SOURCE_BRAND_NAME.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3457
|
-
const sourceBrandPattern = new RegExp(`\\b${escapedSourceBrand}\\b`, "
|
|
3536
|
+
const sourceBrandPattern = new RegExp(`\\b${escapedSourceBrand}\\b`, "gi");
|
|
3458
3537
|
const matches = content.match(sourceBrandPattern);
|
|
3459
3538
|
if (brandName === SOURCE_BRAND_NAME || !matches)
|
|
3460
3539
|
return { content, replacements: 0 };
|
|
@@ -3552,6 +3631,20 @@ function rewriteRootPackageName(content, appName) {
|
|
|
3552
3631
|
return { content: `${JSON.stringify(parsed, null, 2)}
|
|
3553
3632
|
`, replacements: 1 };
|
|
3554
3633
|
}
|
|
3634
|
+
var WORKER_NAME_DECLARATION = /^name = "[^"]*"$/m;
|
|
3635
|
+
function rewriteWorkerName(content, appName) {
|
|
3636
|
+
const error = validateAppName2(appName);
|
|
3637
|
+
if (error)
|
|
3638
|
+
throw new Error(error);
|
|
3639
|
+
const declaration = content.match(WORKER_NAME_DECLARATION)?.[0];
|
|
3640
|
+
if (!declaration) {
|
|
3641
|
+
throw new Error("Cloudflare Worker configuration is missing a name declaration.");
|
|
3642
|
+
}
|
|
3643
|
+
const replacement = `name = "${appName}"`;
|
|
3644
|
+
if (declaration === replacement)
|
|
3645
|
+
return { content, replacements: 0 };
|
|
3646
|
+
return { content: content.replace(WORKER_NAME_DECLARATION, replacement), replacements: 1 };
|
|
3647
|
+
}
|
|
3555
3648
|
function replaceMarkdownSection(content, heading, replacement) {
|
|
3556
3649
|
const marker = `## ${heading}
|
|
3557
3650
|
`;
|
|
@@ -3604,6 +3697,12 @@ into this repository.
|
|
|
3604
3697
|
}
|
|
3605
3698
|
return rewritten;
|
|
3606
3699
|
}
|
|
3700
|
+
var BUYER_README_HEADING = "## Create your product";
|
|
3701
|
+
function stripDistributionReadmeBootstrap(content) {
|
|
3702
|
+
if (!content.includes(BUYER_README_HEADING))
|
|
3703
|
+
return content;
|
|
3704
|
+
return replaceMarkdownSection(content, "Create your product", "");
|
|
3705
|
+
}
|
|
3607
3706
|
var REQUIRED_BUYER_CLI_CONSTANTS = ["CLI_NAME", "CLI_PACKAGE_NAME", "DEFAULT_SCOPE"];
|
|
3608
3707
|
function rewriteBuyerCliConstants(content, scope, brandName) {
|
|
3609
3708
|
const buyerVariablePrefix = BUYER_CLI_NAME.toUpperCase().replaceAll("-", "_");
|
|
@@ -3614,6 +3713,7 @@ function rewriteBuyerCliConstants(content, scope, brandName) {
|
|
|
3614
3713
|
["SOURCE_BRAND_NAME", brandName],
|
|
3615
3714
|
["TEMPLATE_REPOSITORY", "OWNER/REPOSITORY"],
|
|
3616
3715
|
["DELIVERY_BASE_URL", "https://releases.example.com"],
|
|
3716
|
+
["DOCS_BASE_URL", "https://docs.example.com"],
|
|
3617
3717
|
["DELIVERY_URL_VARIABLE", `${buyerVariablePrefix}_DELIVERY_URL`],
|
|
3618
3718
|
["LICENSE_KEY_VARIABLE", `${buyerVariablePrefix}_LICENSE_KEY`],
|
|
3619
3719
|
["LICENSE_KEY_PREFIX", "LICENSE-"]
|
|
@@ -3633,6 +3733,7 @@ function rewriteBuyerCliConstants(content, scope, brandName) {
|
|
|
3633
3733
|
CLI_PACKAGE_NAME,
|
|
3634
3734
|
SOURCE_BRAND_NAME,
|
|
3635
3735
|
DELIVERY_BASE_URL,
|
|
3736
|
+
DOCS_BASE_URL,
|
|
3636
3737
|
DELIVERY_URL_VARIABLE,
|
|
3637
3738
|
LICENSE_KEY_VARIABLE2,
|
|
3638
3739
|
LICENSE_KEY_PREFIX
|
|
@@ -3716,12 +3817,16 @@ function isTemplatePathExcluded(relativePath) {
|
|
|
3716
3817
|
return true;
|
|
3717
3818
|
if (normalized === "internal" || normalized.startsWith("internal/"))
|
|
3718
3819
|
return true;
|
|
3820
|
+
if (normalized === "marketing" || normalized.startsWith("marketing/"))
|
|
3821
|
+
return true;
|
|
3719
3822
|
if (normalized === ".github/workflows/internal-commercial.yml")
|
|
3720
3823
|
return true;
|
|
3721
3824
|
if (normalized === "apps/worker" || normalized.startsWith("apps/worker/"))
|
|
3722
3825
|
return true;
|
|
3723
3826
|
if (parts.includes("dist") || parts.includes("out"))
|
|
3724
3827
|
return true;
|
|
3828
|
+
if (parts.includes(".next") || parts.includes(".source") || parts.includes(".turbo"))
|
|
3829
|
+
return true;
|
|
3725
3830
|
if (normalized === "bun.lock")
|
|
3726
3831
|
return true;
|
|
3727
3832
|
if (normalized === ".vercelignore")
|
|
@@ -3734,6 +3839,8 @@ function isTemplatePathExcluded(relativePath) {
|
|
|
3734
3839
|
return true;
|
|
3735
3840
|
if (normalized === "packages/create-app/src/cli-brand-contract.test.ts")
|
|
3736
3841
|
return true;
|
|
3842
|
+
if (normalized === "packages/create-app/src/commercial-contract.test.ts")
|
|
3843
|
+
return true;
|
|
3737
3844
|
if (normalized === "packages/create-app/LICENSE")
|
|
3738
3845
|
return true;
|
|
3739
3846
|
if (normalized === "packages/create-app/README.md")
|
|
@@ -3762,6 +3869,29 @@ async function assertEmptyTarget(targetDirectory) {
|
|
|
3762
3869
|
throw error;
|
|
3763
3870
|
}
|
|
3764
3871
|
}
|
|
3872
|
+
async function trackedTemplatePaths(sourceDirectory) {
|
|
3873
|
+
const topLevel = await execFileAsync("git", ["rev-parse", "--show-toplevel"], {
|
|
3874
|
+
cwd: sourceDirectory
|
|
3875
|
+
}).then((result) => resolve3(result.stdout.trim())).catch(() => {
|
|
3876
|
+
return;
|
|
3877
|
+
});
|
|
3878
|
+
if (topLevel !== sourceDirectory)
|
|
3879
|
+
return;
|
|
3880
|
+
const { stdout: stdout2 } = await execFileAsync("git", ["ls-files", "-z"], {
|
|
3881
|
+
cwd: sourceDirectory,
|
|
3882
|
+
maxBuffer: 64 * 1024 * 1024
|
|
3883
|
+
});
|
|
3884
|
+
const tracked = new Set;
|
|
3885
|
+
for (const path of stdout2.split("\x00")) {
|
|
3886
|
+
if (!path)
|
|
3887
|
+
continue;
|
|
3888
|
+
tracked.add(path);
|
|
3889
|
+
for (let parent = dirname2(path);parent !== "."; parent = dirname2(parent)) {
|
|
3890
|
+
tracked.add(parent);
|
|
3891
|
+
}
|
|
3892
|
+
}
|
|
3893
|
+
return tracked;
|
|
3894
|
+
}
|
|
3765
3895
|
async function copyLocalTemplate(source, target) {
|
|
3766
3896
|
const sourceDirectory = resolve3(source);
|
|
3767
3897
|
const sourceStat = await lstat(sourceDirectory).catch(() => {
|
|
@@ -3769,29 +3899,28 @@ async function copyLocalTemplate(source, target) {
|
|
|
3769
3899
|
});
|
|
3770
3900
|
if (!sourceStat?.isDirectory())
|
|
3771
3901
|
throw new Error(`Template path is not a directory: ${sourceDirectory}`);
|
|
3772
|
-
await
|
|
3902
|
+
const tracked = await trackedTemplatePaths(sourceDirectory);
|
|
3903
|
+
const isTemplatePathIncluded = (relativePath) => relativePath === "" || !isTemplatePathExcluded(relativePath) && (!tracked || tracked.has(relativePath));
|
|
3904
|
+
await assertNoSymbolicLinks(sourceDirectory, sourceDirectory, isTemplatePathIncluded);
|
|
3773
3905
|
await mkdir4(target, { recursive: true });
|
|
3774
3906
|
await cp(sourceDirectory, target, {
|
|
3775
3907
|
recursive: true,
|
|
3776
|
-
filter: (path) =>
|
|
3777
|
-
const relativePath = relative(sourceDirectory, path);
|
|
3778
|
-
return relativePath === "" || !isTemplatePathExcluded(relativePath);
|
|
3779
|
-
}
|
|
3908
|
+
filter: (path) => isTemplatePathIncluded(relative(sourceDirectory, path).replaceAll("\\", "/"))
|
|
3780
3909
|
});
|
|
3781
3910
|
await assertNoSymbolicLinks(target);
|
|
3782
3911
|
}
|
|
3783
|
-
async function assertNoSymbolicLinks(directory, root = directory,
|
|
3912
|
+
async function assertNoSymbolicLinks(directory, root = directory, isIncluded) {
|
|
3784
3913
|
const entries = await readdir(directory, { withFileTypes: true });
|
|
3785
3914
|
for (const entry of entries) {
|
|
3786
3915
|
const path = join7(directory, entry.name);
|
|
3787
|
-
const relativePath = relative(root, path);
|
|
3788
|
-
if (
|
|
3916
|
+
const relativePath = relative(root, path).replaceAll("\\", "/");
|
|
3917
|
+
if (isIncluded && !isIncluded(relativePath))
|
|
3789
3918
|
continue;
|
|
3790
3919
|
if (entry.isSymbolicLink()) {
|
|
3791
3920
|
throw new Error(`Template contains a symbolic link, which is not supported: ${path}`);
|
|
3792
3921
|
}
|
|
3793
3922
|
if (entry.isDirectory())
|
|
3794
|
-
await assertNoSymbolicLinks(path, root,
|
|
3923
|
+
await assertNoSymbolicLinks(path, root, isIncluded);
|
|
3795
3924
|
}
|
|
3796
3925
|
}
|
|
3797
3926
|
function verifyReleaseMetadata(checksum, manifest, artifactName, digest, templateRef) {
|
|
@@ -3806,14 +3935,14 @@ function verifyReleaseMetadata(checksum, manifest, artifactName, digest, templat
|
|
|
3806
3935
|
throw new Error(`${CLI_NAME2} release manifest does not match the downloaded source artifact.`);
|
|
3807
3936
|
}
|
|
3808
3937
|
}
|
|
3809
|
-
async function downloadLicensedTemplate(target, licenseKey, sourceVersion) {
|
|
3938
|
+
async function downloadLicensedTemplate(target, licenseKey, sourceVersion, onDownloadProgress) {
|
|
3810
3939
|
const baseUrl = deliveryBaseUrl();
|
|
3811
3940
|
const releaseTag = sourceVersion ?? (await resolveRelease(licenseKey, baseUrl)).latest;
|
|
3812
3941
|
const { artifact, checksum: checksumName, manifest: manifestName } = releaseAssetNames(releaseTag);
|
|
3813
3942
|
const temporaryDirectory = await mkdtemp(join7(tmpdir(), `${CLI_NAME2}-`));
|
|
3814
3943
|
const extractedPath = join7(temporaryDirectory, "template");
|
|
3815
3944
|
try {
|
|
3816
|
-
const archivePath = await downloadAsset(licenseKey, releaseTag, artifact, temporaryDirectory, baseUrl);
|
|
3945
|
+
const archivePath = await downloadAsset(licenseKey, releaseTag, artifact, temporaryDirectory, baseUrl, onDownloadProgress);
|
|
3817
3946
|
await downloadAsset(licenseKey, releaseTag, checksumName, temporaryDirectory, baseUrl);
|
|
3818
3947
|
await downloadAsset(licenseKey, releaseTag, manifestName, temporaryDirectory, baseUrl);
|
|
3819
3948
|
const archive = await readFile5(archivePath);
|
|
@@ -3888,6 +4017,14 @@ async function applyBrandRewrite(target, brandName) {
|
|
|
3888
4017
|
}
|
|
3889
4018
|
return report;
|
|
3890
4019
|
}
|
|
4020
|
+
async function renameFrontendWorker(target, appName) {
|
|
4021
|
+
const workerPath = join7(target, "apps/web/wrangler.toml");
|
|
4022
|
+
if (!await stat4(workerPath).catch(() => {
|
|
4023
|
+
return;
|
|
4024
|
+
}))
|
|
4025
|
+
return;
|
|
4026
|
+
await rewriteFile(workerPath, (content) => rewriteWorkerName(content, appName));
|
|
4027
|
+
}
|
|
3891
4028
|
async function generateEnvironmentFiles(target, databasePort = 5432) {
|
|
3892
4029
|
const serverExample = await readFile5(join7(target, "apps/server/.env.example"), "utf8");
|
|
3893
4030
|
await writeFile4(join7(target, "apps/server/.env"), generateServerEnv(serverExample, undefined, databasePort));
|
|
@@ -3901,8 +4038,10 @@ async function generateEnvironmentFiles(target, databasePort = 5432) {
|
|
|
3901
4038
|
async function neutralizeAgentInstructions(target) {
|
|
3902
4039
|
const agentPath = join7(target, "AGENTS.md");
|
|
3903
4040
|
const claudePath = join7(target, "CLAUDE.md");
|
|
4041
|
+
const readmePath = join7(target, "README.md");
|
|
3904
4042
|
await writeFile4(agentPath, rewriteBuyerAgentInstructions(await readFile5(agentPath, "utf8")));
|
|
3905
4043
|
await writeFile4(claudePath, rewriteBuyerClaudeInstructions(await readFile5(claudePath, "utf8")));
|
|
4044
|
+
await writeFile4(readmePath, stripDistributionReadmeBootstrap(await readFile5(readmePath, "utf8")));
|
|
3906
4045
|
const rootPackagePath = join7(target, "package.json");
|
|
3907
4046
|
await writeFile4(rootPackagePath, stripSellerOnlyRootScripts(await readFile5(rootPackagePath, "utf8")));
|
|
3908
4047
|
}
|
|
@@ -3965,12 +4104,13 @@ async function scaffold(options) {
|
|
|
3965
4104
|
if (options.from)
|
|
3966
4105
|
await copyLocalTemplate(options.from, target);
|
|
3967
4106
|
else if (options.licenseKey) {
|
|
3968
|
-
await downloadLicensedTemplate(target, options.licenseKey, options.sourceVersion);
|
|
4107
|
+
await downloadLicensedTemplate(target, options.licenseKey, options.sourceVersion, options.onDownloadProgress);
|
|
3969
4108
|
} else {
|
|
3970
4109
|
throw new Error(`A local source path or a ${CLI_NAME2} licence key is required.`);
|
|
3971
4110
|
}
|
|
3972
4111
|
const packagePath = join7(target, "package.json");
|
|
3973
4112
|
const packageReplacements = await rewriteFile(packagePath, (content) => rewriteRootPackageName(content, options.appName));
|
|
4113
|
+
await renameFrontendWorker(target, options.appName);
|
|
3974
4114
|
const scope = await applyScopeRewrite(target, options.scope);
|
|
3975
4115
|
await neutralizeAgentInstructions(target);
|
|
3976
4116
|
await neutralizeSourceCli(target, options.scope, options.brandName);
|
|
@@ -4009,7 +4149,7 @@ Create a buyer-owned SaaS application from your licensed ${suggestBrandName(CLI_
|
|
|
4009
4149
|
Options:
|
|
4010
4150
|
--license-key <key> Licence key from your purchase (or set ${LICENSE_KEY_VARIABLE})
|
|
4011
4151
|
--source-version <version> Pin an immutable release (default: the latest you are entitled to)
|
|
4012
|
-
--from <path> Use a local
|
|
4152
|
+
--from <path> Use a local Git checkout, tracked files only (maintainer/testing)
|
|
4013
4153
|
--scope <namespace> Advanced workspace namespace override (default: derived from brand)
|
|
4014
4154
|
--brand <name> Display name (default: derived from app name)
|
|
4015
4155
|
--yes Accept recommended defaults for omitted options
|
|
@@ -4222,7 +4362,7 @@ async function collectOptions(parsed) {
|
|
|
4222
4362
|
};
|
|
4223
4363
|
}
|
|
4224
4364
|
async function readCliVersion() {
|
|
4225
|
-
const packageJson = JSON.parse(await readFile6(resolve4(
|
|
4365
|
+
const packageJson = JSON.parse(await readFile6(resolve4(dirname3(fileURLToPath2(import.meta.url)), "../package.json"), "utf8"));
|
|
4226
4366
|
if (!packageJson.version)
|
|
4227
4367
|
throw new Error(`${CLI_NAME} package version is missing.`);
|
|
4228
4368
|
return packageJson.version;
|
|
@@ -4305,35 +4445,71 @@ async function installDependencies(target) {
|
|
|
4305
4445
|
throw new Error(`Dependency installation failed.${commandFailureOutput(stdout2, stderr)}`);
|
|
4306
4446
|
}
|
|
4307
4447
|
}
|
|
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
4448
|
async function occupiedDevelopmentPorts() {
|
|
4315
4449
|
const results = await Promise.all([5173, 8000].map(async (port) => ({ port, available: await isPortAvailable(port) })));
|
|
4316
4450
|
return results.filter((result) => !result.available).map((result) => result.port);
|
|
4317
4451
|
}
|
|
4318
4452
|
async function startDevelopment(target) {
|
|
4319
|
-
await runStep("Starting local Postgres", "Local Postgres is ready", () => runCapturedCommand(["docker", "compose", "up", "-d", "--wait", "postgres"], target));
|
|
4320
4453
|
log.info(`Web http://localhost:5173
|
|
4321
4454
|
API http://localhost:8000`);
|
|
4322
4455
|
outro("Development servers are starting. Ctrl+C stops app servers; Postgres stays running.");
|
|
4323
|
-
const exitCode = await runInherited(["
|
|
4456
|
+
const exitCode = await runInherited(["pnpm", "run", "dev"], { cwd: target });
|
|
4324
4457
|
if (exitCode !== 0 && exitCode !== 130) {
|
|
4325
4458
|
throw new Error(`Development servers exited with code ${exitCode}.`);
|
|
4326
4459
|
}
|
|
4327
4460
|
}
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4461
|
+
var BYTE_UNITS = ["B", "KB", "MB", "GB"];
|
|
4462
|
+
var BYTE_SCALE = 1024;
|
|
4463
|
+
var PROGRESS_MAX = 100;
|
|
4464
|
+
var PROGRESS_BAR_WIDTH = 40;
|
|
4465
|
+
var PROGRESS_MIN_INTERVAL_MS = 80;
|
|
4466
|
+
var PROGRESS_MIN_DELTA = 0.5;
|
|
4467
|
+
function formatBytes(bytes) {
|
|
4468
|
+
let value = bytes;
|
|
4469
|
+
let unit = 0;
|
|
4470
|
+
while (value >= BYTE_SCALE && unit < BYTE_UNITS.length - 1) {
|
|
4471
|
+
value /= BYTE_SCALE;
|
|
4472
|
+
unit += 1;
|
|
4473
|
+
}
|
|
4474
|
+
return `${unit === 0 ? value : value.toFixed(1)} ${BYTE_UNITS[unit]}`;
|
|
4475
|
+
}
|
|
4476
|
+
function supportsAnimation() {
|
|
4477
|
+
return isTTY(process.stdout) && !isCI();
|
|
4478
|
+
}
|
|
4479
|
+
function createScaffoldReporter(pending, expectsDownload) {
|
|
4480
|
+
if (!expectsDownload || !supportsAnimation())
|
|
4481
|
+
return { handle: spinner() };
|
|
4482
|
+
const bar = progress({
|
|
4483
|
+
style: "heavy",
|
|
4484
|
+
size: PROGRESS_BAR_WIDTH,
|
|
4485
|
+
max: PROGRESS_MAX
|
|
4486
|
+
});
|
|
4487
|
+
let shown = 0;
|
|
4488
|
+
let emittedAt = 0;
|
|
4489
|
+
return {
|
|
4490
|
+
handle: bar,
|
|
4491
|
+
onDownloadProgress: (received, total) => {
|
|
4492
|
+
const percent = total ? Math.min(PROGRESS_MAX, received / total * PROGRESS_MAX) : shown;
|
|
4493
|
+
const finished = total !== undefined && received >= total;
|
|
4494
|
+
const now = performance.now();
|
|
4495
|
+
if (!finished && now - emittedAt < PROGRESS_MIN_INTERVAL_MS && percent - shown < PROGRESS_MIN_DELTA) {
|
|
4496
|
+
return;
|
|
4497
|
+
}
|
|
4498
|
+
emittedAt = now;
|
|
4499
|
+
const label = finished ? pending : total === undefined ? `Downloading source ${formatBytes(received)}` : `Downloading source ${formatBytes(received)} / ${formatBytes(total)}`;
|
|
4500
|
+
bar.advance(percent - shown, label);
|
|
4501
|
+
shown = percent;
|
|
4502
|
+
}
|
|
4503
|
+
};
|
|
4504
|
+
}
|
|
4505
|
+
async function runStep(pending, complete, action, handle) {
|
|
4506
|
+
handle.start(pending);
|
|
4331
4507
|
try {
|
|
4332
4508
|
const result = await action();
|
|
4333
|
-
|
|
4509
|
+
handle.stop(complete);
|
|
4334
4510
|
return result;
|
|
4335
4511
|
} catch (error) {
|
|
4336
|
-
|
|
4512
|
+
handle.error(`${pending} failed`);
|
|
4337
4513
|
throw error;
|
|
4338
4514
|
}
|
|
4339
4515
|
}
|
|
@@ -4342,10 +4518,12 @@ async function createApplication(options) {
|
|
|
4342
4518
|
await assertTargetAvailable(target);
|
|
4343
4519
|
assertSiblingTarget(options.from, target);
|
|
4344
4520
|
const databasePort = await findAvailablePort();
|
|
4345
|
-
await mkdir5(
|
|
4346
|
-
const staging = await mkdtemp2(join8(
|
|
4521
|
+
await mkdir5(dirname3(target), { recursive: true });
|
|
4522
|
+
const staging = await mkdtemp2(join8(dirname3(target), `.${options.appName}-${CLI_NAME}-`));
|
|
4523
|
+
const preparing = "Preparing your application";
|
|
4524
|
+
const reporter = createScaffoldReporter(preparing, Boolean(options.licenseKey));
|
|
4347
4525
|
try {
|
|
4348
|
-
await runStep(
|
|
4526
|
+
await runStep(preparing, "Application source prepared", () => scaffold({
|
|
4349
4527
|
appName: options.appName,
|
|
4350
4528
|
scope: options.scope,
|
|
4351
4529
|
brandName: options.brandName,
|
|
@@ -4354,13 +4532,14 @@ async function createApplication(options) {
|
|
|
4354
4532
|
licenseKey: options.licenseKey,
|
|
4355
4533
|
sourceVersion: options.sourceVersion,
|
|
4356
4534
|
initializeGit: options.initializeGit,
|
|
4357
|
-
databasePort
|
|
4358
|
-
|
|
4535
|
+
databasePort,
|
|
4536
|
+
onDownloadProgress: reporter.onDownloadProgress
|
|
4537
|
+
}), reporter.handle);
|
|
4359
4538
|
if (options.deployment) {
|
|
4360
|
-
await runStep("Configuring deployment", "Deployment target locked", () => applyDeploymentSelection2(staging, options.appName, options.deployment));
|
|
4539
|
+
await runStep("Configuring deployment", "Deployment target locked", () => applyDeploymentSelection2(staging, options.appName, options.deployment), spinner());
|
|
4361
4540
|
}
|
|
4362
4541
|
if (options.installDependencies) {
|
|
4363
|
-
await runStep("Installing dependencies with pnpm", "Dependencies installed with pnpm", () => installDependencies(staging), "timer");
|
|
4542
|
+
await runStep("Installing dependencies with pnpm", "Dependencies installed with pnpm", () => installDependencies(staging), spinner({ indicator: "timer" }));
|
|
4364
4543
|
}
|
|
4365
4544
|
if (await stat5(target).catch(() => {
|
|
4366
4545
|
return;
|
|
@@ -4378,12 +4557,7 @@ function completionDetails(target, options, databasePort) {
|
|
|
4378
4557
|
const install = options.installDependencies ? [] : ["vp install --frozen-lockfile"];
|
|
4379
4558
|
return {
|
|
4380
4559
|
directory: `./${targetName}`,
|
|
4381
|
-
commands: [
|
|
4382
|
-
`cd ${targetName}`,
|
|
4383
|
-
...install,
|
|
4384
|
-
"docker compose up -d postgres",
|
|
4385
|
-
"vp run -r --parallel dev"
|
|
4386
|
-
].join(`
|
|
4560
|
+
commands: [`cd ${targetName}`, ...install, "pnpm dev"].join(`
|
|
4387
4561
|
`),
|
|
4388
4562
|
database: `Postgres localhost:${databasePort}`
|
|
4389
4563
|
};
|
|
@@ -4470,6 +4644,7 @@ export {
|
|
|
4470
4644
|
printRunnableCommands,
|
|
4471
4645
|
parseArguments,
|
|
4472
4646
|
occupiedDevelopmentPorts,
|
|
4647
|
+
formatBytes,
|
|
4473
4648
|
findAvailablePort,
|
|
4474
4649
|
describeTargetConflict,
|
|
4475
4650
|
collectLicenseKey
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "superslate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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.2.0",
|
|
43
|
+
"typescript": "^7.0.2",
|
|
44
|
+
"vitest": "^4.1.10"
|
|
44
45
|
},
|
|
45
46
|
"engines": {
|
|
46
47
|
"node": ">=20"
|