cnippet-stack 0.2.0 → 0.2.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/dist/index.js +381 -82
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -557,7 +557,7 @@ var require_logger = __commonJS({
|
|
|
557
557
|
return level;
|
|
558
558
|
},
|
|
559
559
|
// Can be overridden in the host environment
|
|
560
|
-
log: function
|
|
560
|
+
log: function log3(level) {
|
|
561
561
|
level = logger.lookupLevel(level);
|
|
562
562
|
if (typeof console !== "undefined" && logger.lookupLevel(logger.level) <= level) {
|
|
563
563
|
var method = logger.methodMap[level];
|
|
@@ -738,8 +738,8 @@ var require_base = __commonJS({
|
|
|
738
738
|
_internalProtoAccess.resetLoggedProperties();
|
|
739
739
|
}
|
|
740
740
|
};
|
|
741
|
-
var
|
|
742
|
-
exports.log =
|
|
741
|
+
var log3 = _logger2["default"].log;
|
|
742
|
+
exports.log = log3;
|
|
743
743
|
exports.createFrame = _utils.createFrame;
|
|
744
744
|
exports.logger = _logger2["default"];
|
|
745
745
|
}
|
|
@@ -5927,8 +5927,9 @@ var require_lib = __commonJS({
|
|
|
5927
5927
|
});
|
|
5928
5928
|
|
|
5929
5929
|
// src/program.ts
|
|
5930
|
+
import * as clack3 from "@clack/prompts";
|
|
5930
5931
|
import { Command } from "commander";
|
|
5931
|
-
import
|
|
5932
|
+
import pc8 from "picocolors";
|
|
5932
5933
|
|
|
5933
5934
|
// src/commands/doctor.ts
|
|
5934
5935
|
function runDoctor(recipe) {
|
|
@@ -5939,7 +5940,7 @@ function runDoctor(recipe) {
|
|
|
5939
5940
|
|
|
5940
5941
|
// src/commands/init.ts
|
|
5941
5942
|
import * as clack2 from "@clack/prompts";
|
|
5942
|
-
import
|
|
5943
|
+
import pc3 from "picocolors";
|
|
5943
5944
|
|
|
5944
5945
|
// src/compat.ts
|
|
5945
5946
|
var CompatibilityError = class extends Error {
|
|
@@ -5979,10 +5980,12 @@ function checkCompatibility(detection, supports) {
|
|
|
5979
5980
|
);
|
|
5980
5981
|
}
|
|
5981
5982
|
}
|
|
5983
|
+
function isRequireSatisfied(required, detection) {
|
|
5984
|
+
return required === "prisma" && detection.orm === "prisma";
|
|
5985
|
+
}
|
|
5982
5986
|
function checkRequires(recipeName, requires, detection) {
|
|
5983
5987
|
for (const required of requires) {
|
|
5984
|
-
|
|
5985
|
-
if (!satisfied) {
|
|
5988
|
+
if (!isRequireSatisfied(required, detection)) {
|
|
5986
5989
|
throw new CompatibilityError(
|
|
5987
5990
|
`"${recipeName}" requires "${required}" to be installed first. Run \`cnippet-stack init ${required}\` first, then re-run this command.`
|
|
5988
5991
|
);
|
|
@@ -6222,6 +6225,7 @@ function toTemplateContext(detection) {
|
|
|
6222
6225
|
import fs9 from "fs";
|
|
6223
6226
|
import { spawnSync } from "child_process";
|
|
6224
6227
|
import path9 from "path";
|
|
6228
|
+
import { parseDocument, Document } from "yaml";
|
|
6225
6229
|
function parseDependencySpec(spec) {
|
|
6226
6230
|
const atIndex = spec.lastIndexOf("@");
|
|
6227
6231
|
if (atIndex <= 0) return { name: spec, range: "latest" };
|
|
@@ -6263,6 +6267,68 @@ function mergePackageJsonDeps(cwd, dependencies, devDependencies) {
|
|
|
6263
6267
|
}
|
|
6264
6268
|
return changed;
|
|
6265
6269
|
}
|
|
6270
|
+
function findPnpmWorkspaceFile(cwd) {
|
|
6271
|
+
let dir = path9.resolve(cwd);
|
|
6272
|
+
for (; ; ) {
|
|
6273
|
+
const candidate = path9.join(dir, "pnpm-workspace.yaml");
|
|
6274
|
+
if (fs9.existsSync(candidate)) return candidate;
|
|
6275
|
+
const parent = path9.dirname(dir);
|
|
6276
|
+
if (parent === dir) return path9.join(path9.resolve(cwd), "pnpm-workspace.yaml");
|
|
6277
|
+
dir = parent;
|
|
6278
|
+
}
|
|
6279
|
+
}
|
|
6280
|
+
function mergeTrustedBuildScripts(cwd, packageManager, trusted) {
|
|
6281
|
+
if (trusted.length === 0) return false;
|
|
6282
|
+
if (packageManager === "pnpm") {
|
|
6283
|
+
const file = findPnpmWorkspaceFile(cwd);
|
|
6284
|
+
const doc = fs9.existsSync(file) ? parseDocument(fs9.readFileSync(file, "utf8")) : new Document({});
|
|
6285
|
+
const contents = doc.toJS() ?? {};
|
|
6286
|
+
const list = new Set(contents.onlyBuiltDependencies ?? []);
|
|
6287
|
+
const allow = { ...contents.allowBuilds };
|
|
6288
|
+
let changed = !fs9.existsSync(file);
|
|
6289
|
+
for (const name of trusted) {
|
|
6290
|
+
if (!list.has(name)) {
|
|
6291
|
+
list.add(name);
|
|
6292
|
+
changed = true;
|
|
6293
|
+
}
|
|
6294
|
+
if (typeof allow[name] !== "boolean") {
|
|
6295
|
+
allow[name] = true;
|
|
6296
|
+
changed = true;
|
|
6297
|
+
}
|
|
6298
|
+
}
|
|
6299
|
+
if (!changed) return false;
|
|
6300
|
+
doc.set("onlyBuiltDependencies", [...list]);
|
|
6301
|
+
doc.set("allowBuilds", allow);
|
|
6302
|
+
fs9.writeFileSync(file, doc.toString(), "utf8");
|
|
6303
|
+
return true;
|
|
6304
|
+
}
|
|
6305
|
+
if (packageManager === "bun") {
|
|
6306
|
+
const file = path9.join(cwd, "package.json");
|
|
6307
|
+
const pkg = JSON.parse(fs9.readFileSync(file, "utf8"));
|
|
6308
|
+
pkg.trustedDependencies ??= [];
|
|
6309
|
+
let changed = false;
|
|
6310
|
+
for (const name of trusted) {
|
|
6311
|
+
if (!pkg.trustedDependencies.includes(name)) {
|
|
6312
|
+
pkg.trustedDependencies.push(name);
|
|
6313
|
+
changed = true;
|
|
6314
|
+
}
|
|
6315
|
+
}
|
|
6316
|
+
if (changed) {
|
|
6317
|
+
fs9.writeFileSync(file, `${JSON.stringify(pkg, null, 2)}
|
|
6318
|
+
`, "utf8");
|
|
6319
|
+
}
|
|
6320
|
+
return changed;
|
|
6321
|
+
}
|
|
6322
|
+
return false;
|
|
6323
|
+
}
|
|
6324
|
+
function parseIgnoredBuilds(output) {
|
|
6325
|
+
const match = /Ignored build scripts: ([^\n]+)/.exec(output);
|
|
6326
|
+
if (!match?.[1]) return [];
|
|
6327
|
+
return match[1].split(",").map((entry) => entry.trim()).filter(Boolean).map((entry) => {
|
|
6328
|
+
const atIndex = entry.lastIndexOf("@");
|
|
6329
|
+
return atIndex > 0 ? entry.slice(0, atIndex) : entry;
|
|
6330
|
+
});
|
|
6331
|
+
}
|
|
6266
6332
|
function envWithLocalBin(cwd) {
|
|
6267
6333
|
const pathKey = Object.keys(process.env).find((key) => key.toLowerCase() === "path") ?? "PATH";
|
|
6268
6334
|
const existing = process.env[pathKey] ?? "";
|
|
@@ -6273,13 +6339,14 @@ function envWithLocalBin(cwd) {
|
|
|
6273
6339
|
};
|
|
6274
6340
|
}
|
|
6275
6341
|
var defaultCommandRunner = (command, args, cwd) => {
|
|
6276
|
-
const result = spawnSync(command, args, {
|
|
6342
|
+
const result = spawnSync([command, ...args].join(" "), {
|
|
6277
6343
|
cwd,
|
|
6344
|
+
encoding: "utf8",
|
|
6278
6345
|
env: envWithLocalBin(cwd),
|
|
6279
|
-
shell: true
|
|
6280
|
-
stdio: "inherit"
|
|
6346
|
+
shell: true
|
|
6281
6347
|
});
|
|
6282
|
-
|
|
6348
|
+
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
|
|
6349
|
+
return { output, status: result.status };
|
|
6283
6350
|
};
|
|
6284
6351
|
function runInstallCommand(cwd, packageManager, runner = defaultCommandRunner) {
|
|
6285
6352
|
const { command, args } = installCommandFor(packageManager);
|
|
@@ -6547,6 +6614,7 @@ function applyCreate(cwd, file, options) {
|
|
|
6547
6614
|
// src/installer/index.ts
|
|
6548
6615
|
function applyInstall(plan, options) {
|
|
6549
6616
|
const runner = options.runner ?? defaultCommandRunner;
|
|
6617
|
+
const progress = options.progress ?? {};
|
|
6550
6618
|
const files = [];
|
|
6551
6619
|
for (const file of plan.renderedFiles) {
|
|
6552
6620
|
if (file.strategy === "create") {
|
|
@@ -6579,25 +6647,58 @@ function applyInstall(plan, options) {
|
|
|
6579
6647
|
plan.manifest.dependencies,
|
|
6580
6648
|
plan.manifest.devDependencies
|
|
6581
6649
|
);
|
|
6582
|
-
let
|
|
6650
|
+
let depsInstall = {
|
|
6651
|
+
ignoredBuilds: [],
|
|
6652
|
+
status: "not-needed"
|
|
6653
|
+
};
|
|
6583
6654
|
if (depsChanged) {
|
|
6655
|
+
mergeTrustedBuildScripts(
|
|
6656
|
+
options.cwd,
|
|
6657
|
+
options.detection.packageManager,
|
|
6658
|
+
plan.manifest.trustedBuildScripts ?? []
|
|
6659
|
+
);
|
|
6660
|
+
progress.onDepsStart?.(options.detection.packageManager);
|
|
6584
6661
|
const depsResult = runInstallCommand(
|
|
6585
6662
|
options.cwd,
|
|
6586
6663
|
options.detection.packageManager,
|
|
6587
6664
|
runner
|
|
6588
6665
|
);
|
|
6589
|
-
|
|
6666
|
+
const output = depsResult.output;
|
|
6667
|
+
const ignoredBuilds = output ? parseIgnoredBuilds(output) : [];
|
|
6668
|
+
if (depsResult.status === 0) {
|
|
6669
|
+
depsInstall = { ignoredBuilds, output, status: "ok" };
|
|
6670
|
+
} else if (ignoredBuilds.length > 0) {
|
|
6671
|
+
depsInstall = { ignoredBuilds, output, status: "ignored-builds" };
|
|
6672
|
+
} else {
|
|
6673
|
+
depsInstall = { ignoredBuilds: [], output, status: "failed" };
|
|
6674
|
+
}
|
|
6675
|
+
progress.onDepsDone?.(depsInstall);
|
|
6590
6676
|
}
|
|
6677
|
+
const depsUsable = depsInstall.status !== "failed";
|
|
6591
6678
|
const postInstall = plan.postInstall.map((step) => {
|
|
6592
|
-
if (
|
|
6593
|
-
|
|
6679
|
+
if (!depsUsable) {
|
|
6680
|
+
const skipped = { ...step, status: "skipped" };
|
|
6681
|
+
progress.onStepDone?.(skipped);
|
|
6682
|
+
return skipped;
|
|
6594
6683
|
}
|
|
6595
6684
|
const [command, ...args] = step.command.split(" ");
|
|
6596
|
-
if (!command)
|
|
6685
|
+
if (!command) {
|
|
6686
|
+
const skipped = { ...step, status: "skipped" };
|
|
6687
|
+
progress.onStepDone?.(skipped);
|
|
6688
|
+
return skipped;
|
|
6689
|
+
}
|
|
6690
|
+
progress.onStepStart?.(step);
|
|
6597
6691
|
const result = runner(command, args, options.cwd);
|
|
6598
|
-
|
|
6692
|
+
const done = {
|
|
6693
|
+
...step,
|
|
6694
|
+
output: result.output,
|
|
6695
|
+
status: result.status === 0 ? "ok" : "failed"
|
|
6696
|
+
};
|
|
6697
|
+
progress.onStepDone?.(done);
|
|
6698
|
+
return done;
|
|
6599
6699
|
});
|
|
6600
6700
|
return {
|
|
6701
|
+
depsInstall,
|
|
6601
6702
|
envVarsWritten,
|
|
6602
6703
|
files,
|
|
6603
6704
|
manualSteps: plan.manualSteps.map((s) => ({
|
|
@@ -6605,6 +6706,7 @@ function applyInstall(plan, options) {
|
|
|
6605
6706
|
description: s.description,
|
|
6606
6707
|
title: s.title
|
|
6607
6708
|
})),
|
|
6709
|
+
packageManager: options.detection.packageManager,
|
|
6608
6710
|
postInstall,
|
|
6609
6711
|
recipe: options.recipe
|
|
6610
6712
|
};
|
|
@@ -10798,6 +10900,13 @@ var RecipeManifest = external_exports.object({
|
|
|
10798
10900
|
router: external_exports.array(external_exports.enum(["app"]))
|
|
10799
10901
|
}),
|
|
10800
10902
|
tier: external_exports.enum(["free", "pro"]),
|
|
10903
|
+
// Packages whose install-time build scripts this recipe vouches for.
|
|
10904
|
+
// pnpm 10+ and bun block postinstall scripts by default; the CLI merges
|
|
10905
|
+
// these into `pnpm.onlyBuiltDependencies` / `trustedDependencies` in the
|
|
10906
|
+
// target package.json so e.g. `prisma generate` works out of the box.
|
|
10907
|
+
// Optional (not defaulted) so manifests published before this field parse
|
|
10908
|
+
// unchanged over the wire.
|
|
10909
|
+
trustedBuildScripts: external_exports.array(external_exports.string()).optional(),
|
|
10801
10910
|
title: external_exports.string(),
|
|
10802
10911
|
version: external_exports.string()
|
|
10803
10912
|
});
|
|
@@ -11496,11 +11605,189 @@ var HttpRegistrySource = class {
|
|
|
11496
11605
|
}
|
|
11497
11606
|
};
|
|
11498
11607
|
|
|
11608
|
+
// src/ui.ts
|
|
11609
|
+
import pc2 from "picocolors";
|
|
11610
|
+
var FILE_STATUS_LABEL = {
|
|
11611
|
+
appended: "appended",
|
|
11612
|
+
created: "created",
|
|
11613
|
+
injected: "injected",
|
|
11614
|
+
overwritten: "overwritten",
|
|
11615
|
+
skipped: "skipped"
|
|
11616
|
+
};
|
|
11617
|
+
var FILE_STATUS_COLOR = {
|
|
11618
|
+
appended: pc2.yellow,
|
|
11619
|
+
created: pc2.green,
|
|
11620
|
+
injected: pc2.yellow,
|
|
11621
|
+
overwritten: pc2.yellow,
|
|
11622
|
+
skipped: pc2.dim
|
|
11623
|
+
};
|
|
11624
|
+
function renderAppliedFiles(result) {
|
|
11625
|
+
const lines = [];
|
|
11626
|
+
for (const file of result.files) {
|
|
11627
|
+
const color = FILE_STATUS_COLOR[file.status];
|
|
11628
|
+
lines.push(
|
|
11629
|
+
`${color(FILE_STATUS_LABEL[file.status].padEnd(16))} ${file.target}`
|
|
11630
|
+
);
|
|
11631
|
+
}
|
|
11632
|
+
if (result.envVarsWritten.length > 0) {
|
|
11633
|
+
lines.push(
|
|
11634
|
+
`${pc2.cyan("env vars".padEnd(16))} ${result.envVarsWritten.join(", ")}`
|
|
11635
|
+
);
|
|
11636
|
+
}
|
|
11637
|
+
return lines.join("\n");
|
|
11638
|
+
}
|
|
11639
|
+
function tailOf(output, maxLines) {
|
|
11640
|
+
const lines = output.trimEnd().split(/\r?\n/);
|
|
11641
|
+
return lines.slice(-maxLines).join("\n");
|
|
11642
|
+
}
|
|
11643
|
+
function execPrefix(packageManager) {
|
|
11644
|
+
const table = {
|
|
11645
|
+
bun: "bunx",
|
|
11646
|
+
npm: "npx",
|
|
11647
|
+
pnpm: "pnpm exec",
|
|
11648
|
+
yarn: "yarn"
|
|
11649
|
+
};
|
|
11650
|
+
return table[packageManager];
|
|
11651
|
+
}
|
|
11652
|
+
function buildNextSteps(results) {
|
|
11653
|
+
const steps = [];
|
|
11654
|
+
const ignoredBuilds = [
|
|
11655
|
+
...new Set(results.flatMap((r) => r.depsInstall.ignoredBuilds))
|
|
11656
|
+
];
|
|
11657
|
+
const pm = results[0]?.packageManager ?? "npm";
|
|
11658
|
+
if (ignoredBuilds.length > 0 && pm === "pnpm") {
|
|
11659
|
+
steps.push({
|
|
11660
|
+
detail: `pnpm blocked install scripts for ${ignoredBuilds.join(", ")}. Approve the ones you trust, then re-run \`pnpm install\`.`,
|
|
11661
|
+
required: false,
|
|
11662
|
+
title: `Run ${pc2.bold("pnpm approve-builds")} to allow blocked build scripts`
|
|
11663
|
+
});
|
|
11664
|
+
}
|
|
11665
|
+
for (const result of results) {
|
|
11666
|
+
for (const step of result.postInstall) {
|
|
11667
|
+
if (step.status === "ok") continue;
|
|
11668
|
+
const command = `${execPrefix(result.packageManager)} ${step.command}`;
|
|
11669
|
+
steps.push({
|
|
11670
|
+
detail: step.status === "failed" ? `The command failed during install \u2014 fix the error above and re-run it.` : `Skipped because dependency install failed. Install dependencies, then run it.`,
|
|
11671
|
+
required: true,
|
|
11672
|
+
title: `Run ${pc2.bold(command)} \u2014 ${step.description}`
|
|
11673
|
+
});
|
|
11674
|
+
}
|
|
11675
|
+
}
|
|
11676
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11677
|
+
const manual = results.flatMap((r) => r.manualSteps).filter((s) => seen.has(s.title) ? false : (seen.add(s.title), true));
|
|
11678
|
+
for (const step of manual.filter((s) => s.blocking)) {
|
|
11679
|
+
steps.push({ detail: step.description, required: true, title: step.title });
|
|
11680
|
+
}
|
|
11681
|
+
for (const step of manual.filter((s) => !s.blocking)) {
|
|
11682
|
+
steps.push({
|
|
11683
|
+
detail: step.description,
|
|
11684
|
+
required: false,
|
|
11685
|
+
title: step.title
|
|
11686
|
+
});
|
|
11687
|
+
}
|
|
11688
|
+
return steps;
|
|
11689
|
+
}
|
|
11690
|
+
function renderNextSteps(steps) {
|
|
11691
|
+
const lines = [];
|
|
11692
|
+
steps.forEach((step, index) => {
|
|
11693
|
+
const number = `${index + 1}.`.padEnd(3);
|
|
11694
|
+
const badge = step.required ? pc2.red("required") : pc2.dim("optional");
|
|
11695
|
+
lines.push(`${pc2.bold(number)}${step.title} ${badge}`);
|
|
11696
|
+
if (step.detail) {
|
|
11697
|
+
lines.push(pc2.dim(` ${step.detail}`));
|
|
11698
|
+
}
|
|
11699
|
+
});
|
|
11700
|
+
return lines.join("\n");
|
|
11701
|
+
}
|
|
11702
|
+
|
|
11499
11703
|
// src/commands/init.ts
|
|
11704
|
+
function clackProgress() {
|
|
11705
|
+
const spin = clack2.spinner();
|
|
11706
|
+
let depsPm = "";
|
|
11707
|
+
return {
|
|
11708
|
+
onDepsStart: (packageManager) => {
|
|
11709
|
+
depsPm = packageManager;
|
|
11710
|
+
spin.start(`Installing dependencies with ${packageManager}`);
|
|
11711
|
+
},
|
|
11712
|
+
onDepsDone: (outcome) => {
|
|
11713
|
+
if (outcome.status === "ok") {
|
|
11714
|
+
spin.stop(`Dependencies installed with ${depsPm}`);
|
|
11715
|
+
return;
|
|
11716
|
+
}
|
|
11717
|
+
if (outcome.status === "ignored-builds") {
|
|
11718
|
+
spin.stop(
|
|
11719
|
+
`Dependencies installed \u2014 build scripts blocked for ${pc3.bold(
|
|
11720
|
+
outcome.ignoredBuilds.join(", ")
|
|
11721
|
+
)} (see next steps)`
|
|
11722
|
+
);
|
|
11723
|
+
return;
|
|
11724
|
+
}
|
|
11725
|
+
spin.stop(pc3.red("Dependency install failed"), 2);
|
|
11726
|
+
if (outcome.output) {
|
|
11727
|
+
clack2.log.error(pc3.dim(tailOf(outcome.output, 20)));
|
|
11728
|
+
}
|
|
11729
|
+
},
|
|
11730
|
+
onStepStart: (step) => {
|
|
11731
|
+
spin.start(step.description);
|
|
11732
|
+
},
|
|
11733
|
+
onStepDone: (step) => {
|
|
11734
|
+
if (step.status === "ok") {
|
|
11735
|
+
spin.stop(`${step.description} ${pc3.dim(`(${step.command})`)}`);
|
|
11736
|
+
} else if (step.status === "failed") {
|
|
11737
|
+
spin.stop(
|
|
11738
|
+
pc3.red(`${step.description} failed ${pc3.dim(`(${step.command})`)}`),
|
|
11739
|
+
2
|
|
11740
|
+
);
|
|
11741
|
+
} else {
|
|
11742
|
+
clack2.log.warn(
|
|
11743
|
+
`Skipped: ${step.description} ${pc3.dim(`(${step.command})`)}`
|
|
11744
|
+
);
|
|
11745
|
+
}
|
|
11746
|
+
}
|
|
11747
|
+
};
|
|
11748
|
+
}
|
|
11500
11749
|
async function runInit(options) {
|
|
11501
11750
|
const source = options.registrySource ?? new LocalRegistrySource();
|
|
11502
|
-
|
|
11751
|
+
let detection = detectProject(options.cwd);
|
|
11503
11752
|
const manifest = await source.getManifest(options.recipe);
|
|
11753
|
+
const interactive = !options.yes && !options.json && !options.dryRun;
|
|
11754
|
+
const chained = [];
|
|
11755
|
+
const missing = manifest.requires.filter(
|
|
11756
|
+
(required) => !isRequireSatisfied(required, detection)
|
|
11757
|
+
);
|
|
11758
|
+
if (missing.length > 0 && interactive) {
|
|
11759
|
+
for (const required of missing) {
|
|
11760
|
+
if (options.chain?.includes(required)) {
|
|
11761
|
+
throw new CompatibilityError(
|
|
11762
|
+
`Recipe dependency cycle detected: ${[...options.chain, options.recipe, required].join(" -> ")}.`
|
|
11763
|
+
);
|
|
11764
|
+
}
|
|
11765
|
+
}
|
|
11766
|
+
clack2.log.warn(
|
|
11767
|
+
`${pc3.bold(options.recipe)} requires ${pc3.bold(missing.join(", "))}, which isn't set up in this project yet.`
|
|
11768
|
+
);
|
|
11769
|
+
const install = await clack2.confirm({
|
|
11770
|
+
message: `Set up ${missing.join(", ")} now, then continue with ${options.recipe}?`
|
|
11771
|
+
});
|
|
11772
|
+
if (clack2.isCancel(install) || !install) {
|
|
11773
|
+
throw new CompatibilityError(
|
|
11774
|
+
`"${options.recipe}" requires "${missing.join('", "')}" to be installed first. Run \`cnippet-stack init ${missing[0]}\` first, then re-run this command.`
|
|
11775
|
+
);
|
|
11776
|
+
}
|
|
11777
|
+
for (const required of missing) {
|
|
11778
|
+
const sub = await runInit({
|
|
11779
|
+
...options,
|
|
11780
|
+
answers: void 0,
|
|
11781
|
+
chain: [...options.chain ?? [], options.recipe],
|
|
11782
|
+
recipe: required
|
|
11783
|
+
});
|
|
11784
|
+
chained.push(sub);
|
|
11785
|
+
}
|
|
11786
|
+
detection = detectProject(options.cwd);
|
|
11787
|
+
clack2.log.success(
|
|
11788
|
+
`${pc3.bold(missing.join(", "))} installed \u2014 continuing with ${pc3.bold(options.recipe)}.`
|
|
11789
|
+
);
|
|
11790
|
+
}
|
|
11504
11791
|
checkCompatibility(detection, manifest.supports);
|
|
11505
11792
|
checkRequires(options.recipe, manifest.requires, detection);
|
|
11506
11793
|
const suppliedAnswers = options.answers !== void 0 ? JSON.parse(options.answers) : void 0;
|
|
@@ -11518,15 +11805,21 @@ async function runInit(options) {
|
|
|
11518
11805
|
if (options.dryRun) {
|
|
11519
11806
|
if (!options.json) {
|
|
11520
11807
|
clack2.log.info(
|
|
11521
|
-
`Install plan for ${
|
|
11808
|
+
`Install plan for ${pc3.bold(options.recipe)} (dry run \u2014 nothing written):`
|
|
11522
11809
|
);
|
|
11523
|
-
|
|
11810
|
+
clack2.log.message(renderPreview(preview));
|
|
11524
11811
|
}
|
|
11525
|
-
return {
|
|
11812
|
+
return {
|
|
11813
|
+
applied: false,
|
|
11814
|
+
chained,
|
|
11815
|
+
preview,
|
|
11816
|
+
recipe: options.recipe,
|
|
11817
|
+
recipeVersion: plan.manifest.version
|
|
11818
|
+
};
|
|
11526
11819
|
}
|
|
11527
11820
|
if (!options.yes && !options.json) {
|
|
11528
|
-
clack2.log.
|
|
11529
|
-
|
|
11821
|
+
clack2.log.step(`Install plan for ${pc3.bold(options.recipe)}`);
|
|
11822
|
+
clack2.log.message(renderPreview(preview));
|
|
11530
11823
|
const confirmed = await clack2.confirm({ message: "Apply these changes?" });
|
|
11531
11824
|
if (clack2.isCancel(confirmed) || !confirmed) {
|
|
11532
11825
|
throw new PromptCancelledError();
|
|
@@ -11536,15 +11829,28 @@ async function runInit(options) {
|
|
|
11536
11829
|
cwd: options.cwd,
|
|
11537
11830
|
detection,
|
|
11538
11831
|
overwrite: options.overwrite ?? false,
|
|
11832
|
+
progress: options.json ? void 0 : clackProgress(),
|
|
11539
11833
|
recipe: options.recipe,
|
|
11540
11834
|
runner: options.runner,
|
|
11541
11835
|
timestamp: (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")
|
|
11542
11836
|
});
|
|
11543
|
-
|
|
11837
|
+
if (!options.json) {
|
|
11838
|
+
clack2.log.success(`${pc3.bold(options.recipe)} applied`);
|
|
11839
|
+
const fileLines = renderAppliedFiles(result);
|
|
11840
|
+
if (fileLines) clack2.log.message(fileLines);
|
|
11841
|
+
}
|
|
11842
|
+
return {
|
|
11843
|
+
applied: true,
|
|
11844
|
+
chained,
|
|
11845
|
+
preview,
|
|
11846
|
+
recipe: options.recipe,
|
|
11847
|
+
recipeVersion: plan.manifest.version,
|
|
11848
|
+
result
|
|
11849
|
+
};
|
|
11544
11850
|
}
|
|
11545
11851
|
|
|
11546
11852
|
// src/commands/list.ts
|
|
11547
|
-
import
|
|
11853
|
+
import pc4 from "picocolors";
|
|
11548
11854
|
async function runList(options = {}) {
|
|
11549
11855
|
const source = options.registrySource ?? new LocalRegistrySource();
|
|
11550
11856
|
const recipes = await source.list();
|
|
@@ -11557,15 +11863,15 @@ async function runList(options = {}) {
|
|
|
11557
11863
|
return;
|
|
11558
11864
|
}
|
|
11559
11865
|
for (const recipe of recipes) {
|
|
11560
|
-
const tier = recipe.tier === "pro" ?
|
|
11561
|
-
console.log(`${
|
|
11562
|
-
console.log(` ${
|
|
11866
|
+
const tier = recipe.tier === "pro" ? pc4.magenta("[pro]") : pc4.green("[free]");
|
|
11867
|
+
console.log(`${pc4.bold(recipe.name)} ${tier} \u2014 ${recipe.title}`);
|
|
11868
|
+
console.log(` ${pc4.dim(recipe.description)}`);
|
|
11563
11869
|
}
|
|
11564
11870
|
}
|
|
11565
11871
|
|
|
11566
11872
|
// src/commands/login.ts
|
|
11567
11873
|
import { spawn } from "child_process";
|
|
11568
|
-
import
|
|
11874
|
+
import pc5 from "picocolors";
|
|
11569
11875
|
function resolveBaseUrl(options) {
|
|
11570
11876
|
return options.registry ?? process.env.CNIPPET_REGISTRY_URL ?? DEFAULT_REGISTRY_URL;
|
|
11571
11877
|
}
|
|
@@ -11598,11 +11904,11 @@ async function runLogin(options = {}) {
|
|
|
11598
11904
|
}
|
|
11599
11905
|
const start = await startRes.json();
|
|
11600
11906
|
console.log(`
|
|
11601
|
-
Go to: ${
|
|
11602
|
-
console.log(`Confirm this code: ${
|
|
11907
|
+
Go to: ${pc5.bold(pc5.cyan(start.verificationUrl))}`);
|
|
11908
|
+
console.log(`Confirm this code: ${pc5.bold(start.code)}
|
|
11603
11909
|
`);
|
|
11604
11910
|
openBrowser(start.verificationUrl);
|
|
11605
|
-
console.log(
|
|
11911
|
+
console.log(pc5.dim("Waiting for approval..."));
|
|
11606
11912
|
const deadline = Date.now() + start.expiresIn * 1e3;
|
|
11607
11913
|
while (Date.now() < deadline) {
|
|
11608
11914
|
await sleep(2e3);
|
|
@@ -11618,7 +11924,7 @@ Go to: ${pc4.bold(pc4.cyan(start.verificationUrl))}`);
|
|
|
11618
11924
|
const poll = await pollRes.json();
|
|
11619
11925
|
if (poll.status === "approved" && poll.token) {
|
|
11620
11926
|
saveCliConfig({ baseUrl, token: poll.token });
|
|
11621
|
-
console.log(
|
|
11927
|
+
console.log(pc5.green("\u2714 Logged in."));
|
|
11622
11928
|
return;
|
|
11623
11929
|
}
|
|
11624
11930
|
}
|
|
@@ -11626,7 +11932,7 @@ Go to: ${pc4.bold(pc4.cyan(start.verificationUrl))}`);
|
|
|
11626
11932
|
}
|
|
11627
11933
|
|
|
11628
11934
|
// src/commands/logout.ts
|
|
11629
|
-
import
|
|
11935
|
+
import pc6 from "picocolors";
|
|
11630
11936
|
async function runLogout() {
|
|
11631
11937
|
const config = loadCliConfig();
|
|
11632
11938
|
if (!config) {
|
|
@@ -11641,11 +11947,11 @@ async function runLogout() {
|
|
|
11641
11947
|
} catch {
|
|
11642
11948
|
}
|
|
11643
11949
|
clearCliConfig();
|
|
11644
|
-
console.log(
|
|
11950
|
+
console.log(pc6.green("\u2714 Logged out."));
|
|
11645
11951
|
}
|
|
11646
11952
|
|
|
11647
11953
|
// src/commands/whoami.ts
|
|
11648
|
-
import
|
|
11954
|
+
import pc7 from "picocolors";
|
|
11649
11955
|
async function runWhoami() {
|
|
11650
11956
|
const config = loadCliConfig();
|
|
11651
11957
|
if (!config) {
|
|
@@ -11657,15 +11963,15 @@ async function runWhoami() {
|
|
|
11657
11963
|
});
|
|
11658
11964
|
if (!res.ok) {
|
|
11659
11965
|
console.log(
|
|
11660
|
-
|
|
11966
|
+
pc7.red("\u2716 Session is no longer valid."),
|
|
11661
11967
|
"Run `cnippet-stack login` again."
|
|
11662
11968
|
);
|
|
11663
11969
|
return;
|
|
11664
11970
|
}
|
|
11665
11971
|
const whoami = await res.json();
|
|
11666
|
-
console.log(`${
|
|
11972
|
+
console.log(`${pc7.bold(whoami.name)} <${whoami.email}>`);
|
|
11667
11973
|
console.log(
|
|
11668
|
-
whoami.hasActiveLicense ?
|
|
11974
|
+
whoami.hasActiveLicense ? pc7.green("License: active") : pc7.dim("License: none \u2014 pro recipes require a license.")
|
|
11669
11975
|
);
|
|
11670
11976
|
}
|
|
11671
11977
|
|
|
@@ -11687,46 +11993,31 @@ function sendTelemetry(registrySource, event) {
|
|
|
11687
11993
|
}).catch(() => {
|
|
11688
11994
|
});
|
|
11689
11995
|
}
|
|
11996
|
+
function collectResults(outcome) {
|
|
11997
|
+
return [
|
|
11998
|
+
...outcome.chained.flatMap(collectResults),
|
|
11999
|
+
...outcome.applied && outcome.result ? [outcome.result] : []
|
|
12000
|
+
];
|
|
12001
|
+
}
|
|
11690
12002
|
function reportOutcome(outcome, json) {
|
|
11691
12003
|
if (json) {
|
|
11692
12004
|
console.log(JSON.stringify(outcome, null, 2));
|
|
11693
12005
|
return;
|
|
11694
12006
|
}
|
|
11695
|
-
if (!outcome.applied || !outcome.result)
|
|
11696
|
-
|
|
11697
|
-
|
|
11698
|
-
for (const file of outcome.result.files) {
|
|
11699
|
-
console.log(` ${pc7.dim(file.status)} ${file.target}`);
|
|
11700
|
-
}
|
|
11701
|
-
if (outcome.result.envVarsWritten.length > 0) {
|
|
11702
|
-
console.log(
|
|
11703
|
-
` env vars written: ${outcome.result.envVarsWritten.join(", ")}`
|
|
11704
|
-
);
|
|
11705
|
-
}
|
|
11706
|
-
if (outcome.result.postInstall.length > 0) {
|
|
11707
|
-
console.log(`
|
|
11708
|
-
${pc7.bold("Post-install:")}`);
|
|
11709
|
-
for (const step of outcome.result.postInstall) {
|
|
11710
|
-
if (step.status === "ok") {
|
|
11711
|
-
console.log(` ${pc7.green("\u2713")} ${step.description} (${step.command})`);
|
|
11712
|
-
} else {
|
|
11713
|
-
const reason = step.status === "skipped" ? "skipped \u2014 dependency install failed" : "failed";
|
|
11714
|
-
console.log(
|
|
11715
|
-
` ${pc7.red("\u2716")} ${step.description} \u2014 ${reason}. Run \`${step.command}\` yourself once dependencies are installed.`
|
|
11716
|
-
);
|
|
11717
|
-
}
|
|
11718
|
-
}
|
|
12007
|
+
if (!outcome.applied || !outcome.result) {
|
|
12008
|
+
clack3.outro(pc8.dim("Dry run \u2014 nothing was written."));
|
|
12009
|
+
return;
|
|
11719
12010
|
}
|
|
11720
|
-
|
|
11721
|
-
|
|
11722
|
-
|
|
11723
|
-
|
|
11724
|
-
console.log(
|
|
11725
|
-
` ${step.blocking ? pc7.red("[required]") : pc7.dim("[optional]")} ${step.title}`
|
|
11726
|
-
);
|
|
11727
|
-
console.log(` ${step.description}`);
|
|
11728
|
-
}
|
|
12011
|
+
const steps = buildNextSteps(collectResults(outcome));
|
|
12012
|
+
if (steps.length > 0) {
|
|
12013
|
+
clack3.log.step(pc8.bold("Next steps"));
|
|
12014
|
+
clack3.log.message(renderNextSteps(steps));
|
|
11729
12015
|
}
|
|
12016
|
+
const installed = [
|
|
12017
|
+
...outcome.chained.map((sub) => sub.recipe),
|
|
12018
|
+
outcome.recipe
|
|
12019
|
+
];
|
|
12020
|
+
clack3.outro(pc8.green(`${installed.join(" + ")} installed \u2714`));
|
|
11730
12021
|
}
|
|
11731
12022
|
function createProgram() {
|
|
11732
12023
|
const program = new Command();
|
|
@@ -11753,12 +12044,17 @@ function createProgram() {
|
|
|
11753
12044
|
).action(async (recipe, opts) => {
|
|
11754
12045
|
const registrySource = resolveRegistrySource(opts);
|
|
11755
12046
|
const startedAt = Date.now();
|
|
11756
|
-
if (!opts.json
|
|
11757
|
-
|
|
11758
|
-
|
|
11759
|
-
"Anonymous usage data is sent to help improve cnippet \u2014 set CNIPPET_TELEMETRY=0 to opt out."
|
|
11760
|
-
)
|
|
12047
|
+
if (!opts.json) {
|
|
12048
|
+
clack3.intro(
|
|
12049
|
+
`${pc8.bgCyan(pc8.black(" cnippet-stack "))} ${pc8.dim(`v${cliVersion()}`)}`
|
|
11761
12050
|
);
|
|
12051
|
+
if (!opts.dryRun) {
|
|
12052
|
+
clack3.log.message(
|
|
12053
|
+
pc8.dim(
|
|
12054
|
+
"Anonymous usage data is sent to help improve cnippet \u2014 set CNIPPET_TELEMETRY=0 to opt out."
|
|
12055
|
+
)
|
|
12056
|
+
);
|
|
12057
|
+
}
|
|
11762
12058
|
}
|
|
11763
12059
|
try {
|
|
11764
12060
|
const outcome = await runInit({
|
|
@@ -11789,17 +12085,20 @@ function createProgram() {
|
|
|
11789
12085
|
});
|
|
11790
12086
|
}
|
|
11791
12087
|
if (error instanceof CompatibilityError) {
|
|
11792
|
-
console.error(
|
|
12088
|
+
if (opts.json) console.error(pc8.red(`\u2716 ${error.message}`));
|
|
12089
|
+
else clack3.cancel(error.message);
|
|
11793
12090
|
process.exitCode = 1;
|
|
11794
12091
|
return;
|
|
11795
12092
|
}
|
|
11796
12093
|
if (error instanceof PromptCancelledError) {
|
|
11797
|
-
console.log(
|
|
12094
|
+
if (opts.json) console.log(pc8.dim("Aborted \u2014 nothing was changed."));
|
|
12095
|
+
else clack3.cancel("Aborted \u2014 nothing was changed.");
|
|
11798
12096
|
process.exitCode = 1;
|
|
11799
12097
|
return;
|
|
11800
12098
|
}
|
|
11801
12099
|
if (error instanceof RegistryHttpError) {
|
|
11802
|
-
console.error(
|
|
12100
|
+
if (opts.json) console.error(pc8.red(`\u2716 ${error.message}`));
|
|
12101
|
+
else clack3.cancel(error.message);
|
|
11803
12102
|
process.exitCode = 1;
|
|
11804
12103
|
return;
|
|
11805
12104
|
}
|
|
@@ -11821,7 +12120,7 @@ function createProgram() {
|
|
|
11821
12120
|
});
|
|
11822
12121
|
} catch (error) {
|
|
11823
12122
|
if (error instanceof RegistryHttpError) {
|
|
11824
|
-
console.error(
|
|
12123
|
+
console.error(pc8.red(`\u2716 ${error.message}`));
|
|
11825
12124
|
process.exitCode = 1;
|
|
11826
12125
|
return;
|
|
11827
12126
|
}
|
|
@@ -11839,7 +12138,7 @@ function createProgram() {
|
|
|
11839
12138
|
await runLogin({ registry: opts.registry });
|
|
11840
12139
|
} catch (error) {
|
|
11841
12140
|
if (error instanceof LoginError) {
|
|
11842
|
-
console.error(
|
|
12141
|
+
console.error(pc8.red(`\u2716 ${error.message}`));
|
|
11843
12142
|
process.exitCode = 1;
|
|
11844
12143
|
return;
|
|
11845
12144
|
}
|
package/package.json
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
"@clack/prompts": "^0.10.0",
|
|
9
9
|
"commander": "^13.1.0",
|
|
10
10
|
"picocolors": "^1.1.1",
|
|
11
|
-
"ts-morph": "^25.0.1"
|
|
11
|
+
"ts-morph": "^25.0.1",
|
|
12
|
+
"yaml": "^2.9.0"
|
|
12
13
|
},
|
|
13
14
|
"description": "CLI that installs full integrations (auth, payments, email, storage) into your existing Next.js app — detection, prompts, safe file injection, env setup.",
|
|
14
15
|
"devDependencies": {
|
|
@@ -62,5 +63,5 @@
|
|
|
62
63
|
},
|
|
63
64
|
"type": "module",
|
|
64
65
|
"types": "./dist/index.d.ts",
|
|
65
|
-
"version": "0.2.
|
|
66
|
+
"version": "0.2.1"
|
|
66
67
|
}
|