create-payload-canvas 0.5.1 → 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.
package/README.md CHANGED
@@ -27,7 +27,7 @@ The CLI asks for the project name, your Postgres details, which starter features
27
27
  5. Sets the package name, removes the repo-only scripts and the dev dependencies they needed, and points `payload-canvas` at a source (see below).
28
28
  6. Writes `.env` from `.env.example` with your `DATABASE_URL` and a new random `PAYLOAD_SECRET`.
29
29
  7. Creates the database.
30
- 8. Installs dependencies with pnpm (npm if pnpm is missing).
30
+ 8. Installs dependencies with pnpm (npm if pnpm is missing), then writes `src/payload-types.ts` again for the kept features.
31
31
  9. Runs `seed:demo` if you chose to seed. On a fresh database the seed creates the tables. Without the demo seed, it runs `seed:classes`, which adds the editable classes that the ready-made sections use.
32
32
  10. Prints a summary with the exact next commands.
33
33
 
@@ -35,17 +35,20 @@ It never runs `payload migrate`. Local development uses `push: true`, so the sch
35
35
 
36
36
  ## Features
37
37
 
38
- The starter has a few parts you can leave out. All of them are kept by default. Without `--yes`, the CLI shows a list with all of them checked. Uncheck the ones you do not want. A `--no-<feature>` flag leaves a feature out without asking.
38
+ The starter has a few optional parts. Google reviews is off by default; all the others are on. Without `--yes`, the CLI shows a list with the default ones checked. Check or uncheck the ones you want. A `--<feature>` flag adds a feature and a `--no-<feature>` flag leaves it out, without asking.
39
39
 
40
- | Feature | Flag | What you lose |
40
+ | Feature | Flag | What it changes |
41
41
  |---|---|---|
42
- | Admin look | `--no-look` | Neutral colors and finer details in the admin (`src/admin/look`). |
42
+ | Admin look | `--no-look` | Neutral colors and finer details in the admin (`src/admin/look`), status badges and list cells by field type (`src/admin/list`), and array rows named by their content (`src/admin/fields`). |
43
43
  | Sidebar navigation | `--no-nav` | The grouped sidebar with icons (`src/admin/nav`). The admin uses Payload's default sidebar. |
44
- | Dashboard widgets | `--no-dashboard` | Quick actions, recent edits, drafts, form messages, site checks, scheduled publishes and analytics (`src/admin/dashboard`). The admin shows Payload's collection cards. |
44
+ | Dashboard widgets | `--no-dashboard` | Quick actions, recent edits, open comments, form messages, site checks, broken links (404s and links to missing pages), unused media (with a full list at `/admin/unused-media`), scheduled publishes and analytics; "Waiting to publish" can be added (`src/admin/dashboard`). The reference index behind unused media and broken links (`src/admin/references`, `pnpm references:rebuild`). The admin shows Payload's collection cards. |
45
45
  | Demo content | `--no-demo` | The demo pages, posts and images seed (`scripts/seed-demo.ts` and the `seed:demo` script). This also turns off seeding. |
46
46
  | AI assistant | `--no-ai` | The chat panel in the page builder, and its settings in `.env`. |
