@django-stack/cli 0.10.0 → 0.12.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 +2 -2
- package/dist/bin.js +122 -52
- package/dist/index.d.ts +13 -4
- package/dist/index.js +1 -1
- package/dist/{production-Ct_qoeXk.js → production-1O8-1QUl.js} +126 -24
- package/package.json +3 -3
- package/templates/backend/Dockerfile +7 -2
- package/templates/backend/config/settings.py +12 -0
- package/templates/backend/config/studio.py +35 -3
- package/templates/backend/config/urls.py +11 -0
- package/templates/backend/content/admin.py +22 -5
- package/templates/backend/content/dashboard.py +112 -0
- package/templates/backend/content/seed.py +8 -1
- package/templates/backend/content/tests/test_api.py +15 -0
- package/templates/backend/content/tests/test_dashboard.py +77 -0
- package/templates/backend/pyproject.toml +1 -1
- package/templates/base/.github/workflows/deploy.yml +10 -1
- package/templates/base/AGENTS.md +78 -0
- package/templates/base/Caddyfile +24 -0
- package/templates/base/README.md +74 -6
- package/templates/base/SECURITY.md +2 -0
- package/templates/base/compose.yaml +21 -2
- package/templates/base/env.example +25 -0
- package/templates/frontend-astro/package.json +1 -1
- package/templates/frontend-next/package.json +3 -3
- package/templates/frontend-nuxt/package.json +2 -2
- package/templates/frontend-sveltekit/package.json +2 -2
- package/templates/frontend-templates/template.json +5 -0
- package/templates/frontend-templates/website/__init__.py +0 -0
- package/templates/frontend-templates/website/apps.py +6 -0
- package/templates/frontend-templates/website/context_processors.py +16 -0
- package/templates/frontend-templates/website/forms.py +20 -0
- package/templates/frontend-templates/website/static/website/favicon.svg +1 -0
- package/templates/frontend-templates/website/static/website/global.css +232 -0
- package/templates/frontend-templates/website/templates/website/404.html +10 -0
- package/templates/frontend-templates/website/templates/website/base.html +44 -0
- package/templates/frontend-templates/website/templates/website/blocks/call_to_action.html +5 -0
- package/templates/frontend-templates/website/templates/website/blocks/image.html +4 -0
- package/templates/frontend-templates/website/templates/website/blocks/quote.html +4 -0
- package/templates/frontend-templates/website/templates/website/blocks/text.html +2 -0
- package/templates/frontend-templates/website/templates/website/home.html +8 -0
- package/templates/frontend-templates/website/templates/website/includes/contact_form.html +21 -0
- package/templates/frontend-templates/website/templates/website/includes/post_card.html +9 -0
- package/templates/frontend-templates/website/templates/website/includes/post_list.html +11 -0
- package/templates/frontend-templates/website/templates/website/page.html +10 -0
- package/templates/frontend-templates/website/templates/website/post.html +20 -0
- package/templates/frontend-templates/website/templates/website/post_list.html +11 -0
- package/templates/frontend-templates/website/templates/website/robots.txt +5 -0
- package/templates/frontend-templates/website/templates/website/rss.xml +16 -0
- package/templates/frontend-templates/website/templates/website/sitemap.xml +8 -0
- package/templates/frontend-templates/website/tests/__init__.py +0 -0
- package/templates/frontend-templates/website/tests/test_site.py +102 -0
- package/templates/frontend-templates/website/urls.py +17 -0
- package/templates/frontend-templates/website/views.py +176 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @django-stack/cli
|
|
2
2
|
|
|
3
|
-
The production-ready
|
|
3
|
+
The production-ready batteries included Django stack. Create and manage DjangoStack projects: a Django backend, a frontend, Postgres and Docker Compose in one command.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npx @django-stack/cli init my-site
|
|
@@ -27,7 +27,7 @@ Run `dstack help <command>` for the options of a command. Each command
|
|
|
27
27
|
checks for a newer version once a day; turn that off with
|
|
28
28
|
`--no-update-check` or `DJANGO_STACK_NO_UPDATE_CHECK=1`.
|
|
29
29
|
|
|
30
|
-
Read the [documentation](https://
|
|
30
|
+
Read the [documentation](https://djangostack.org/) of
|
|
31
31
|
[DjangoStack](https://github.com/dwarsbit/django-stack).
|
|
32
32
|
|
|
33
33
|
## License
|
package/dist/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as
|
|
2
|
+
import { $ as describeMove, A as activeProject, B as DEFAULT_STACK, C as CLI_VERSION, D as CAPTCHA_APP, E as generateSecret, F as projectStack, G as applies, H as NODE_FRONTENDS, I as projectUrls, J as pendingChoices, L as readLocalRun, M as composeOutput, N as findProjectDirectory, O as RUN_DOMAIN, P as parseProfiles, Q as chooseFreePorts, R as readProject, S as validateTimeZone, T as generateProjectSecrets, U as PROMPT_ORDER, V as NAMED_PRESETS, W as STACK_OPTIONS, X as stackOption, Y as resolvePreset, Z as DEV_PORTS, _ as toProjectName, at as dockerRunning, b as usesValkey, c as frontendPackageManager, ct as quoteEnvValue, d as lockFrontend, et as envPorts, f as normalizeDatabaseUrl, g as timeZones, h as setNotificationsApp, i as validateDomain, it as dockerNotRunning, j as compose, k as RUN_ENV, l as localS3, lt as setEnvValues, m as scaffold, n as postgresUrl, nt as runDoctor, o as LOCAL_S3, ot as isDockerDown, p as relativeDirectory, q as encodePreset, r as productionEnv$1, rt as checkTools, s as cacheEnv, st as parseEnv, t as missingS3, tt as portValues, u as lockBackend, v as umamiEnv, w as PACKAGE_NAME, x as validateProjectName, y as updateFrontendLock, z as runningServices } from "./production-1O8-1QUl.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";
|
|
@@ -3929,10 +3929,9 @@ function choices(key, stack = {}) {
|
|
|
3929
3929
|
}
|
|
3930
3930
|
/**
|
|
3931
3931
|
* The stack from, in order of precedence, the flags, the preset, the prompts
|
|
3932
|
-
* and the defaults.
|
|
3933
|
-
* the external database choice; it returns the version, or a URL.
|
|
3932
|
+
* and the defaults.
|
|
3934
3933
|
*/
|
|
3935
|
-
async function chooseStack(options, interactive
|
|
3934
|
+
async function chooseStack(options, interactive) {
|
|
3936
3935
|
let preset;
|
|
3937
3936
|
if (options.preset) try {
|
|
3938
3937
|
preset = resolvePreset(options.preset);
|
|
@@ -3940,18 +3939,12 @@ async function chooseStack(options, interactive, askDatabase) {
|
|
|
3940
3939
|
fail(error.message);
|
|
3941
3940
|
}
|
|
3942
3941
|
const stack = { ...DEFAULT_STACK };
|
|
3943
|
-
let databaseUrl;
|
|
3944
3942
|
for (const key of PROMPT_ORDER) {
|
|
3945
3943
|
const option = stackOption(key);
|
|
3946
3944
|
if (options.fromCli(key)) stack[key] = options[key];
|
|
3947
3945
|
else if (key === "storage" && options.fromCli("localS3") && options.localS3) stack[key] = "s3";
|
|
3948
3946
|
else if (preset) stack[key] = preset[key];
|
|
3949
3947
|
else if (!interactive || !applies(key, stack)) continue;
|
|
3950
|
-
else if (key === "postgres" && askDatabase && !options.databaseUrl) {
|
|
3951
|
-
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
3948
|
else {
|
|
3956
3949
|
const initialValue = key === "storage" && stack.media === "s3" ? "s3" : stack[key];
|
|
3957
3950
|
stack[key] = unwrap(await p.select({
|
|
@@ -3961,10 +3954,7 @@ async function chooseStack(options, interactive, askDatabase) {
|
|
|
3961
3954
|
}));
|
|
3962
3955
|
}
|
|
3963
3956
|
}
|
|
3964
|
-
return
|
|
3965
|
-
stack,
|
|
3966
|
-
databaseUrl
|
|
3967
|
-
};
|
|
3957
|
+
return stack;
|
|
3968
3958
|
}
|
|
3969
3959
|
function systemTimeZone() {
|
|
3970
3960
|
try {
|
|
@@ -4009,28 +3999,11 @@ async function init(directoryArgument, options) {
|
|
|
4009
3999
|
validate: (value) => validateProjectName(value ?? "")
|
|
4010
4000
|
}));
|
|
4011
4001
|
}
|
|
4012
|
-
const
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
...value,
|
|
4018
|
-
label: `Bundled Postgres, ${value.label.toLowerCase()}`
|
|
4019
|
-
})), {
|
|
4020
|
-
value: "external",
|
|
4021
|
-
label: "External Postgres",
|
|
4022
|
-
hint: "a URL to your own server"
|
|
4023
|
-
}]
|
|
4024
|
-
}));
|
|
4025
|
-
if (choice !== "external") return { postgres: choice };
|
|
4026
|
-
return { databaseUrl: unwrap(await p.text({
|
|
4027
|
-
message: "Postgres URL",
|
|
4028
|
-
placeholder: "postgres://user:password@host:5432/name",
|
|
4029
|
-
validate: (value) => value?.startsWith("postgres") ? void 0 : "Enter a postgres:// URL."
|
|
4030
|
-
})) };
|
|
4031
|
-
});
|
|
4032
|
-
const stack = chosen.stack;
|
|
4033
|
-
const databaseUrl = options.databaseUrl ?? chosen.databaseUrl;
|
|
4002
|
+
const stack = await chooseStack(options, interactive);
|
|
4003
|
+
if (!applies("analytics", stack) && stack.analytics !== "off") {
|
|
4004
|
+
if (options.fromCli("analytics")) fail("--analytics umami needs a frontend: a headless project has no pages to count.");
|
|
4005
|
+
stack.analytics = "off";
|
|
4006
|
+
}
|
|
4034
4007
|
const pending = pendingChoices(stack);
|
|
4035
4008
|
const unavailable = pending.filter((item) => !item.isDefault);
|
|
4036
4009
|
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.`);
|
|
@@ -4065,7 +4038,8 @@ async function init(directoryArgument, options) {
|
|
|
4065
4038
|
name,
|
|
4066
4039
|
frontend: stack.frontend,
|
|
4067
4040
|
demoContent,
|
|
4068
|
-
databaseUrl,
|
|
4041
|
+
databaseUrl: options.databaseUrl,
|
|
4042
|
+
postgres: stack.postgres,
|
|
4069
4043
|
localS3: stack.storage === "s3",
|
|
4070
4044
|
analytics: stack.analytics === "umami",
|
|
4071
4045
|
cache: stack.cache,
|
|
@@ -4127,7 +4101,7 @@ async function init(directoryArgument, options) {
|
|
|
4127
4101
|
command(`cd ${relativeDirectory(target)}`),
|
|
4128
4102
|
command(startCommand),
|
|
4129
4103
|
"",
|
|
4130
|
-
`${pc.bold("Site")} ${url(`http://localhost:${ports.ports.SITE_PORT}`)}`,
|
|
4104
|
+
stack.frontend === "headless" ? `${pc.bold("API")} ${url(`http://localhost:${ports.ports.SITE_PORT}/api/`)}` : `${pc.bold("Site")} ${url(`http://localhost:${ports.ports.SITE_PORT}`)}`,
|
|
4131
4105
|
`${pc.bold("Admin")} ${url(`http://localhost:${ports.ports.SITE_PORT}/admin/`)}`,
|
|
4132
4106
|
`${pc.bold("Mail")} ${url(`http://localhost:${ports.ports.MAIL_PORT}`)}`,
|
|
4133
4107
|
"",
|
|
@@ -4166,7 +4140,7 @@ async function preset(code, options) {
|
|
|
4166
4140
|
p.outro(`Use it with ${command(`${selfCommand()} init --preset ${code in NAMED_PRESETS ? code : encodePreset(stack)}`)}`);
|
|
4167
4141
|
return;
|
|
4168
4142
|
}
|
|
4169
|
-
const
|
|
4143
|
+
const stack = await chooseStack(options, isInteractive(options.yes));
|
|
4170
4144
|
const encoded = encodePreset(stack);
|
|
4171
4145
|
p.outro(`Create this stack with ${command(`${selfCommand()} init --preset ${encoded}`)}`);
|
|
4172
4146
|
}
|
|
@@ -4185,7 +4159,7 @@ function validateUrl(value) {
|
|
|
4185
4159
|
if (!value) return void 0;
|
|
4186
4160
|
return /^https?:\/\/[^\s/]+/.test(value) ? void 0 : "Enter a URL that starts with https://.";
|
|
4187
4161
|
}
|
|
4188
|
-
const required = (label) => (value) => value.trim() ? void 0 : `Enter the ${label}.`;
|
|
4162
|
+
const required$1 = (label) => (value) => value.trim() ? void 0 : `Enter the ${label}.`;
|
|
4189
4163
|
/**
|
|
4190
4164
|
* An SMTP URL for EMAIL_URL: smtps:// on port 465, smtp:// on 25, otherwise
|
|
4191
4165
|
* smtp+tls:// (STARTTLS), with the user name and password URL-encoded.
|
|
@@ -4281,7 +4255,7 @@ const SETTING_GROUPS = [
|
|
|
4281
4255
|
name: "S3_BUCKET",
|
|
4282
4256
|
message: "S3 bucket",
|
|
4283
4257
|
placeholder: "example-media",
|
|
4284
|
-
validate: required("bucket")
|
|
4258
|
+
validate: required$1("bucket")
|
|
4285
4259
|
},
|
|
4286
4260
|
{
|
|
4287
4261
|
name: "S3_ENDPOINT_URL",
|
|
@@ -4294,18 +4268,18 @@ const SETTING_GROUPS = [
|
|
|
4294
4268
|
name: "S3_REGION",
|
|
4295
4269
|
message: "S3 region",
|
|
4296
4270
|
placeholder: "eu-central-1",
|
|
4297
|
-
validate: required("region")
|
|
4271
|
+
validate: required$1("region")
|
|
4298
4272
|
},
|
|
4299
4273
|
{
|
|
4300
4274
|
name: "S3_ACCESS_KEY_ID",
|
|
4301
4275
|
message: "S3 access key ID",
|
|
4302
|
-
validate: required("access key ID")
|
|
4276
|
+
validate: required$1("access key ID")
|
|
4303
4277
|
},
|
|
4304
4278
|
{
|
|
4305
4279
|
name: "S3_SECRET_ACCESS_KEY",
|
|
4306
4280
|
message: "S3 secret access key",
|
|
4307
4281
|
secret: true,
|
|
4308
|
-
validate: required("secret access key")
|
|
4282
|
+
validate: required$1("secret access key")
|
|
4309
4283
|
},
|
|
4310
4284
|
{
|
|
4311
4285
|
name: "S3_PUBLIC_URL",
|
|
@@ -4460,6 +4434,86 @@ async function settings(options) {
|
|
|
4460
4434
|
else p.outro(pc.green("Done"));
|
|
4461
4435
|
}
|
|
4462
4436
|
// src/commands/production.ts
|
|
4437
|
+
const required = (label) => (value) => value?.trim() ? void 0 : `Enter the ${label}.`;
|
|
4438
|
+
/**
|
|
4439
|
+
* Asks for the server's database: the bundled Postgres, or your own as a URL
|
|
4440
|
+
* or as separate values. Returns the URL of your own, or undefined.
|
|
4441
|
+
*/
|
|
4442
|
+
async function askDatabase() {
|
|
4443
|
+
if (unwrap(await p.select({
|
|
4444
|
+
message: "Database on the server",
|
|
4445
|
+
initialValue: "bundled",
|
|
4446
|
+
options: [{
|
|
4447
|
+
value: "bundled",
|
|
4448
|
+
label: "Bundled Postgres",
|
|
4449
|
+
hint: "a container next to the backend"
|
|
4450
|
+
}, {
|
|
4451
|
+
value: "external",
|
|
4452
|
+
label: "Your own Postgres",
|
|
4453
|
+
hint: "a managed database or another server"
|
|
4454
|
+
}]
|
|
4455
|
+
})) === "bundled") return void 0;
|
|
4456
|
+
if (unwrap(await p.select({
|
|
4457
|
+
message: "How do you want to enter it?",
|
|
4458
|
+
initialValue: "parts",
|
|
4459
|
+
options: [{
|
|
4460
|
+
value: "parts",
|
|
4461
|
+
label: "Host, port, name, user and password",
|
|
4462
|
+
hint: "any password works"
|
|
4463
|
+
}, {
|
|
4464
|
+
value: "url",
|
|
4465
|
+
label: "A postgres:// URL",
|
|
4466
|
+
hint: "special characters in the password URL-encoded"
|
|
4467
|
+
}]
|
|
4468
|
+
})) === "url") {
|
|
4469
|
+
const value = unwrap(await p.text({
|
|
4470
|
+
message: "Postgres URL",
|
|
4471
|
+
placeholder: "postgres://user:password@db.example.com:5432/app",
|
|
4472
|
+
validate: (value) => {
|
|
4473
|
+
try {
|
|
4474
|
+
normalizeDatabaseUrl((value ?? "").trim());
|
|
4475
|
+
return;
|
|
4476
|
+
} catch (error) {
|
|
4477
|
+
return error.message;
|
|
4478
|
+
}
|
|
4479
|
+
}
|
|
4480
|
+
}));
|
|
4481
|
+
return normalizeDatabaseUrl(value.trim());
|
|
4482
|
+
}
|
|
4483
|
+
const host = unwrap(await p.text({
|
|
4484
|
+
message: "Host",
|
|
4485
|
+
placeholder: "db.example.com",
|
|
4486
|
+
validate: required("host")
|
|
4487
|
+
}));
|
|
4488
|
+
const port = unwrap(await p.text({
|
|
4489
|
+
message: "Port",
|
|
4490
|
+
placeholder: "5432",
|
|
4491
|
+
defaultValue: "5432",
|
|
4492
|
+
validate: (value) => {
|
|
4493
|
+
if (!value) return void 0;
|
|
4494
|
+
const port = Number(value);
|
|
4495
|
+
return Number.isInteger(port) && port > 0 && port < 65536 ? void 0 : "Enter a port, such as 5432.";
|
|
4496
|
+
}
|
|
4497
|
+
}));
|
|
4498
|
+
const name = unwrap(await p.text({
|
|
4499
|
+
message: "Database name",
|
|
4500
|
+
placeholder: "app",
|
|
4501
|
+
validate: required("name")
|
|
4502
|
+
}));
|
|
4503
|
+
const user = unwrap(await p.text({
|
|
4504
|
+
message: "User",
|
|
4505
|
+
placeholder: "app",
|
|
4506
|
+
validate: required("user")
|
|
4507
|
+
}));
|
|
4508
|
+
const password = unwrap(await p.password({ message: "Password" }));
|
|
4509
|
+
return postgresUrl({
|
|
4510
|
+
host: host.trim(),
|
|
4511
|
+
port: port.trim(),
|
|
4512
|
+
name: name.trim(),
|
|
4513
|
+
user: user.trim(),
|
|
4514
|
+
password
|
|
4515
|
+
});
|
|
4516
|
+
}
|
|
4463
4517
|
async function production(directoryArgument, options) {
|
|
4464
4518
|
const interactive = isInteractive(options.yes);
|
|
4465
4519
|
const directory = resolve(directoryArgument ?? ".");
|
|
@@ -4477,12 +4531,21 @@ async function production(directoryArgument, options) {
|
|
|
4477
4531
|
validate: (v) => validateDomain(v ?? "")
|
|
4478
4532
|
}));
|
|
4479
4533
|
}
|
|
4534
|
+
const devEnv = readFileSync(envPath, "utf8");
|
|
4535
|
+
let databaseUrl = options.databaseUrl;
|
|
4536
|
+
if (databaseUrl) try {
|
|
4537
|
+
normalizeDatabaseUrl(databaseUrl);
|
|
4538
|
+
} catch (error) {
|
|
4539
|
+
fail(error.message);
|
|
4540
|
+
}
|
|
4541
|
+
else if (interactive && !parseEnv(devEnv).PRODUCTION_DATABASE_URL) databaseUrl = await askDatabase();
|
|
4480
4542
|
const context = projectContext(directory);
|
|
4481
4543
|
let result;
|
|
4482
4544
|
try {
|
|
4483
|
-
result = productionEnv$1(
|
|
4545
|
+
result = productionEnv$1(devEnv, {
|
|
4484
4546
|
domain,
|
|
4485
4547
|
emailUrl: options.emailUrl,
|
|
4548
|
+
databaseUrl,
|
|
4486
4549
|
media: context.media
|
|
4487
4550
|
});
|
|
4488
4551
|
} catch (error) {
|
|
@@ -4541,6 +4604,7 @@ function applyStack(envText, env, from, to) {
|
|
|
4541
4604
|
if (from[key] === to[key]) continue;
|
|
4542
4605
|
const value = option.values.find((candidate) => candidate.value === to[key]);
|
|
4543
4606
|
if (!value) throw new Error(`${JSON.stringify(to[key])} is not a ${option.key}.`);
|
|
4607
|
+
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.");
|
|
4544
4608
|
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.`);
|
|
4545
4609
|
if (key === "storage") {
|
|
4546
4610
|
if (profiles.includes("prod")) throw new Error("--storage only changes development; this .env runs the prod profile.");
|
|
@@ -4639,7 +4703,8 @@ function ensureRunEnv(project) {
|
|
|
4639
4703
|
if (!existsSync(path)) {
|
|
4640
4704
|
const result = productionEnv$1(readFileSync(join(project.directory, ".env"), "utf8"), {
|
|
4641
4705
|
domain: RUN_DOMAIN,
|
|
4642
|
-
emailUrl: "console://"
|
|
4706
|
+
emailUrl: "console://",
|
|
4707
|
+
bundledDatabase: true
|
|
4643
4708
|
});
|
|
4644
4709
|
writeFileSync(path, result.text, { mode: 384 });
|
|
4645
4710
|
p.log.success(`Wrote ${pc.bold(RUN_ENV)}: the prod profile at ${url(`https://${RUN_DOMAIN}`)}, with its own secrets and data.`);
|
|
@@ -4695,7 +4760,8 @@ async function dev(options) {
|
|
|
4695
4760
|
intro("Develop");
|
|
4696
4761
|
project = await freeDevPorts(project);
|
|
4697
4762
|
urlsNote(project, "Starting");
|
|
4698
|
-
|
|
4763
|
+
const reloads = project.services.frontend ? "backend/ and frontend/" : "backend/";
|
|
4764
|
+
p.log.message(pc.dim(`Changes in ${reloads} reload. Press Ctrl+C to stop.`));
|
|
4699
4765
|
exitWith(compose(project, ["up", ...options.build ? ["--build"] : []]));
|
|
4700
4766
|
}
|
|
4701
4767
|
/** `dstack stop`: stop the containers of the project and of its local run; volumes and data stay. */
|
|
@@ -4844,6 +4910,10 @@ async function types(options) {
|
|
|
4844
4910
|
"--file",
|
|
4845
4911
|
"openapi.json"
|
|
4846
4912
|
], false)) !== 0) fail("The backend could not write its schema.");
|
|
4913
|
+
if (!project.services.frontend) {
|
|
4914
|
+
p.outro(`Wrote ${pc.bold("backend/openapi.json")}`);
|
|
4915
|
+
return;
|
|
4916
|
+
}
|
|
4847
4917
|
const generate = [
|
|
4848
4918
|
"npx",
|
|
4849
4919
|
"headless",
|
|
@@ -4931,7 +5001,7 @@ function checkSteps(project, tests) {
|
|
|
4931
5001
|
],
|
|
4932
5002
|
devOnly: true
|
|
4933
5003
|
});
|
|
4934
|
-
return steps.filter((step) => !(step.devOnly && project.prod));
|
|
5004
|
+
return steps.filter((step) => !(step.devOnly && project.prod) && (step.service !== "frontend" || project.services.frontend));
|
|
4935
5005
|
}
|
|
4936
5006
|
/** `dstack check`: the doctor's checks, Django's checks, the linters and, with --tests, the tests. */
|
|
4937
5007
|
async function check(options) {
|
|
@@ -5029,8 +5099,8 @@ async function update(options) {
|
|
|
5029
5099
|
];
|
|
5030
5100
|
if (compose(target, build) !== 0) fail("Building the images failed.");
|
|
5031
5101
|
}
|
|
5032
|
-
const volume = project.prod ? void 0 : nodeModulesVolume(project);
|
|
5033
|
-
if (volume) {
|
|
5102
|
+
const volume = project.prod || !project.services.frontend ? void 0 : nodeModulesVolume(project);
|
|
5103
|
+
if (volume && project.services.frontend) {
|
|
5034
5104
|
compose(project, [
|
|
5035
5105
|
"rm",
|
|
5036
5106
|
"--stop",
|
|
@@ -5313,7 +5383,7 @@ function createProgram() {
|
|
|
5313
5383
|
styleArgumentText: (text) => pc.yellow(text),
|
|
5314
5384
|
styleDescriptionText: (text) => text
|
|
5315
5385
|
}).configureOutput({ outputError: (text, write) => write(pc.red(text)) });
|
|
5316
|
-
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>", "
|
|
5386
|
+
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, {
|
|
5317
5387
|
...options,
|
|
5318
5388
|
installPrompt: program.opts().installPrompt,
|
|
5319
5389
|
fromCli: (option) => cmd.getOptionValueSource(option) === "cli"
|
|
@@ -5359,7 +5429,7 @@ function createProgram() {
|
|
|
5359
5429
|
fromCli: (option) => cmd.getOptionValueSource(option) === "cli"
|
|
5360
5430
|
}));
|
|
5361
5431
|
for (const flag of stackFlags()) presetCommand.addOption(flag);
|
|
5362
|
-
program.command("production").description("Write .env.production for a server").argument("[directory]", "The project directory", ".").option("--domain <domain>", "The domain of the site, such as example.com").option("--email-url <url>", "The SMTP server, such as smtp+tls://user:pass@host:587").option("--force", "Overwrite an existing .env.production").option("-y, --yes", "Don't ask; fail when --domain is missing").action(production);
|
|
5432
|
+
program.command("production").description("Write .env.production for a server").argument("[directory]", "The project directory", ".").option("--domain <domain>", "The domain of the site, such as example.com").option("--email-url <url>", "The SMTP server, such as smtp+tls://user:pass@host:587").option("--database-url <url>", "Your own Postgres for the server instead of the bundled one").option("--force", "Overwrite an existing .env.production").option("-y, --yes", "Don't ask; fail when --domain is missing").action(production);
|
|
5363
5433
|
program.command("settings").description("Ask for the missing production settings and credentials, and save them").addOption(dirOption()).option("--file <file>", "The file to change (default: .env.production, or .env on a server)").option("--set <NAME=value>", "Set a value without asking, such as --set ADMIN_EMAIL=you@example.com", (value, previous = []) => [...previous, value]).option("--all", "Ask for every setting, also the ones that are set").option("-y, --yes", "Don't ask; fail when a setting is still missing").action(settings);
|
|
5364
5434
|
program.command("doctor").description("Check a project and its services").argument("[directory]", "The project directory", ".").action(doctor);
|
|
5365
5435
|
program.command("self-update").description(`Update ${BIN} to the latest version on npm`).action(selfUpdate);
|
package/dist/index.d.ts
CHANGED
|
@@ -20,6 +20,10 @@ 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;
|
|
25
|
+
/** An external Postgres for the server; it takes the place of PRODUCTION_DATABASE_URL. */
|
|
26
|
+
databaseUrl?: string;
|
|
23
27
|
}
|
|
24
28
|
interface ProductionEnv {
|
|
25
29
|
text: string;
|
|
@@ -54,12 +58,12 @@ export declare const STACK_OPTIONS: readonly [{
|
|
|
54
58
|
readonly value: "headless";
|
|
55
59
|
readonly label: "Headless";
|
|
56
60
|
readonly hint: "just the API and the admin";
|
|
57
|
-
readonly generated:
|
|
61
|
+
readonly generated: true;
|
|
58
62
|
}, {
|
|
59
63
|
readonly value: "templates";
|
|
60
64
|
readonly label: "Django templates";
|
|
61
65
|
readonly hint: "no Node, Django renders the site";
|
|
62
|
-
readonly generated:
|
|
66
|
+
readonly generated: true;
|
|
63
67
|
}, {
|
|
64
68
|
readonly value: "nuxt";
|
|
65
69
|
readonly label: "Nuxt";
|
|
@@ -86,7 +90,7 @@ export declare const STACK_OPTIONS: readonly [{
|
|
|
86
90
|
readonly value: "latest";
|
|
87
91
|
readonly label: "Latest";
|
|
88
92
|
readonly hint: "Postgres 18";
|
|
89
|
-
readonly generated:
|
|
93
|
+
readonly generated: true;
|
|
90
94
|
}];
|
|
91
95
|
}, {
|
|
92
96
|
readonly key: "analytics";
|
|
@@ -290,8 +294,13 @@ interface ScaffoldOptions {
|
|
|
290
294
|
frontend: Frontend;
|
|
291
295
|
/** Demo pages and posts on the first start. */
|
|
292
296
|
demoContent: boolean;
|
|
293
|
-
/**
|
|
297
|
+
/**
|
|
298
|
+
* An external Postgres URL for production, saved as PRODUCTION_DATABASE_URL;
|
|
299
|
+
* development runs the bundled Postgres either way.
|
|
300
|
+
*/
|
|
294
301
|
databaseUrl?: string;
|
|
302
|
+
/** The bundled Postgres's version, and the backend's Postgres client; stable by default. */
|
|
303
|
+
postgres?: Stack["postgres"];
|
|
295
304
|
/** Store uploads in the local S3 service in development, like S3 in production. */
|
|
296
305
|
localS3?: boolean;
|
|
297
306
|
/** Page analytics with the bundled Umami (the `analytics` profile). */
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { B as DEFAULT_STACK, a as FRONTENDS, V as NAMED_PRESETS, W as STACK_OPTIONS, C as VERSION, s as cacheEnv, K as decodePreset, q as encodePreset, T as generateProjectSecrets, E as generateSecret, f as normalizeDatabaseUrl, st as parseEnv, J as pendingChoices, r as productionEnv, Y as resolvePreset, nt as runDoctor, m as scaffold, lt as setEnvValues, h as setNotificationsApp, _ as toProjectName, b as usesValkey, i as validateDomain, x as validateProjectName } from "./production-1O8-1QUl.js";
|