@django-stack/cli 0.9.0 → 0.11.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 (78) hide show
  1. package/dist/bin.js +65 -20
  2. package/dist/index.d.ts +24 -9
  3. package/dist/index.js +1 -1
  4. package/dist/{production-M2SsuuBb.js → production-WThC1aS2.js} +267 -30
  5. package/package.json +1 -1
  6. package/templates/backend/Dockerfile +7 -2
  7. package/templates/backend/config/settings.py +13 -0
  8. package/templates/backend/config/urls.py +11 -0
  9. package/templates/backend/content/admin.py +14 -5
  10. package/templates/backend/content/models.py +2 -2
  11. package/templates/backend/content/tests/test_api.py +15 -3
  12. package/templates/backend/pyproject.toml +1 -1
  13. package/templates/base/.github/workflows/deploy.yml +10 -1
  14. package/templates/base/AGENTS.md +86 -8
  15. package/templates/base/Caddyfile +24 -0
  16. package/templates/base/README.md +74 -6
  17. package/templates/base/SECURITY.md +2 -0
  18. package/templates/base/compose.yaml +28 -2
  19. package/templates/base/env.example +37 -0
  20. package/templates/frontend-astro/AGENTS.md +3 -3
  21. package/templates/frontend-astro/Dockerfile +6 -3
  22. package/templates/frontend-astro/astro.config.mjs +16 -5
  23. package/templates/frontend-astro/package.json +1 -1
  24. package/templates/frontend-astro/pnpm-workspace.yaml +17 -0
  25. package/templates/frontend-astro/src/components/ContactForm.astro +19 -3
  26. package/templates/frontend-astro/src/middleware.ts +9 -1
  27. package/templates/frontend-next/AGENTS.md +3 -3
  28. package/templates/frontend-next/Dockerfile +5 -2
  29. package/templates/frontend-next/package.json +3 -3
  30. package/templates/frontend-next/pnpm-workspace.yaml +11 -0
  31. package/templates/frontend-next/src/app/[...path]/page.tsx +2 -1
  32. package/templates/frontend-next/src/components/ContactForm.tsx +14 -3
  33. package/templates/frontend-next/src/instrumentation.ts +12 -0
  34. package/templates/frontend-next/src/proxy.ts +4 -1
  35. package/templates/frontend-nuxt/AGENTS.md +3 -3
  36. package/templates/frontend-nuxt/Dockerfile +5 -2
  37. package/templates/frontend-nuxt/nuxt.config.ts +7 -3
  38. package/templates/frontend-nuxt/package.json +2 -2
  39. package/templates/frontend-nuxt/pnpm-workspace.yaml +13 -0
  40. package/templates/frontend-nuxt/server/plugins/errors.ts +14 -0
  41. package/templates/frontend-nuxt/server/routes/_content/layout.get.ts +4 -0
  42. package/templates/frontend-nuxt/src/components/ContactForm.vue +15 -2
  43. package/templates/frontend-sveltekit/AGENTS.md +3 -3
  44. package/templates/frontend-sveltekit/Dockerfile +6 -3
  45. package/templates/frontend-sveltekit/package.json +2 -2
  46. package/templates/frontend-sveltekit/pnpm-workspace.yaml +11 -0
  47. package/templates/frontend-sveltekit/src/hooks.server.ts +13 -1
  48. package/templates/frontend-sveltekit/src/lib/components/ContactForm.svelte +15 -2
  49. package/templates/frontend-sveltekit/src/routes/+layout.server.ts +3 -0
  50. package/templates/frontend-sveltekit/src/routes/[...path]/+page.svelte +1 -1
  51. package/templates/frontend-sveltekit/vite.config.ts +10 -3
  52. package/templates/frontend-templates/template.json +5 -0
  53. package/templates/frontend-templates/website/__init__.py +0 -0
  54. package/templates/frontend-templates/website/apps.py +6 -0
  55. package/templates/frontend-templates/website/context_processors.py +16 -0
  56. package/templates/frontend-templates/website/forms.py +20 -0
  57. package/templates/frontend-templates/website/static/website/favicon.svg +1 -0
  58. package/templates/frontend-templates/website/static/website/global.css +232 -0
  59. package/templates/frontend-templates/website/templates/website/404.html +10 -0
  60. package/templates/frontend-templates/website/templates/website/base.html +44 -0
  61. package/templates/frontend-templates/website/templates/website/blocks/call_to_action.html +5 -0
  62. package/templates/frontend-templates/website/templates/website/blocks/image.html +4 -0
  63. package/templates/frontend-templates/website/templates/website/blocks/quote.html +4 -0
  64. package/templates/frontend-templates/website/templates/website/blocks/text.html +2 -0
  65. package/templates/frontend-templates/website/templates/website/home.html +8 -0
  66. package/templates/frontend-templates/website/templates/website/includes/contact_form.html +21 -0
  67. package/templates/frontend-templates/website/templates/website/includes/post_card.html +9 -0
  68. package/templates/frontend-templates/website/templates/website/includes/post_list.html +11 -0
  69. package/templates/frontend-templates/website/templates/website/page.html +10 -0
  70. package/templates/frontend-templates/website/templates/website/post.html +20 -0
  71. package/templates/frontend-templates/website/templates/website/post_list.html +11 -0
  72. package/templates/frontend-templates/website/templates/website/robots.txt +5 -0
  73. package/templates/frontend-templates/website/templates/website/rss.xml +16 -0
  74. package/templates/frontend-templates/website/templates/website/sitemap.xml +8 -0
  75. package/templates/frontend-templates/website/tests/__init__.py +0 -0
  76. package/templates/frontend-templates/website/tests/test_site.py +102 -0
  77. package/templates/frontend-templates/website/urls.py +17 -0
  78. package/templates/frontend-templates/website/views.py +176 -0
