cnippet-stack 0.1.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 +395 -92
- package/package.json +6 -5
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,12 +5927,20 @@ 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";
|
|
5933
|
+
|
|
5934
|
+
// src/commands/doctor.ts
|
|
5935
|
+
function runDoctor(recipe) {
|
|
5936
|
+
console.log(
|
|
5937
|
+
`\`cnippet-stack doctor ${recipe}\` isn't implemented yet \u2014 post-install verification lands in Phase 7.`
|
|
5938
|
+
);
|
|
5939
|
+
}
|
|
5932
5940
|
|
|
5933
5941
|
// src/commands/init.ts
|
|
5934
5942
|
import * as clack2 from "@clack/prompts";
|
|
5935
|
-
import
|
|
5943
|
+
import pc3 from "picocolors";
|
|
5936
5944
|
|
|
5937
5945
|
// src/compat.ts
|
|
5938
5946
|
var CompatibilityError = class extends Error {
|
|
@@ -5972,10 +5980,12 @@ function checkCompatibility(detection, supports) {
|
|
|
5972
5980
|
);
|
|
5973
5981
|
}
|
|
5974
5982
|
}
|
|
5983
|
+
function isRequireSatisfied(required, detection) {
|
|
5984
|
+
return required === "prisma" && detection.orm === "prisma";
|
|
5985
|
+
}
|
|
5975
5986
|
function checkRequires(recipeName, requires, detection) {
|
|
5976
5987
|
for (const required of requires) {
|
|
5977
|
-
|
|
5978
|
-
if (!satisfied) {
|
|
5988
|
+
if (!isRequireSatisfied(required, detection)) {
|
|
5979
5989
|
throw new CompatibilityError(
|
|
5980
5990
|
`"${recipeName}" requires "${required}" to be installed first. Run \`cnippet-stack init ${required}\` first, then re-run this command.`
|
|
5981
5991
|
);
|
|
@@ -6215,6 +6225,7 @@ function toTemplateContext(detection) {
|
|
|
6215
6225
|
import fs9 from "fs";
|
|
6216
6226
|
import { spawnSync } from "child_process";
|
|
6217
6227
|
import path9 from "path";
|
|
6228
|
+
import { parseDocument, Document } from "yaml";
|
|
6218
6229
|
function parseDependencySpec(spec) {
|
|
6219
6230
|
const atIndex = spec.lastIndexOf("@");
|
|
6220
6231
|
if (atIndex <= 0) return { name: spec, range: "latest" };
|
|
@@ -6256,6 +6267,68 @@ function mergePackageJsonDeps(cwd, dependencies, devDependencies) {
|
|
|
6256
6267
|
}
|
|
6257
6268
|
return changed;
|
|
6258
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
|
+
}
|
|
6259
6332
|
function envWithLocalBin(cwd) {
|
|
6260
6333
|
const pathKey = Object.keys(process.env).find((key) => key.toLowerCase() === "path") ?? "PATH";
|
|
6261
6334
|
const existing = process.env[pathKey] ?? "";
|
|
@@ -6266,13 +6339,14 @@ function envWithLocalBin(cwd) {
|
|
|
6266
6339
|
};
|
|
6267
6340
|
}
|
|
6268
6341
|
var defaultCommandRunner = (command, args, cwd) => {
|
|
6269
|
-
const result = spawnSync(command, args, {
|
|
6342
|
+
const result = spawnSync([command, ...args].join(" "), {
|
|
6270
6343
|
cwd,
|
|
6344
|
+
encoding: "utf8",
|
|
6271
6345
|
env: envWithLocalBin(cwd),
|
|
6272
|
-
shell: true
|
|
6273
|
-
stdio: "inherit"
|
|
6346
|
+
shell: true
|
|
6274
6347
|
});
|
|
6275
|
-
|
|
6348
|
+
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
|
|
6349
|
+
return { output, status: result.status };
|
|
6276
6350
|
};
|
|
6277
6351
|
function runInstallCommand(cwd, packageManager, runner = defaultCommandRunner) {
|
|
6278
6352
|
const { command, args } = installCommandFor(packageManager);
|
|
@@ -6540,6 +6614,7 @@ function applyCreate(cwd, file, options) {
|
|
|
6540
6614
|
// src/installer/index.ts
|
|
6541
6615
|
function applyInstall(plan, options) {
|
|
6542
6616
|
const runner = options.runner ?? defaultCommandRunner;
|
|
6617
|
+
const progress = options.progress ?? {};
|
|
6543
6618
|
const files = [];
|
|
6544
6619
|
for (const file of plan.renderedFiles) {
|
|
6545
6620
|
if (file.strategy === "create") {
|
|
@@ -6572,25 +6647,58 @@ function applyInstall(plan, options) {
|
|
|
6572
6647
|
plan.manifest.dependencies,
|
|
6573
6648
|
plan.manifest.devDependencies
|
|
6574
6649
|
);
|
|
6575
|
-
let
|
|
6650
|
+
let depsInstall = {
|
|
6651
|
+
ignoredBuilds: [],
|
|
6652
|
+
status: "not-needed"
|
|
6653
|
+
};
|
|
6576
6654
|
if (depsChanged) {
|
|
6655
|
+
mergeTrustedBuildScripts(
|
|
6656
|
+
options.cwd,
|
|
6657
|
+
options.detection.packageManager,
|
|
6658
|
+
plan.manifest.trustedBuildScripts ?? []
|
|
6659
|
+
);
|
|
6660
|
+
progress.onDepsStart?.(options.detection.packageManager);
|
|
6577
6661
|
const depsResult = runInstallCommand(
|
|
6578
6662
|
options.cwd,
|
|
6579
6663
|
options.detection.packageManager,
|
|
6580
6664
|
runner
|
|
6581
6665
|
);
|
|
6582
|
-
|
|
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);
|
|
6583
6676
|
}
|
|
6677
|
+
const depsUsable = depsInstall.status !== "failed";
|
|
6584
6678
|
const postInstall = plan.postInstall.map((step) => {
|
|
6585
|
-
if (
|
|
6586
|
-
|
|
6679
|
+
if (!depsUsable) {
|
|
6680
|
+
const skipped = { ...step, status: "skipped" };
|
|
6681
|
+
progress.onStepDone?.(skipped);
|
|
6682
|
+
return skipped;
|
|
6587
6683
|
}
|
|
6588
6684
|
const [command, ...args] = step.command.split(" ");
|
|
6589
|
-
if (!command)
|
|
6685
|
+
if (!command) {
|
|
6686
|
+
const skipped = { ...step, status: "skipped" };
|
|
6687
|
+
progress.onStepDone?.(skipped);
|
|
6688
|
+
return skipped;
|
|
6689
|
+
}
|
|
6690
|
+
progress.onStepStart?.(step);
|
|
6590
6691
|
const result = runner(command, args, options.cwd);
|
|
6591
|
-
|
|
6692
|
+
const done = {
|
|
6693
|
+
...step,
|
|
6694
|
+
output: result.output,
|
|
6695
|
+
status: result.status === 0 ? "ok" : "failed"
|
|
6696
|
+
};
|
|
6697
|
+
progress.onStepDone?.(done);
|
|
6698
|
+
return done;
|
|
6592
6699
|
});
|
|
6593
6700
|
return {
|
|
6701
|
+
depsInstall,
|
|
6594
6702
|
envVarsWritten,
|
|
6595
6703
|
files,
|
|
6596
6704
|
manualSteps: plan.manualSteps.map((s) => ({
|
|
@@ -6598,6 +6706,7 @@ function applyInstall(plan, options) {
|
|
|
6598
6706
|
description: s.description,
|
|
6599
6707
|
title: s.title
|
|
6600
6708
|
})),
|
|
6709
|
+
packageManager: options.detection.packageManager,
|
|
6601
6710
|
postInstall,
|
|
6602
6711
|
recipe: options.recipe
|
|
6603
6712
|
};
|
|
@@ -10791,6 +10900,13 @@ var RecipeManifest = external_exports.object({
|
|
|
10791
10900
|
router: external_exports.array(external_exports.enum(["app"]))
|
|
10792
10901
|
}),
|
|
10793
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(),
|
|
10794
10910
|
title: external_exports.string(),
|
|
10795
10911
|
version: external_exports.string()
|
|
10796
10912
|
});
|
|
@@ -11489,11 +11605,189 @@ var HttpRegistrySource = class {
|
|
|
11489
11605
|
}
|
|
11490
11606
|
};
|
|
11491
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
|
+
|
|
11492
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
|
+
}
|
|
11493
11749
|
async function runInit(options) {
|
|
11494
11750
|
const source = options.registrySource ?? new LocalRegistrySource();
|
|
11495
|
-
|
|
11751
|
+
let detection = detectProject(options.cwd);
|
|
11496
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
|
+
}
|
|
11497
11791
|
checkCompatibility(detection, manifest.supports);
|
|
11498
11792
|
checkRequires(options.recipe, manifest.requires, detection);
|
|
11499
11793
|
const suppliedAnswers = options.answers !== void 0 ? JSON.parse(options.answers) : void 0;
|
|
@@ -11511,15 +11805,21 @@ async function runInit(options) {
|
|
|
11511
11805
|
if (options.dryRun) {
|
|
11512
11806
|
if (!options.json) {
|
|
11513
11807
|
clack2.log.info(
|
|
11514
|
-
`Install plan for ${
|
|
11808
|
+
`Install plan for ${pc3.bold(options.recipe)} (dry run \u2014 nothing written):`
|
|
11515
11809
|
);
|
|
11516
|
-
|
|
11810
|
+
clack2.log.message(renderPreview(preview));
|
|
11517
11811
|
}
|
|
11518
|
-
return {
|
|
11812
|
+
return {
|
|
11813
|
+
applied: false,
|
|
11814
|
+
chained,
|
|
11815
|
+
preview,
|
|
11816
|
+
recipe: options.recipe,
|
|
11817
|
+
recipeVersion: plan.manifest.version
|
|
11818
|
+
};
|
|
11519
11819
|
}
|
|
11520
11820
|
if (!options.yes && !options.json) {
|
|
11521
|
-
clack2.log.
|
|
11522
|
-
|
|
11821
|
+
clack2.log.step(`Install plan for ${pc3.bold(options.recipe)}`);
|
|
11822
|
+
clack2.log.message(renderPreview(preview));
|
|
11523
11823
|
const confirmed = await clack2.confirm({ message: "Apply these changes?" });
|
|
11524
11824
|
if (clack2.isCancel(confirmed) || !confirmed) {
|
|
11525
11825
|
throw new PromptCancelledError();
|
|
@@ -11529,22 +11829,28 @@ async function runInit(options) {
|
|
|
11529
11829
|
cwd: options.cwd,
|
|
11530
11830
|
detection,
|
|
11531
11831
|
overwrite: options.overwrite ?? false,
|
|
11832
|
+
progress: options.json ? void 0 : clackProgress(),
|
|
11532
11833
|
recipe: options.recipe,
|
|
11533
11834
|
runner: options.runner,
|
|
11534
11835
|
timestamp: (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")
|
|
11535
11836
|
});
|
|
11536
|
-
|
|
11537
|
-
}
|
|
11538
|
-
|
|
11539
|
-
|
|
11540
|
-
|
|
11541
|
-
|
|
11542
|
-
|
|
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,23 +11963,24 @@ 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
|
|
|
11672
11978
|
// src/program.ts
|
|
11673
11979
|
function resolveRegistrySource(opts) {
|
|
11674
11980
|
if (opts.localRegistry) return new LocalRegistrySource();
|
|
11675
|
-
|
|
11676
|
-
|
|
11981
|
+
return new HttpRegistrySource(
|
|
11982
|
+
opts.registry ?? process.env.CNIPPET_REGISTRY_URL ?? DEFAULT_REGISTRY_URL
|
|
11983
|
+
);
|
|
11677
11984
|
}
|
|
11678
11985
|
function sendTelemetry(registrySource, event) {
|
|
11679
11986
|
if (process.env.CNIPPET_TELEMETRY === "0") return;
|
|
@@ -11686,46 +11993,31 @@ function sendTelemetry(registrySource, event) {
|
|
|
11686
11993
|
}).catch(() => {
|
|
11687
11994
|
});
|
|
11688
11995
|
}
|
|
11996
|
+
function collectResults(outcome) {
|
|
11997
|
+
return [
|
|
11998
|
+
...outcome.chained.flatMap(collectResults),
|
|
11999
|
+
...outcome.applied && outcome.result ? [outcome.result] : []
|
|
12000
|
+
];
|
|
12001
|
+
}
|
|
11689
12002
|
function reportOutcome(outcome, json) {
|
|
11690
12003
|
if (json) {
|
|
11691
12004
|
console.log(JSON.stringify(outcome, null, 2));
|
|
11692
12005
|
return;
|
|
11693
12006
|
}
|
|
11694
|
-
if (!outcome.applied || !outcome.result)
|
|
11695
|
-
|
|
11696
|
-
|
|
11697
|
-
for (const file of outcome.result.files) {
|
|
11698
|
-
console.log(` ${pc7.dim(file.status)} ${file.target}`);
|
|
11699
|
-
}
|
|
11700
|
-
if (outcome.result.envVarsWritten.length > 0) {
|
|
11701
|
-
console.log(
|
|
11702
|
-
` env vars written: ${outcome.result.envVarsWritten.join(", ")}`
|
|
11703
|
-
);
|
|
11704
|
-
}
|
|
11705
|
-
if (outcome.result.postInstall.length > 0) {
|
|
11706
|
-
console.log(`
|
|
11707
|
-
${pc7.bold("Post-install:")}`);
|
|
11708
|
-
for (const step of outcome.result.postInstall) {
|
|
11709
|
-
if (step.status === "ok") {
|
|
11710
|
-
console.log(` ${pc7.green("\u2713")} ${step.description} (${step.command})`);
|
|
11711
|
-
} else {
|
|
11712
|
-
const reason = step.status === "skipped" ? "skipped \u2014 dependency install failed" : "failed";
|
|
11713
|
-
console.log(
|
|
11714
|
-
` ${pc7.red("\u2716")} ${step.description} \u2014 ${reason}. Run \`${step.command}\` yourself once dependencies are installed.`
|
|
11715
|
-
);
|
|
11716
|
-
}
|
|
11717
|
-
}
|
|
12007
|
+
if (!outcome.applied || !outcome.result) {
|
|
12008
|
+
clack3.outro(pc8.dim("Dry run \u2014 nothing was written."));
|
|
12009
|
+
return;
|
|
11718
12010
|
}
|
|
11719
|
-
|
|
11720
|
-
|
|
11721
|
-
|
|
11722
|
-
|
|
11723
|
-
console.log(
|
|
11724
|
-
` ${step.blocking ? pc7.red("[required]") : pc7.dim("[optional]")} ${step.title}`
|
|
11725
|
-
);
|
|
11726
|
-
console.log(` ${step.description}`);
|
|
11727
|
-
}
|
|
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));
|
|
11728
12015
|
}
|
|
12016
|
+
const installed = [
|
|
12017
|
+
...outcome.chained.map((sub) => sub.recipe),
|
|
12018
|
+
outcome.recipe
|
|
12019
|
+
];
|
|
12020
|
+
clack3.outro(pc8.green(`${installed.join(" + ")} installed \u2714`));
|
|
11729
12021
|
}
|
|
11730
12022
|
function createProgram() {
|
|
11731
12023
|
const program = new Command();
|
|
@@ -11752,12 +12044,17 @@ function createProgram() {
|
|
|
11752
12044
|
).action(async (recipe, opts) => {
|
|
11753
12045
|
const registrySource = resolveRegistrySource(opts);
|
|
11754
12046
|
const startedAt = Date.now();
|
|
11755
|
-
if (!opts.json
|
|
11756
|
-
|
|
11757
|
-
|
|
11758
|
-
"Anonymous usage data is sent to help improve cnippet \u2014 set CNIPPET_TELEMETRY=0 to opt out."
|
|
11759
|
-
)
|
|
12047
|
+
if (!opts.json) {
|
|
12048
|
+
clack3.intro(
|
|
12049
|
+
`${pc8.bgCyan(pc8.black(" cnippet-stack "))} ${pc8.dim(`v${cliVersion()}`)}`
|
|
11760
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
|
+
}
|
|
11761
12058
|
}
|
|
11762
12059
|
try {
|
|
11763
12060
|
const outcome = await runInit({
|
|
@@ -11788,17 +12085,20 @@ function createProgram() {
|
|
|
11788
12085
|
});
|
|
11789
12086
|
}
|
|
11790
12087
|
if (error instanceof CompatibilityError) {
|
|
11791
|
-
console.error(
|
|
12088
|
+
if (opts.json) console.error(pc8.red(`\u2716 ${error.message}`));
|
|
12089
|
+
else clack3.cancel(error.message);
|
|
11792
12090
|
process.exitCode = 1;
|
|
11793
12091
|
return;
|
|
11794
12092
|
}
|
|
11795
12093
|
if (error instanceof PromptCancelledError) {
|
|
11796
|
-
console.log(
|
|
12094
|
+
if (opts.json) console.log(pc8.dim("Aborted \u2014 nothing was changed."));
|
|
12095
|
+
else clack3.cancel("Aborted \u2014 nothing was changed.");
|
|
11797
12096
|
process.exitCode = 1;
|
|
11798
12097
|
return;
|
|
11799
12098
|
}
|
|
11800
12099
|
if (error instanceof RegistryHttpError) {
|
|
11801
|
-
console.error(
|
|
12100
|
+
if (opts.json) console.error(pc8.red(`\u2716 ${error.message}`));
|
|
12101
|
+
else clack3.cancel(error.message);
|
|
11802
12102
|
process.exitCode = 1;
|
|
11803
12103
|
return;
|
|
11804
12104
|
}
|
|
@@ -11814,10 +12114,13 @@ function createProgram() {
|
|
|
11814
12114
|
false
|
|
11815
12115
|
).action(async (opts) => {
|
|
11816
12116
|
try {
|
|
11817
|
-
await runList({
|
|
12117
|
+
await runList({
|
|
12118
|
+
json: opts.json,
|
|
12119
|
+
registrySource: resolveRegistrySource(opts)
|
|
12120
|
+
});
|
|
11818
12121
|
} catch (error) {
|
|
11819
12122
|
if (error instanceof RegistryHttpError) {
|
|
11820
|
-
console.error(
|
|
12123
|
+
console.error(pc8.red(`\u2716 ${error.message}`));
|
|
11821
12124
|
process.exitCode = 1;
|
|
11822
12125
|
return;
|
|
11823
12126
|
}
|
|
@@ -11835,7 +12138,7 @@ function createProgram() {
|
|
|
11835
12138
|
await runLogin({ registry: opts.registry });
|
|
11836
12139
|
} catch (error) {
|
|
11837
12140
|
if (error instanceof LoginError) {
|
|
11838
|
-
console.error(
|
|
12141
|
+
console.error(pc8.red(`\u2716 ${error.message}`));
|
|
11839
12142
|
process.exitCode = 1;
|
|
11840
12143
|
return;
|
|
11841
12144
|
}
|
package/package.json
CHANGED
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
"cnippet-stack": "./dist/index.js"
|
|
5
5
|
},
|
|
6
6
|
"bugs": "https://github.com/cnippet-dev/cnippet-stack/issues",
|
|
7
|
-
"description": "CLI that installs full integrations (auth, payments, email, storage) into your existing Next.js app — detection, prompts, safe file injection, env setup.",
|
|
8
7
|
"dependencies": {
|
|
9
8
|
"@clack/prompts": "^0.10.0",
|
|
10
9
|
"commander": "^13.1.0",
|
|
11
10
|
"picocolors": "^1.1.1",
|
|
12
|
-
"ts-morph": "^25.0.1"
|
|
11
|
+
"ts-morph": "^25.0.1",
|
|
12
|
+
"yaml": "^2.9.0"
|
|
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": {
|
|
15
16
|
"@cnippet/registry": "workspace:*",
|
|
16
17
|
"@cnippet/validators": "workspace:*",
|
|
@@ -49,9 +50,9 @@
|
|
|
49
50
|
"access": "public"
|
|
50
51
|
},
|
|
51
52
|
"repository": {
|
|
53
|
+
"directory": "packages/cli",
|
|
52
54
|
"type": "git",
|
|
53
|
-
"url": "git+https://github.com/cnippet-dev/cnippet-stack.git"
|
|
54
|
-
"directory": "packages/cli"
|
|
55
|
+
"url": "git+https://github.com/cnippet-dev/cnippet-stack.git"
|
|
55
56
|
},
|
|
56
57
|
"scripts": {
|
|
57
58
|
"build": "tsup",
|
|
@@ -62,5 +63,5 @@
|
|
|
62
63
|
},
|
|
63
64
|
"type": "module",
|
|
64
65
|
"types": "./dist/index.d.ts",
|
|
65
|
-
"version": "0.1
|
|
66
|
+
"version": "0.2.1"
|
|
66
67
|
}
|