create-payload-canvas 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/README.md CHANGED
@@ -14,7 +14,7 @@ pnpm create payload-canvas my-website
14
14
  npx create-payload-canvas my-website
15
15
  ```
16
16
 
17
- The CLI asks for the project name, your Postgres details, and whether to seed demo content. It asks nothing when you pass `--yes`, or when the input is not a terminal (CI, agents). Every question has a flag.
17
+ The CLI asks for the project name, your Postgres details, which starter features to keep, and whether to seed demo content. It asks nothing when you pass `--yes`, or when the input is not a terminal (CI, agents). Every question has a flag.
18
18
 
19
19
  ## What it does
20
20
 
@@ -22,16 +22,31 @@ The CLI asks for the project name, your Postgres details, and whether to seed de
22
22
  2. Copies the starter app (`apps/starter`) and the AI guides it links to (`docs/ai`).
23
23
  - Inside a checkout of the repo, it copies from the checkout.
24
24
  - Anywhere else, it downloads the repo from GitHub (`jon8800/payload-canvas`).
25
- 3. Leaves out repo-only files: `node_modules`, `.next`, `.turbo`, `.env`, `next-env.d.ts`, Docker files (they expect the monorepo), `scripts/setup.ts`, `scripts/lib`, `scripts/_*`, `docs/qa`, screenshots, and uploaded files in `public/media`.
26
- 4. Sets the package name, removes the repo-only scripts and the dev dependencies they needed, and points `payload-canvas` at a source (see below).
27
- 5. Writes `.env` from `.env.example` with your `DATABASE_URL` and a new random `PAYLOAD_SECRET`.
28
- 6. Creates the database.
29
- 7. Installs dependencies with pnpm (npm if pnpm is missing).
30
- 8. Runs `seed:demo` if you chose to seed. On a fresh database the seed creates the tables.
31
- 9. Prints a summary with the exact next commands.
25
+ 3. Leaves out repo-only files: `node_modules`, `.next`, `.turbo`, `.env`, `next-env.d.ts`, Docker files (they expect the monorepo), `scripts/setup.ts` and its helpers `scripts/lib/create-database.ts` and `scripts/lib/generate-env.ts`, `scripts/_*`, `docs/qa`, screenshots, and uploaded files in `public/media`.
26
+ 4. Removes the starter features you left out (see [Features](#features)).
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
+ 6. Writes `.env` from `.env.example` with your `DATABASE_URL` and a new random `PAYLOAD_SECRET`.
29
+ 7. Creates the database.
30
+ 8. Installs dependencies with pnpm (npm if pnpm is missing).
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
+ 10. Prints a summary with the exact next commands.
32
33
 
33
34
  It never runs `payload migrate`. Local development uses `push: true`, so the schema syncs when you run `pnpm dev`.
34
35
 
36
+ ## Features
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.
39
+
40
+ | Feature | Flag | What you lose |
41
+ |---|---|---|
42
+ | Admin look | `--no-look` | Neutral colors and finer details in the admin (`src/admin/look`). |
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. |
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
+ | AI assistant | `--no-ai` | The chat panel in the page builder, and its settings in `.env`. |
47
+
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.
49
+
35
50
  ## Flags
36
51
 
37
52
  | Flag | Meaning |
@@ -48,6 +63,7 @@ It never runs `payload migrate`. Local development uses `push: true`, so the sch
48
63
  | `--skip-db` | Do not connect to Postgres. Writes `.env` only. This also skips seeding. |
49
64
  | `--seed` / `--no-seed` | Seed demo content, or not. |
50
65
  | `--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. |
51
67
  | `--github` | Download the starter from GitHub, even when the CLI runs inside a checkout. |
52
68
  | `--ref <name>` | Branch or tag to download. Default `main`. |
53
69
  | `--packages <path>` | Path to a checkout of the repo. The CLI packs the `payload-canvas` package from there. |
@@ -58,7 +74,7 @@ It never runs `payload migrate`. Local development uses `push: true`, so the sch
58
74
  | Case | `payload-canvas` in the new project |
59
75
  |---|---|
60
76
  | 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. |
61
- | Neither | The version on npm (`^0.4.0`). |
77
+ | Neither | The version on npm (`^0.5.0`). |
62
78
 