package/dist/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { $ as dockerRunning, A as projectStack, C as RUN_DOMAIN, D as composeOutput, E as compose, F as DEFAULT_STACK, G as DEV_PORTS, H as pendingChoices, I as NAMED_PRESETS, J as envPorts, K as chooseFreePorts, L as PROMPT_ORDER, M as readLocalRun, N as readProject, O as findProjectDirectory, P as runningServices, Q as dockerNotRunning, R as STACK_OPTIONS, S as generateSecret, T as activeProject, U as resolvePreset, V as encodePreset, W as stackOption, X as runDoctor, Y as portValues, Z as checkTools, _ as validateProjectName, a as LOCAL_S3, b as PACKAGE_NAME, c as lockBackend, d as scaffold, et as isDockerDown, f as setNotificationsApp, g as usesValkey, h as umamiEnv, j as projectUrls, k as parseProfiles, m as toProjectName, n as productionEnv$1, nt as quoteEnvValue, o as cacheEnv, p as timeZones, q as describeMove, r as validateDomain, rt as setEnvValues, s as localS3, t as missingS3, tt as parseEnv, u as relativeDirectory, v as validateTimeZone, w as RUN_ENV, x as generateProjectSecrets, y as CLI_VERSION, z as applies } from "./production-M2SsuuBb.js";
2
+ import { $ as envPorts, A as compose, B as NAMED_PRESETS, C as PACKAGE_NAME, D as RUN_DOMAIN, E as CAPTCHA_APP, F as projectUrls, H as PROMPT_ORDER, I as readLocalRun, J as resolvePreset, K as encodePreset, L as readProject, M as findProjectDirectory, N as parseProfiles, O as RUN_ENV, P as projectStack, Q as describeMove, R as runningServices, S as CLI_VERSION, T as generateSecret, U as STACK_OPTIONS, V as NODE_FRONTENDS, W as applies, X as DEV_PORTS, Y as stackOption, Z as chooseFreePorts, _ as umamiEnv, a as LOCAL_S3, at as isDockerDown, b as validateProjectName, c as localS3, ct as setEnvValues, et as portValues, f as relativeDirectory, g as toProjectName, h as timeZones, it as dockerRunning, j as composeOutput, k as activeProject, l as lockBackend, m as setNotificationsApp, n as productionEnv$1, nt as checkTools, o as cacheEnv, ot as parseEnv, p as scaffold, q as pendingChoices, r as validateDomain, rt as dockerNotRunning, s as frontendPackageManager, st as quoteEnvValue, t as missingS3, tt as runDoctor, u as lockFrontend, v as updateFrontendLock, w as generateProjectSecrets, x as validateTimeZone, y as usesValkey, z as DEFAULT_STACK } from "./production-WThC1aS2.js";
3
3
  import { createRequire } from "node:module";
