create-payload-stack 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +219 -66
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -68,6 +68,120 @@ function defaultConnectionString(db, projectSlug) {
|
|
|
68
68
|
return `${db.connectionPrefix}${projectSlug}${db.connectionSuffix}`;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
// src/scheduler.ts
|
|
72
|
+
import { cp } from "fs/promises";
|
|
73
|
+
import { existsSync } from "fs";
|
|
74
|
+
import path2 from "path";
|
|
75
|
+
|
|
76
|
+
// src/variants.ts
|
|
77
|
+
import { rm } from "fs/promises";
|
|
78
|
+
import path from "path";
|
|
79
|
+
var VARIANT_DIR = "variants";
|
|
80
|
+
function variantPath(step) {
|
|
81
|
+
return path.join(VARIANT_DIR, step);
|
|
82
|
+
}
|
|
83
|
+
async function removeVariants(directory) {
|
|
84
|
+
await rm(path.join(directory, VARIANT_DIR), { force: true, recursive: true });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/scheduler.ts
|
|
88
|
+
var SCHEDULER_PACKAGE = "@payload-solutions/plugin-action-scheduler";
|
|
89
|
+
var SCHEDULER_VERSION = "0.2.0";
|
|
90
|
+
var VARIANT_SCHEDULER = variantPath("scheduler-plugin");
|
|
91
|
+
var SEAM_FILES = ["plugin.ts", "jobs.ts", "hooks.ts"];
|
|
92
|
+
var ACTION_DIR = "actions";
|
|
93
|
+
var RUNNER_KEYS = ["clock", "vercel", "server", "later"];
|
|
94
|
+
var DOCS = "https://payload.solutions/docs/payload-stack/scheduling";
|
|
95
|
+
var ALTERNATIVES = [
|
|
96
|
+
"# - Payload Clock: https://payloadclock.com (free; retries and alerts, works on any host)",
|
|
97
|
+
'# - Vercel cron: a "crons" entry in vercel.json hitting the same path',
|
|
98
|
+
"# - Long-lived server: RUN_JOBS_IN_PROCESS=true (never on serverless)",
|
|
99
|
+
"# - Any cron you already have: curl the endpoint with the CRON_SECRET bearer token",
|
|
100
|
+
"# - By hand: Scheduled Actions -> Run queue, in the admin",
|
|
101
|
+
`# ${DOCS}`
|
|
102
|
+
];
|
|
103
|
+
var RUNNER_CHOICES = {
|
|
104
|
+
clock: {
|
|
105
|
+
label: "Payload Clock",
|
|
106
|
+
hint: "free hosted clock, retries and alerts, any host",
|
|
107
|
+
cronSecret: true,
|
|
108
|
+
inProcess: false,
|
|
109
|
+
next: () => [
|
|
110
|
+
"# scheduled actions need a clock. Sign up at https://payloadclock.com (free), point it at",
|
|
111
|
+
"# POST/GET /api/payload-jobs/run?allQueues=true with the CRON_SECRET in .env",
|
|
112
|
+
"# or use one of the others:",
|
|
113
|
+
...ALTERNATIVES.slice(1)
|
|
114
|
+
]
|
|
115
|
+
},
|
|
116
|
+
vercel: {
|
|
117
|
+
label: "Vercel cron",
|
|
118
|
+
hint: "vercel.json; once a minute on Pro, once a day on Hobby",
|
|
119
|
+
cronSecret: true,
|
|
120
|
+
inProcess: false,
|
|
121
|
+
next: () => [
|
|
122
|
+
"# vercel.json runs the queue every minute (Hobby plans get one run a day, which is coarse",
|
|
123
|
+
"# for a scheduler: https://payloadclock.com is free and runs every minute on any plan)",
|
|
124
|
+
"# add CRON_SECRET from .env to the Vercel project: Vercel sends it as the bearer token",
|
|
125
|
+
...ALTERNATIVES
|
|
126
|
+
]
|
|
127
|
+
},
|
|
128
|
+
server: {
|
|
129
|
+
label: "This server",
|
|
130
|
+
hint: "RUN_JOBS_IN_PROCESS=true; docker, Fly, Railway, a VM",
|
|
131
|
+
cronSecret: true,
|
|
132
|
+
inProcess: true,
|
|
133
|
+
next: () => [
|
|
134
|
+
"# RUN_JOBS_IN_PROCESS=true in .env runs the queue in this process, every minute.",
|
|
135
|
+
"# Never set it on Vercel or another serverless host, and run one instance with it.",
|
|
136
|
+
...ALTERNATIVES
|
|
137
|
+
]
|
|
138
|
+
},
|
|
139
|
+
later: {
|
|
140
|
+
label: "Decide later",
|
|
141
|
+
hint: "nothing runs the queue; the admin Run queue button still does",
|
|
142
|
+
cronSecret: false,
|
|
143
|
+
inProcess: false,
|
|
144
|
+
next: () => [
|
|
145
|
+
"# nothing runs the queue yet, so scheduled actions wait. Pick one when you deploy:",
|
|
146
|
+
...ALTERNATIVES
|
|
147
|
+
]
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
function swapSchedulerPackages(packageJson, enabled) {
|
|
151
|
+
const deps = { ...packageJson.dependencies };
|
|
152
|
+
const devDeps = { ...packageJson.devDependencies };
|
|
153
|
+
delete devDeps[SCHEDULER_PACKAGE];
|
|
154
|
+
if (enabled) deps[SCHEDULER_PACKAGE] = SCHEDULER_VERSION;
|
|
155
|
+
else delete deps[SCHEDULER_PACKAGE];
|
|
156
|
+
return { ...packageJson, dependencies: sortKeys(deps), devDependencies: sortKeys(devDeps) };
|
|
157
|
+
}
|
|
158
|
+
function sortKeys(obj) {
|
|
159
|
+
return Object.fromEntries(Object.entries(obj).sort(([a], [b]) => a.localeCompare(b)));
|
|
160
|
+
}
|
|
161
|
+
async function applySchedulerChoice(directory, enabled) {
|
|
162
|
+
if (!enabled) return;
|
|
163
|
+
const variant = path2.join(directory, VARIANT_SCHEDULER);
|
|
164
|
+
if (!existsSync(variant)) {
|
|
165
|
+
throw new Error(`The template is missing ${VARIANT_SCHEDULER}; it cannot scaffold with the scheduler.`);
|
|
166
|
+
}
|
|
167
|
+
for (const name of SEAM_FILES) {
|
|
168
|
+
await cp(path2.join(variant, name), path2.join(directory, "src/scheduler", name));
|
|
169
|
+
}
|
|
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"));
|
|
172
|
+
}
|
|
173
|
+
function renderVercelJson() {
|
|
174
|
+
return `${JSON.stringify(
|
|
175
|
+
{
|
|
176
|
+
$schema: "https://openapi.vercel.sh/vercel.json",
|
|
177
|
+
crons: [{ path: "/api/payload-jobs/run?allQueues=true", schedule: "* * * * *" }]
|
|
178
|
+
},
|
|
179
|
+
null,
|
|
180
|
+
2
|
|
181
|
+
)}
|
|
182
|
+
`;
|
|
183
|
+
}
|
|
184
|
+
|
|
71
185
|
// src/storage.ts
|
|
72
186
|
var LOCAL_STORAGE_CONFIG = `// Local disk (./media): fine for development, lost on redeploy on Vercel and other ephemeral hosts.
|
|
73
187
|
// Move uploads to Vercel Blob, S3, R2, Azure, GCS or Uploadthing: https://payload.solutions/docs/payload-stack/storage`;
|
|
@@ -253,6 +367,9 @@ function helpText(version2) {
|
|
|
253
367
|
--no-emails Keep emails as React Email components in code
|
|
254
368
|
--consent Cookie banner and audited legal pages (Payload Consent)
|
|
255
369
|
--no-consent Plain legal pages, no banner
|
|
370
|
+
--scheduler Scheduled and recurring actions (Payload Action Scheduler)
|
|
371
|
+
--no-scheduler No scheduler
|
|
372
|
+
--runner <kind> ${RUNNER_KEYS.join(" | ")} (what runs the queue; default: clock)
|
|
256
373
|
--use-pnpm | --use-npm | --use-yarn | --use-bun
|
|
257
374
|
--no-install Skip dependency installation
|
|
258
375
|
--no-git Skip git init
|
|
@@ -287,6 +404,8 @@ function parse(argv) {
|
|
|
287
404
|
storage: { type: "string" },
|
|
288
405
|
emails: { type: "boolean" },
|
|
289
406
|
consent: { type: "boolean" },
|
|
407
|
+
scheduler: { type: "boolean" },
|
|
408
|
+
runner: { type: "string" },
|
|
290
409
|
"use-pnpm": { type: "boolean" },
|
|
291
410
|
"use-npm": { type: "boolean" },
|
|
292
411
|
"use-yarn": { type: "boolean" },
|
|
@@ -314,6 +433,8 @@ function parse(argv) {
|
|
|
314
433
|
storage: values.storage,
|
|
315
434
|
emails: values.emails,
|
|
316
435
|
consent: values.consent,
|
|
436
|
+
scheduler: values.scheduler,
|
|
437
|
+
runner: values.runner,
|
|
317
438
|
packageManager,
|
|
318
439
|
install: values.install ?? true,
|
|
319
440
|
git: values.git ?? true,
|
|
@@ -331,31 +452,18 @@ function parse(argv) {
|
|
|
331
452
|
// src/cli.ts
|
|
332
453
|
import * as p from "@clack/prompts";
|
|
333
454
|
import { execa as execa2 } from "execa";
|
|
334
|
-
import { existsSync as
|
|
335
|
-
import
|
|
455
|
+
import { existsSync as existsSync6 } from "fs";
|
|
456
|
+
import path7 from "path";
|
|
336
457
|
import pc2 from "picocolors";
|
|
337
458
|
|
|
338
459
|
// src/configure.ts
|
|
339
460
|
import { readFile, writeFile } from "fs/promises";
|
|
340
|
-
import
|
|
341
|
-
|
|
342
|
-
// src/consent.ts
|
|
343
|
-
import { cp, rm as rm2, rmdir } from "fs/promises";
|
|
344
|
-
import { existsSync } from "fs";
|
|
345
|
-
import path2 from "path";
|
|
346
|
-
|
|
347
|
-
// src/variants.ts
|
|
348
|
-
import { rm } from "fs/promises";
|
|
349
|
-
import path from "path";
|
|
350
|
-
var VARIANT_DIR = "variants";
|
|
351
|
-
function variantPath(step) {
|
|
352
|
-
return path.join(VARIANT_DIR, step);
|
|
353
|
-
}
|
|
354
|
-
async function removeVariants(directory) {
|
|
355
|
-
await rm(path.join(directory, VARIANT_DIR), { force: true, recursive: true });
|
|
356
|
-
}
|
|
461
|
+
import path5 from "path";
|
|
357
462
|
|
|
358
463
|
// 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";
|
|
359
467
|
var CONSENT_PACKAGE = "@payload-solutions/plugin-consent";
|
|
360
468
|
var CONSENT_REACT_PACKAGE = "@payload-solutions/consent-react";
|
|
361
469
|
var CONSENT_VERSION = "0.1.0";
|
|
@@ -364,7 +472,7 @@ var CONSENT_PACKAGES = {
|
|
|
364
472
|
[CONSENT_REACT_PACKAGE]: CONSENT_VERSION
|
|
365
473
|
};
|
|
366
474
|
var VARIANT_CONSENT = variantPath("consent-plugin");
|
|
367
|
-
var
|
|
475
|
+
var SEAM_FILES2 = [
|
|
368
476
|
"plugin.ts",
|
|
369
477
|
"collections.ts",
|
|
370
478
|
"seed.ts",
|
|
@@ -383,32 +491,32 @@ function swapConsentPackages(packageJson, enabled) {
|
|
|
383
491
|
if (enabled) deps[pkg] = CONSENT_PACKAGES[pkg];
|
|
384
492
|
else delete deps[pkg];
|
|
385
493
|
}
|
|
386
|
-
return { ...packageJson, dependencies:
|
|
494
|
+
return { ...packageJson, dependencies: sortKeys2(deps), devDependencies: sortKeys2(devDeps) };
|
|
387
495
|
}
|
|
388
|
-
function
|
|
496
|
+
function sortKeys2(obj) {
|
|
389
497
|
return Object.fromEntries(Object.entries(obj).sort(([a], [b]) => a.localeCompare(b)));
|
|
390
498
|
}
|
|
391
499
|
async function applyConsentChoice(directory, enabled) {
|
|
392
500
|
if (!enabled) return;
|
|
393
|
-
const variant =
|
|
394
|
-
if (!
|
|
501
|
+
const variant = path3.join(directory, VARIANT_CONSENT);
|
|
502
|
+
if (!existsSync2(variant)) {
|
|
395
503
|
throw new Error(`The template is missing ${VARIANT_CONSENT}; it cannot scaffold with consent.`);
|
|
396
504
|
}
|
|
397
|
-
for (const name of [...
|
|
398
|
-
await
|
|
505
|
+
for (const name of [...SEAM_FILES2, ...BANNER_FILES]) {
|
|
506
|
+
await cp2(path3.join(variant, name), path3.join(directory, "src/consent", name), { recursive: true });
|
|
399
507
|
}
|
|
400
|
-
await
|
|
508
|
+
await cp2(path3.join(variant, "tests/consent.spec.ts"), path3.join(directory, "tests/unit/consent.spec.ts"));
|
|
401
509
|
for (const name of TEMPLATE_LEGAL_FILES) {
|
|
402
|
-
await rm2(
|
|
510
|
+
await rm2(path3.join(directory, name), { force: true, recursive: true });
|
|
403
511
|
}
|
|
404
|
-
await rmdir(
|
|
512
|
+
await rmdir(path3.join(directory, "src/seed")).catch(() => {
|
|
405
513
|
});
|
|
406
514
|
}
|
|
407
515
|
|
|
408
516
|
// src/emails.ts
|
|
409
|
-
import { cp as
|
|
410
|
-
import { existsSync as
|
|
411
|
-
import
|
|
517
|
+
import { cp as cp3, rm as rm3 } from "fs/promises";
|
|
518
|
+
import { existsSync as existsSync3 } from "fs";
|
|
519
|
+
import path4 from "path";
|
|
412
520
|
var EMAILS_PACKAGE = "@payload-solutions/plugin-emails";
|
|
413
521
|
var EMAILS_VERSION = "0.1.0";
|
|
414
522
|
var REACT_EMAIL_COMPONENTS = ["@react-email/components", "^1.0.12"];
|
|
@@ -473,24 +581,24 @@ function swapEmailsPackages(packageJson, enabled) {
|
|
|
473
581
|
Object.assign(deps, REACT_EMAIL_ONLY_PACKAGES);
|
|
474
582
|
scripts[EMAIL_DEV_SCRIPT[0]] = EMAIL_DEV_SCRIPT[1];
|
|
475
583
|
}
|
|
476
|
-
return { ...packageJson, dependencies:
|
|
584
|
+
return { ...packageJson, dependencies: sortKeys3(deps), devDependencies: sortKeys3(devDeps), scripts: sortKeys3(scripts) };
|
|
477
585
|
}
|
|
478
|
-
function
|
|
586
|
+
function sortKeys3(obj) {
|
|
479
587
|
return Object.fromEntries(Object.entries(obj).sort(([a], [b]) => a.localeCompare(b)));
|
|
480
588
|
}
|
|
481
589
|
var REACT_EMAIL_FILES = ["src/emails/send.ts", "src/components/auth/email"];
|
|
482
590
|
async function applyEmailsChoice(directory, enabled) {
|
|
483
|
-
const variant =
|
|
591
|
+
const variant = path4.join(directory, VARIANT_EMAILS);
|
|
484
592
|
if (enabled) {
|
|
485
|
-
if (!
|
|
593
|
+
if (!existsSync3(variant)) {
|
|
486
594
|
throw new Error(`The template is missing ${VARIANT_EMAILS}; it cannot scaffold with emails.`);
|
|
487
595
|
}
|
|
488
596
|
for (const name of ["index.ts", "hooks.ts", "template.tsx", "variables.ts", "definitions"]) {
|
|
489
|
-
await
|
|
597
|
+
await cp3(path4.join(variant, name), path4.join(directory, "src/emails", name), { recursive: true });
|
|
490
598
|
}
|
|
491
|
-
await
|
|
599
|
+
await cp3(path4.join(variant, "tests/emails.spec.ts"), path4.join(directory, "tests/unit/emails.spec.ts"));
|
|
492
600
|
for (const name of REACT_EMAIL_FILES) {
|
|
493
|
-
await rm3(
|
|
601
|
+
await rm3(path4.join(directory, name), { force: true, recursive: true });
|
|
494
602
|
}
|
|
495
603
|
}
|
|
496
604
|
}
|
|
@@ -498,7 +606,7 @@ async function applyEmailsChoice(directory, enabled) {
|
|
|
498
606
|
// src/utils.ts
|
|
499
607
|
import slugifyBase from "@sindresorhus/slugify";
|
|
500
608
|
import { randomBytes } from "crypto";
|
|
501
|
-
import { existsSync as
|
|
609
|
+
import { existsSync as existsSync4, readdirSync } from "fs";
|
|
502
610
|
import { execa } from "execa";
|
|
503
611
|
function slugify(input) {
|
|
504
612
|
return slugifyBase(input.trim()).replace(/^-+|-+$/g, "") || "my-saas";
|
|
@@ -507,7 +615,7 @@ function generateSecret(bytes = 32) {
|
|
|
507
615
|
return randomBytes(bytes).toString("base64url");
|
|
508
616
|
}
|
|
509
617
|
function isDirectoryEmpty(dir) {
|
|
510
|
-
if (!
|
|
618
|
+
if (!existsSync4(dir)) return true;
|
|
511
619
|
const entries = readdirSync(dir).filter((e) => ![".git", ".DS_Store"].includes(e));
|
|
512
620
|
return entries.length === 0;
|
|
513
621
|
}
|
|
@@ -696,7 +804,7 @@ function swapStoragePackage(packageJson, storage) {
|
|
|
696
804
|
if (pkg !== storage?.packageName) delete deps[pkg];
|
|
697
805
|
}
|
|
698
806
|
if (storage) deps[storage.packageName] = payloadVersion;
|
|
699
|
-
return { ...packageJson, dependencies:
|
|
807
|
+
return { ...packageJson, dependencies: sortKeys4(deps) };
|
|
700
808
|
}
|
|
701
809
|
function storageChoice(key) {
|
|
702
810
|
return key === "none" ? null : STORAGE_CHOICES[key];
|
|
@@ -708,7 +816,7 @@ function swapDatabasePackage(packageJson, db) {
|
|
|
708
816
|
if (other.packageName !== db.packageName) delete deps[other.packageName];
|
|
709
817
|
}
|
|
710
818
|
deps[db.packageName] = payloadVersion;
|
|
711
|
-
return { ...packageJson, dependencies:
|
|
819
|
+
return { ...packageJson, dependencies: sortKeys4(deps) };
|
|
712
820
|
}
|
|
713
821
|
function stripWorkspaceDependencies(packageJson) {
|
|
714
822
|
const next = { ...packageJson };
|
|
@@ -731,7 +839,10 @@ function renderEnv(example, o) {
|
|
|
731
839
|
DATABASE_URL: o.connectionString,
|
|
732
840
|
PAYLOAD_SECRET: generateSecret(24),
|
|
733
841
|
BETTER_AUTH_SECRET: generateSecret(32),
|
|
734
|
-
NEXT_PUBLIC_APP_URL: o.appUrl ?? "http://localhost:3000"
|
|
842
|
+
NEXT_PUBLIC_APP_URL: o.appUrl ?? "http://localhost:3000",
|
|
843
|
+
// A clock needs a secret to get past jobs.access.run; without one that endpoint is admin-only.
|
|
844
|
+
...o.cronSecret ? { CRON_SECRET: generateSecret(32) } : {},
|
|
845
|
+
...o.inProcessRunner ? { RUN_JOBS_IN_PROCESS: "true" } : {}
|
|
735
846
|
};
|
|
736
847
|
const lines = example.split("\n").map((line) => {
|
|
737
848
|
const m = /^#?\s?([A-Z0-9_]+)=(.*)$/.exec(line);
|
|
@@ -742,7 +853,7 @@ function renderEnv(example, o) {
|
|
|
742
853
|
});
|
|
743
854
|
return lines.join("\n");
|
|
744
855
|
}
|
|
745
|
-
function
|
|
856
|
+
function sortKeys4(obj) {
|
|
746
857
|
return Object.fromEntries(Object.entries(obj).sort(([a], [b]) => a.localeCompare(b)));
|
|
747
858
|
}
|
|
748
859
|
function escapeRegExp(s) {
|
|
@@ -750,8 +861,8 @@ function escapeRegExp(s) {
|
|
|
750
861
|
}
|
|
751
862
|
async function configureProject(o) {
|
|
752
863
|
const db = DB_CHOICES[o.db];
|
|
753
|
-
const read = (rel) => readFile(
|
|
754
|
-
const write = (rel, content) => writeFile(
|
|
864
|
+
const read = (rel) => readFile(path5.join(o.directory, rel), "utf8");
|
|
865
|
+
const write = (rel, content) => writeFile(path5.join(o.directory, rel), content);
|
|
755
866
|
await write("src/stack.config.ts", renderStackConfig(o));
|
|
756
867
|
const storage = storageChoice(o.storage);
|
|
757
868
|
const payloadConfig = await read("src/payload.config.ts");
|
|
@@ -759,18 +870,33 @@ async function configureProject(o) {
|
|
|
759
870
|
await write("src/payload.config.ts", o.emails ? addEmailsPlugin(withAdapters) : withAdapters);
|
|
760
871
|
await applyEmailsChoice(o.directory, o.emails);
|
|
761
872
|
await applyConsentChoice(o.directory, o.consent);
|
|
873
|
+
await applySchedulerChoice(o.directory, o.scheduler);
|
|
762
874
|
await removeVariants(o.directory);
|
|
875
|
+
if (o.scheduler && o.runner === "vercel") {
|
|
876
|
+
await write("vercel.json", renderVercelJson());
|
|
877
|
+
}
|
|
763
878
|
const pkg = JSON.parse(await read("package.json"));
|
|
764
|
-
const configured =
|
|
765
|
-
|
|
766
|
-
|
|
879
|
+
const configured = swapSchedulerPackages(
|
|
880
|
+
swapConsentPackages(
|
|
881
|
+
swapEmailsPackages(swapStoragePackage(swapDatabasePackage(pkg, db), storage), o.emails),
|
|
882
|
+
o.consent
|
|
883
|
+
),
|
|
884
|
+
o.scheduler
|
|
767
885
|
);
|
|
768
886
|
await write(
|
|
769
887
|
"package.json",
|
|
770
888
|
JSON.stringify(renameProject(stripWorkspaceDependencies(configured), o.slug), null, 2) + "\n"
|
|
771
889
|
);
|
|
890
|
+
const runner = RUNNER_CHOICES[o.runner];
|
|
772
891
|
const example = await read(".env.example");
|
|
773
|
-
await write(
|
|
892
|
+
await write(
|
|
893
|
+
".env",
|
|
894
|
+
renderEnv(example, {
|
|
895
|
+
connectionString: o.connectionString,
|
|
896
|
+
cronSecret: o.scheduler && runner.cronSecret,
|
|
897
|
+
inProcessRunner: o.scheduler && runner.inProcess
|
|
898
|
+
})
|
|
899
|
+
);
|
|
774
900
|
}
|
|
775
901
|
|
|
776
902
|
// src/options.ts
|
|
@@ -778,9 +904,9 @@ var AUTH_METHODS = ["email-password", "magic-link", "passkey"];
|
|
|
778
904
|
var SOCIAL_PROVIDERS = ["google", "github"];
|
|
779
905
|
|
|
780
906
|
// src/template.ts
|
|
781
|
-
import { cp as
|
|
782
|
-
import { existsSync as
|
|
783
|
-
import
|
|
907
|
+
import { cp as cp4, mkdir, rm as rm4 } from "fs/promises";
|
|
908
|
+
import { existsSync as existsSync5 } from "fs";
|
|
909
|
+
import path6 from "path";
|
|
784
910
|
import { Readable } from "stream";
|
|
785
911
|
import { pipeline } from "stream/promises";
|
|
786
912
|
import * as tar from "tar";
|
|
@@ -808,29 +934,29 @@ Check your network, or pass --local-template <path> to scaffold from a local che
|
|
|
808
934
|
}
|
|
809
935
|
})
|
|
810
936
|
);
|
|
811
|
-
if (!
|
|
937
|
+
if (!existsSync5(path6.join(dest, "package.json"))) {
|
|
812
938
|
throw new Error(`The downloaded archive did not contain ${TEMPLATE_PATH}/package.json (branch "${branch}").`);
|
|
813
939
|
}
|
|
814
940
|
}
|
|
815
941
|
async function copyLocalTemplate({ from, dest }) {
|
|
816
|
-
const source =
|
|
817
|
-
if (!
|
|
942
|
+
const source = path6.resolve(from);
|
|
943
|
+
if (!existsSync5(path6.join(source, "package.json"))) {
|
|
818
944
|
throw new Error(`--local-template must point at a directory containing package.json (got ${source})`);
|
|
819
945
|
}
|
|
820
946
|
await mkdir(dest, { recursive: true });
|
|
821
|
-
await
|
|
947
|
+
await cp4(source, dest, {
|
|
822
948
|
recursive: true,
|
|
823
949
|
filter: (src) => {
|
|
824
|
-
const rel =
|
|
825
|
-
const top = rel.split(
|
|
950
|
+
const rel = path6.relative(source, src);
|
|
951
|
+
const top = rel.split(path6.sep)[0];
|
|
826
952
|
return !["node_modules", ".next", ".turbo", ".env", "test-results", "playwright-report", "next-env.d.ts", "tsconfig.tsbuildinfo", "CLAUDE.md"].includes(top ?? "");
|
|
827
953
|
}
|
|
828
954
|
});
|
|
829
|
-
await rm4(
|
|
955
|
+
await rm4(path6.join(dest, "CLAUDE.md"), { force: true });
|
|
830
956
|
}
|
|
831
957
|
|
|
832
958
|
// src/cli.ts
|
|
833
|
-
var
|
|
959
|
+
var DOCS2 = "https://payload.solutions/docs/payload-stack";
|
|
834
960
|
var CONSENT_DOCS = "https://payload.solutions/docs/plugins/payload-consent/audit";
|
|
835
961
|
function bail(message = "Cancelled.") {
|
|
836
962
|
p.cancel(message);
|
|
@@ -865,7 +991,7 @@ async function run(flags, positionalName) {
|
|
|
865
991
|
})
|
|
866
992
|
);
|
|
867
993
|
const slug = slugify(name);
|
|
868
|
-
const directory =
|
|
994
|
+
const directory = path7.resolve(process.cwd(), slug);
|
|
869
995
|
if (!isDirectoryEmpty(directory)) {
|
|
870
996
|
bail(`Directory ./${slug} already exists and is not empty.`);
|
|
871
997
|
}
|
|
@@ -966,6 +1092,30 @@ async function run(flags, positionalName) {
|
|
|
966
1092
|
inactive: "Plain legal pages, no banner"
|
|
967
1093
|
})
|
|
968
1094
|
));
|
|
1095
|
+
const scheduler = flags.scheduler ?? (flags.defaults ? true : guard(
|
|
1096
|
+
await p.confirm({
|
|
1097
|
+
message: "Scheduled and recurring actions",
|
|
1098
|
+
initialValue: true,
|
|
1099
|
+
active: "Trial reminders, dunning, sweeps, and a ledger in the admin (Payload Action Scheduler)",
|
|
1100
|
+
inactive: "No scheduler"
|
|
1101
|
+
})
|
|
1102
|
+
));
|
|
1103
|
+
const runnerFlag = flags.runner;
|
|
1104
|
+
if (runnerFlag && !RUNNER_KEYS.includes(runnerFlag)) {
|
|
1105
|
+
bail(`Unknown value "${flags.runner}" for --runner. Allowed: ${RUNNER_KEYS.join(", ")}`);
|
|
1106
|
+
}
|
|
1107
|
+
if (runnerFlag && !scheduler) bail("--runner needs --scheduler. Nothing runs a queue that does not exist.");
|
|
1108
|
+
const runner = !scheduler ? "later" : runnerFlag ?? (flags.defaults ? "clock" : guard(
|
|
1109
|
+
await p.select({
|
|
1110
|
+
message: "What runs the queue",
|
|
1111
|
+
initialValue: "clock",
|
|
1112
|
+
options: RUNNER_KEYS.map((key) => ({
|
|
1113
|
+
value: key,
|
|
1114
|
+
label: RUNNER_CHOICES[key].label,
|
|
1115
|
+
hint: RUNNER_CHOICES[key].hint
|
|
1116
|
+
}))
|
|
1117
|
+
})
|
|
1118
|
+
));
|
|
969
1119
|
const packageManager = flags.packageManager ?? await detectPackageManager();
|
|
970
1120
|
const options = {
|
|
971
1121
|
name,
|
|
@@ -980,6 +1130,8 @@ async function run(flags, positionalName) {
|
|
|
980
1130
|
storage,
|
|
981
1131
|
emails,
|
|
982
1132
|
consent,
|
|
1133
|
+
scheduler,
|
|
1134
|
+
runner,
|
|
983
1135
|
packageManager,
|
|
984
1136
|
install: flags.install,
|
|
985
1137
|
git: flags.git
|
|
@@ -1015,7 +1167,7 @@ async function run(flags, positionalName) {
|
|
|
1015
1167
|
${error instanceof Error ? error.message.split("\n")[0] : ""}`);
|
|
1016
1168
|
}
|
|
1017
1169
|
}
|
|
1018
|
-
if (options.git && !
|
|
1170
|
+
if (options.git && !existsSync6(path7.join(directory, ".git"))) {
|
|
1019
1171
|
try {
|
|
1020
1172
|
await execa2("git", ["init", "-b", "main"], { cwd: directory });
|
|
1021
1173
|
await execa2("git", ["add", "-A"], { cwd: directory });
|
|
@@ -1037,10 +1189,11 @@ ${error instanceof Error ? error.message.split("\n")[0] : ""}`);
|
|
|
1037
1189
|
...consent ? [
|
|
1038
1190
|
"# the seeded legal pages are templates, not policy: the homepage shows the six steps in development",
|
|
1039
1191
|
`# ${CONSENT_DOCS}`
|
|
1040
|
-
] : []
|
|
1192
|
+
] : [],
|
|
1193
|
+
...scheduler ? ["# scheduled actions are under System -> Scheduled Actions in the admin", ...RUNNER_CHOICES[runner].next()] : []
|
|
1041
1194
|
];
|
|
1042
1195
|
p.note(steps.join("\n"), "Done. Next steps:");
|
|
1043
|
-
p.outro(`Docs: ${pc2.underline(
|
|
1196
|
+
p.outro(`Docs: ${pc2.underline(DOCS2)}`);
|
|
1044
1197
|
}
|
|
1045
1198
|
|
|
1046
1199
|
// src/index.ts
|
package/package.json
CHANGED