63
79
  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`.
64
80
 
@@ -74,6 +90,9 @@ npx create-payload-canvas my-website --yes --db-url postgresql://postgres:postgr
74
90
  # Only the files, no install, no database
75
91
  npx create-payload-canvas my-website --yes --no-install --skip-db
76
92
 
93
+ # Without the AI assistant and the demo content
94
+ npx create-payload-canvas my-website --yes --no-ai --no-demo
95
+
77
96
  # From GitHub, with the package from a local checkout (before it is published)
78
97
  npx create-payload-canvas my-website --yes --github --packages C:\code\payload-canvas
79
98
 
@@ -111,5 +130,7 @@ If the copy or pack step fails, the CLI deletes the half-made folder. If the ins
111
130
  ## Maintainers
112
131
 
113
132
  - 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.
134
+ - `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.
114
135
  - 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.
115
136
  - Build with `pnpm build`. Run from the repo with `node dist/index.js`. `pnpm pack` builds first (`prepack`).
package/dist/config.js CHANGED
@@ -9,12 +9,13 @@ 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.4.0';
12
+ export const PUBLISHED_VERSION = '^0.5.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.
16
16
  // - Docker files expect the repo root as build context and the workspace packages.
17
- // - `setup.ts` is the repo's own scaffold script; the CLI replaces it.
17
+ // - `setup.ts` is the repo's own scaffold script; the CLI replaces it. The two `scripts/lib` files
18
+ // were its helpers. Keep `scripts/lib/classes.ts`: the seed scripts import it.
18
19
  // - `_` scripts are developer test helpers.