4
4
  import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
5
5
  import { basename, delimiter, dirname, join, posix, relative, resolve, sep } from "node:path";
@@ -3930,7 +3930,8 @@ function choices(key, stack = {}) {
3930
3930
  /**
3931
3931
  * The stack from, in order of precedence, the flags, the preset, the prompts
3932
3932
  * and the defaults. `askDatabase` asks for the Postgres version together with
3933
- * the external database choice; it returns the version, or a URL.
3933
+ * the external database choice; it returns the version, and the URL for
3934
+ * production when there is one.
3934
3935
  */
3935
3936
  async function chooseStack(options, interactive, askDatabase) {
3936
3937
  let preset;
@@ -3949,10 +3950,9 @@ async function chooseStack(options, interactive, askDatabase) {
3949
3950
  else if (!interactive || !applies(key, stack)) continue;
3950
3951
  else if (key === "postgres" && askDatabase && !options.databaseUrl) {
3951
3952
  const answer = await askDatabase(stack[key]);
3952
- if ("databaseUrl" in answer) databaseUrl = answer.databaseUrl;
3953
- else stack[key] = answer.postgres;
3954
- } else if (key === "postgres" && options.databaseUrl) continue;
3955
- else {
3953
+ if (answer.databaseUrl) databaseUrl = answer.databaseUrl;
3954
+ stack[key] = answer.postgres;
3955
+ } else {
3956
3956
  const initialValue = key === "storage" && stack.media === "s3" ? "s3" : stack[key];
3957
3957
  stack[key] = unwrap(await p.select({
3958
3958
  message: option.message,
@@ -4018,19 +4018,31 @@ async function init(directoryArgument, options) {
4018
4018
  label: `Bundled Postgres, ${value.label.toLowerCase()}`
4019
4019
  })), {
4020
4020
  value: "external",
4021
- label: "External Postgres",
4022
- hint: "a URL to your own server"
4021
+ label: "External Postgres in production",
4022
+ hint: "a URL to your server; development runs the bundled one"
4023
4023
  }]
4024
4024
  }));
4025
4025
  if (choice !== "external") return { postgres: choice };
4026
- return { databaseUrl: unwrap(await p.text({
4027
- message: "Postgres URL",
4026
+ const databaseUrl = unwrap(await p.text({
4027
+ message: "Postgres URL for production",
4028
4028
  placeholder: "postgres://user:password@host:5432/name",
4029
4029
  validate: (value) => value?.startsWith("postgres") ? void 0 : "Enter a postgres:// URL."
4030
- })) };
4030
+ }));
4031
+ return {
4032
+ postgres: unwrap(await p.select({
4033
+ message: "Its Postgres version",
4034
+ initialValue: initial,
4035
+ options: choices("postgres")
4036
+ })),
4037
+ databaseUrl
4038
+ };
4031
4039
  });
4032
4040
  const stack = chosen.stack;
4033
4041
  const databaseUrl = options.databaseUrl ?? chosen.databaseUrl;
4042
+ if (!applies("analytics", stack) && stack.analytics !== "off") {
4043
+ if (options.fromCli("analytics")) fail("--analytics umami needs a frontend: a headless project has no pages to count.");
4044
+ stack.analytics = "off";
4045
+ }
4034
4046
  const pending = pendingChoices(stack);
4035
4047
  const unavailable = pending.filter((item) => !item.isDefault);
