create-payload-stack 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +2 -2
  2. package/dist/index.js +197 -57
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -6,11 +6,11 @@ Scaffold a SaaS on Payload CMS in one command.
6
6
  npx create-payload-stack@latest
7
7
  ```
8
8
 
9
- Nine prompts: project name, database (PostgreSQL, MongoDB, SQLite, Vercel Postgres), connection string, sign-in methods, organizations, billing, media storage (skip for local disk, or Vercel Blob, AWS S3, Cloudflare R2, Azure Blob Storage, Google Cloud Storage, Uploadthing), transactional emails (edit copy in the Payload admin, or keep React Email components in code) and cookie consent (banner with audited legal pages, or plain legal pages). The CLI downloads [Payload Stack](https://www.payloadstack.com), writes your answers into `src/stack.config.ts`, swaps the Payload database adapter, wires the storage adapter into `payload.config.ts`, generates `.env` with fresh secrets, installs dependencies and initializes git.
9
+ Twelve prompts: project name, database (PostgreSQL, MongoDB, SQLite, Vercel Postgres), connection string, sign-in methods, organizations, billing, media storage (skip for local disk, or Vercel Blob, AWS S3, Cloudflare R2, Azure Blob Storage, Google Cloud Storage, Uploadthing), transactional emails (edit copy in the Payload admin, or keep React Email components in code) cookie consent (banner with audited legal pages, or plain legal pages), scheduled actions and what runs them, and the coding agents you use (Claude Code, Codex, Cursor: task skills and MCP servers on top of `AGENTS.md`). The CLI downloads [Payload Stack](https://www.payloadstack.com), writes your answers into `src/stack.config.ts`, swaps the Payload database adapter, wires the storage adapter into `payload.config.ts`, generates `.env` with fresh secrets, installs dependencies and initializes git.
10
10
 
11
11
  ```bash
12
12
  # non-interactive
