@django-stack/cli 0.0.1 → 0.2.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 +27 -3
- package/dist/bin.js +2282 -2
- package/dist/index.d.ts +252 -1
- package/dist/index.js +1 -4
- package/dist/stack-CTyqYWCV.js +800 -0
- package/package.json +15 -7
- package/templates/backend/.dockerignore +11 -0
- package/templates/backend/Dockerfile +45 -0
- package/templates/backend/config/__init__.py +0 -0
- package/templates/backend/config/asgi.py +7 -0
- package/templates/backend/config/settings.py +87 -0
- package/templates/backend/config/studio.py +20 -0
- package/templates/backend/config/urls.py +9 -0
- package/templates/backend/config/wsgi.py +7 -0
- package/templates/backend/conftest.py +7 -0
- package/templates/backend/content/__init__.py +0 -0
- package/templates/backend/content/admin.py +107 -0
- package/templates/backend/content/apps.py +6 -0
- package/templates/backend/content/blocks.py +51 -0
- package/templates/backend/content/migrations/0001_initial.py +418 -0
- package/templates/backend/content/migrations/__init__.py +0 -0
- package/templates/backend/content/models.py +234 -0
- package/templates/backend/content/seed.py +153 -0
- package/templates/backend/content/tests/__init__.py +0 -0
- package/templates/backend/content/tests/test_api.py +73 -0
- package/templates/backend/entrypoint.sh +27 -0
- package/templates/backend/manage.py +16 -0
- package/templates/backend/pyproject.toml +33 -0
- package/templates/backend/vendor/.gitkeep +0 -0
- package/templates/base/.github/workflows/deploy.yml +70 -0
- package/templates/base/AGENTS.md +79 -0
- package/templates/base/CLAUDE.md +1 -0
- package/templates/base/Caddyfile +51 -0
- package/templates/base/README.md +87 -0
- package/templates/base/SECURITY.md +12 -0
- package/templates/base/compose.yaml +282 -0
- package/templates/base/env.example +83 -0
- package/templates/base/gitignore +15 -0
- package/templates/base/package.json +21 -0
- package/templates/frontend-astro/.dockerignore +4 -0
- package/templates/frontend-astro/Dockerfile +32 -0
- package/templates/frontend-astro/astro.config.mjs +38 -0
- package/templates/frontend-astro/package.json +31 -0
- package/templates/frontend-astro/public/favicon.svg +1 -0
- package/templates/frontend-astro/src/components/Blocks.astro +32 -0
- package/templates/frontend-astro/src/components/ContactForm.astro +68 -0
- package/templates/frontend-astro/src/components/Image.astro +34 -0
- package/templates/frontend-astro/src/components/PostCard.astro +41 -0
- package/templates/frontend-astro/src/components/PostList.astro +39 -0
- package/templates/frontend-astro/src/components/RichText.astro +14 -0
- package/templates/frontend-astro/src/components/Seo.astro +20 -0
- package/templates/frontend-astro/src/components/blocks/CallToAction.astro +23 -0
- package/templates/frontend-astro/src/components/blocks/Image.astro +24 -0
- package/templates/frontend-astro/src/components/blocks/Quote.astro +10 -0
- package/templates/frontend-astro/src/components/blocks/Text.astro +8 -0
- package/templates/frontend-astro/src/layouts/Base.astro +122 -0
- package/templates/frontend-astro/src/lib/api.ts +24 -0
- package/templates/frontend-astro/src/lib/cache.ts +58 -0
- package/templates/frontend-astro/src/lib/headless.ts +344 -0
- package/templates/frontend-astro/src/middleware.ts +69 -0
- package/templates/frontend-astro/src/pages/404.astro +13 -0
- package/templates/frontend-astro/src/pages/[...slug].astro +21 -0
- package/templates/frontend-astro/src/pages/blog/[slug].astro +50 -0
- package/templates/frontend-astro/src/pages/blog/index.astro +15 -0
- package/templates/frontend-astro/src/pages/category/[slug].astro +25 -0
- package/templates/frontend-astro/src/pages/index.astro +28 -0
- package/templates/frontend-astro/src/pages/preview/exit.ts +7 -0
- package/templates/frontend-astro/src/pages/preview/index.ts +26 -0
- package/templates/frontend-astro/src/pages/revalidate.ts +19 -0
- package/templates/frontend-astro/src/pages/robots.txt.ts +7 -0
- package/templates/frontend-astro/src/pages/rss.xml.ts +19 -0
- package/templates/frontend-astro/src/pages/sitemap.xml.ts +29 -0
- package/templates/frontend-astro/src/pages/tag/[slug].astro +24 -0
- package/templates/frontend-astro/src/styles/global.css +101 -0
- package/templates/frontend-astro/test/blocks.test.ts +42 -0
- package/templates/frontend-astro/test/cache.test.ts +22 -0
- package/templates/frontend-astro/tsconfig.json +5 -0
- package/templates/frontend-astro/vendor/.gitkeep +0 -0
- package/templates/frontend-astro/vitest.config.ts +8 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
import PostList from "../../components/PostList.astro";
|
|
3
|
+
import Base from "../../layouts/Base.astro";
|
|
4
|
+
import { getContent } from "../../lib/api";
|
|
5
|
+
|
|
6
|
+
const content = getContent(Astro.cookies);
|
|
7
|
+
const slug = Astro.params.slug ?? "";
|
|
8
|
+
const tag = await content.getTag(slug);
|
|
9
|
+
|
|
10
|
+
if (!tag) {
|
|
11
|
+
return Astro.rewrite("/404");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const page = Math.max(1, Number(Astro.url.searchParams.get("page")) || 1);
|
|
15
|
+
const result = await content.getPosts({ tag: slug, page, limit: 9 });
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
<Base page={{ title: `#${tag.name}` }}>
|
|
19
|
+
<section class="container">
|
|
20
|
+
<p class="muted">Tag</p>
|
|
21
|
+
<h1>#{tag.name}</h1>
|
|
22
|
+
<PostList result={result} base={`/tag/${slug}`} />
|
|
23
|
+
</section>
|
|
24
|
+
</Base>
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/* A small, plain stylesheet: replace it with your own design. */
|
|
2
|
+
:root {
|
|
3
|
+
--color-bg: #ffffff;
|
|
4
|
+
--color-text: #1d2420;
|
|
5
|
+
--color-muted: #5b6862;
|
|
6
|
+
--color-line: #e3e8e4;
|
|
7
|
+
--color-accent: #0c4b33;
|
|
8
|
+
--color-accent-soft: #e8f3ee;
|
|
9
|
+
--font-body: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
10
|
+
--width: 72rem;
|
|
11
|
+
--width-text: 44rem;
|
|
12
|
+
color-scheme: light dark;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@media (prefers-color-scheme: dark) {
|
|
16
|
+
:root {
|
|
17
|
+
--color-bg: #111614;
|
|
18
|
+
--color-text: #e6ebe8;
|
|
19
|
+
--color-muted: #a1aba6;
|
|
20
|
+
--color-line: #2a322e;
|
|
21
|
+
--color-accent: #6fcf9f;
|
|
22
|
+
--color-accent-soft: #1b2a23;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
*,
|
|
27
|
+
*::before,
|
|
28
|
+
*::after {
|
|
29
|
+
box-sizing: border-box;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
body {
|
|
33
|
+
margin: 0;
|
|
34
|
+
background: var(--color-bg);
|
|
35
|
+
color: var(--color-text);
|
|
36
|
+
font-family: var(--font-body);
|
|
37
|
+
line-height: 1.6;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
a {
|
|
41
|
+
color: var(--color-accent);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
img {
|
|
45
|
+
max-width: 100%;
|
|
46
|
+
height: auto;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.container {
|
|
50
|
+
width: min(100% - 2rem, var(--width));
|
|
51
|
+
margin-inline: auto;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.narrow {
|
|
55
|
+
width: min(100% - 2rem, var(--width-text));
|
|
56
|
+
margin-inline: auto;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.muted {
|
|
60
|
+
color: var(--color-muted);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.prose h2,
|
|
64
|
+
.prose h3 {
|
|
65
|
+
line-height: 1.25;
|
|
66
|
+
margin-top: 2em;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.prose blockquote {
|
|
70
|
+
margin: 2rem 0;
|
|
71
|
+
padding-left: 1.25rem;
|
|
72
|
+
border-left: 4px solid var(--color-accent);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.button {
|
|
76
|
+
display: inline-block;
|
|
77
|
+
padding: 0.6rem 1.2rem;
|
|
78
|
+
border-radius: 0.5rem;
|
|
79
|
+
background: var(--color-accent);
|
|
80
|
+
color: var(--color-bg);
|
|
81
|
+
font-weight: 600;
|
|
82
|
+
text-decoration: none;
|
|
83
|
+
border: 0;
|
|
84
|
+
cursor: pointer;
|
|
85
|
+
font-family: inherit;
|
|
86
|
+
font-size: inherit;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.grid {
|
|
90
|
+
display: grid;
|
|
91
|
+
gap: 2rem;
|
|
92
|
+
grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.preview-banner {
|
|
96
|
+
background: #fde68a;
|
|
97
|
+
color: #422006;
|
|
98
|
+
padding: 0.5rem 1rem;
|
|
99
|
+
text-align: center;
|
|
100
|
+
font-size: 0.9rem;
|
|
101
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { experimental_AstroContainer as AstroContainer } from "astro/container";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import Blocks from "../src/components/Blocks.astro";
|
|
4
|
+
|
|
5
|
+
const image = {
|
|
6
|
+
id: "1",
|
|
7
|
+
type: "image",
|
|
8
|
+
name: "Photo",
|
|
9
|
+
alt: "A photo",
|
|
10
|
+
url: "http://localhost:3000/media/photo",
|
|
11
|
+
width: 1600,
|
|
12
|
+
height: 900,
|
|
13
|
+
focal_point: null,
|
|
14
|
+
sizes: { content: { url: "http://localhost:3000/media/content.webp", width: 1200, height: 675 } },
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
describe("Blocks", () => {
|
|
18
|
+
it("renders every block type and skips unknown ones", async () => {
|
|
19
|
+
const container = await AstroContainer.create();
|
|
20
|
+
const html = await container.renderToString(Blocks, {
|
|
21
|
+
props: {
|
|
22
|
+
blocks: [
|
|
23
|
+
{ id: "a", type: "text", value: { body: "<p>Hello <strong>world</strong></p>" } },
|
|
24
|
+
{ id: "b", type: "image", value: { image, caption: "A caption" } },
|
|
25
|
+
{ id: "c", type: "quote", value: { text: "Quoted", attribution: "Someone" } },
|
|
26
|
+
{
|
|
27
|
+
id: "d",
|
|
28
|
+
type: "call_to_action",
|
|
29
|
+
value: { title: "Act", text: "", button_label: "Go", button_url: "/go" },
|
|
30
|
+
},
|
|
31
|
+
{ id: "e", type: "unknown", value: {} },
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
expect(html).toContain("<strong>world</strong>");
|
|
36
|
+
expect(html).toContain('src="http://localhost:3000/media/content.webp"');
|
|
37
|
+
expect(html).toContain('alt="A photo"');
|
|
38
|
+
expect(html).toContain("A caption");
|
|
39
|
+
expect(html).toContain("Quoted");
|
|
40
|
+
expect(html).toContain('href="/go"');
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { getCached, purgeAll, store } from "../src/lib/cache";
|
|
3
|
+
|
|
4
|
+
describe("page cache", () => {
|
|
5
|
+
it("stores, serves and purges pages", async () => {
|
|
6
|
+
purgeAll();
|
|
7
|
+
expect(getCached("/about")).toBeNull();
|
|
8
|
+
const response = await store(
|
|
9
|
+
"/about",
|
|
10
|
+
new Response("<h1>About</h1>", { headers: { "Content-Type": "text/html" } }),
|
|
11
|
+
);
|
|
12
|
+
expect(response.headers.get("X-Cache")).toBe("MISS");
|
|
13
|
+
expect(await response.text()).toBe("<h1>About</h1>");
|
|
14
|
+
|
|
15
|
+
const hit = getCached("/about");
|
|
16
|
+
expect(hit?.headers.get("X-Cache")).toBe("HIT");
|
|
17
|
+
expect(await hit?.text()).toBe("<h1>About</h1>");
|
|
18
|
+
|
|
19
|
+
expect(purgeAll()).toBe(1);
|
|
20
|
+
expect(getCached("/about")).toBeNull();
|
|
21
|
+
});
|
|
22
|
+
});
|
|
File without changes
|