47
+ | Media gallery | `--no-gallery` | Media as a grid of thumbnails with a lightbox and drag-and-drop upload ([`payload-media-gallery`](https://www.npmjs.com/package/payload-media-gallery)). Without it, Media uses Payload's table. |
48
+ | Workflows | `--no-workflows` | The visual workflow builder (Automation → Workflows), the Run tab on jobs, and the starter tasks in `src/jobs` ([`payload-workflows`](https://www.npmjs.com/package/payload-workflows)). |
49
+ | Google reviews | `--reviews` (off by default) | Adds a Reviews collection and Review Settings that sync a Google place's reviews ([`payload-google-reviews`](https://www.npmjs.com/package/payload-google-reviews), with the optional scraper). You set the place in the admin after setup. |
47
50
 
48
- The CLI deletes the feature's files and removes its lines from `src/payload.config.ts`, `src/app/(payload)/custom.scss`, `.env.example` and the admin import map. The project type-checks with any mix of features.
51
+ For each feature that is off, the CLI deletes its files and its npm packages from `package.json`, and removes its lines from `src/payload.config.ts`, `next.config.ts`, `src/app/(payload)/custom.scss`, `.env.example`, `README.md` and the admin import map. After the install it runs `generate:types`, so `src/payload-types.ts` matches the kept features. The project type-checks with any mix of features.
49
52
 
50
53
  ## Flags
51
54
 
@@ -63,7 +66,8 @@ The CLI deletes the feature's files and removes its lines from `src/payload.conf
63
66
  | `--skip-db` | Do not connect to Postgres. Writes `.env` only. This also skips seeding. |
64
67
  | `--seed` / `--no-seed` | Seed demo content, or not. |
65
68
  | `--no-install` | Do not install. This also skips seeding. |
66
- | `--no-look`, `--no-nav`, `--no-dashboard`, `--no-demo`, `--no-ai` | Leave out a starter feature. See [Features](#features). `--no-demo` also turns off seeding. |
69
+ | `--no-look`, `--no-nav`, `--no-dashboard`, `--no-demo`, `--no-ai`, `--no-gallery`, `--no-workflows` | Leave out a starter feature. See [Features](#features). `--no-demo` also turns off seeding. |
70
+ | `--reviews` | Add Google reviews, which is off by default. Every feature also takes `--<feature>` and `--no-<feature>`. |
67
71
  | `--github` | Download the starter from GitHub, even when the CLI runs inside a checkout. |
68
72
  | `--ref <name>` | Branch or tag to download. Default `main`. |
69
73
  | `--packages <path>` | Path to a checkout of the repo. The CLI packs the `payload-canvas` package from there. |
@@ -74,7 +78,7 @@ The CLI deletes the feature's files and removes its lines from `src/payload.conf
74
78
  | Case | `payload-canvas` in the new project |
75
79
  |---|---|
76
80
  | The CLI runs inside a checkout, or you pass `--packages <path>` | a `file:./.tarballs/...` tarball, packed with `pnpm pack` (it builds `dist/` first). Commit `.tarballs/` with the project. |
77
- | Neither | The version on npm (`^0.5.1`). |
81
+ | Neither | The version on npm (`^0.6.0`). |
78
82
 
79
83
  Without `--packages`, and outside a checkout, the CLI checks the npm registry first. If the package is not on npm (for example, a `--ref` with an unreleased version), it stops before it writes any file and tells you to use `--packages <path>` or `--no-install`.
80
84
 
@@ -93,6 +97,9 @@ npx create-payload-canvas my-website --yes --no-install --skip-db
93
97
  # Without the AI assistant and the demo content
94
98
  npx create-payload-canvas my-website --yes --no-ai --no-demo
95
99
 
100
+ # With Google reviews, without the media gallery and workflows
101
+ npx create-payload-canvas my-website --yes --reviews --no-gallery --no-workflows
102
+
96
103
  # From GitHub, with the package from a local checkout (before it is published)
97
104
  npx create-payload-canvas my-website --yes --github --packages C:\code\payload-canvas
98
105
 
@@ -130,7 +137,7 @@ If the copy or pack step fails, the CLI deletes the half-made folder. If the ins
130
137
  ## Maintainers
131
138
 
132
139
  - The GitHub owner is `GITHUB_OWNER` in `src/config.ts`. The list of files the CLI leaves out is `SKIP_NAMES` and `SKIP_PATHS` in the same file.
133
- - The starter's `starter-features.json` lists each feature's files and scripts. In code, a line with `@feature:<id>` starts a part that belongs to a feature, and a line with `@end:<id>` ends it. The prompt and the flags come from `FEATURES` in `src/config.ts`. Add a new feature in both places.
140
+ - The starter's `starter-features.json` lists each feature's files, scripts and npm `dependencies`. In code, a line with `@feature:<id>` starts a part that belongs to a feature, and a line with `@end:<id>` ends it (in Markdown, inside an HTML comment). The prompt, the flags and the default (`on`) come from `FEATURES` in `src/config.ts`. Add a new feature in both places.
134
141
  - `pnpm check:features` copies the starter for every mix of features, applies it, and runs the starter's TypeScript check. Run it after you change a feature. `pnpm test` runs the unit tests.
135
142
  - After copying, the CLI checks the new project for leftovers (`workspace:` links, paths into `packages/`, `baseUrl`, Docker files) and prints a warning for each one it finds.
136
143
  - Build with `pnpm build`. Run from the repo with `node dist/index.js`. `pnpm pack` builds first (`prepack`).
package/dist/config.js CHANGED
@@ -9,7 +9,7 @@ export function tarballUrl(ref) {
9
9
  return `https://codeload.github.com/${GITHUB_OWNER}/${GITHUB_REPO}/tar.gz/${ref}`;
10
10
  }
11
11
  // Version range for the payload-canvas package when it comes from npm.
12
- export const PUBLISHED_VERSION = '^0.5.1';
12
+ export const PUBLISHED_VERSION = '^0.6.0';
13
13
  // The package the starter depends on: its npm name and its folder under `packages/` in the repo.
14
14
  export const BUILDER_PACKAGE = { name: 'payload-canvas', dir: 'payload-canvas' };
15
15
  // Files and folders of the starter that only make sense inside the repo. The CLI does not copy them.
@@ -34,14 +34,21 @@ export const SKIP_NAMES = new Set([
34
34
  'docker-compose.yml',
35
35
  ]);
36
36
  // Paths relative to the starter folder.
37
- export const SKIP_PATHS = ['scripts/setup.ts', 'scripts/lib/create-database.ts', 'scripts/lib/generate-env.ts', 'docs/qa'];
38
- // Starter features the user can turn off. The CLI asks about these and has a `--no-<id>` flag for each.
39
- // The starter's `starter-features.json` says which files, config lines and scripts belong to each one.
40
- // A feature in that file but not in this list is always kept.
37
+ // `uploads`: files visitors sent with forms (private, never part of a new project).
38
+ export const SKIP_PATHS = ['scripts/setup.ts', 'scripts/lib/create-database.ts', 'scripts/lib/generate-env.ts', 'docs/qa', 'uploads'];
39
+ // Starter features the user can turn on or off. The CLI asks about these and has a `--<id>` and a
40
+ // `--no-<id>` flag for each. `on: false` features are off unless the user picks them.
41
+ // The starter's `starter-features.json` says which files, config lines, scripts and dependencies
42
+ // belong to each one. A feature in that file but not in this list is always kept.
41
43
  export const FEATURES = [
42
- { id: 'look', label: 'Admin look', hint: 'Neutral colors and finer details in the admin' },
43
- { id: 'nav', label: 'Sidebar navigation', hint: 'Grouped sidebar with icons' },
44
- { id: 'dashboard', label: 'Dashboard widgets', hint: 'Quick actions, recent edits, form messages, site checks, analytics' },
45
- { id: 'demo', label: 'Demo content', hint: 'The demo pages, posts and images seed' },
46
- { id: 'ai', label: 'AI assistant', hint: 'The chat panel in the page builder' },
44
+ { id: 'look', label: 'Admin look', hint: 'Neutral colors and finer details in the admin', on: true },
45
+ { id: 'nav', label: 'Sidebar navigation', hint: 'Grouped sidebar with icons', on: true },
46
+ { id: 'dashboard', label: 'Dashboard widgets', hint: 'Quick actions, recent edits, form messages, site checks, analytics', on: true },
47
+ { id: 'demo', label: 'Demo content', hint: 'The demo pages, posts and images seed', on: true },
48
+ { id: 'ai', label: 'AI assistant', hint: 'The chat panel in the page builder', on: true },
49
+ { id: 'gallery', label: 'Media gallery', hint: 'Media as a grid with a lightbox and drag-and-drop upload (payload-media-gallery)', on: true },
50
+ { id: 'workflows', label: 'Workflows', hint: 'A visual workflow builder and a graph of every job run (payload-workflows)', on: true },
51
+ { id: 'reviews', label: 'Google reviews', hint: 'Syncs Google reviews into a collection; needs a Google place (payload-google-reviews)', on: false },
47
52
  ];
53
+ /** The features that are on when the user passes no flags. */
54
+ export const DEFAULT_FEATURES = FEATURES.filter((feature) => feature.on).map((feature) => feature.id);
package/dist/features.js CHANGED
@@ -53,11 +53,14 @@ function importMapKey(removedPath) {
53
53
  return rel.slice('src'.length).replace(/\.[cm]?[jt]sx?$/, '');
54
54
  }
55
55
  /**
56
- * Removes the importMap.js lines that point into deleted files or folders: the import lines
57
- * and the map entries. `removedPaths` are relative to the project root, like `src/admin/nav`.
56
+ * Removes the importMap.js lines that point into deleted files or folders, or into removed
57
+ * packages: the import lines and the map entries. `removedPaths` are relative to the project root,
58
+ * like `src/admin/nav`. `removedPackages` are npm names, like `payload-workflows`.
58
59
  */
59
- export function stripImportMap(text, removedPaths) {
60
+ export function stripImportMap(text, removedPaths, removedPackages = []) {
60
61
  const keys = removedPaths.map(importMapKey).filter((key) => key !== null);
62
+ // A package specifier starts right after a quote: 'pkg/rsc' or "pkg/rsc#Name".
63
+ keys.push(...removedPackages.flatMap((name) => [`'${name}`, `"${name}`]));
61
64
  if (keys.length === 0)
62
65
  return text;
63
66
  // The key must end at a path separator, a quote or the `#export` part, so `/admin/nav` does not match `/admin/navbar`.
@@ -114,8 +117,8 @@ export function filesWithMarkers(targetDir) {
114
117
  }
115
118
  /**
116
119
  * Turns the starter's features on or off in a copied project. For each known feature that is not in
117
- * `enabledIds`, it deletes the feature's files, its package.json scripts, its marked regions and its
118
- * importMap.js lines. Then it deletes `starter-features.json`.
120
+ * `enabledIds`, it deletes the feature's files, its package.json scripts and dependencies, its marked
121
+ * regions and its importMap.js lines. Then it deletes `starter-features.json`.
119
122
  * Returns the ids it turned off, or null when the starter has no manifest (then it changes nothing).
120
123
  */
121
124
  export function applyFeatures(targetDir, enabledIds) {
@@ -141,17 +144,22 @@ export function applyFeatures(targetDir, enabledIds) {
141
144
  const removedPaths = off.flatMap((feature) => feature.paths);
142
145
  for (const rel of removedPaths)
143
146
  fs.rmSync(resolveInside(targetDir, rel), { recursive: true, force: true });
147
+ const removedPackages = off.flatMap((feature) => feature.dependencies ?? []);
144
148
  const importMap = path.join(targetDir, IMPORT_MAP_FILE);
145
- if (removedPaths.length > 0 && fs.existsSync(importMap)) {
146
- fs.writeFileSync(importMap, stripImportMap(fs.readFileSync(importMap, 'utf8'), removedPaths));
149
+ if ((removedPaths.length > 0 || removedPackages.length > 0) && fs.existsSync(importMap)) {
150
+ fs.writeFileSync(importMap, stripImportMap(fs.readFileSync(importMap, 'utf8'), removedPaths, removedPackages));
147
151
  }
148
- // package.json scripts.
152
+ // package.json scripts and dependencies.
149
153
  const scripts = off.flatMap((feature) => feature.scripts ?? []);
150
154
  const pkgFile = path.join(targetDir, 'package.json');
151
- if (scripts.length > 0 && fs.existsSync(pkgFile)) {
155
+ if ((scripts.length > 0 || removedPackages.length > 0) && fs.existsSync(pkgFile)) {
152
156
  const pkg = JSON.parse(fs.readFileSync(pkgFile, 'utf8'));
153
157
  for (const name of scripts)
154
158
  delete pkg.scripts?.[name];
159
+ for (const name of removedPackages) {
160
+ delete pkg.dependencies?.[name];
161
+ delete pkg.devDependencies?.[name];
162
+ }
155
163
  fs.writeFileSync(pkgFile, `${JSON.stringify(pkg, null, 2)}\n`);
156
164
  }
157
165
  fs.rmSync(path.join(targetDir, MANIFEST_FILE));
package/dist/index.js CHANGED
@@ -109,6 +109,12 @@ async function main() {
109
109
  if (!installed) {
110
110
  throw new Error(`"${manager} install" failed. The project is in ${targetDir}. Fix the error above, then run "${manager} install" there.`);
111
111
  }
112
+ // The starter's payload-types.ts has the collections of every feature. Write it again for the kept ones.
113
+ if (removed && removed.length > 0) {
114
+ p.log.step('Updating src/payload-types.ts for the chosen features...');
115
+ if (!run(manager, ['run', 'generate:types'], targetDir))
116
+ p.log.warn(`Updating the types failed. Run "${manager} run generate:types" in ${targetDir}.`);
117
+ }
112
118
  }
113
119
  // 5. Seed.
114
120
  let seeded = false;
package/dist/options.js CHANGED
@@ -14,12 +14,15 @@ Project:
14
14
  --no-install Do not install dependencies (this also skips seeding)
15
15
  --seed / --no-seed Seed demo content (default: yes with --yes, else ask)
16
16
 
17
- Features (all kept by default; without --yes the CLI asks about the ones you did not pass):
17
+ Features (without --yes the CLI asks about the ones you did not pass):
18
18
  --no-look Leave out the admin look (neutral colors, finer details)
19
19
  --no-nav Leave out the grouped sidebar navigation
20
20
  --no-dashboard Leave out the dashboard widgets
21
21
  --no-demo Leave out the demo content seed (this also turns off seeding)
22
22
  --no-ai Leave out the AI assistant in the page builder
23
+ --no-gallery Leave out the media gallery (Media keeps Payload's table)
24
+ --no-workflows Leave out the workflow builder and the job run viewer
25
+ --reviews Add Google reviews (off by default; needs a Google place)
23
26
 
24
27
  Database (default user and password: postgres):
25
28
  --db-url <url> postgresql://user:pass@host:5432/name (replaces the flags below)
@@ -39,8 +42,28 @@ Source:
39
42
 
40
43
  -h, --help Show this help
41
44
  `;
42
- // One `--no-<id>` flag per starter feature.
43
- const featureFlags = Object.fromEntries(FEATURES.map((feature) => [`no-${feature.id}`, { type: 'boolean' }]));
45
+ // A `--<id>` and a `--no-<id>` flag per starter feature. Help lists the one that changes the default.
46
+ const featureFlags = Object.fromEntries(FEATURES.flatMap((feature) => [
47
+ [feature.id, { type: 'boolean' }],
48
+ [`no-${feature.id}`, { type: 'boolean' }],
49
+ ]));
50
+ /**
51
+ * The features chosen with flags: `--<id>` turns one on, `--no-<id>` turns it off. Returns the
52
+ * features that are on (flag or default) and the ids the flags decided, which the CLI does not ask about.
53
+ */
54
+ export function featuresFromFlags(values) {
55
+ const decided = new Set();
56
+ const features = FEATURES.filter((feature) => {
57
+ const on = values[feature.id] === true;
58
+ const off = values[`no-${feature.id}`] === true;
59
+ if (on && off)
60
+ throw new Error(`--${feature.id} and --no-${feature.id} cannot be used together.`);
61
+ if (on || off)
62
+ decided.add(feature.id);
63
+ return on || (!off && feature.on);
64
+ }).map((feature) => feature.id);
65
+ return { features, decided };
66
+ }
44
67
  function parseFlags() {
45
68
  return parseArgs({
46
69
  allowPositionals: true,
@@ -156,20 +179,21 @@ export async function resolveOptions() {
156
179
  db.name = values['db-name'];
157
180
  // Source.
158
181
  const packages = values.packages ? path.resolve(process.cwd(), values.packages) : null;
159
- // Features. A --no-<id> flag turns one off without asking. Ask about the rest.
160
- const turnedOff = new Set(FEATURES.filter((feature) => values[`no-${feature.id}`] === true).map((feature) => feature.id));
161
- const open = FEATURES.filter((feature) => !turnedOff.has(feature.id));
162
- let features = open.map((feature) => feature.id);
182
+ // Features. A --<id> or --no-<id> flag decides one without asking. Ask about the rest.
183
+ const fromFlags = featuresFromFlags(values);
184
+ let features = fromFlags.features;
185
+ const open = FEATURES.filter((feature) => !fromFlags.decided.has(feature.id));
163
186
  if (!yes && open.length > 0) {
164
187
  const answer = await p.multiselect({
165
188
  message: 'Which starter features do you want? (space toggles, enter confirms)',
166
189
  options: open.map((feature) => ({ value: feature.id, label: feature.label, hint: feature.hint })),
167
- initialValues: features,
190
+ initialValues: open.filter((feature) => feature.on).map((feature) => feature.id),
168
191
  required: false,
169
192
  });
170
193
  if (p.isCancel(answer))
171
194
  cancelled();
172
- features = answer;
195
+ const picked = new Set(answer);
196
+ features = FEATURES.filter((feature) => (fromFlags.decided.has(feature.id) ? features.includes(feature.id) : picked.has(feature.id))).map((feature) => feature.id);
173
197
  }
174
198
  const demo = features.includes('demo');
175
199
  // Install and seed.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-payload-canvas",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "Create a new Payload CMS website with Payload Canvas: the visual page builder, Postgres, a seeded demo and the AI guides.",
5
5
  "keywords": [
6
6
  "payload",