@django-stack/cli 0.12.0 → 0.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +57 -14
- package/dist/index.js +1 -1
- package/dist/{production-1O8-1QUl.js → production-lgZRSpyw.js} +35 -1
- package/package.json +1 -1
- package/templates/backend/pyproject.toml +1 -1
- package/templates/base/AGENTS.md +22 -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/dist/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
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
|
|
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 removeLeftovers, d as lockFrontend, dt as setEnvValues, 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 parseEnv, 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 leftoverVolumes, t as missingS3, tt as portValues, u as lockBackend, ut as quoteEnvValue, v as umamiEnv, w as PACKAGE_NAME, x as validateProjectName, y as updateFrontendLock, z as runningServices } from "./production-lgZRSpyw.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";
|
|
@@ -1029,6 +1029,24 @@ function probeRequest(probe, failureThreshold) {
|
|
|
1029
1029
|
...check
|
|
1030
1030
|
};
|
|
1031
1031
|
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Which registry a Magic Containers registry is. Bunny returns no type, so
|
|
1034
|
+
* this goes by the host name, and by the display name or a type field when
|
|
1035
|
+
* there is no host name. Public Docker Hub and GitHub are built in.
|
|
1036
|
+
*/
|
|
1037
|
+
function registryKind(item) {
|
|
1038
|
+
const host = (text(item.hostName) ?? text(item.host) ?? text(item.url) ?? "").toLowerCase();
|
|
1039
|
+
const label = `${text(item.type) ?? ""} ${text(item.displayName) ?? ""}`.toLowerCase().replace(/[\s_-]/g, "");
|
|
1040
|
+
if (host) {
|
|
1041
|
+
if (/(^|\.|\/)(docker\.io|docker\.com)(\/|$)/.test(host)) return "dockerHub";
|
|
1042
|
+
if (/(^|\.|\/)ghcr\.io(\/|$)/.test(host)) return "gitHub";
|
|
1043
|
+
if (/(^|\.|\/)bunny\.net(\/|$)/.test(host)) return "bunny";
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
if (label.includes("dockerhub")) return "dockerHub";
|
|
1047
|
+
if (label.includes("github") || label.includes("ghcr")) return "gitHub";
|
|
1048
|
+
if (label.includes("bunny")) return "bunny";
|
|
1049
|
+
}
|
|
1032
1050
|
/** The image fields of a container. */
|
|
1033
1051
|
function imageFields(container, options) {
|
|
1034
1052
|
const { image } = container;
|
|
@@ -1310,25 +1328,29 @@ var BunnyClient = class {
|
|
|
1310
1328
|
const list = listOf(await this.api.mc("GET", "/registries"));
|
|
1311
1329
|
const registries = {};
|
|
1312
1330
|
let namespace;
|
|
1313
|
-
|
|
1314
|
-
|
|
1331
|
+
const listed = [];
|
|
1332
|
+
const ordered = [...list].sort((a, b) => Number(b.isPublic === true) - Number(a.isPublic === true));
|
|
1333
|
+
for (const item of ordered) {
|
|
1315
1334
|
const id = text(item.id);
|
|
1316
1335
|
if (!id) continue;
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1336
|
+
const kind = registryKind(item);
|
|
1337
|
+
listed.push(text(item.displayName) ?? text(item.hostName) ?? id);
|
|
1338
|
+
if (kind === "dockerHub" && !registries.dockerHub) registries.dockerHub = id;
|
|
1339
|
+
else if (kind === "gitHub" && !registries.gitHub) registries.gitHub = id;
|
|
1340
|
+
else if (kind === "bunny" && !registries.bunny) {
|
|
1320
1341
|
registries.bunny = id;
|
|
1321
1342
|
namespace = text(item.namespace ?? item.accountId ?? item.userName ?? item.username);
|
|
1322
1343
|
}
|
|
1323
1344
|
}
|
|
1324
1345
|
return {
|
|
1325
1346
|
registries,
|
|
1326
|
-
namespace
|
|
1347
|
+
namespace,
|
|
1348
|
+
listed
|
|
1327
1349
|
};
|
|
1328
1350
|
}
|
|
1329
1351
|
async ensureRegistries() {
|
|
1330
1352
|
const found = await this.registries();
|
|
1331
|
-
if (!found.registries.dockerHub) throw new Error(
|
|
1353
|
+
if (!found.registries.dockerHub) throw new Error(`Could not find the public Docker Hub registry among the registries Bunny returned (${found.listed.join(", ") || "none"}). Please report this at https://github.com/dwarsbit/django-stack/issues.`);
|
|
1332
1354
|
if (!found.registries.bunny) {
|
|
1333
1355
|
const created = await this.api.mc("POST", "/registries", {
|
|
1334
1356
|
displayName: "Bunny Container Registry",
|
|
@@ -3978,6 +4000,24 @@ function checkComputer(checks = checkTools()) {
|
|
|
3978
4000
|
});
|
|
3979
4001
|
return !checks.some((check) => !check.ok && !check.warning);
|
|
3980
4002
|
}
|
|
4003
|
+
/**
|
|
4004
|
+
* Docker keeps the volumes of a deleted project. A new project with the same
|
|
4005
|
+
* name would start on the old database, whose password is not the new one in
|
|
4006
|
+
* .env: "password authentication failed for user app". Offers to remove them.
|
|
4007
|
+
*/
|
|
4008
|
+
async function handleLeftovers(name, interactive) {
|
|
4009
|
+
const volumes = leftoverVolumes(name);
|
|
4010
|
+
if (!volumes.length) return;
|
|
4011
|
+
const removeCommand = command("docker compose down --volumes");
|
|
4012
|
+
p.log.warn(`Docker still has the data of an earlier project named ${pc.bold(name)} (${volumes.join(", ")}). The new project gets a new database password, so it cannot start on that database.`);
|
|
4013
|
+
if (interactive && unwrap(await p.confirm({
|
|
4014
|
+
message: `Remove the data of the earlier ${name}? Its database and uploads are deleted for good.`,
|
|
4015
|
+
initialValue: false
|
|
4016
|
+
}))) {
|
|
4017
|
+
if (removeLeftovers(name, volumes)) p.log.success(`Removed the data of the earlier ${name}.`);
|
|
4018
|
+
else fail("Could not remove it. Stop what uses it, or choose another --name.");
|
|
4019
|
+
} else p.log.warn(`Run ${removeCommand} in the new project before you start it, or choose another --name.`);
|
|
4020
|
+
}
|
|
3981
4021
|
async function init(directoryArgument, options) {
|
|
3982
4022
|
const interactive = isInteractive(options.yes);
|
|
3983
4023
|
intro("Create a project");
|
|
@@ -3999,6 +4039,7 @@ async function init(directoryArgument, options) {
|
|
|
3999
4039
|
validate: (value) => validateProjectName(value ?? "")
|
|
4000
4040
|
}));
|
|
4001
4041
|
}
|
|
4042
|
+
if (dockerReady) await handleLeftovers(name, interactive);
|
|
4002
4043
|
const stack = await chooseStack(options, interactive);
|
|
4003
4044
|
if (!applies("analytics", stack) && stack.analytics !== "off") {
|
|
4004
4045
|
if (options.fromCli("analytics")) fail("--analytics umami needs a frontend: a headless project has no pages to count.");
|
|
@@ -5066,6 +5107,12 @@ function nodeModulesVolume(project) {
|
|
|
5066
5107
|
return;
|
|
5067
5108
|
}
|
|
5068
5109
|
}
|
|
5110
|
+
/** The backend packages `dstack update` upgrades without `--all`. */
|
|
5111
|
+
const BACKEND_PACKAGES = [
|
|
5112
|
+
"django-stack",
|
|
5113
|
+
"django-headless",
|
|
5114
|
+
"django-content-studio"
|
|
5115
|
+
];
|
|
5069
5116
|
/** `dstack update`: newer images, newer DjangoStack packages, and a restart of what was running. */
|
|
5070
5117
|
async function update(options) {
|
|
5071
5118
|
const project = open(options);
|
|
@@ -5074,16 +5121,12 @@ async function update(options) {
|
|
|
5074
5121
|
const running = targets.filter((target) => runningServices(target).length > 0);
|
|
5075
5122
|
const lock = join(project.directory, "backend", "uv.lock");
|
|
5076
5123
|
if (existsSync(lock) && has("uv")) {
|
|
5077
|
-
const args = options.all ? ["lock", "--upgrade"] : [
|
|
5078
|
-
"lock",
|
|
5079
|
-
"--upgrade-package",
|
|
5080
|
-
"django-stack"
|
|
5081
|
-
];
|
|
5124
|
+
const args = options.all ? ["lock", "--upgrade"] : ["lock", ...BACKEND_PACKAGES.flatMap((name) => ["--upgrade-package", name])];
|
|
5082
5125
|
if (spawnSync("uv", args, {
|
|
5083
5126
|
cwd: join(project.directory, "backend"),
|
|
5084
5127
|
stdio: "inherit"
|
|
5085
5128
|
}).status !== 0) fail("uv could not update backend/uv.lock.");
|
|
5086
|
-
p.log.success(options.all ? "Updated backend/uv.lock" :
|
|
5129
|
+
p.log.success(options.all ? "Updated backend/uv.lock" : `Updated ${BACKEND_PACKAGES.join(", ")} in backend/uv.lock`);
|
|
5087
5130
|
} else if (existsSync(lock)) p.log.warn("uv is not installed, so backend/uv.lock stays as it is: https://docs.astral.sh/uv/");
|
|
5088
5131
|
if (frontendPackageManager(project.directory)) {
|
|
5089
5132
|
const lockfile = updateFrontendLock(project.directory, options.all);
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
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,
|
|
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, lt as parseEnv, J as pendingChoices, r as productionEnv, Y as resolvePreset, nt as runDoctor, m as scaffold, dt as setEnvValues, h as setNotificationsApp, _ as toProjectName, b as usesValkey, i as validateDomain, x as validateProjectName } from "./production-lgZRSpyw.js";
|
|
@@ -117,6 +117,40 @@ function dockerRunning(runCommand = run) {
|
|
|
117
117
|
"{{.Server.Version}}"
|
|
118
118
|
]));
|
|
119
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* The Docker volumes of an earlier Compose project with this name, such as
|
|
122
|
+
* `<name>_pgdata`. A new project with the same name would reuse them, and
|
|
123
|
+
* Postgres keeps the password of the data it was created with.
|
|
124
|
+
*/
|
|
125
|
+
function leftoverVolumes(name, runCommand = run) {
|
|
126
|
+
return (runCommand("docker", [
|
|
127
|
+
"volume",
|
|
128
|
+
"ls",
|
|
129
|
+
"--quiet",
|
|
130
|
+
"--filter",
|
|
131
|
+
`label=com.docker.compose.project=${name}`
|
|
132
|
+
]) ?? "").split("\n").filter(Boolean);
|
|
133
|
+
}
|
|
134
|
+
/** Removes the containers and volumes of the Compose project with this name. Returns whether it worked. */
|
|
135
|
+
function removeLeftovers(name, volumes, runCommand = run) {
|
|
136
|
+
const containers = (runCommand("docker", [
|
|
137
|
+
"ps",
|
|
138
|
+
"--all",
|
|
139
|
+
"--quiet",
|
|
140
|
+
"--filter",
|
|
141
|
+
`label=com.docker.compose.project=${name}`
|
|
142
|
+
]) ?? "").split("\n").filter(Boolean);
|
|
143
|
+
if (containers.length && runCommand("docker", [
|
|
144
|
+
"rm",
|
|
145
|
+
"--force",
|
|
146
|
+
...containers
|
|
147
|
+
]) === void 0) return false;
|
|
148
|
+
return runCommand("docker", [
|
|
149
|
+
"volume",
|
|
150
|
+
"rm",
|
|
151
|
+
...volumes
|
|
152
|
+
]) !== void 0;
|
|
153
|
+
}
|
|
120
154
|
/** Docker and Docker Compose, which run the project. */
|
|
121
155
|
function checkDocker({ run: runCommand = run, platform = process.platform } = {}) {
|
|
122
156
|
const client = parseVersion(runCommand("docker", ["--version"]));
|
|
@@ -1746,4 +1780,4 @@ function productionEnv(devEnv, options) {
|
|
|
1746
1780
|
todo
|
|
1747
1781
|
};
|
|
1748
1782
|
}
|
|
1749
|
-
export { describeMove as $, activeProject as A, DEFAULT_STACK as B, CLI_VERSION as C, CAPTCHA_APP as D, generateSecret as E, projectStack as F, applies as G, NODE_FRONTENDS as H, projectUrls as I, pendingChoices as J, decodePreset as K, readLocalRun as L, composeOutput as M, findProjectDirectory as N, RUN_DOMAIN as O, parseProfiles as P, chooseFreePorts as Q, readProject as R, validateTimeZone as S, generateProjectSecrets as T, PROMPT_ORDER as U, NAMED_PRESETS as V, STACK_OPTIONS as W, stackOption as X, resolvePreset as Y, DEV_PORTS as Z, toProjectName as _, FRONTENDS as a, dockerRunning as at, usesValkey as b, frontendPackageManager as c,
|
|
1783
|
+
export { describeMove as $, activeProject as A, DEFAULT_STACK as B, CLI_VERSION as C, CAPTCHA_APP as D, generateSecret as E, projectStack as F, applies as G, NODE_FRONTENDS as H, projectUrls as I, pendingChoices as J, decodePreset as K, readLocalRun as L, composeOutput as M, findProjectDirectory as N, RUN_DOMAIN as O, parseProfiles as P, chooseFreePorts as Q, readProject as R, validateTimeZone as S, generateProjectSecrets as T, PROMPT_ORDER as U, NAMED_PRESETS as V, STACK_OPTIONS as W, stackOption as X, resolvePreset as Y, DEV_PORTS as Z, toProjectName as _, FRONTENDS as a, dockerRunning as at, usesValkey as b, frontendPackageManager as c, removeLeftovers as ct, lockFrontend as d, setEnvValues as dt, envPorts as et, normalizeDatabaseUrl as f, timeZones as g, setNotificationsApp as h, validateDomain as i, dockerNotRunning as it, compose as j, RUN_ENV as k, localS3 as l, parseEnv as lt, scaffold as m, postgresUrl as n, runDoctor as nt, LOCAL_S3 as o, isDockerDown as ot, relativeDirectory as p, encodePreset as q, productionEnv as r, checkTools as rt, cacheEnv as s, leftoverVolumes as st, missingS3 as t, portValues as tt, lockBackend as u, quoteEnvValue as ut, umamiEnv as v, PACKAGE_NAME as w, validateProjectName as x, updateFrontendLock as y, runningServices as z };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@django-stack/cli",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.2",
|
|
4
4
|
"description": "The production-ready batteries included Django stack. Create and manage DjangoStack projects: a Django backend, a frontend, Postgres and Docker Compose in one command.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Leon van der Grient",
|
|
@@ -5,7 +5,7 @@ description = "The Django backend of {{project_name}}."
|
|
|
5
5
|
requires-python = ">=3.12"
|
|
6
6
|
dependencies = [
|
|
7
7
|
"Django>=6.1,<6.2",
|
|
8
|
-
"django-stack[server,s3]>=0.12.
|
|
8
|
+
"django-stack[server,s3]>=0.12.2",
|
|
9
9
|
"django-headless[schema]>=1.1.1",
|
|
10
10
|
"django-content-studio[images,huey]>=1.11.0",
|
|
11
11
|
]
|
package/templates/base/AGENTS.md
CHANGED
|
@@ -19,6 +19,28 @@ There is no Node and no frontend service: the pages read the content with
|
|
|
19
19
|
the ORM.
|
|
20
20
|
<!-- @end -->
|
|
21
21
|
|
|
22
|
+
## Skills
|
|
23
|
+
|
|
24
|
+
Django Headless and Django Content Studio each publish an agent skill. Read the skill before you change what it
|
|
25
|
+
covers, and use the options it shows instead of writing your own
|
|
26
|
+
serializers, views or admin code:
|
|
27
|
+
|
|
28
|
+
- Django Headless, for the API (`@expose()`, permissions, filters, the
|
|
29
|
+
TypeScript client):
|
|
30
|
+
https://raw.githubusercontent.com/dwarsbit/django-headless/main/skills/django-headless/SKILL.md
|
|
31
|
+
- Django Content Studio, for the admin (`ModelStudio`, the edit page,
|
|
32
|
+
widgets, the dashboard, the media library):
|
|
33
|
+
https://djangocontentstudio.org/skills/django-content-studio/SKILL.md
|
|
34
|
+
|
|
35
|
+
Their full documentation is available as Markdown at
|
|
36
|
+
https://djangoheadless.org/llms.txt and
|
|
37
|
+
https://djangocontentstudio.org/llms.txt. If a skill is installed in your
|
|
38
|
+
skills folder, such as `.claude/skills/`, it takes the place of the link.
|
|
39
|
+
|
|
40
|
+
The skills follow the latest release; `backend/uv.lock` pins the installed
|
|
41
|
+
one. When a skill shows an option the project does not have, upgrade both
|
|
42
|
+
packages with `npx @django-stack/cli update` instead of working around it.
|
|
43
|
+
|
|
22
44
|
## Layout
|
|
23
45
|
|
|
24
46
|
```
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@django-headless/client": "^1.1.1",
|
|
16
|
-
"@django-stack/next": "^0.12.
|
|
17
|
-
"@django-stack/react": "^0.12.
|
|
18
|
-
"@django-stack/utils": "^0.12.
|
|
16
|
+
"@django-stack/next": "^0.12.2",
|
|
17
|
+
"@django-stack/react": "^0.12.2",
|
|
18
|
+
"@django-stack/utils": "^0.12.2",
|
|
19
19
|
"next": "^16.4.4",
|
|
20
20
|
"react": "^19.3.1",
|
|
21
21
|
"react-dom": "^19.3.1",
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@django-headless/client": "^1.1.1",
|
|
18
|
-
"@django-stack/utils": "^0.12.
|
|
19
|
-
"@django-stack/vue": "^0.12.
|
|
18
|
+
"@django-stack/utils": "^0.12.2",
|
|
19
|
+
"@django-stack/vue": "^0.12.2",
|
|
20
20
|
"nuxt": "^4.6.3",
|
|
21
21
|
"nuxt-security": "^2.7.0",
|
|
22
22
|
"vue": "^3.5.44",
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@django-headless/client": "^1.1.1",
|
|
22
|
-
"@django-stack/svelte": "^0.12.
|
|
23
|
-
"@django-stack/utils": "^0.12.
|
|
22
|
+
"@django-stack/svelte": "^0.12.2",
|
|
23
|
+
"@django-stack/utils": "^0.12.2"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@sveltejs/adapter-node": "^6.0.1",
|