19
20
  export const SKIP_NAMES = new Set([
20
21
  'node_modules',
@@ -33,4 +34,14 @@ export const SKIP_NAMES = new Set([
33
34
  'docker-compose.yml',
34
35
  ]);
35
36
  // Paths relative to the starter folder.
36
- export const SKIP_PATHS = ['scripts/setup.ts', 'scripts/lib', 'docs/qa'];
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.
41
+ 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' },
47
+ ];
@@ -0,0 +1,159 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { FEATURES } from './config.js';
4
+ /**
5
+ * The starter's list of features. Each one names the files and scripts to delete when it is off.
6
+ * Labels and hints for the prompt are in `FEATURES` (config.ts).
7
+ */
8
+ export const MANIFEST_FILE = 'starter-features.json';
9
+ /** Payload writes this file. It imports every admin component by path. */
10
+ export const IMPORT_MAP_FILE = 'src/app/(payload)/admin/importMap.js';
11
+ const OPEN = /@feature:([\w-]+)/;
12
+ const CLOSE = /@end:([\w-]+)/;
13
+ const SKIP_DIRS = new Set(['node_modules', '.next', '.turbo', '.git', '.tarballs']);
14
+ /**
15
+ * Removes the marked regions of features that are off. A line with `@feature:<id>` opens a region
16
+ * and a line with `@end:<id>` closes it. The marker lines always go. The lines between them go
17
+ * when `<id>` is not in `enabledIds`. `file` only makes error messages clearer.
18
+ */
19
+ export function stripRegions(text, enabledIds, file = 'text') {
20
+ const enabled = new Set(enabledIds);
21
+ const open = [];
22
+ const kept = [];
23
+ const lines = text.split('\n');
24
+ lines.forEach((line, index) => {
25
+ const start = OPEN.exec(line);
26
+ if (start) {
27
+ open.push({ id: start[1], line: index + 1 });
28
+ return;
29
+ }
30
+ const end = CLOSE.exec(line);
31
+ if (end) {
32
+ const last = open.pop();
33
+ if (!last)
34
+ throw new Error(`${file}:${index + 1}: "@end:${end[1]}" has no matching "@feature:${end[1]}".`);
35
+ if (last.id !== end[1]) {
36
+ throw new Error(`${file}:${index + 1}: "@end:${end[1]}" closes "@feature:${last.id}" from line ${last.line}.`);
37
+ }
38
+ return;
39
+ }
40
+ if (open.every((region) => enabled.has(region.id)))
41
+ kept.push(line);
42
+ });
43
+ const unclosed = open.pop();
44
+ if (unclosed)
45
+ throw new Error(`${file}:${unclosed.line}: "@feature:${unclosed.id}" is never closed with "@end:${unclosed.id}".`);
46
+ return kept.join('\n');
47
+ }
48
+ /** The part of a deleted `src/` path that importMap.js uses, e.g. `src/admin/nav` -> `/admin/nav`. */
49
+ function importMapKey(removedPath) {
50
+ const rel = removedPath.replace(/\\/g, '/').replace(/\/+$/, '');
51
+ if (!rel.startsWith('src/'))
52
+ return null;
53
+ return rel.slice('src'.length).replace(/\.[cm]?[jt]sx?$/, '');
54
+ }
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`.
58
+ */
59
+ export function stripImportMap(text, removedPaths) {
60
+ const keys = removedPaths.map(importMapKey).filter((key) => key !== null);
61
+ if (keys.length === 0)
62
+ return text;
63
+ // The key must end at a path separator, a quote or the `#export` part, so `/admin/nav` does not match `/admin/navbar`.
64
+ const pointsIntoRemoved = (line) => keys.some((key) => {
65
+ let at = line.indexOf(key);
66
+ while (at !== -1) {
67
+ if (/^[/'"#]/.test(line.slice(at + key.length)))
68
+ return true;
69
+ at = line.indexOf(key, at + 1);
70
+ }
71
+ return false;
72
+ });
73
+ return text
74
+ .split('\n')
75
+ .filter((line) => !pointsIntoRemoved(line))
76
+ .join('\n');
77
+ }
78
+ /** Reads `starter-features.json`. Returns null when the starter has none (older versions). */
79
+ export function readManifest(targetDir) {
80
+ const file = path.join(targetDir, MANIFEST_FILE);
81
+ if (!fs.existsSync(file))
82
+ return null;
83
+ const manifest = JSON.parse(fs.readFileSync(file, 'utf8'));
84
+ if (!Array.isArray(manifest.features))
85
+ throw new Error(`${MANIFEST_FILE} has no "features" list.`);
86
+ return manifest;
87
+ }
88
+ /** Resolves a manifest path inside the project. Throws if it points outside. */
89
+ function resolveInside(targetDir, rel) {
90
+ const full = path.resolve(targetDir, rel);
91
+ const back = path.relative(targetDir, full);
92
+ if (!back || back.startsWith('..') || path.isAbsolute(back))
93
+ throw new Error(`${MANIFEST_FILE}: "${rel}" is outside the project.`);
94
+ return full;
95
+ }
96
+ /** All files under `dir`, without dependency and build folders. */
97
+ function listFiles(dir) {
98
+ return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
99
+ const full = path.join(dir, entry.name);
100
+ if (entry.isDirectory())
101
+ return SKIP_DIRS.has(entry.name) ? [] : listFiles(full);
102
+ return entry.isFile() ? [full] : [];
103
+ });
104
+ }
105
+ /** The project files that hold `@feature:` or `@end:` markers, with their text. */
106
+ export function filesWithMarkers(targetDir) {
107
+ return listFiles(targetDir).flatMap((file) => {
108
+ const buffer = fs.readFileSync(file);
109
+ if (buffer.includes(0))
110
+ return []; // binary file
111
+ const text = buffer.toString('utf8');
112
+ return OPEN.test(text) || CLOSE.test(text) ? [{ file, text }] : [];
113
+ });
114
+ }
115
+ /**
116
+ * 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`.
119
+ * Returns the ids it turned off, or null when the starter has no manifest (then it changes nothing).
120
+ */
121
+ export function applyFeatures(targetDir, enabledIds) {
122
+ const manifest = readManifest(targetDir);
123
+ if (!manifest)
124
+ return null;
125
+ const enabled = new Set(enabledIds);
126
+ const known = new Set(FEATURES.map((feature) => feature.id));
127
+ const off = manifest.features.filter((feature) => known.has(feature.id) && !enabled.has(feature.id));
128
+ const offIds = new Set(off.map((feature) => feature.id));
129
+ const keptIds = manifest.features.map((feature) => feature.id).filter((id) => !offIds.has(id));
130
+ // Marked regions. Do this before deleting files, so a bad marker stops everything early.
131
+ for (const { file, text } of filesWithMarkers(targetDir)) {
132
+ const rel = path.relative(targetDir, file).split(path.sep).join('/');
133
+ for (const match of text.matchAll(/@(?:feature|end):([\w-]+)/g)) {
134
+ if (!manifest.features.some((feature) => feature.id === match[1])) {
135
+ throw new Error(`${rel} marks "${match[1]}", but ${MANIFEST_FILE} has no feature with that id.`);
136
+ }
137
+ }
138
+ fs.writeFileSync(file, stripRegions(text, keptIds, rel));
139
+ }
140
+ // Files and folders.
141
+ const removedPaths = off.flatMap((feature) => feature.paths);
142
+ for (const rel of removedPaths)
143
+ fs.rmSync(resolveInside(targetDir, rel), { recursive: true, force: true });
144
+ 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));
147
+ }
148
+ // package.json scripts.
149
+ const scripts = off.flatMap((feature) => feature.scripts ?? []);
150
+ const pkgFile = path.join(targetDir, 'package.json');
151
+ if (scripts.length > 0 && fs.existsSync(pkgFile)) {
152
+ const pkg = JSON.parse(fs.readFileSync(pkgFile, 'utf8'));
153
+ for (const name of scripts)
154
+ delete pkg.scripts?.[name];
155
+ fs.writeFileSync(pkgFile, `${JSON.stringify(pkg, null, 2)}\n`);
156
+ }
157
+ fs.rmSync(path.join(targetDir, MANIFEST_FILE));
158
+ return [...offIds];
159
+ }
package/dist/index.js CHANGED
@@ -2,8 +2,9 @@
2
2
  import * as p from '@clack/prompts';
