pagelathe 0.1.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/LICENSE +202 -0
- package/dist/bin.js +10 -0
- package/dist/chunk-LRG7WLGI.js +1020 -0
- package/dist/index.js +9 -0
- package/dist/registry/app/astro.config.mjs +7 -0
- package/dist/registry/app/package.json +29 -0
- package/dist/registry/app/public/favicon.svg +4 -0
- package/dist/registry/app/scripts/vendor-sections.mjs +50 -0
- package/dist/registry/app/src/components/SectionRenderer.astro +23 -0
- package/dist/registry/app/src/components/sections/.gitkeep +0 -0
- package/dist/registry/app/src/content/_schema.ts +27 -0
- package/dist/registry/app/src/content/landing/index.yaml +135 -0
- package/dist/registry/app/src/content.config.ts +10 -0
- package/dist/registry/app/src/layouts/Base.astro +24 -0
- package/dist/registry/app/src/pages/index.astro +13 -0
- package/dist/registry/app/src/styles/global.css +20 -0
- package/dist/registry/app/tsconfig.json +1 -0
- package/dist/registry/sections/codeDemo/schema.ts +46 -0
- package/dist/registry/sections/codeDemo/section.astro +55 -0
- package/dist/registry/sections/features/schema.ts +44 -0
- package/dist/registry/sections/features/section.astro +25 -0
- package/dist/registry/sections/finalCta/schema.ts +30 -0
- package/dist/registry/sections/finalCta/section.astro +18 -0
- package/dist/registry/sections/footer/schema.ts +44 -0
- package/dist/registry/sections/footer/section.astro +29 -0
- package/dist/registry/sections/header/schema.ts +35 -0
- package/dist/registry/sections/header/section.astro +28 -0
- package/dist/registry/sections/hero/schema.ts +50 -0
- package/dist/registry/sections/hero/section.astro +56 -0
- package/dist/registry/sections/package.json +23 -0
- package/dist/registry/sections/pricing/schema.ts +55 -0
- package/dist/registry/sections/pricing/section.astro +29 -0
- package/dist/registry/sections/src/a11y.ts +34 -0
- package/dist/registry/sections/src/env.d.ts +7 -0
- package/dist/registry/sections/src/manifest.ts +41 -0
- package/dist/registry/sections/src/page.ts +48 -0
- package/dist/registry/sections/src/registry.ts +62 -0
- package/dist/registry/sections/src/render-harness.ts +11 -0
- package/dist/registry/sections/test/chrome.test.ts +29 -0
- package/dist/registry/sections/test/codeDemo.test.ts +16 -0
- package/dist/registry/sections/test/features.test.ts +18 -0
- package/dist/registry/sections/test/finalCta.test.ts +15 -0
- package/dist/registry/sections/test/hero.test.ts +30 -0
- package/dist/registry/sections/test/page.test.ts +58 -0
- package/dist/registry/sections/test/pricing.test.ts +22 -0
- package/dist/registry/sections/test/registry-complete.test.ts +38 -0
- package/dist/registry/sections/test/registry.test.ts +12 -0
- package/dist/registry/sections/test/validate.test.ts +29 -0
- package/dist/registry/sections/tsconfig.json +8 -0
- package/dist/registry/sections/vitest.config.ts +7 -0
- package/package.json +56 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { propsSchema, manifest } from "../pricing/schema.js";
|
|
3
|
+
import Pricing from "../pricing/section.astro";
|
|
4
|
+
import { renderToHtml } from "../src/render-harness.js";
|
|
5
|
+
|
|
6
|
+
describe("pricing", () => {
|
|
7
|
+
it("validates defaultProps and requires >=1 feature per tier", () => {
|
|
8
|
+
expect(propsSchema.safeParse(manifest.defaultProps).success).toBe(true);
|
|
9
|
+
const bad = {
|
|
10
|
+
heading: "H",
|
|
11
|
+
tiers: [
|
|
12
|
+
{ name: "x", price: "$0", description: "d", features: [], cta: { label: "a", href: "#" } },
|
|
13
|
+
],
|
|
14
|
+
};
|
|
15
|
+
expect(propsSchema.safeParse(bad).success).toBe(false);
|
|
16
|
+
});
|
|
17
|
+
it("renders tier names and prices", async () => {
|
|
18
|
+
const html = await renderToHtml(Pricing, propsSchema.parse(manifest.defaultProps));
|
|
19
|
+
expect(html).toContain("Open source");
|
|
20
|
+
expect(html).toContain("$29");
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { listSections, getSection, pageDocumentSchema } from "../src/registry.js";
|
|
3
|
+
|
|
4
|
+
const EXPECTED = ["header", "hero", "features", "codeDemo", "pricing", "finalCta", "footer"];
|
|
5
|
+
|
|
6
|
+
describe("registry completeness", () => {
|
|
7
|
+
it("registers exactly the M2 section set", () => {
|
|
8
|
+
const types = listSections()
|
|
9
|
+
.map((s) => s.manifest.type)
|
|
10
|
+
.sort();
|
|
11
|
+
expect(types).toEqual([...EXPECTED].sort());
|
|
12
|
+
});
|
|
13
|
+
it("every manifest.defaultProps validates against its own propsSchema", () => {
|
|
14
|
+
for (const s of listSections()) {
|
|
15
|
+
const r = s.propsSchema.safeParse(s.manifest.defaultProps);
|
|
16
|
+
expect(r.success, `${s.manifest.type} defaultProps invalid`).toBe(true);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
it("every manifest.componentFile is section.astro and type matches dir", () => {
|
|
20
|
+
for (const s of listSections()) {
|
|
21
|
+
expect(s.manifest.componentFile).toBe("section.astro");
|
|
22
|
+
expect(getSection(s.manifest.type)).toBe(s);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
it("pageDocumentSchema accepts a full document built from defaultProps", () => {
|
|
26
|
+
const doc = {
|
|
27
|
+
meta: { title: "T", description: "D" },
|
|
28
|
+
theme: { tokens: {} },
|
|
29
|
+
archetype: "sdk-infra",
|
|
30
|
+
sections: listSections().map((s, i) => ({
|
|
31
|
+
type: s.manifest.type,
|
|
32
|
+
id: `${s.manifest.type}-${i}`,
|
|
33
|
+
props: s.manifest.defaultProps,
|
|
34
|
+
})),
|
|
35
|
+
};
|
|
36
|
+
expect(pageDocumentSchema.safeParse(doc).success).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { listSections, getSection } from "../src/registry.js";
|
|
3
|
+
|
|
4
|
+
describe("registry (Task 1 baseline)", () => {
|
|
5
|
+
it("starts empty and grows as sections register", () => {
|
|
6
|
+
// Task 1: no sections yet. Later tasks add entries; this asserts the API shape.
|
|
7
|
+
expect(Array.isArray(listSections())).toBe(true);
|
|
8
|
+
});
|
|
9
|
+
it("getSection returns undefined for unknown types", () => {
|
|
10
|
+
expect(getSection("does-not-exist")).toBeUndefined();
|
|
11
|
+
});
|
|
12
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { listSections } from "../src/registry.js";
|
|
6
|
+
|
|
7
|
+
const sectionsDir = fileURLToPath(new URL("..", import.meta.url));
|
|
8
|
+
|
|
9
|
+
describe("registry validation gate", () => {
|
|
10
|
+
it("every section has a real component file and valid defaults", () => {
|
|
11
|
+
const errors: string[] = [];
|
|
12
|
+
for (const s of listSections()) {
|
|
13
|
+
const dir = join(sectionsDir, s.manifest.type);
|
|
14
|
+
const file = join(dir, "section.astro");
|
|
15
|
+
// type must map to a real <type>/ directory carrying both files (ties manifest.type to its dir name)
|
|
16
|
+
if (!existsSync(file)) errors.push(`${s.manifest.type}: missing section.astro`);
|
|
17
|
+
if (!existsSync(join(dir, "schema.ts"))) errors.push(`${s.manifest.type}: missing schema.ts`);
|
|
18
|
+
if (s.manifest.componentFile !== "section.astro")
|
|
19
|
+
errors.push(`${s.manifest.type}: bad componentFile`);
|
|
20
|
+
if (!s.propsSchema.safeParse(s.manifest.defaultProps).success)
|
|
21
|
+
errors.push(`${s.manifest.type}: bad defaults`);
|
|
22
|
+
// entrySchema must accept a well-formed entry built from the section's own defaults
|
|
23
|
+
const entry = { type: s.manifest.type, id: "x", props: s.manifest.defaultProps };
|
|
24
|
+
if (!s.entrySchema.safeParse(entry).success)
|
|
25
|
+
errors.push(`${s.manifest.type}: entrySchema rejects defaults`);
|
|
26
|
+
}
|
|
27
|
+
expect(errors).toEqual([]);
|
|
28
|
+
});
|
|
29
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pagelathe",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AI landing-page builder for technical founders — bring your key, ship a fast static Astro page in minutes.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"author": "Göktuğ Aşcı",
|
|
8
|
+
"homepage": "https://github.com/gasci/pagelathe#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/gasci/pagelathe.git",
|
|
12
|
+
"directory": "packages/cli"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/gasci/pagelathe/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"landing-page",
|
|
19
|
+
"astro",
|
|
20
|
+
"tailwindcss",
|
|
21
|
+
"ai",
|
|
22
|
+
"openrouter",
|
|
23
|
+
"static-site",
|
|
24
|
+
"cli",
|
|
25
|
+
"developer-tools",
|
|
26
|
+
"generator"
|
|
27
|
+
],
|
|
28
|
+
"bin": {
|
|
29
|
+
"pagelathe": "./dist/bin.js"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist"
|
|
33
|
+
],
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=20.11.0"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@clack/prompts": "^0.7.0",
|
|
39
|
+
"commander": "^12.1.0",
|
|
40
|
+
"yaml": "^2.6.0",
|
|
41
|
+
"zod": "^3.24.1",
|
|
42
|
+
"zod-to-json-schema": "^3.24.1"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"tsup": "^8.3.5",
|
|
46
|
+
"typescript": "^5.9.3",
|
|
47
|
+
"vitest": "^2.1.8",
|
|
48
|
+
"@pagelathe/sections": "0.0.0"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsup",
|
|
52
|
+
"dev": "tsup --watch",
|
|
53
|
+
"typecheck": "tsc --noEmit",
|
|
54
|
+
"test": "vitest run"
|
|
55
|
+
}
|
|
56
|
+
}
|