4036
4048
  if (unavailable.length) fail(`${unavailable.map((item) => item.choice).join(", ")} ${unavailable.length > 1 ? "are" : "is"} not available yet. Leave ${unavailable.length > 1 ? "them" : "it"} at the default.`);
@@ -4066,10 +4078,13 @@ async function init(directoryArgument, options) {
4066
4078
  frontend: stack.frontend,
4067
4079
  demoContent,
4068
4080
  databaseUrl,
4081
+ postgres: stack.postgres,
4069
4082
  localS3: stack.storage === "s3",
4070
4083
  analytics: stack.analytics === "umami",
4071
4084
  cache: stack.cache,
4072
4085
  notifications: stack.notifications === "on",
4086
+ captcha: stack.captcha === "on",
4087
+ packageManager: stack.packageManager,
4073
4088
  errors: stack.errors,
4074
4089
  tasks: stack.tasks,
4075
4090
  preset: encodePreset(stack),
@@ -4087,6 +4102,16 @@ async function init(directoryArgument, options) {
4087
4102
  if (lockBackend(target)) spinner.stop(`Locked the backend dependencies in ${pc.bold("backend/uv.lock")}`);
4088
4103
  else spinner.error("Could not lock the backend dependencies. The image build resolves them instead.");
4089
4104
  }
4105
+ if (NODE_FRONTENDS.includes(stack.frontend)) {
4106
+ const lockfile = {
4107
+ pnpm: "pnpm-lock.yaml",
4108
+ npm: "package-lock.json",
4109
+ bun: "bun.lock"
4110
+ }[stack.packageManager];
4111
+ spinner.start(`Locking the frontend dependencies with ${stack.packageManager}`);
4112
+ if (lockFrontend(target, stack.packageManager)) spinner.stop(`Locked the frontend dependencies in ${pc.bold(`frontend/${lockfile}`)}`);
4113
+ else spinner.error("Could not lock the frontend dependencies. The image build resolves them instead.");
4114
+ }
4090
4115
  if (options.git && has("git")) try {
4091
4116
  execFileSync("git", ["init", "--quiet"], {
4092
4117
  cwd: target,
@@ -4115,7 +4140,7 @@ async function init(directoryArgument, options) {
4115
4140
  command(`cd ${relativeDirectory(target)}`),
4116
4141
  command(startCommand),
4117
4142
  "",
4118
- `${pc.bold("Site")} ${url(`http://localhost:${ports.ports.SITE_PORT}`)}`,
4143
+ stack.frontend === "headless" ? `${pc.bold("API")} ${url(`http://localhost:${ports.ports.SITE_PORT}/api/`)}` : `${pc.bold("Site")} ${url(`http://localhost:${ports.ports.SITE_PORT}`)}`,
4119
4144
  `${pc.bold("Admin")} ${url(`http://localhost:${ports.ports.SITE_PORT}/admin/`)}`,
4120
4145
  `${pc.bold("Mail")} ${url(`http://localhost:${ports.ports.MAIL_PORT}`)}`,
4121
4146
  "",
@@ -4512,7 +4537,8 @@ const SWITCHABLE = [
4512
4537
  "media",
4513
4538
  "cache",
4514
4539
  "tasks",
4515
- "notifications"
4540
+ "notifications",
4541
+ "captcha"
4516
4542
  ];
4517
4543
  const EMPTY_S3 = Object.fromEntries(Object.keys(LOCAL_S3).map((key) => [key, key === "S3_CREATE_BUCKET" ? "0" : ""]));
4518
4544
  /**
@@ -4528,6 +4554,7 @@ function applyStack(envText, env, from, to) {
4528
4554
  if (from[key] === to[key]) continue;
4529
4555
  const value = option.values.find((candidate) => candidate.value === to[key]);
4530
4556
  if (!value) throw new Error(`${JSON.stringify(to[key])} is not a ${option.key}.`);
4557
+ if (key === "postgres") throw new Error("--postgres can't change on an existing project: a new major version needs a backup and a restore. See the docs on backups.");
4531
4558
  if (!SWITCHABLE.includes(key) || !value.generated) throw new Error(`${option.flag} ${to[key]} can't be set on an existing project in this version yet.`);
4532
4559
  if (key === "storage") {
4533
4560
  if (profiles.includes("prod")) throw new Error("--storage only changes development; this .env runs the prod profile.");
@@ -4580,6 +4607,10 @@ function applyStack(envText, env, from, to) {
4580
4607
  }
4581
4608
  }
4582
4609
  if (key === "notifications") changes.push(to.notifications === "on" ? "Notifications are on: content_studio.notifications is in INSTALLED_APPS." : "Notifications are off: content_studio.notifications is out of INSTALLED_APPS; its tables stay.");
4610
+ if (key === "captcha") {
4611
+ text = setEnvValues(text, { CAPTCHA: to.captcha });
4612
+ changes.push(to.captcha === "on" ? "The captcha of the contact form is on." : "The captcha is off: public forms need no captcha token.");
4613
+ }
4583
4614
  if (key === "media") changes.push(to.media === "s3" ? "Uploads in production go to S3; dstack settings asks for the S3 settings." : "Uploads in production stay on the server.");
4584
4615
  }
4585
4616
  return {
@@ -4622,7 +4653,8 @@ function ensureRunEnv(project) {
4622
4653
  if (!existsSync(path)) {
4623
4654
  const result = productionEnv$1(readFileSync(join(project.directory, ".env"), "utf8"), {
4624
4655
  domain: RUN_DOMAIN,
4625
- emailUrl: "console://"
4656
+ emailUrl: "console://",
4657
+ bundledDatabase: true
4626
4658
  });
4627
4659
  writeFileSync(path, result.text, { mode: 384 });
4628
4660
  p.log.success(`Wrote ${pc.bold(RUN_ENV)}: the prod profile at ${url(`https://${RUN_DOMAIN}`)}, with its own secrets and data.`);
@@ -4678,7 +4710,8 @@ async function dev(options) {
4678
4710
  intro("Develop");
4679
4711
  project = await freeDevPorts(project);
4680
4712
  urlsNote(project, "Starting");
4681
- p.log.message(pc.dim("Changes in backend/ and frontend/ reload. Press Ctrl+C to stop."));
4713
+ const reloads = project.services.frontend ? "backend/ and frontend/" : "backend/";
4714
+ p.log.message(pc.dim(`Changes in ${reloads} reload. Press Ctrl+C to stop.`));
4682
4715
  exitWith(compose(project, ["up", ...options.build ? ["--build"] : []]));
4683
4716
  }
4684
4717
  /** `dstack stop`: stop the containers of the project and of its local run; volumes and data stay. */
@@ -4827,6 +4860,10 @@ async function types(options) {
4827
4860
  "--file",
4828
4861
  "openapi.json"
4829
4862
  ], false)) !== 0) fail("The backend could not write its schema.");
4863
+ if (!project.services.frontend) {
4864
+ p.outro(`Wrote ${pc.bold("backend/openapi.json")}`);
4865
+ return;
4866
+ }
4830
4867
  const generate = [
4831
4868
  "npx",
4832
4869
  "headless",
@@ -4914,7 +4951,7 @@ function checkSteps(project, tests) {
4914
4951
  ],
4915
4952
  devOnly: true
4916
4953
  });
4917
- return steps.filter((step) => !(step.devOnly && project.prod));
4954
+ return steps.filter((step) => !(step.devOnly && project.prod) && (step.service !== "frontend" || project.services.frontend));
4918
4955
  }