3
3
  import fs from 'node:fs';
4
4
  import path from 'node:path';
5
- import { BUILDER_PACKAGE } from './config.js';
5
+ import { BUILDER_PACKAGE, FEATURES } from './config.js';
6
6
  import { buildDatabaseUrl, checkDatabase, createDatabase } from './database.js';
7
+ import { applyFeatures } from './features.js';
7
8
  import { resolveOptions } from './options.js';
8
9
  import { builderPackageIsPublished, checkProject, hasBuilderPackage, packBuilderPackage, updatePackageJson, writeEnv, } from './project.js';
9
10
  import { hasCommand, run } from './run.js';
@@ -47,10 +48,12 @@ async function main() {
47
48
  throw new Error(`The database "${db.name}" already exists on ${db.host}:${db.port}. Pass --reuse-db to use it, or pick another --db-name.`);
48
49
  }
49
50
  }
50
- // 1. Copy the starter. 2. Point payload-canvas at a local tarball or at npm. Write .env.
51
+ // 1. Copy the starter and remove the features the user left out. 2. Point payload-canvas at a
52
+ // local tarball or at npm. Write .env.
51
53
  // If this part fails, delete the half-made folder so the command can run again.
52
54
  const spinner = p.spinner();
53
55
  let tarball = null;
56
+ let removed = null;
54
57
  try {
55
58
  fs.mkdirSync(targetDir, { recursive: true });
56
59
  if (localRepo) {
@@ -67,7 +70,10 @@ async function main() {
67
70
  fs.rmSync(downloaded, { recursive: true, force: true });
68
71
  }
69
72
  }
73
+ removed = applyFeatures(targetDir, options.features);
70
74
  spinner.stop('Starter files ready.');
75
+ if (!removed)
76
+ p.log.warn('This starter version has no feature list, so all features are kept.');
71
77
  spinner.start(packagesRoot ? `Packing ${BUILDER_PACKAGE.name}...` : 'Writing package.json and .env...');
72
78
  tarball = packagesRoot ? packBuilderPackage(packagesRoot, targetDir) : null;
73
79
  updatePackageJson(targetDir, name, tarball, manager);
@@ -112,6 +118,13 @@ async function main() {
112
118
  if (!seeded)
113
119
  p.log.warn(`Seeding failed. Run "${manager} run seed:demo" in ${targetDir} to try again.`);
114
120
  }
121
+ else if (installed && !options.skipDb) {
122
+ // The ready-made sections use the site's editable classes (card, button-primary...): add them
123
+ // without the demo pages, or the sections look unstyled.
124
+ p.log.step('Adding the editable classes...');
125
+ if (!run(manager, ['run', 'seed:classes'], targetDir))
126
+ p.log.warn(`Adding the classes failed. Run "${manager} run seed:classes" in ${targetDir} to try again.`);
127
+ }
115
128
  // 6. Summary.
116
129
  const relative = path.relative(process.cwd(), targetDir);
117
130
  const cdTarget = !relative ? null : relative.startsWith('..') || path.isAbsolute(relative) ? targetDir : relative;
@@ -120,6 +133,7 @@ async function main() {
120
133
  `Project: ${targetDir}`,
121
134
  options.skipDb ? 'Database: not set up (--skip-db)' : `Database: ${db.name} on ${db.host}:${db.port}`,
122
135
  `Demo data: ${seeded ? 'seeded' : 'not seeded'}`,
136
+ `Features: ${FEATURES.filter((feature) => !removed?.includes(feature.id)).map((feature) => feature.label).join(', ') || 'none'}`,
123
137
  '',
124
138
  'Next steps:',
125
139
  ...[
package/dist/options.js CHANGED
@@ -2,7 +2,7 @@ import * as p from '@clack/prompts';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { parseArgs } from 'node:util';
5
- import { DEFAULT_REF } from './config.js';
5
+ import { DEFAULT_REF, FEATURES } from './config.js';
6
6
  import { databaseNameFor, parseDatabaseUrl } from './database.js';
7
7
  export const HELP = `Usage: create-payload-canvas [project-dir] [flags]
8
8
 
@@ -14,6 +14,13 @@ 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):
18
+ --no-look Leave out the admin look (neutral colors, finer details)
19
+ --no-nav Leave out the grouped sidebar navigation
20
+ --no-dashboard Leave out the dashboard widgets
21
+ --no-demo Leave out the demo content seed (this also turns off seeding)
22
+ --no-ai Leave out the AI assistant in the page builder
23
+
17
24
  Database (default user and password: postgres):
18
25
  --db-url <url> postgresql://user:pass@host:5432/name (replaces the flags below)
19
26
  --db-host <host> Default: localhost
@@ -32,6 +39,8 @@ Source:
32
39
 
33
40
  -h, --help Show this help
34
41
  `;
42
+ // One `--no-<id>` flag per starter feature.
43
+ const featureFlags = Object.fromEntries(FEATURES.map((feature) => [`no-${feature.id}`, { type: 'boolean' }]));
35
44
  function parseFlags() {
36
45
  return parseArgs({
37
46
  allowPositionals: true,
@@ -48,6 +57,7 @@ function parseFlags() {
48
57
  seed: { type: 'boolean' },
49
58
  'no-seed': { type: 'boolean' },
50
59
  'no-install': { type: 'boolean' },
60
+ ...featureFlags,
51
61
  github: { type: 'boolean' },
52
62
  ref: { type: 'string' },
53
63
  packages: { type: 'string' },
@@ -146,9 +156,25 @@ export async function resolveOptions() {
146
156
  db.name = values['db-name'];
147
157
  // Source.
148
158
  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);
163
+ if (!yes && open.length > 0) {
164
+ const answer = await p.multiselect({
165
+ message: 'Which starter features do you want? (space toggles, enter confirms)',
166
+ options: open.map((feature) => ({ value: feature.id, label: feature.label, hint: feature.hint })),
167
+ initialValues: features,
168
+ required: false,
169
+ });
170
+ if (p.isCancel(answer))
171
+ cancelled();
172
+ features = answer;
173
+ }
174
+ const demo = features.includes('demo');
149
175
  // Install and seed.
150
176
  const install = values['no-install'] !== true;
151
- const canSeed = install && !skipDb;
177
+ const canSeed = install && !skipDb && demo;
152
178
  let seed;
153
179
  if (values['no-seed']) {
154
180
  seed = false;
@@ -169,7 +195,9 @@ export async function resolveOptions() {
169
195
  seed = answer;
170
196
  }
171
197
  if (seed && !canSeed) {
172
- p.log.warn(`Seeding needs installed dependencies and a database, so ${skipDb ? '--skip-db' : '--no-install'} turns it off.`);
198
+ const reason = !demo ? 'the demo content feature' : 'installed dependencies and a database';
199
+ const cause = !demo ? 'leaving it out' : skipDb ? '--skip-db' : '--no-install';
200
+ p.log.warn(`Seeding needs ${reason}, so ${cause} turns it off.`);
173
201
  seed = false;
174
202
  }
175
203
  return {
@@ -183,5 +211,6 @@ export async function resolveOptions() {
183
211
  github: values.github === true,
184
212
  ref: values.ref ?? DEFAULT_REF,
185
213
  packages,
214
+ features,
186
215
  };
187
216
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-payload-canvas",
3
- "version": "0.4.0",
3
+ "version": "0.5.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",
@@ -54,6 +54,8 @@
54
54
  "scripts": {
55
55
  "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc",
56
56
  "dev": "tsx src/index.ts",
57
- "typecheck": "tsc --noEmit"
57
+ "typecheck": "tsc --noEmit -p tsconfig.check.json",
58
+ "test": "node --import tsx --test \"src/**/*.test.ts\"",
59
+ "check:features": "tsx scripts/check-features.ts"
58
60
  }
59
61
  }