13
- npx create-payload-stack@latest ridgeline -d postgres --auth email-password,magic-link,passkey --social github --billing organization --storage vercel-blob --emails --consent -y
13
+ npx create-payload-stack@latest ridgeline -d postgres --auth email-password,magic-link,passkey --social github --billing organization --storage vercel-blob --emails --consent --agents claude,cursor -y
14
14
  ```
15
15
 
16
16
  Run `npx create-payload-stack@latest --help` for every flag. Documentation: https://payload.solutions/docs/payload-stack
package/dist/index.js CHANGED
@@ -8,6 +8,74 @@ import pc3 from "picocolors";
8
8
  import { parseArgs } from "util";
9
9
  import pc from "picocolors";
10
10
 
11
+ // src/agents.ts
12
+ import { cp, mkdir, rm, writeFile } from "fs/promises";
13
+ import { existsSync } from "fs";
14
+ import path from "path";
15
+ var AGENT_KEYS = ["claude", "codex", "cursor"];
16
+ var AGENT_CHOICES = {
17
+ claude: { label: "Claude Code", hint: "CLAUDE.md, skills, MCP servers in .mcp.json" },
18
+ codex: { label: "Codex", hint: "skills in .agents/skills" },
19
+ cursor: { label: "Cursor", hint: "skills, MCP servers in .cursor/mcp.json" }
20
+ };
21
+ var CLAUDE_SKILLS_DIR = ".claude/skills";
22
+ var AGENTS_SKILLS_DIR = ".agents/skills";
23
+ function mcpServers({ consent }) {
24
+ return {
25
+ "next-devtools": { command: "npx", args: ["-y", "next-devtools-mcp@latest"] },
26
+ playwright: { command: "npx", args: ["-y", "@playwright/mcp@latest"] },
27
+ shadcn: { command: "npx", args: ["-y", "shadcn@latest", "mcp"] },
28
+ ...consent ? { "payload-consent": { command: "npx", args: ["payload-consent", "mcp"] } } : {}
29
+ };
30
+ }
31
+ function renderMcpJson(servers) {
32
+ return `${JSON.stringify({ mcpServers: servers }, null, 2)}
33
+ `;
34
+ }
35
+ function parseAgents(raw) {
36
+ if (raw === void 0) return {};
37
+ const items = raw.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
38
+ if (items.length === 1 && items[0] === "none") return { agents: [] };
39
+ for (const item of items) {
40
+ if (!AGENT_KEYS.includes(item)) {
41
+ return { error: `Unknown value "${item}" for --agents. Allowed: ${AGENT_KEYS.join(", ")}, none` };
42
+ }
43
+ }
44
+ return { agents: AGENT_KEYS.filter((key) => items.includes(key)) };
45
+ }
46
+ function agentFiles(agents) {
47
+ const has = (key) => agents.includes(key);
48
+ return [
49
+ "AGENTS.md",
50
+ ...has("claude") ? ["CLAUDE.md", CLAUDE_SKILLS_DIR, ".claude/settings.json", ".mcp.json"] : [],
51
+ ...has("codex") || has("cursor") && !has("claude") ? [AGENTS_SKILLS_DIR] : [],
52
+ ...has("cursor") ? [".cursor/mcp.json"] : []
53
+ ];
54
+ }
55
+ async function applyAgentsChoice(directory, agents, { consent }) {
56
+ const has = (key) => agents.includes(key);
57
+ const at = (rel) => path.join(directory, rel);
58
+ const servers = renderMcpJson(mcpServers({ consent }));
59
+ if (agentFiles(agents).includes(AGENTS_SKILLS_DIR)) {
60
+ if (!existsSync(at(CLAUDE_SKILLS_DIR))) {
61
+ throw new Error(`The template is missing ${CLAUDE_SKILLS_DIR}; it cannot scaffold agent skills.`);
62
+ }
63
+ await mkdir(path.dirname(at(AGENTS_SKILLS_DIR)), { recursive: true });
64
+ await cp(at(CLAUDE_SKILLS_DIR), at(AGENTS_SKILLS_DIR), { recursive: true });
65
+ }
66
+ if (has("claude")) {
67
+ await writeFile(at(".mcp.json"), servers);
68
+ } else {
69
+ for (const rel of ["CLAUDE.md", ".claude", ".mcp.json"]) {
70
+ await rm(at(rel), { force: true, recursive: true });
71
+ }
72
+ }
73
+ if (has("cursor")) {
74
+ await mkdir(at(".cursor"), { recursive: true });
75
+ await writeFile(at(".cursor/mcp.json"), servers);
76
+ }
77
+ }
78
+
11
79
  // src/databases.ts
12
80
  var DB_CHOICES = {
13
81
  postgres: {
@@ -69,24 +137,24 @@ function defaultConnectionString(db, projectSlug) {
69
137
  }
70
138
 
71
139
  // src/scheduler.ts
72
- import { cp } from "fs/promises";
73
- import { existsSync } from "fs";
74
- import path2 from "path";
140
+ import { cp as cp2 } from "fs/promises";
141
+ import { existsSync as existsSync2 } from "fs";
142
+ import path3 from "path";
75
143
 
76
144
  // src/variants.ts
77
- import { rm } from "fs/promises";
78
- import path from "path";
145
+ import { rm as rm2 } from "fs/promises";
146
+ import path2 from "path";
79
147
  var VARIANT_DIR = "variants";
80
148
  function variantPath(step) {
81
- return path.join(VARIANT_DIR, step);
149
+ return path2.join(VARIANT_DIR, step);
82
150
  }
83
151
  async function removeVariants(directory) {
84
- await rm(path.join(directory, VARIANT_DIR), { force: true, recursive: true });
152
+ await rm2(path2.join(directory, VARIANT_DIR), { force: true, recursive: true });
85
153
  }
86
154
 
87
155
  // src/scheduler.ts
88
156
  var SCHEDULER_PACKAGE = "@payload-solutions/plugin-action-scheduler";
89
- var SCHEDULER_VERSION = "0.2.0";
157
+ var SCHEDULER_VERSION = "0.3.0";
90
158
  var VARIANT_SCHEDULER = variantPath("scheduler-plugin");
91
159
  var SEAM_FILES = ["plugin.ts", "jobs.ts", "hooks.ts"];
92
160
  var ACTION_DIR = "actions";
@@ -160,15 +228,15 @@ function sortKeys(obj) {
160
228
  }
161
229
  async function applySchedulerChoice(directory, enabled) {
162
230
  if (!enabled) return;
163
- const variant = path2.join(directory, VARIANT_SCHEDULER);
164
- if (!existsSync(variant)) {
231
+ const variant = path3.join(directory, VARIANT_SCHEDULER);
232
+ if (!existsSync2(variant)) {
165
233
  throw new Error(`The template is missing ${VARIANT_SCHEDULER}; it cannot scaffold with the scheduler.`);
166
234
  }
167
235
  for (const name of SEAM_FILES) {
168
- await cp(path2.join(variant, name), path2.join(directory, "src/scheduler", name));
236
+ await cp2(path3.join(variant, name), path3.join(directory, "src/scheduler", name));
169
237
  }
170
- await cp(path2.join(variant, ACTION_DIR), path2.join(directory, "src/scheduler", ACTION_DIR), { recursive: true });
171
- await cp(path2.join(variant, "tests/scheduler.spec.ts"), path2.join(directory, "tests/unit/scheduler.spec.ts"));
238
+ await cp2(path3.join(variant, ACTION_DIR), path3.join(directory, "src/scheduler", ACTION_DIR), { recursive: true });
239
+ await cp2(path3.join(variant, "tests/scheduler.spec.ts"), path3.join(directory, "tests/unit/scheduler.spec.ts"));
172
240
  }
173
241
  function renderVercelJson() {
174
242
  return `${JSON.stringify(
@@ -370,6 +438,7 @@ function helpText(version2) {
370
438
  --scheduler Scheduled and recurring actions (Payload Action Scheduler)
371
439
  --no-scheduler No scheduler
372
440
  --runner <kind> ${RUNNER_KEYS.join(" | ")} (what runs the queue; default: clock)
441
+ --agents <list> Comma-separated: ${AGENT_KEYS.join(", ")}, or none (skills and MCP servers)
373
442
  --use-pnpm | --use-npm | --use-yarn | --use-bun
374
443
  --no-install Skip dependency installation
375
444
  --no-git Skip git init
@@ -384,6 +453,7 @@ function helpText(version2) {
384
453
  $ npx create-payload-stack@latest ridgeline
385
454
  $ npx create-payload-stack@latest ridgeline -d sqlite --auth email-password,passkey --billing none -y
386
455
  $ npx create-payload-stack@latest ridgeline --storage vercel-blob -y
456
+ $ npx create-payload-stack@latest ridgeline --agents claude,cursor -y
387
457
 
388
458
  Docs: https://payload.solutions/docs/payload-stack
389
459
  `;
@@ -406,6 +476,7 @@ function parse(argv) {
406
476
  consent: { type: "boolean" },
407
477
  scheduler: { type: "boolean" },
408
478
  runner: { type: "string" },
479
+ agents: { type: "string" },
409
480
  "use-pnpm": { type: "boolean" },
410
481
  "use-npm": { type: "boolean" },
411
482
  "use-yarn": { type: "boolean" },
@@ -435,6 +506,7 @@ function parse(argv) {
435
506
  consent: values.consent,
436
507
  scheduler: values.scheduler,
437
508
  runner: values.runner,
509
+ agents: values.agents,
438
510
  packageManager,
439
511
  install: values.install ?? true,
440
512
  git: values.git ?? true,
@@ -452,21 +524,21 @@ function parse(argv) {
452
524
  // src/cli.ts
453
525
  import * as p from "@clack/prompts";
454
526
  import { execa as execa2 } from "execa";
455
- import { existsSync as existsSync6 } from "fs";
456
- import path7 from "path";
527
+ import { existsSync as existsSync7 } from "fs";
528
+ import path8 from "path";
457
529
  import pc2 from "picocolors";
458
530
 
459
531
  // src/configure.ts
460
- import { readFile, writeFile } from "fs/promises";
461
- import path5 from "path";
532
+ import { readFile, writeFile as writeFile2 } from "fs/promises";
533
+ import path6 from "path";
462
534
 
463
535
  // src/consent.ts
464
- import { cp as cp2, rm as rm2, rmdir } from "fs/promises";
465
- import { existsSync as existsSync2 } from "fs";
466
- import path3 from "path";
536
+ import { cp as cp3, rm as rm3, rmdir } from "fs/promises";
537
+ import { existsSync as existsSync3 } from "fs";
538
+ import path4 from "path";
467
539
  var CONSENT_PACKAGE = "@payload-solutions/plugin-consent";
468
540
  var CONSENT_REACT_PACKAGE = "@payload-solutions/consent-react";
469
- var CONSENT_VERSION = "0.1.0";
541
+ var CONSENT_VERSION = "0.1.1";
470
542
  var CONSENT_PACKAGES = {
471
543
  [CONSENT_PACKAGE]: CONSENT_VERSION,
472
544
  [CONSENT_REACT_PACKAGE]: CONSENT_VERSION
@@ -498,27 +570,27 @@ function sortKeys2(obj) {
498
570
  }
499
571
  async function applyConsentChoice(directory, enabled) {
500
572
  if (!enabled) return;
501
- const variant = path3.join(directory, VARIANT_CONSENT);
502
- if (!existsSync2(variant)) {
573
+ const variant = path4.join(directory, VARIANT_CONSENT);
574
+ if (!existsSync3(variant)) {
503
575
  throw new Error(`The template is missing ${VARIANT_CONSENT}; it cannot scaffold with consent.`);
504
576
  }
505
577
  for (const name of [...SEAM_FILES2, ...BANNER_FILES]) {
506
- await cp2(path3.join(variant, name), path3.join(directory, "src/consent", name), { recursive: true });
578
+ await cp3(path4.join(variant, name), path4.join(directory, "src/consent", name), { recursive: true });
507
579
  }
508
- await cp2(path3.join(variant, "tests/consent.spec.ts"), path3.join(directory, "tests/unit/consent.spec.ts"));
580
+ await cp3(path4.join(variant, "tests/consent.spec.ts"), path4.join(directory, "tests/unit/consent.spec.ts"));
509
581
  for (const name of TEMPLATE_LEGAL_FILES) {
510
- await rm2(path3.join(directory, name), { force: true, recursive: true });
582
+ await rm3(path4.join(directory, name), { force: true, recursive: true });
511
583
  }
512
- await rmdir(path3.join(directory, "src/seed")).catch(() => {
584
+ await rmdir(path4.join(directory, "src/seed")).catch(() => {
513
585
  });
514
586
  }
515
587
 
516
588
  // src/emails.ts
517
- import { cp as cp3, rm as rm3 } from "fs/promises";
518
- import { existsSync as existsSync3 } from "fs";
519
- import path4 from "path";
589
+ import { cp as cp4, rm as rm4 } from "fs/promises";
590
+ import { existsSync as existsSync4 } from "fs";
591
+ import path5 from "path";
520
592
  var EMAILS_PACKAGE = "@payload-solutions/plugin-emails";
521
- var EMAILS_VERSION = "0.1.0";
593
+ var EMAILS_VERSION = "0.1.1";
522
594
  var REACT_EMAIL_COMPONENTS = ["@react-email/components", "^1.0.12"];
523
595
  var REACT_EMAIL_ONLY_PACKAGES = {
524
596
  "@react-email/render": "^2.1.0",
@@ -588,17 +660,17 @@ function sortKeys3(obj) {
588
660
  }
589
661
  var REACT_EMAIL_FILES = ["src/emails/send.ts", "src/components/auth/email"];
590
662
  async function applyEmailsChoice(directory, enabled) {
591
- const variant = path4.join(directory, VARIANT_EMAILS);
663
+ const variant = path5.join(directory, VARIANT_EMAILS);
592
664
  if (enabled) {
593
- if (!existsSync3(variant)) {
665
+ if (!existsSync4(variant)) {
594
666
  throw new Error(`The template is missing ${VARIANT_EMAILS}; it cannot scaffold with emails.`);
595
667
  }
596
668
  for (const name of ["index.ts", "hooks.ts", "template.tsx", "variables.ts", "definitions"]) {
597
- await cp3(path4.join(variant, name), path4.join(directory, "src/emails", name), { recursive: true });
669
+ await cp4(path5.join(variant, name), path5.join(directory, "src/emails", name), { recursive: true });
598
670
  }
599
- await cp3(path4.join(variant, "tests/emails.spec.ts"), path4.join(directory, "tests/unit/emails.spec.ts"));
671
+ await cp4(path5.join(variant, "tests/emails.spec.ts"), path5.join(directory, "tests/unit/emails.spec.ts"));
600
672
  for (const name of REACT_EMAIL_FILES) {
601
- await rm3(path4.join(directory, name), { force: true, recursive: true });
673
+ await rm4(path5.join(directory, name), { force: true, recursive: true });
602
674
  }
603
675
  }
604
676
  }
@@ -606,7 +678,7 @@ async function applyEmailsChoice(directory, enabled) {
606
678
  // src/utils.ts
607
679
  import slugifyBase from "@sindresorhus/slugify";
608
680
  import { randomBytes } from "crypto";
609
- import { existsSync as existsSync4, readdirSync } from "fs";
681
+ import { existsSync as existsSync5, readdirSync } from "fs";
610
682
  import { execa } from "execa";
611
683
  function slugify(input) {
612
684
  return slugifyBase(input.trim()).replace(/^-+|-+$/g, "") || "my-saas";
@@ -615,7 +687,7 @@ function generateSecret(bytes = 32) {
615
687
  return randomBytes(bytes).toString("base64url");
616
688
  }
617
689
  function isDirectoryEmpty(dir) {
618
- if (!existsSync4(dir)) return true;
690
+ if (!existsSync5(dir)) return true;
619
691
  const entries = readdirSync(dir).filter((e) => ![".git", ".DS_Store"].includes(e));
620
692
  return entries.length === 0;
621
693
  }
@@ -650,6 +722,8 @@ function runCommand(pm, script) {
650
722
  // src/configure.ts
651
723
  function renderStackConfig(o) {
652
724
  const list = (items) => `[${items.map((i) => `'${i}'`).join(", ")}]`;
725
+ const parts = ["Authentication", ...o.organizations ? ["organizations"] : [], ...o.billing === "none" ? [] : ["subscriptions"], "an admin panel"];
726
+ const description = `${parts.slice(0, -1).join(", ")} and ${parts.at(-1)}, wired together on Payload CMS and Next.js.`;
653
727
  const billingBlock = o.billing === "none" ? ` billing: { provider: 'none' },` : ` billing: {
654
728
  provider: 'stripe',
655
729
  attachedTo: '${o.billing}', // or '${o.billing === "organization" ? "user" : "organization"}'
@@ -708,7 +782,7 @@ export default defineStack({
708
782
  name: '${o.name.replace(/'/g, "\\'")}',
709
783
  tagline: 'A new SaaS, built on Payload CMS.',
710
784
  description:
711
- 'Authentication, organizations, subscriptions and an admin panel, wired together on Payload CMS and Next.js.',
785
+ '${description}',
712
786
  url: process.env.NEXT_PUBLIC_APP_URL ?? 'http://localhost:3000',
713
787
  support: { email: 'support@example.com' },
714
788
 
@@ -856,13 +930,31 @@ function renderEnv(example, o) {
856
930
  function sortKeys4(obj) {
857
931
  return Object.fromEntries(Object.entries(obj).sort(([a], [b]) => a.localeCompare(b)));
858
932
  }
933
+ function renderTestEnv(source, db, projectSlug) {
934
+ const urls = {
935
+ sqlite: {
936
+ int: "file:./test.db",
937
+ e2e: "file:./e2e.db",
938
+ note: "# SQLite: two local files, created on first run. Delete them to start the suites from empty."
939
+ },
940
+ mongodb: {
941
+ int: `mongodb://127.0.0.1/${projectSlug}-test`,
942
+ e2e: `mongodb://127.0.0.1/${projectSlug}-e2e`,
943
+ note: "# MongoDB: two databases on a local server (mongodb://127.0.0.1:27017). docker-compose.yml does not start one."
944
+ }
945
+ };
946
+ const target = urls[db.key];
947
+ if (!target) return source;
948
+ return source.replace(/^DATABASE_URL=.*$/m, `${target.note}
949
+ DATABASE_URL=${target.int}`).replace(/^E2E_DATABASE_URL=.*$/m, `E2E_DATABASE_URL=${target.e2e}`);
950
+ }
859
951
  function escapeRegExp(s) {
860
952
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
861
953
  }
862
954
  async function configureProject(o) {
863
955
  const db = DB_CHOICES[o.db];
864
- const read = (rel) => readFile(path5.join(o.directory, rel), "utf8");
865
- const write = (rel, content) => writeFile(path5.join(o.directory, rel), content);
956
+ const read = (rel) => readFile(path6.join(o.directory, rel), "utf8");
957
+ const write = (rel, content) => writeFile2(path6.join(o.directory, rel), content);
866
958
  await write("src/stack.config.ts", renderStackConfig(o));
867
959
  const storage = storageChoice(o.storage);
868
960
  const payloadConfig = await read("src/payload.config.ts");
@@ -872,6 +964,7 @@ async function configureProject(o) {
872
964
  await applyConsentChoice(o.directory, o.consent);
873
965
  await applySchedulerChoice(o.directory, o.scheduler);
874
966
  await removeVariants(o.directory);
967
+ await applyAgentsChoice(o.directory, o.agents, { consent: o.consent });
875
968
  if (o.scheduler && o.runner === "vercel") {
876
969
  await write("vercel.json", renderVercelJson());
877
970
  }
@@ -887,6 +980,18 @@ async function configureProject(o) {
887
980
  "package.json",
888
981
  JSON.stringify(renameProject(stripWorkspaceDependencies(configured), o.slug), null, 2) + "\n"
889
982
  );
983
+ await write("test.env", renderTestEnv(await read("test.env"), db, o.slug));
984
+ if (db.key === "sqlite") {
985
+ const ignore = await read(".gitignore");
986
+ if (!/^\*\.db$/m.test(ignore)) await write(".gitignore", `${ignore.trimEnd()}
987
+
988
+ # SQLite databases
989
+ *.db
990
+ *.db-journal
991
+ *.db-wal
992
+ *.db-shm
993
+ `);
994
+ }
890
995
  const runner = RUNNER_CHOICES[o.runner];
891
996
  const example = await read(".env.example");
892
997
  await write(
@@ -904,9 +1009,9 @@ var AUTH_METHODS = ["email-password", "magic-link", "passkey"];
904
1009
  var SOCIAL_PROVIDERS = ["google", "github"];
905
1010
 
906
1011
  // src/template.ts
907
- import { cp as cp4, mkdir, rm as rm4 } from "fs/promises";
908
- import { existsSync as existsSync5 } from "fs";
909
- import path6 from "path";
1012
+ import { cp as cp5, mkdir as mkdir2 } from "fs/promises";
1013
+ import { existsSync as existsSync6 } from "fs";
1014
+ import path7 from "path";
910
1015
  import { Readable } from "stream";
911
1016
  import { pipeline } from "stream/promises";
912
1017
  import * as tar from "tar";
@@ -921,7 +1026,7 @@ async function downloadTemplate({ dest, branch }) {
921
1026
  Check your network, or pass --local-template <path> to scaffold from a local checkout.`
922
1027
  );
923
1028
  }
924
- await mkdir(dest, { recursive: true });
1029
+ await mkdir2(dest, { recursive: true });
925
1030
  const prefixDepth = 1 + TEMPLATE_PATH.split("/").length;
926
1031
  await pipeline(
927
1032
  Readable.fromWeb(response.body),
@@ -934,25 +1039,25 @@ Check your network, or pass --local-template <path> to scaffold from a local che
934
1039
  }
935
1040
  })
936
1041
  );
937
- if (!existsSync5(path6.join(dest, "package.json"))) {
1042
+ if (!existsSync6(path7.join(dest, "package.json"))) {
938
1043
  throw new Error(`The downloaded archive did not contain ${TEMPLATE_PATH}/package.json (branch "${branch}").`);
939
1044
  }
940
1045
  }
941
1046
  async function copyLocalTemplate({ from, dest }) {
942
- const source = path6.resolve(from);
943
- if (!existsSync5(path6.join(source, "package.json"))) {
1047
+ const source = path7.resolve(from);
1048
+ if (!existsSync6(path7.join(source, "package.json"))) {
944
1049
  throw new Error(`--local-template must point at a directory containing package.json (got ${source})`);
945
1050
  }
946
- await mkdir(dest, { recursive: true });
947
- await cp4(source, dest, {
1051
+ await mkdir2(dest, { recursive: true });
1052
+ await cp5(source, dest, {
948
1053
  recursive: true,
949
1054
  filter: (src) => {
950
- const rel = path6.relative(source, src);
951
- const top = rel.split(path6.sep)[0];
952
- return !["node_modules", ".next", ".turbo", ".env", "test-results", "playwright-report", "next-env.d.ts", "tsconfig.tsbuildinfo", "CLAUDE.md"].includes(top ?? "");
1055
+ const rel = path7.relative(source, src);
1056
+ const top = rel.split(path7.sep)[0];
1057
+ if (rel === path7.join(".claude", "settings.local.json")) return false;
1058
+ return !["node_modules", ".next", ".turbo", ".env", "test-results", "playwright-report", "next-env.d.ts", "tsconfig.tsbuildinfo"].includes(top ?? "");
953
1059
  }
954
1060
  });
955
- await rm4(path6.join(dest, "CLAUDE.md"), { force: true });
956
1061
  }
957
1062
 
958
1063
  // src/cli.ts
@@ -991,7 +1096,7 @@ async function run(flags, positionalName) {
991
1096
  })
992
1097
  );
993
1098
  const slug = slugify(name);
994
- const directory = path7.resolve(process.cwd(), slug);
1099
+ const directory = path8.resolve(process.cwd(), slug);
995
1100
  if (!isDirectoryEmpty(directory)) {
996
1101
  bail(`Directory ./${slug} already exists and is not empty.`);
997
1102
  }
@@ -1116,6 +1221,16 @@ async function run(flags, positionalName) {
1116
1221
  }))
1117
1222
  })
1118
1223
  ));
1224
+ const agentsFlag = parseAgents(flags.agents);
1225
+ if (agentsFlag.error) bail(agentsFlag.error);
1226
+ const agents = agentsFlag.agents ?? (flags.defaults ? [...AGENT_KEYS] : guard(
1227
+ await p.multiselect({
1228
+ message: "Coding agents",
1229
+ required: false,
1230
+ initialValues: [...AGENT_KEYS],
1231
+ options: AGENT_KEYS.map((key) => ({ value: key, label: AGENT_CHOICES[key].label, hint: AGENT_CHOICES[key].hint }))
1232
+ })
1233
+ ));
1119
1234
  const packageManager = flags.packageManager ?? await detectPackageManager();
1120
1235
  const options = {
1121
1236
  name,
@@ -1132,6 +1247,7 @@ async function run(flags, positionalName) {
1132
1247
  consent,
1133
1248
  scheduler,
1134
1249
  runner,
1250
+ agents,
1135
1251
  packageManager,
1136
1252
  install: flags.install,
1137
1253
  git: flags.git
@@ -1161,13 +1277,14 @@ async function run(flags, positionalName) {
1161
1277
  env: { ...process.env, CI: "true" }
1162
1278
  });
1163
1279
  s2.stop(`Installed dependencies with ${packageManager}`);
1280
+ await generateArtifacts(directory, packageManager, slug);
1164
1281
  } catch (error) {
1165
1282
  s2.stop("Dependency installation failed");
1166
1283
  p.log.warn(`Run ${pc2.cyan(installCommand(packageManager))} inside ./${slug} to retry.
1167
1284
  ${error instanceof Error ? error.message.split("\n")[0] : ""}`);
1168
1285
  }
1169
1286
  }
1170
- if (options.git && !existsSync6(path7.join(directory, ".git"))) {
1287
+ if (options.git && !existsSync7(path8.join(directory, ".git"))) {
1171
1288
  try {
1172
1289
  await execa2("git", ["init", "-b", "main"], { cwd: directory });
1173
1290
  await execa2("git", ["add", "-A"], { cwd: directory });
@@ -1190,11 +1307,34 @@ ${error instanceof Error ? error.message.split("\n")[0] : ""}`);
1190
1307
  "# the seeded legal pages are templates, not policy: the homepage shows the six steps in development",
1191
1308
  `# ${CONSENT_DOCS}`
1192
1309
  ] : [],
1193
- ...scheduler ? ["# scheduled actions are under System -> Scheduled Actions in the admin", ...RUNNER_CHOICES[runner].next()] : []
1310
+ ...scheduler ? ["# scheduled actions are under System -> Scheduled Actions in the admin", ...RUNNER_CHOICES[runner].next()] : [],
1311
+ ...agents.length ? [
1312
+ `# agents: AGENTS.md and the stack-* skills are set up for ${listWords(agents.map((key) => AGENT_CHOICES[key].label))}`,
1313
+ `# agents finish with ${runCommand(packageManager, "verify")} (typecheck, lint, unit tests)`
1314
+ ] : []
1194
1315
  ];
1195
1316
  p.note(steps.join("\n"), "Done. Next steps:");
1196
1317
  p.outro(`Docs: ${pc2.underline(DOCS2)}`);
1197
1318
  }
1319
+ async function generateArtifacts(directory, packageManager, slug) {
1320
+ const s = p.spinner();
1321
+ s.start("Generating the admin import map and Payload types");
1322
+ const failed = [];
1323
+ for (const script of ["generate:importmap", "generate:types"]) {
1324
+ const [bin, ...args] = runCommand(packageManager, script).split(" ");
1325
+ try {
1326
+ await execa2(bin, args, { cwd: directory, env: { ...process.env, CI: "true" } });
1327
+ } catch {
1328
+ failed.push(runCommand(packageManager, script));
1329
+ }
1330
+ }
1331
+ if (failed.length === 0) {
1332
+ s.stop("Generated the admin import map and Payload types");
1333
+ return;
1334
+ }
1335
+ s.stop("Could not generate every file");
1336
+ p.log.warn(`Run ${failed.map((c) => pc2.cyan(c)).join(" and ")} inside ./${slug} before the first build.`);
1337
+ }
1198
1338
 
1199
1339
  // src/index.ts
1200
1340
  var require2 = createRequire(import.meta.url);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-payload-stack",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Scaffold a SaaS on Payload CMS: Better Auth, organizations, Stripe subscriptions and a shadcn dashboard, in one command.",
5
5
  "keywords": [
6
6
  "payload",