4919
4956
  /** `dstack check`: the doctor's checks, Django's checks, the linters and, with --tests, the tests. */
4920
4957
  async function check(options) {
@@ -4998,6 +5035,11 @@ async function update(options) {
4998
5035
  }).status !== 0) fail("uv could not update backend/uv.lock.");
4999
5036
  p.log.success(options.all ? "Updated backend/uv.lock" : "Updated django-stack in backend/uv.lock");
5000
5037
  } else if (existsSync(lock)) p.log.warn("uv is not installed, so backend/uv.lock stays as it is: https://docs.astral.sh/uv/");
5038
+ if (frontendPackageManager(project.directory)) {
5039
+ const lockfile = updateFrontendLock(project.directory, options.all);
5040
+ if (!lockfile) fail("The package manager could not update the frontend's lockfile.");
5041
+ p.log.success(options.all ? `Updated frontend/${lockfile}` : `Updated the @django-stack packages in frontend/${lockfile}`);
5042
+ }
5001
5043
  for (const target of targets) {
5002
5044
  if (compose(target, ["pull", "--ignore-buildable"]) !== 0) fail("Pulling the images failed.");
5003
5045
  const build = [
@@ -5007,8 +5049,8 @@ async function update(options) {
5007
5049
  ];
5008
5050
  if (compose(target, build) !== 0) fail("Building the images failed.");
5009
5051
  }
5010
- const volume = project.prod ? void 0 : nodeModulesVolume(project);
5011
- if (volume) {
5052
+ const volume = project.prod || !project.services.frontend ? void 0 : nodeModulesVolume(project);
5053
+ if (volume && project.services.frontend) {
5012
5054
  compose(project, [
5013
5055
  "rm",
5014
5056
  "--stop",
@@ -5058,6 +5100,9 @@ async function config(options) {
5058
5100
  if (!existsSync(compose) || !/^ {2}umami:/m.test(readFileSync(compose, "utf8"))) fail("compose.yaml has no umami service. Add it as the analytics docs describe, then run this again.");
5059
5101
  }
5060
5102
  const settingsPath = join(project.directory, "backend", "config", "settings.py");
5103
+ if (next.captcha === "on" && current.captcha !== "on") {
5104
+ if (!existsSync(settingsPath) || !CAPTCHA_APP.test(readFileSync(settingsPath, "utf8"))) fail("The backend has no django_stack.contrib.captcha. Add it and the widget as the captcha docs describe, then run this again.");
5105
+ }
5061
5106
  let settingsText;
5062
5107
  if (next.notifications !== current.notifications) {
5063
5108
  if (!existsSync(settingsPath)) fail("This project has no backend/config/settings.py.");
@@ -5288,7 +5333,7 @@ function createProgram() {
5288
5333
  styleArgumentText: (text) => pc.yellow(text),
5289
5334
  styleDescriptionText: (text) => text
5290
5335
  }).configureOutput({ outputError: (text, write) => write(pc.red(text)) });
5291
- const initCommand = program.command("init").alias("create").description("Create a project").argument("[directory]", "Where to create the project").option("--name <name>", "The project name (default: from the directory)").option("--preset <preset>", `A preset code from a previous init, or ${Object.keys(NAMED_PRESETS).join(", ")}`).option("--database-url <url>", "Use an external Postgres instead of the bundled one").option("--time-zone <zone>", "The time zone of the backend, such as Europe/Amsterdam (default: UTC)").addOption(new Option("--local-s3", "Same as --storage s3").hideHelp()).option("--no-demo-content", "Start with an empty site").option("--no-git", "Don't create a git repository").option("--start", "Run `docker compose up` when done").option("-y, --yes", "Accept the defaults without asking").addOption(new Option("--link <repository>", "Take the packages from a DjangoStack checkout").hideHelp()).action((directory, options, cmd) => init(directory, {
5336
+ const initCommand = program.command("init").alias("create").description("Create a project").argument("[directory]", "Where to create the project").option("--name <name>", "The project name (default: from the directory)").option("--preset <preset>", `A preset code from a previous init, or ${Object.keys(NAMED_PRESETS).join(", ")}`).option("--database-url <url>", "An external Postgres for production; development runs the bundled one").option("--time-zone <zone>", "The time zone of the backend, such as Europe/Amsterdam (default: UTC)").addOption(new Option("--local-s3", "Same as --storage s3").hideHelp()).option("--no-demo-content", "Start with an empty site").option("--no-git", "Don't create a git repository").option("--start", "Run `docker compose up` when done").option("-y, --yes", "Accept the defaults without asking").addOption(new Option("--link <repository>", "Take the packages from a DjangoStack checkout").hideHelp()).action((directory, options, cmd) => init(directory, {
5292
5337
  ...options,
5293
5338
  installPrompt: program.opts().installPrompt,
5294
5339
  fromCli: (option) => cmd.getOptionValueSource(option) === "cli"
@@ -5301,7 +5346,7 @@ function createProgram() {
5301
5346
  program.command("logs").description("Show the logs of the services").argument("[services...]", "Only these services, such as backend").addOption(dirOption()).option("--no-follow", "Print the logs and exit").option("-n, --tail <lines>", "Only the last lines of each service").action(logs);
5302
5347
  program.command("status").description("Show the containers and whether the database, cache, worker and backups are healthy").addOption(dirOption()).action(status);
5303
5348
  program.command("check").description("Check the project: setup, Django checks, migrations and linters").addOption(dirOption()).option("--tests", "Run the backend and frontend tests too").action(check);
5304
- program.command("update").description("Pull and rebuild the images and update the DjangoStack packages").addOption(dirOption()).option("--all", "Update every backend package in uv.lock, not only django-stack").option("--no-cache", "Rebuild the images from scratch").option("--no-restart", "Don't restart what was running").action(update);
5349
+ program.command("update").description("Pull and rebuild the images and update the DjangoStack packages").addOption(dirOption()).option("--all", "Update every package in the lockfiles, not only DjangoStack's").option("--no-cache", "Rebuild the images from scratch").option("--no-restart", "Don't restart what was running").action(update);
5305
5350
  const configCommand = program.command("config").description("Change the stack options of a project").addOption(dirOption()).option("-y, --yes", "Don't ask; change only what the flags set").action((options, cmd) => config({
5306
5351
  ...options,
5307
5352
  fromCli: (option) => cmd.getOptionValueSource(option) === "cli"
package/dist/index.d.ts CHANGED
@@ -20,6 +20,8 @@ interface ProductionOptions {
20
20
  emailUrl?: string;
21
21
  /** Where uploads go: `s3` asks for the S3 settings (the `media` stack option). */
22
22
  media?: string;
23
+ /** Keep the bundled database even with PRODUCTION_DATABASE_URL: the local run on this machine. */
24
+ bundledDatabase?: boolean;
23
25
  }
24
26
  interface ProductionEnv {
25
27
  text: string;
@@ -54,12 +56,12 @@ export declare const STACK_OPTIONS: readonly [{
54
56
  readonly value: "headless";
55
57
  readonly label: "Headless";
56
58
  readonly hint: "just the API and the admin";
57
- readonly generated: false;
59
+ readonly generated: true;
58
60
  }, {
59
61
  readonly value: "templates";
60
62
  readonly label: "Django templates";
61
63
  readonly hint: "no Node, Django renders the site";
62
- readonly generated: false;
64
+ readonly generated: true;
63
65
  }, {
64
66
  readonly value: "nuxt";
65
67
  readonly label: "Nuxt";
@@ -86,7 +88,7 @@ export declare const STACK_OPTIONS: readonly [{
86
88
  readonly value: "latest";
87
89
  readonly label: "Latest";
88
90
  readonly hint: "Postgres 18";
89
- readonly generated: false;
91
+ readonly generated: true;
90
92
  }];
91
93
  }, {
92
94
  readonly key: "analytics";
@@ -174,7 +176,7 @@ export declare const STACK_OPTIONS: readonly [{
174
176
  readonly value: "on";
175
177
  readonly label: "On";
176
178
  readonly hint: "proof of work, no tracking";
177
- readonly generated: false;
179
+ readonly generated: true;
178
180
  }, {
179
181
  readonly value: "off";
180
182
  readonly label: "Off";
@@ -206,7 +208,8 @@ export declare const STACK_OPTIONS: readonly [{
206
208
  readonly values: readonly [{
207
209
  readonly value: "pnpm";
208
210
  readonly label: "pnpm";
209
- readonly generated: false;
211
+ readonly hint: "with a day's delay on new releases";
212
+ readonly generated: true;
210
213
  }, {
211
214
  readonly value: "npm";
212
215
  readonly label: "npm";
@@ -214,7 +217,8 @@ export declare const STACK_OPTIONS: readonly [{
214
217
  }, {
215
218
  readonly value: "bun";
216
219
  readonly label: "Bun";
217
- readonly generated: false;
220
+ readonly hint: "for installs; the frontend runs on Node.js";
221
+ readonly generated: true;
218
222
  }];
219
223
  }, {
220
224
  readonly key: "media";
@@ -247,8 +251,8 @@ export declare const STACK_OPTIONS: readonly [{
247
251
  }, {
248
252
  readonly value: "sentry";
249
253
  readonly label: "Sentry";
250
- readonly hint: "or GlitchTip, with SENTRY_DSN";
251
- readonly generated: false;
254
+ readonly hint: "sentry.io, with SENTRY_DSN";
255
+ readonly generated: true;
252
256
  }, {
253
257
  readonly value: "off";
254
258
  readonly label: "Off";
@@ -288,14 +292,23 @@ interface ScaffoldOptions {
288
292
  frontend: Frontend;
289
293
  /** Demo pages and posts on the first start. */
290
294
  demoContent: boolean;
291
- /** An external Postgres URL; the bundled Postgres when not set. */
295
+ /**
296
+ * An external Postgres URL for production, saved as PRODUCTION_DATABASE_URL;
297
+ * development runs the bundled Postgres either way.
298
+ */
292
299
  databaseUrl?: string;
300
+ /** The bundled Postgres's version, and the backend's Postgres client; stable by default. */
301
+ postgres?: Stack["postgres"];
293
302
  /** Store uploads in the local S3 service in development, like S3 in production. */
294
303
  localS3?: boolean;
295
304
  /** Page analytics with the bundled Umami (the `analytics` profile). */
296
305
  analytics?: boolean;
297
306
  /** Content Studio's notifications app; on by default. */
298
307
  notifications?: boolean;
308
+ /** The captcha of the contact form; on by default. */
309
+ captcha?: boolean;
310
+ /** The frontend's package manager; pnpm by default. */
311
+ packageManager?: PackageManager;
299
312
  /** How errors are reported in production; by email by default. */
300
313
  errors?: Stack["errors"];
301
314
  /** The cache backend; the database by default. */
@@ -313,6 +326,8 @@ interface ScaffoldOptions {
313
326
  /** Where the templates are; the ones in the package by default. */
314
327
  templatesDir?: string;
315
328
  }
329
+ /** A package manager of the frontend. */
330
+ type PackageManager = Stack["packageManager"];
316
331
  interface ScaffoldResult {
317
332
  directory: string;
318
333
  adminUsername: string;
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export { F as DEFAULT_STACK, i as FRONTENDS, I as NAMED_PRESETS, R as STACK_OPTIONS, y as VERSION, o as cacheEnv, B as decodePreset, V as encodePreset, x as generateProjectSecrets, S as generateSecret, l as normalizeDatabaseUrl, tt as parseEnv, H as pendingChoices, n as productionEnv, U as resolvePreset, X as runDoctor, d as scaffold, rt as setEnvValues, f as setNotificationsApp, m as toProjectName, g as usesValkey, r as validateDomain, _ as validateProjectName } from "./production-M2SsuuBb.js";
1
+ export { z as DEFAULT_STACK, i as FRONTENDS, B as NAMED_PRESETS, U as STACK_OPTIONS, S as VERSION, o as cacheEnv, G as decodePreset, K as encodePreset, w as generateProjectSecrets, T as generateSecret, d as normalizeDatabaseUrl, ot as parseEnv, q as pendingChoices, n as productionEnv, J as resolvePreset, tt as runDoctor, p as scaffold, ct as setEnvValues, m as setNotificationsApp, g as toProjectName, y as usesValkey, r as validateDomain, b as validateProjectName } from "./production-WThC1aS2.js";