create-cartwright 0.1.0 → 2.0.0-beta.1

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 (51) hide show
  1. package/dist/approve.d.ts +2 -0
  2. package/dist/approve.js +16 -0
  3. package/dist/approve.js.map +1 -0
  4. package/dist/approve.test.d.ts +1 -0
  5. package/dist/approve.test.js +23 -0
  6. package/dist/approve.test.js.map +1 -0
  7. package/dist/brief.d.ts +26 -0
  8. package/dist/brief.js +25 -0
  9. package/dist/brief.js.map +1 -0
  10. package/dist/brief.test.d.ts +1 -0
  11. package/dist/brief.test.js +23 -0
  12. package/dist/brief.test.js.map +1 -0
  13. package/dist/generate/index.d.ts +4 -0
  14. package/dist/generate/index.js +42 -0
  15. package/dist/generate/index.js.map +1 -0
  16. package/dist/generate/index.test.d.ts +1 -0
  17. package/dist/generate/index.test.js +47 -0
  18. package/dist/generate/index.test.js.map +1 -0
  19. package/dist/index.js +56 -78
  20. package/dist/index.js.map +1 -1
  21. package/dist/inject.d.ts +2 -0
  22. package/dist/inject.js +21 -0
  23. package/dist/inject.js.map +1 -0
  24. package/dist/inject.test.d.ts +1 -0
  25. package/dist/inject.test.js +37 -0
  26. package/dist/inject.test.js.map +1 -0
  27. package/dist/interview.d.ts +8 -0
  28. package/dist/interview.js +73 -0
  29. package/dist/interview.js.map +1 -0
  30. package/dist/interview.test.d.ts +1 -0
  31. package/dist/interview.test.js +50 -0
  32. package/dist/interview.test.js.map +1 -0
  33. package/dist/key-step.d.ts +12 -0
  34. package/dist/key-step.js +22 -0
  35. package/dist/key-step.js.map +1 -0
  36. package/dist/key-step.test.d.ts +1 -0
  37. package/dist/key-step.test.js +56 -0
  38. package/dist/key-step.test.js.map +1 -0
  39. package/dist/llm.d.ts +2 -0
  40. package/dist/llm.js +51 -0
  41. package/dist/llm.js.map +1 -0
  42. package/dist/llm.test.d.ts +1 -0
  43. package/dist/llm.test.js +32 -0
  44. package/dist/llm.test.js.map +1 -0
  45. package/dist/scaffold.d.ts +10 -0
  46. package/dist/scaffold.js +86 -0
  47. package/dist/scaffold.js.map +1 -0
  48. package/dist/scaffold.test.d.ts +1 -0
  49. package/dist/scaffold.test.js +14 -0
  50. package/dist/scaffold.test.js.map +1 -0
  51. package/package.json +5 -3
@@ -0,0 +1,2 @@
1
+ import { type ShopBrief } from "./brief";
2
+ export declare function summarizeBuild(brief: ShopBrief): string;
@@ -0,0 +1,16 @@
1
+ import pc from "picocolors";
2
+ export function summarizeBuild(brief) {
3
+ const lines = [
4
+ pc.bold(`${brief.storeName} (${brief.slug})`),
5
+ pc.dim(brief.tagline),
6
+ "",
7
+ `${pc.bold("Sælger:")} ${brief.sells}`,
8
+ `${pc.bold("Målgruppe:")} ${brief.audience}`,
9
+ `${pc.bold("Tone:")} ${brief.tone}`,
10
+ "",
11
+ `${pc.bold("Katalog:")} ${brief.categories.length} kategorier, ${brief.products.length} produkter`,
12
+ `${pc.bold("Palette:")} ${brief.palette.primary} (Primær) / ${brief.palette.background} (Baggrund)`,
13
+ ];
14
+ return lines.join("\n");
15
+ }
16
+ //# sourceMappingURL=approve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approve.js","sourceRoot":"","sources":["../src/approve.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAG5B,MAAM,UAAU,cAAc,CAAC,KAAgB;IAC7C,MAAM,KAAK,GAAG;QACZ,EAAE,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,IAAI,GAAG,CAAC;QAC7C,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC;QACrB,EAAE;QACF,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,KAAK,CAAC,KAAK,EAAE;QACtC,GAAG,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,KAAK,CAAC,QAAQ,EAAE;QAC5C,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,IAAI,EAAE;QACnC,EAAE;QACF,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,gBAAgB,KAAK,CAAC,QAAQ,CAAC,MAAM,YAAY;QAClG,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,eAAe,KAAK,CAAC,OAAO,CAAC,UAAU,aAAa;KACpG,CAAC;IACF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { summarizeBuild } from "./approve";
3
+ describe("summarizeBuild", () => {
4
+ it("formaterer brief pænt til clack note", () => {
5
+ const brief = {
6
+ storeName: "Test Shop",
7
+ slug: "test-shop",
8
+ tagline: "Test",
9
+ sells: "Testing",
10
+ audience: "Testers",
11
+ tone: "Fun",
12
+ country: "DK",
13
+ currency: "DKK",
14
+ palette: { primary: "#000000", background: "#ffffff" },
15
+ categories: [{ name: "Cats", slug: "cats" }],
16
+ products: [{ name: "Cat 1", categorySlug: "cats", priceMinor: 1000, blurb: "A cat" }]
17
+ };
18
+ const lines = summarizeBuild(brief);
19
+ expect(lines).toContain("Test Shop (test-shop)");
20
+ expect(lines).toContain("1 kategorier, 1 produkter");
21
+ });
22
+ });
23
+ //# sourceMappingURL=approve.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approve.test.js","sourceRoot":"","sources":["../src/approve.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAG3C,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,KAAK,GAAc;YACvB,SAAS,EAAE,WAAW;YACtB,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,MAAM;YACf,KAAK,EAAE,SAAS;YAChB,QAAQ,EAAE,SAAS;YACnB,IAAI,EAAE,KAAK;YACX,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,KAAK;YACf,OAAO,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE;YACtD,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;YAC5C,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;SACtF,CAAC;QAEF,MAAM,KAAK,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;QACpC,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,uBAAuB,CAAC,CAAC;QACjD,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,2BAA2B,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,26 @@
1
+ import { z } from "zod";
2
+ export declare const shopBriefSchema: z.ZodObject<{
3
+ storeName: z.ZodString;
4
+ slug: z.ZodString;
5
+ tagline: z.ZodString;
6
+ sells: z.ZodString;
7
+ audience: z.ZodString;
8
+ tone: z.ZodString;
9
+ country: z.ZodString;
10
+ currency: z.ZodString;
11
+ palette: z.ZodObject<{
12
+ primary: z.ZodString;
13
+ background: z.ZodString;
14
+ }, z.core.$strip>;
15
+ categories: z.ZodArray<z.ZodObject<{
16
+ name: z.ZodString;
17
+ slug: z.ZodString;
18
+ }, z.core.$strip>>;
19
+ products: z.ZodArray<z.ZodObject<{
20
+ name: z.ZodString;
21
+ categorySlug: z.ZodString;
22
+ priceMinor: z.ZodNumber;
23
+ blurb: z.ZodString;
24
+ }, z.core.$strip>>;
25
+ }, z.core.$strip>;
26
+ export type ShopBrief = z.infer<typeof shopBriefSchema>;
package/dist/brief.js ADDED
@@ -0,0 +1,25 @@
1
+ import { z } from "zod";
2
+ const slug = z.string().regex(/^[a-z][a-z0-9-]*$/);
3
+ const hex = z.string().regex(/^#[0-9a-fA-F]{6}$/);
4
+ export const shopBriefSchema = z.object({
5
+ storeName: z.string().min(1),
6
+ slug,
7
+ tagline: z.string().min(1),
8
+ sells: z.string().min(1),
9
+ audience: z.string().min(1),
10
+ tone: z.string().min(1),
11
+ country: z.string().regex(/^[A-Z]{2}$/),
12
+ currency: z.string().regex(/^[A-Z]{3}$/),
13
+ palette: z.object({ primary: hex, background: hex }),
14
+ categories: z.array(z.object({ name: z.string().min(1), slug })).min(1).max(8),
15
+ products: z
16
+ .array(z.object({
17
+ name: z.string().min(1),
18
+ categorySlug: z.string().min(1),
19
+ priceMinor: z.number().int().positive(),
20
+ blurb: z.string().min(1),
21
+ }))
22
+ .min(1)
23
+ .max(40),
24
+ });
25
+ //# sourceMappingURL=brief.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brief.js","sourceRoot":"","sources":["../src/brief.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC;AACnD,MAAM,GAAG,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC;AAElD,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5B,IAAI;IACJ,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACxB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC;IACvC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC;IACxC,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC;IACpD,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9E,QAAQ,EAAE,CAAC;SACR,KAAK,CACJ,CAAC,CAAC,MAAM,CAAC;QACP,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QACvB,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QAC/B,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;QACvC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;KACzB,CAAC,CACH;SACA,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,EAAE,CAAC;CACX,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { shopBriefSchema } from "./brief";
3
+ const valid = {
4
+ storeName: "Nordkaffe", slug: "nordkaffe", tagline: "Specialkaffe fra Aarhus",
5
+ sells: "Ristet specialkaffe og bryggeudstyr", audience: "Hjemme-baristaer",
6
+ tone: "Afslappet og kyndig", country: "DK", currency: "DKK",
7
+ palette: { primary: "#3b2417", background: "#faf6f0" },
8
+ categories: [{ name: "Bønner", slug: "bonner" }],
9
+ products: [{ name: "Etiopien Yirgacheffe", categorySlug: "bonner", priceMinor: 9500, blurb: "Floral og lys." }],
10
+ };
11
+ describe("shopBriefSchema", () => {
12
+ it("accepterer et gyldigt brief", () => {
13
+ expect(shopBriefSchema.safeParse(valid).success).toBe(true);
14
+ });
15
+ it("afviser ugyldig hex-farve", () => {
16
+ const bad = { ...valid, palette: { ...valid.palette, primary: "brun" } };
17
+ expect(shopBriefSchema.safeParse(bad).success).toBe(false);
18
+ });
19
+ it("afviser tomt produkt-array", () => {
20
+ expect(shopBriefSchema.safeParse({ ...valid, products: [] }).success).toBe(false);
21
+ });
22
+ });
23
+ //# sourceMappingURL=brief.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brief.test.js","sourceRoot":"","sources":["../src/brief.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE1C,MAAM,KAAK,GAAG;IACZ,SAAS,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,yBAAyB;IAC7E,KAAK,EAAE,qCAAqC,EAAE,QAAQ,EAAE,kBAAkB;IAC1E,IAAI,EAAE,qBAAqB,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK;IAC3D,OAAO,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE;IACtD,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAChD,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,sBAAsB,EAAE,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC;CAChH,CAAC;AAEF,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,MAAM,CAAC,eAAe,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACnC,MAAM,GAAG,GAAG,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,EAAE,GAAG,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC;QACzE,MAAM,CAAC,eAAe,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,CAAC,eAAe,CAAC,SAAS,CAAC,EAAE,GAAG,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACpF,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { type ShopBrief } from "../brief";
2
+ export declare function generateThemeCss(brief: ShopBrief): string;
3
+ export declare function generatePromptModule(brief: ShopBrief): string;
4
+ export declare function generateSeedData(brief: ShopBrief): string;
@@ -0,0 +1,42 @@
1
+ export function generateThemeCss(brief) {
2
+ return `/**
3
+ * Theme: ${brief.storeName}
4
+ * Generated by create-cartwright
5
+ */
6
+ :root {
7
+ --color-accent: ${brief.palette.primary};
8
+ --color-cream: ${brief.palette.background};
9
+ --color-sand: ${brief.palette.background}; /* Fallback until more colors requested */
10
+ --color-ink: #1a1a1a;
11
+ --color-muted: #726d62;
12
+ --color-success: ${brief.palette.primary};
13
+ }
14
+ `;
15
+ }
16
+ export function generatePromptModule(brief) {
17
+ return `/**
18
+ * AI System Prompt: ${brief.storeName}
19
+ * Generated by create-cartwright
20
+ */
21
+ export const systemPrompt = \`
22
+ Du er AI-assistent for ${brief.storeName}.
23
+ Tagline: ${brief.tagline}
24
+ Vi sælger: ${brief.sells}
25
+ Målgruppe: ${brief.audience}
26
+ Tone of voice: ${brief.tone}
27
+ \`;
28
+ `;
29
+ }
30
+ export function generateSeedData(brief) {
31
+ return `/**
32
+ * Seed data: ${brief.storeName}
33
+ * Generated by create-cartwright
34
+ */
35
+ export const seedData = {
36
+ categories: ${JSON.stringify(brief.categories, null, 2)},
37
+ products: ${JSON.stringify(brief.products, null, 2)},
38
+ pages: []
39
+ };
40
+ `;
41
+ }
42
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/generate/index.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,gBAAgB,CAAC,KAAgB;IAC/C,OAAO;YACG,KAAK,CAAC,SAAS;;;;oBAIP,KAAK,CAAC,OAAO,CAAC,OAAO;mBACtB,KAAK,CAAC,OAAO,CAAC,UAAU;kBACzB,KAAK,CAAC,OAAO,CAAC,UAAU;;;qBAGrB,KAAK,CAAC,OAAO,CAAC,OAAO;;CAEzC,CAAC;AACF,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,KAAgB;IACnD,OAAO;uBACc,KAAK,CAAC,SAAS;;;;yBAIb,KAAK,CAAC,SAAS;WAC7B,KAAK,CAAC,OAAO;aACX,KAAK,CAAC,KAAK;aACX,KAAK,CAAC,QAAQ;iBACV,KAAK,CAAC,IAAI;;CAE1B,CAAC;AACF,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAgB;IAC/C,OAAO;gBACO,KAAK,CAAC,SAAS;;;;gBAIf,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;cAC3C,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;;;CAGpD,CAAC;AACF,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,47 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { generateThemeCss, generatePromptModule } from "./index";
3
+ const mockBrief = {
4
+ storeName: "Test",
5
+ slug: "test",
6
+ tagline: "Test tag",
7
+ sells: "Ting",
8
+ audience: "Folk",
9
+ tone: "Sjov",
10
+ country: "DK",
11
+ currency: "DKK",
12
+ palette: { primary: "#112233", background: "#ffeedd" },
13
+ categories: [],
14
+ products: []
15
+ };
16
+ describe("generateThemeCss", () => {
17
+ it("laver css fil med palette", () => {
18
+ const css = generateThemeCss(mockBrief);
19
+ expect(css).toContain("--color-accent: #112233;");
20
+ expect(css).toContain("--color-cream: #ffeedd;");
21
+ expect(css).toContain("--color-sand: #ffeedd;"); // For nu bare bg
22
+ });
23
+ });
24
+ describe("generatePromptModule", () => {
25
+ it("laver ts modul med system prompt", () => {
26
+ const ts = generatePromptModule(mockBrief);
27
+ expect(ts).toContain("export const systemPrompt");
28
+ expect(ts).toContain("Test tag");
29
+ expect(ts).toContain("Sjov");
30
+ });
31
+ });
32
+ import { generateSeedData } from "./index";
33
+ describe("generateSeedData", () => {
34
+ it("laver ts modul med categories og products", () => {
35
+ const brief = {
36
+ ...mockBrief,
37
+ categories: [{ name: "Kaffe", slug: "kaffe" }],
38
+ products: [{ name: "Bønne", categorySlug: "kaffe", priceMinor: 1000, blurb: "God" }]
39
+ };
40
+ const ts = generateSeedData(brief);
41
+ expect(ts).toContain("export const seedData = {");
42
+ expect(ts).toContain("Kaffe");
43
+ expect(ts).toContain("Bønne");
44
+ expect(ts).toContain("1000");
45
+ });
46
+ });
47
+ //# sourceMappingURL=index.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../../src/generate/index.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAGjE,MAAM,SAAS,GAAc;IAC3B,SAAS,EAAE,MAAM;IACjB,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,UAAU;IACnB,KAAK,EAAE,MAAM;IACb,QAAQ,EAAE,MAAM;IAChB,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,IAAI;IACb,QAAQ,EAAE,KAAK;IACf,OAAO,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE;IACtD,UAAU,EAAE,EAAE;IACd,QAAQ,EAAE,EAAE;CACb,CAAC;AAEF,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACnC,MAAM,GAAG,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC;QACxC,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,0BAA0B,CAAC,CAAC;QAClD,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,yBAAyB,CAAC,CAAC;QACjD,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,wBAAwB,CAAC,CAAC,CAAC,iBAAiB;IACpE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,MAAM,EAAE,GAAG,oBAAoB,CAAC,SAAS,CAAC,CAAC;QAC3C,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,2BAA2B,CAAC,CAAC;QAClD,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QACjC,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAC3C,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,KAAK,GAAc;YACvB,GAAG,SAAS;YACZ,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;YAC9C,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;SACrF,CAAC;QACF,MAAM,EAAE,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;QACnC,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,2BAA2B,CAAC,CAAC;QAClD,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAC9B,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAC9B,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
package/dist/index.js CHANGED
@@ -13,26 +13,19 @@
13
13
  * fresh AUTH_SECRET, injects the project name into brand.config.ts, and
14
14
  * optionally installs deps + inits git.
15
15
  */
16
- import { intro, outro, text, select, confirm, cancel, isCancel, spinner, note, } from "@clack/prompts";
16
+ import { intro, outro, text, select, confirm, cancel, isCancel, spinner, note, password, } from "@clack/prompts";
17
17
  import { downloadTemplate } from "giget";
18
18
  import pc from "picocolors";
19
19
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
20
20
  import { join, resolve } from "node:path";
21
- import { execSync } from "node:child_process";
22
- import { randomBytes } from "node:crypto";
23
21
  import { parseArgs } from "node:util";
24
22
  const TEMPLATE_REPO = "github:Teloz1870/cartwright-template";
25
23
  const DEFAULT_REF = "v0.1.0-beta";
26
- function detectPackageManager() {
27
- const ua = process.env.npm_config_user_agent ?? "";
28
- if (ua.startsWith("pnpm"))
29
- return "pnpm";
30
- if (ua.startsWith("yarn"))
31
- return "yarn";
32
- if (ua.startsWith("bun"))
33
- return "bun";
34
- return "npm";
35
- }
24
+ import { detectPackageManager, generateAuthSecret, patchEnvLocal, patchBrandConfigContent, tryGitInit, tryInstall, databaseNote } from "./scaffold";
25
+ import { resolveKeyMode } from "./key-step";
26
+ import { runInterview } from "./interview";
27
+ import { summarizeBuild } from "./approve";
28
+ import { injectBriefFiles } from "./inject";
36
29
  function exitOnCancel(value) {
37
30
  if (isCancel(value)) {
38
31
  cancel("Cancelled.");
@@ -40,73 +33,15 @@ function exitOnCancel(value) {
40
33
  }
41
34
  return value;
42
35
  }
43
- function generateAuthSecret() {
44
- return randomBytes(32).toString("hex");
45
- }
46
- function patchEnvLocal(targetDir, authSecret) {
47
- const envExamplePath = join(targetDir, ".env.example");
48
- if (!existsSync(envExamplePath))
49
- return;
50
- const example = readFileSync(envExamplePath, "utf8");
51
- const patched = example.replace(/^AUTH_SECRET=.*/m, `AUTH_SECRET="${authSecret}"`);
52
- writeFileSync(join(targetDir, ".env.local"), patched);
53
- }
54
36
  function patchBrandConfig(targetDir, projectName) {
55
37
  const path = join(targetDir, "brand.config.ts");
56
38
  if (!existsSync(path))
57
39
  return;
58
40
  const original = readFileSync(path, "utf8");
59
- const titled = projectName
60
- .split("-")
61
- .map((s) => s.charAt(0).toUpperCase() + s.slice(1))
62
- .join(" ");
63
- // Replace the first storeName + storeSlug occurrences in the brand object.
64
- const patched = original
65
- .replace(/storeName:\s*"[^"]*"/, `storeName: "${titled}"`)
66
- .replace(/storeSlug:\s*"[^"]*"/, `storeSlug: "${projectName}"`);
41
+ const patched = patchBrandConfigContent(original, projectName);
67
42
  if (patched !== original)
68
43
  writeFileSync(path, patched);
69
44
  }
70
- function tryGitInit(targetDir) {
71
- try {
72
- execSync("git init -q && git add -A && git commit -q -m 'feat: initial commit from create-cartwright'", { cwd: targetDir, stdio: "ignore" });
73
- return true;
74
- }
75
- catch {
76
- return false;
77
- }
78
- }
79
- function tryInstall(targetDir, pm) {
80
- try {
81
- execSync(`${pm} install`, { cwd: targetDir, stdio: "ignore" });
82
- return true;
83
- }
84
- catch {
85
- return false;
86
- }
87
- }
88
- function databaseNote(db) {
89
- switch (db) {
90
- case "turso":
91
- return [
92
- pc.bold("Turso setup (production):"),
93
- " turso db create my-shop-db",
94
- " turso db tokens create my-shop-db",
95
- pc.dim(" Set TURSO_DATABASE_URL + TURSO_AUTH_TOKEN in Vercel."),
96
- ].join("\n");
97
- case "postgres":
98
- return [
99
- pc.bold("Postgres setup:"),
100
- " Update DATABASE_URL in .env.local to your Postgres URL.",
101
- pc.dim(" Prisma schema currently uses 'sqlite' provider — switch to 'postgresql' in prisma/schema.prisma and run a fresh migration."),
102
- ].join("\n");
103
- case "sqlite":
104
- return [
105
- pc.bold("SQLite (local only):"),
106
- " No extra setup. dev.db will be created on first migration.",
107
- ].join("\n");
108
- }
109
- }
110
45
  async function run() {
111
46
  const { values, positionals } = parseArgs({
112
47
  allowPositionals: true,
@@ -115,13 +50,14 @@ async function run() {
115
50
  db: { type: "string" },
116
51
  ai: { type: "boolean" },
117
52
  "no-ai": { type: "boolean" },
53
+ "ai-gen": { type: "boolean" },
118
54
  ref: { type: "string" },
119
55
  pm: { type: "string" },
120
56
  "no-install": { type: "boolean" },
121
57
  "no-git": { type: "boolean" },
122
58
  },
123
59
  });
124
- intro(`${pc.bgYellow(pc.black(" create-cartwright "))} ${pc.dim("v0.1.0-beta")}`);
60
+ intro(`${pc.bgYellow(pc.black(" create-cartwright "))} ${pc.dim("v2.0.0-beta")}`);
125
61
  // ── Project name ────────────────────────────────────────────────────────
126
62
  const defaultName = positionals[0] ?? "my-cartwright-shop";
127
63
  const projectName = values.yes
@@ -166,6 +102,38 @@ async function run() {
166
102
  message: "Include AI commerce features? (requires Anthropic + Gemini API keys)",
167
103
  initialValue: true,
168
104
  })));
105
+ // ── AI Generation (V2) ───────────────────────────────────────────────────
106
+ let generatedBrief = undefined;
107
+ let storeSlugOverride = undefined;
108
+ let storeNameOverride = undefined;
109
+ const useAiGen = values["ai-gen"] ?? (values.yes ? false : exitOnCancel(await confirm({
110
+ message: "Vil du prøve den nye AI-scaffolder (v2)? (Kræver Gemini API Key)",
111
+ initialValue: true,
112
+ })));
113
+ if (useAiGen) {
114
+ const keyMode = await resolveKeyMode({
115
+ getEnvKey: () => process.env.GEMINI_API_KEY,
116
+ promptKey: async () => exitOnCancel(await password({ message: "Indtast Gemini API Key:" })),
117
+ confirmManual: async () => exitOnCancel(await confirm({ message: "Key fejlede. Fortsæt med manuel scaffold (v1)?", initialValue: true }))
118
+ });
119
+ if (keyMode.type === "key") {
120
+ const initialPrompt = exitOnCancel(await text({ message: "Hvad slags butik vil du bygge?" }));
121
+ generatedBrief = await runInterview({
122
+ apiKey: keyMode.key,
123
+ initialPrompt,
124
+ askUser: async (q) => exitOnCancel(await text({ message: pc.cyan("AI:") + " " + q })),
125
+ logMsg: (msg) => console.log(pc.dim(msg))
126
+ });
127
+ console.log("\n" + summarizeBuild(generatedBrief) + "\n");
128
+ const ok = exitOnCancel(await confirm({ message: "Ser dette rigtigt ud? (Klar til at bygge)", initialValue: true }));
129
+ if (!ok) {
130
+ cancel("Afbrudt af bruger.");
131
+ process.exit(0);
132
+ }
133
+ storeSlugOverride = generatedBrief.slug;
134
+ storeNameOverride = generatedBrief.storeName;
135
+ }
136
+ }
169
137
  // ── Tooling defaults ────────────────────────────────────────────────────
170
138
  const detected = detectPackageManager();
171
139
  const packageManager = values.pm ?? detected;
@@ -173,9 +141,11 @@ async function run() {
173
141
  const initGit = !values["no-git"];
174
142
  const templateRef = values.ref ?? DEFAULT_REF;
175
143
  // ── Pre-flight ──────────────────────────────────────────────────────────
176
- const targetDir = resolve(process.cwd(), projectName);
144
+ const finalSlug = storeSlugOverride ?? projectName;
145
+ const finalProjectName = storeNameOverride ?? projectName;
146
+ const targetDir = resolve(process.cwd(), finalSlug);
177
147
  if (existsSync(targetDir)) {
178
- cancel(`Directory "${projectName}" already exists.`);
148
+ cancel(`Directory "${finalSlug}" already exists.`);
179
149
  process.exit(1);
180
150
  }
181
151
  // ── Template fetch ──────────────────────────────────────────────────────
@@ -196,7 +166,14 @@ async function run() {
196
166
  // ── Customise the scaffold ──────────────────────────────────────────────
197
167
  const authSecret = generateAuthSecret();
198
168
  patchEnvLocal(targetDir, authSecret);
199
- patchBrandConfig(targetDir, projectName);
169
+ if (generatedBrief) {
170
+ injectBriefFiles(targetDir, generatedBrief);
171
+ // patchBrandConfig vil nu bruge briefets værdier (som vi gav videre via finalSlug)
172
+ patchBrandConfig(targetDir, finalSlug);
173
+ }
174
+ else {
175
+ patchBrandConfig(targetDir, finalProjectName);
176
+ }
200
177
  // ── Git init ────────────────────────────────────────────────────────────
201
178
  if (initGit) {
202
179
  const gitOk = tryGitInit(targetDir);
@@ -223,12 +200,13 @@ async function run() {
223
200
  : "";
224
201
  const lines = [
225
202
  pc.green("✓") +
226
- ` Created ${pc.bold(projectName)} at ${pc.dim(targetDir)}`,
203
+ ` Created ${pc.bold(finalProjectName)} at ${pc.dim(targetDir)}`,
227
204
  pc.green("✓") + ` AUTH_SECRET generated and written to .env.local`,
228
205
  pc.green("✓") + ` brand.config.ts patched (storeName + storeSlug)`,
206
+ generatedBrief ? pc.green("✓") + ` AI brief injected` : "",
229
207
  "",
230
208
  pc.bold("Next steps:"),
231
- ` cd ${projectName}`,
209
+ ` cd ${finalSlug}`,
232
210
  ` npx prisma migrate deploy ${pc.dim("# create / sync the DB schema")}`,
233
211
  ` npx prisma db seed ${pc.dim("# seed demo products + categories")}`,
234
212
  ` ${runCmd} dev ${pc.dim("# http://localhost:3000")}`,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { type ShopBrief } from "./brief";
2
+ export declare function injectBriefFiles(targetDir: string, brief: ShopBrief): void;
package/dist/inject.js ADDED
@@ -0,0 +1,21 @@
1
+ import { writeFileSync, mkdirSync, existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { generateThemeCss, generatePromptModule, generateSeedData } from "./generate";
4
+ export function injectBriefFiles(targetDir, brief) {
5
+ // Opret mapper
6
+ const cssDir = join(targetDir, "themes");
7
+ const promptsDir = join(targetDir, "lib", "ai", "prompts");
8
+ const seedsDir = join(targetDir, "industry-templates", brief.slug);
9
+ [cssDir, promptsDir, seedsDir].forEach((dir) => {
10
+ if (!existsSync(dir)) {
11
+ mkdirSync(dir, { recursive: true });
12
+ }
13
+ });
14
+ // Skriv filer
15
+ writeFileSync(join(cssDir, `${brief.slug}.css`), generateThemeCss(brief));
16
+ writeFileSync(join(promptsDir, `${brief.slug}.ts`), generatePromptModule(brief));
17
+ writeFileSync(join(seedsDir, "seed-data.ts"), generateSeedData(brief));
18
+ // TODO: Opdater industry-templates/index.ts for at registrere den nye seed, men
19
+ // det falder måske under advanced M2 scope, eller vi kan lave et simpelt append her:
20
+ }
21
+ //# sourceMappingURL=inject.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inject.js","sourceRoot":"","sources":["../src/inject.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEtF,MAAM,UAAU,gBAAgB,CAAC,SAAiB,EAAE,KAAgB;IAClE,eAAe;IACf,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IACzC,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;IAC3D,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,EAAE,oBAAoB,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAEnE,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;QAC7C,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACrB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACtC,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,cAAc;IACd,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,KAAK,CAAC,IAAI,MAAM,CAAC,EAAE,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC;IAC1E,aAAa,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,KAAK,CAAC,IAAI,KAAK,CAAC,EAAE,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAAC;IACjF,aAAa,CAAC,IAAI,CAAC,QAAQ,EAAE,cAAc,CAAC,EAAE,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC;IAEvE,iFAAiF;IACjF,qFAAqF;AACvF,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,37 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { injectBriefFiles } from "./inject";
3
+ import * as fs from "node:fs";
4
+ vi.mock("node:fs", () => ({
5
+ existsSync: vi.fn(),
6
+ mkdirSync: vi.fn(),
7
+ writeFileSync: vi.fn(),
8
+ }));
9
+ describe("injectBriefFiles", () => {
10
+ beforeEach(() => {
11
+ vi.clearAllMocks();
12
+ });
13
+ it("opretter mapper og skriver filer", () => {
14
+ vi.mocked(fs.existsSync).mockReturnValue(false);
15
+ const brief = {
16
+ storeName: "Test",
17
+ slug: "test-slug",
18
+ tagline: "Test tag",
19
+ sells: "Ting",
20
+ audience: "Folk",
21
+ tone: "Sjov",
22
+ country: "DK",
23
+ currency: "DKK",
24
+ palette: { primary: "#112233", background: "#ffeedd" },
25
+ categories: [],
26
+ products: []
27
+ };
28
+ injectBriefFiles("/tmp/target", brief);
29
+ expect(fs.mkdirSync).toHaveBeenCalledTimes(3);
30
+ expect(fs.writeFileSync).toHaveBeenCalledTimes(3);
31
+ const writeCalls = vi.mocked(fs.writeFileSync).mock.calls;
32
+ expect(writeCalls[0][0]).toContain("themes");
33
+ expect(writeCalls[1][0]).toContain("prompts");
34
+ expect(writeCalls[2][0]).toContain("industry-templates");
35
+ });
36
+ });
37
+ //# sourceMappingURL=inject.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inject.test.js","sourceRoot":"","sources":["../src/inject.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAG9B,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC;IACxB,UAAU,EAAE,EAAE,CAAC,EAAE,EAAE;IACnB,SAAS,EAAE,EAAE,CAAC,EAAE,EAAE;IAClB,aAAa,EAAE,EAAE,CAAC,EAAE,EAAE;CACvB,CAAC,CAAC,CAAC;AAEJ,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,UAAU,CAAC,GAAG,EAAE;QACd,EAAE,CAAC,aAAa,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;QAEhD,MAAM,KAAK,GAAc;YACvB,SAAS,EAAE,MAAM;YACjB,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,UAAU;YACnB,KAAK,EAAE,MAAM;YACb,QAAQ,EAAE,MAAM;YAChB,IAAI,EAAE,MAAM;YACZ,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,KAAK;YACf,OAAO,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE;YACtD,UAAU,EAAE,EAAE;YACd,QAAQ,EAAE,EAAE;SACb,CAAC;QAEF,gBAAgB,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC;QAEvC,MAAM,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QAC9C,MAAM,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QAElD,MAAM,UAAU,GAAG,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC;QAC1D,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAW,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QACvD,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAW,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;QACxD,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAW,CAAC,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { type ShopBrief } from "./brief";
2
+ export interface InterviewDeps {
3
+ apiKey: string;
4
+ initialPrompt: string;
5
+ askUser: (question: string) => Promise<string>;
6
+ logMsg: (msg: string) => void;
7
+ }
8
+ export declare function runInterview(deps: InterviewDeps): Promise<ShopBrief>;
@@ -0,0 +1,73 @@
1
+ import { generateJson } from "./llm";
2
+ const interviewSchema = {
3
+ type: "object",
4
+ properties: {
5
+ isComplete: { type: "boolean" },
6
+ nextQuestion: { type: "string", description: "The next question to ask if not complete." },
7
+ brief: {
8
+ type: "object",
9
+ description: "The complete shop brief. Only provided when isComplete is true.",
10
+ properties: {
11
+ storeName: { type: "string" },
12
+ slug: { type: "string" },
13
+ tagline: { type: "string" },
14
+ sells: { type: "string" },
15
+ audience: { type: "string" },
16
+ tone: { type: "string" },
17
+ country: { type: "string" },
18
+ currency: { type: "string" },
19
+ palette: {
20
+ type: "object",
21
+ properties: {
22
+ primary: { type: "string" },
23
+ background: { type: "string" },
24
+ },
25
+ },
26
+ categories: {
27
+ type: "array",
28
+ items: {
29
+ type: "object",
30
+ properties: {
31
+ name: { type: "string" },
32
+ slug: { type: "string" },
33
+ },
34
+ },
35
+ },
36
+ products: {
37
+ type: "array",
38
+ items: {
39
+ type: "object",
40
+ properties: {
41
+ name: { type: "string" },
42
+ categorySlug: { type: "string" },
43
+ priceMinor: { type: "integer" },
44
+ blurb: { type: "string" },
45
+ },
46
+ },
47
+ },
48
+ }
49
+ }
50
+ },
51
+ required: ["isComplete", "nextQuestion"]
52
+ };
53
+ export async function runInterview(deps) {
54
+ const history = [];
55
+ history.push(`Brugerens indledende forespørgsel: ${deps.initialPrompt}`);
56
+ const systemInstructions = `
57
+ Du er en e-commerce ekspert. Din opgave er at indsamle nok information til at bygge et 'ShopBrief'.
58
+ Stil ét spørgsmål ad gangen for at udfylde de manglende felter.
59
+ Når du har nok info til at gætte resten kompetent (eller brugeren beder dig om at udfylde resten selv), så sæt isComplete = true og udfyld brief-objektet komplet.
60
+ `;
61
+ while (true) {
62
+ const prompt = `${systemInstructions}\n\nSamtalehistorik:\n${history.join("\n")}`;
63
+ deps.logMsg("Tænker...");
64
+ const res = await generateJson(deps.apiKey, prompt, interviewSchema);
65
+ if (res.isComplete && res.brief) {
66
+ return res.brief;
67
+ }
68
+ const answer = await deps.askUser(res.nextQuestion);
69
+ history.push(`AI: ${res.nextQuestion}`);
70
+ history.push(`Bruger: ${answer}`);
71
+ }
72
+ }
73
+ //# sourceMappingURL=interview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interview.js","sourceRoot":"","sources":["../src/interview.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAUrC,MAAM,eAAe,GAAG;IACtB,IAAI,EAAE,QAAQ;IACd,UAAU,EAAE;QACV,UAAU,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;QAC/B,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,2CAA2C,EAAE;QAC1F,KAAK,EAAE;YACL,IAAI,EAAE,QAAQ;YACd,WAAW,EAAE,iEAAiE;YAC9E,UAAU,EAAE;gBACV,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBAC7B,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBACxB,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBAC3B,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBACzB,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBAC5B,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBACxB,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBAC3B,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBAC5B,OAAO,EAAE;oBACP,IAAI,EAAE,QAAQ;oBACd,UAAU,EAAE;wBACV,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;wBAC3B,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;qBAC/B;iBACF;gBACD,UAAU,EAAE;oBACV,IAAI,EAAE,OAAO;oBACb,KAAK,EAAE;wBACL,IAAI,EAAE,QAAQ;wBACd,UAAU,EAAE;4BACV,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;4BACxB,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;yBACzB;qBACF;iBACF;gBACD,QAAQ,EAAE;oBACR,IAAI,EAAE,OAAO;oBACb,KAAK,EAAE;wBACL,IAAI,EAAE,QAAQ;wBACd,UAAU,EAAE;4BACV,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;4BACxB,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;4BAChC,UAAU,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;4BAC/B,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;yBAC1B;qBACF;iBACF;aACF;SACF;KACF;IACD,QAAQ,EAAE,CAAC,YAAY,EAAE,cAAc,CAAC;CACzC,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAmB;IACpD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,OAAO,CAAC,IAAI,CAAC,sCAAsC,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;IAEzE,MAAM,kBAAkB,GAAG;;;;CAI5B,CAAC;IAEA,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,MAAM,GAAG,GAAG,kBAAkB,yBAAyB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAElF,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QACzB,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,eAAe,CAAQ,CAAC;QAE5E,IAAI,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;YAChC,OAAO,GAAG,CAAC,KAAkB,CAAC;QAChC,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QACpD,OAAO,CAAC,IAAI,CAAC,OAAO,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC;QACxC,OAAO,CAAC,IAAI,CAAC,WAAW,MAAM,EAAE,CAAC,CAAC;IACpC,CAAC;AACH,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,50 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { runInterview } from "./interview";
3
+ import * as llm from "./llm";
4
+ vi.mock("./llm", () => ({
5
+ generateJson: vi.fn(),
6
+ }));
7
+ describe("runInterview", () => {
8
+ beforeEach(() => {
9
+ vi.clearAllMocks();
10
+ });
11
+ it("returnerer ShopBrief når isComplete er true", async () => {
12
+ vi.mocked(llm.generateJson).mockResolvedValueOnce({
13
+ isComplete: true,
14
+ nextQuestion: "",
15
+ brief: { storeName: "Test", slug: "test" } // For testens skyld er validation løs her
16
+ });
17
+ const res = await runInterview({
18
+ apiKey: "key",
19
+ initialPrompt: "Jeg vil lave en kaffeshop",
20
+ askUser: async () => "svar",
21
+ logMsg: () => { }
22
+ });
23
+ expect(res).toEqual({ storeName: "Test", slug: "test" });
24
+ });
25
+ it("stiller spørgsmål og opsamler svar indtil isComplete", async () => {
26
+ vi.mocked(llm.generateJson)
27
+ .mockResolvedValueOnce({
28
+ isComplete: false,
29
+ nextQuestion: "Hvilken type kaffe?",
30
+ })
31
+ .mockResolvedValueOnce({
32
+ isComplete: true,
33
+ nextQuestion: "",
34
+ brief: { storeName: "Kaffe", slug: "kaffe" }
35
+ });
36
+ const askUser = vi.fn().mockResolvedValueOnce("Specialkaffe");
37
+ const res = await runInterview({
38
+ apiKey: "key",
39
+ initialPrompt: "kaffe",
40
+ askUser,
41
+ logMsg: () => { }
42
+ });
43
+ expect(askUser).toHaveBeenCalledWith("Hvilken type kaffe?");
44
+ expect(res).toEqual({ storeName: "Kaffe", slug: "kaffe" });
45
+ // Check at prompten i 2. kald indeholder brugerens svar
46
+ const secondCallArg = vi.mocked(llm.generateJson).mock.calls[1][1];
47
+ expect(secondCallArg).toContain("Specialkaffe");
48
+ });
49
+ });
50
+ //# sourceMappingURL=interview.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interview.test.js","sourceRoot":"","sources":["../src/interview.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,GAAG,MAAM,OAAO,CAAC;AAE7B,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;IACtB,YAAY,EAAE,EAAE,CAAC,EAAE,EAAE;CACtB,CAAC,CAAC,CAAC;AAEJ,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,UAAU,CAAC,GAAG,EAAE;QACd,EAAE,CAAC,aAAa,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;QAC3D,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,qBAAqB,CAAC;YAChD,UAAU,EAAE,IAAI;YAChB,YAAY,EAAE,EAAE;YAChB,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,0CAA0C;SACtF,CAAC,CAAC;QAEH,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC;YAC7B,MAAM,EAAE,KAAK;YACb,aAAa,EAAE,2BAA2B;YAC1C,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,MAAM;YAC3B,MAAM,EAAE,GAAG,EAAE,GAAE,CAAC;SACjB,CAAC,CAAC;QAEH,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;QACpE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC;aACxB,qBAAqB,CAAC;YACrB,UAAU,EAAE,KAAK;YACjB,YAAY,EAAE,qBAAqB;SACpC,CAAC;aACD,qBAAqB,CAAC;YACrB,UAAU,EAAE,IAAI;YAChB,YAAY,EAAE,EAAE;YAChB,KAAK,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE;SAC7C,CAAC,CAAC;QAEL,MAAM,OAAO,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,qBAAqB,CAAC,cAAc,CAAC,CAAC;QAC9D,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC;YAC7B,MAAM,EAAE,KAAK;YACb,aAAa,EAAE,OAAO;YACtB,OAAO;YACP,MAAM,EAAE,GAAG,EAAE,GAAE,CAAC;SACjB,CAAC,CAAC;QAEH,MAAM,CAAC,OAAO,CAAC,CAAC,oBAAoB,CAAC,qBAAqB,CAAC,CAAC;QAC5D,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QAE3D,wDAAwD;QACxD,MAAM,aAAa,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAW,CAAC;QAC7E,MAAM,CAAC,aAAa,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,12 @@
1
+ export type KeyModeResult = {
2
+ type: "key";
3
+ key: string;
4
+ } | {
5
+ type: "manual";
6
+ };
7
+ export interface KeyStepDeps {
8
+ getEnvKey: () => string | undefined;
9
+ promptKey: () => Promise<string>;
10
+ confirmManual: () => Promise<boolean>;
11
+ }
12
+ export declare function resolveKeyMode(deps: KeyStepDeps): Promise<KeyModeResult>;
@@ -0,0 +1,22 @@
1
+ import { validateKey } from "./llm";
2
+ export async function resolveKeyMode(deps) {
3
+ let currentKey = deps.getEnvKey();
4
+ if (currentKey) {
5
+ const isValid = await validateKey(currentKey);
6
+ if (isValid)
7
+ return { type: "key", key: currentKey };
8
+ }
9
+ while (true) {
10
+ currentKey = await deps.promptKey();
11
+ if (!currentKey) {
12
+ const isManualOk = await deps.confirmManual();
13
+ if (isManualOk)
14
+ return { type: "manual" };
15
+ throw new Error("abort");
16
+ }
17
+ const isValid = await validateKey(currentKey);
18
+ if (isValid)
19
+ return { type: "key", key: currentKey };
20
+ }
21
+ }
22
+ //# sourceMappingURL=key-step.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"key-step.js","sourceRoot":"","sources":["../src/key-step.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAUpC,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAiB;IACpD,IAAI,UAAU,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;IAElC,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,UAAU,CAAC,CAAC;QAC9C,IAAI,OAAO;YAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC;IACvD,CAAC;IAED,OAAO,IAAI,EAAE,CAAC;QACZ,UAAU,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;QACpC,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;YAC9C,IAAI,UAAU;gBAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;YAC1C,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;QAC3B,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,UAAU,CAAC,CAAC;QAC9C,IAAI,OAAO;YAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC;IACvD,CAAC;AACH,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,56 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { resolveKeyMode } from "./key-step";
3
+ import * as llm from "./llm";
4
+ vi.mock("./llm", () => ({
5
+ validateKey: vi.fn(),
6
+ }));
7
+ describe("resolveKeyMode", () => {
8
+ beforeEach(() => {
9
+ vi.clearAllMocks();
10
+ delete process.env.GEMINI_API_KEY;
11
+ });
12
+ afterEach(() => {
13
+ delete process.env.GEMINI_API_KEY;
14
+ });
15
+ it("returnerer key hvis valid env key", async () => {
16
+ process.env.GEMINI_API_KEY = "env-key";
17
+ vi.mocked(llm.validateKey).mockResolvedValue(true);
18
+ const res = await resolveKeyMode({
19
+ getEnvKey: () => process.env.GEMINI_API_KEY,
20
+ promptKey: async () => "prompt-key",
21
+ confirmManual: async () => false,
22
+ });
23
+ expect(res).toEqual({ type: "key", key: "env-key" });
24
+ });
25
+ it("prompter hvis invalid env key og returnerer ny key", async () => {
26
+ process.env.GEMINI_API_KEY = "bad-env-key";
27
+ vi.mocked(llm.validateKey).mockResolvedValueOnce(false); // env fejler
28
+ vi.mocked(llm.validateKey).mockResolvedValueOnce(true); // prompt virker
29
+ const res = await resolveKeyMode({
30
+ getEnvKey: () => process.env.GEMINI_API_KEY,
31
+ promptKey: async () => "prompt-key",
32
+ confirmManual: async () => false,
33
+ });
34
+ expect(res).toEqual({ type: "key", key: "prompt-key" });
35
+ expect(llm.validateKey).toHaveBeenCalledTimes(2);
36
+ });
37
+ it("falder tilbage til manual hvis prompt er tomt", async () => {
38
+ process.env.GEMINI_API_KEY = "bad-env-key";
39
+ vi.mocked(llm.validateKey).mockResolvedValueOnce(false);
40
+ const res = await resolveKeyMode({
41
+ getEnvKey: () => process.env.GEMINI_API_KEY,
42
+ promptKey: async () => "",
43
+ confirmManual: async () => true,
44
+ });
45
+ expect(res).toEqual({ type: "manual" });
46
+ });
47
+ it("aborts hvis bruger afviser manual mode", async () => {
48
+ vi.mocked(llm.validateKey).mockResolvedValueOnce(false);
49
+ await expect(resolveKeyMode({
50
+ getEnvKey: () => undefined,
51
+ promptKey: async () => "",
52
+ confirmManual: async () => false,
53
+ })).rejects.toThrow(/abort/i);
54
+ });
55
+ });
56
+ //# sourceMappingURL=key-step.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"key-step.test.js","sourceRoot":"","sources":["../src/key-step.test.ts"],"names":[],"mappings":"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"}
package/dist/llm.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare function validateKey(key: string): Promise<boolean>;
2
+ export declare function generateJson(key: string, prompt: string, jsonSchema: unknown): Promise<unknown>;
package/dist/llm.js ADDED
@@ -0,0 +1,51 @@
1
+ const GEMINI_API_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent";
2
+ export async function validateKey(key) {
3
+ try {
4
+ const res = await fetch(`${GEMINI_API_URL}?key=${key}`, {
5
+ method: "POST",
6
+ headers: { "Content-Type": "application/json" },
7
+ body: JSON.stringify({
8
+ contents: [{ role: "user", parts: [{ text: "Hello" }] }],
9
+ }),
10
+ });
11
+ if (!res.ok) {
12
+ if (res.status === 400 || res.status === 401 || res.status === 403) {
13
+ return false;
14
+ }
15
+ throw new Error(`Unexpected status ${res.status}`);
16
+ }
17
+ return true;
18
+ }
19
+ catch (error) {
20
+ if (error instanceof Error && error.message.includes("status")) {
21
+ throw error;
22
+ }
23
+ return false;
24
+ }
25
+ }
26
+ export async function generateJson(key, prompt, jsonSchema) {
27
+ const res = await fetch(`${GEMINI_API_URL}?key=${key}`, {
28
+ method: "POST",
29
+ headers: { "Content-Type": "application/json" },
30
+ body: JSON.stringify({
31
+ contents: [{ role: "user", parts: [{ text: prompt }] }],
32
+ generationConfig: {
33
+ responseMimeType: "application/json",
34
+ responseSchema: jsonSchema,
35
+ },
36
+ }),
37
+ });
38
+ if (!res.ok) {
39
+ if (res.status === 429) {
40
+ throw new Error("HTTP 429: Rate limit exceeded for Gemini API.");
41
+ }
42
+ throw new Error(`HTTP ${res.status}: Failed to generate content.`);
43
+ }
44
+ const data = (await res.json());
45
+ const text = data.candidates?.[0]?.content?.parts?.[0]?.text;
46
+ if (!text) {
47
+ throw new Error("No text content in Gemini response.");
48
+ }
49
+ return JSON.parse(text);
50
+ }
51
+ //# sourceMappingURL=llm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm.js","sourceRoot":"","sources":["../src/llm.ts"],"names":[],"mappings":"AAAA,MAAM,cAAc,GAAG,0FAA0F,CAAC;AAElH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,GAAW;IAC3C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,cAAc,QAAQ,GAAG,EAAE,EAAE;YACtD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;aACzD,CAAC;SACH,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBACnE,OAAO,KAAK,CAAC;YACf,CAAC;YACD,MAAM,IAAI,KAAK,CAAC,qBAAqB,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;QACrD,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC/D,MAAM,KAAK,CAAC;QACd,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,GAAW,EAAE,MAAc,EAAE,UAAmB;IACjF,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,cAAc,QAAQ,GAAG,EAAE,EAAE;QACtD,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;YACnB,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;YACvD,gBAAgB,EAAE;gBAChB,gBAAgB,EAAE,kBAAkB;gBACpC,cAAc,EAAE,UAAU;aAC3B;SACF,CAAC;KACH,CAAC,CAAC;IAEH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;QACnE,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,QAAQ,GAAG,CAAC,MAAM,+BAA+B,CAAC,CAAC;IACrE,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAQ,CAAC;IACvC,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC;IAC7D,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;IACzD,CAAC;IAED,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,32 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { generateJson, validateKey } from "./llm";
3
+ describe("generateJson", () => {
4
+ it("parser JSON ud af Gemini-svaret", async () => {
5
+ const fakeBody = {
6
+ candidates: [{ content: { parts: [{ text: '{"ok":true}' }] } }],
7
+ };
8
+ vi.stubGlobal("fetch", vi.fn(async () => new Response(JSON.stringify(fakeBody), { status: 200 })));
9
+ const out = await generateJson("test-key", "byg noget", { type: "object" });
10
+ expect(out).toEqual({ ok: true });
11
+ });
12
+ it("kaster ved HTTP-fejl", async () => {
13
+ vi.stubGlobal("fetch", vi.fn(async () => new Response("nej", { status: 429 })));
14
+ await expect(generateJson("k", "p", {})).rejects.toThrow(/429|rate/i);
15
+ });
16
+ });
17
+ describe("validateKey", () => {
18
+ it("returnerer true hvis auth virker", async () => {
19
+ const fakeBody = {
20
+ candidates: [{ content: { parts: [{ text: '{"ok":true}' }] } }],
21
+ };
22
+ vi.stubGlobal("fetch", vi.fn(async () => new Response(JSON.stringify(fakeBody), { status: 200 })));
23
+ const out = await validateKey("test-key");
24
+ expect(out).toBe(true);
25
+ });
26
+ it("returnerer false hvis unauthorized (400 / 401 / 403)", async () => {
27
+ vi.stubGlobal("fetch", vi.fn(async () => new Response("Bad Request", { status: 400 })));
28
+ const out = await validateKey("bad-key");
29
+ expect(out).toBe(false);
30
+ });
31
+ });
32
+ //# sourceMappingURL=llm.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm.test.js","sourceRoot":"","sources":["../src/llm.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAElD,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,EAAE,CAAC,iCAAiC,EAAE,KAAK,IAAI,EAAE;QAC/C,MAAM,QAAQ,GAAG;YACf,UAAU,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,EAAE,EAAE,CAAC;SAChE,CAAC;QACF,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;QACnG,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC,UAAU,EAAE,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC5E,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE;QACpC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,IAAI,QAAQ,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;QAChF,MAAM,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,kCAAkC,EAAE,KAAK,IAAI,EAAE;QAChD,MAAM,QAAQ,GAAG;YACf,UAAU,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,EAAE,EAAE,CAAC;SAChE,CAAC;QACF,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;QACnG,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,UAAU,CAAC,CAAC;QAC1C,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;QACpE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,IAAI,QAAQ,CAAC,aAAa,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;QACxF,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,SAAS,CAAC,CAAC;QACzC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,10 @@
1
+ export type Database = "turso" | "postgres" | "sqlite";
2
+ export type PackageManager = "pnpm" | "npm" | "yarn" | "bun";
3
+ export declare function detectPackageManager(): PackageManager;
4
+ export declare function generateAuthSecret(): string;
5
+ export declare function patchEnvLocal(targetDir: string, authSecret: string): void;
6
+ export declare function titleCase(projectName: string): string;
7
+ export declare function patchBrandConfigContent(original: string, projectName: string): string;
8
+ export declare function tryGitInit(targetDir: string): boolean;
9
+ export declare function tryInstall(targetDir: string, pm: PackageManager): boolean;
10
+ export declare function databaseNote(db: Database): string;
@@ -0,0 +1,86 @@
1
+ import { existsSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { execSync } from "node:child_process";
4
+ import { randomBytes } from "node:crypto";
5
+ import pc from "picocolors";
6
+ export function detectPackageManager() {
7
+ const ua = process.env.npm_config_user_agent ?? "";
8
+ if (ua.startsWith("pnpm"))
9
+ return "pnpm";
10
+ if (ua.startsWith("yarn"))
11
+ return "yarn";
12
+ if (ua.startsWith("bun"))
13
+ return "bun";
14
+ return "npm";
15
+ }
16
+ export function generateAuthSecret() {
17
+ return randomBytes(32).toString("hex");
18
+ }
19
+ export function patchEnvLocal(targetDir, authSecret) {
20
+ const envExamplePath = join(targetDir, ".env.example");
21
+ if (!existsSync(envExamplePath))
22
+ return;
23
+ const example = readFileSync(envExamplePath, "utf8");
24
+ const patched = example.replace(/^AUTH_SECRET=.*/m, `AUTH_SECRET="${authSecret}"`);
25
+ writeFileSync(join(targetDir, ".env.local"), patched);
26
+ }
27
+ export function titleCase(projectName) {
28
+ return projectName
29
+ .split("-")
30
+ .map((s) => s.charAt(0).toUpperCase() + s.slice(1))
31
+ .join(" ");
32
+ }
33
+ export function patchBrandConfigContent(original, projectName) {
34
+ return original
35
+ .replace(/storeName:\s*"[^"]*"/, `storeName: "${titleCase(projectName)}"`)
36
+ .replace(/storeSlug:\s*"[^"]*"/, `storeSlug: "${projectName}"`);
37
+ }
38
+ export function tryGitInit(targetDir) {
39
+ try {
40
+ execSync("git init -q && git add -A && git commit -q -m 'feat: initial commit from create-cartwright'", { cwd: targetDir, stdio: "ignore" });
41
+ return true;
42
+ }
43
+ catch {
44
+ return false;
45
+ }
46
+ }
47
+ export function tryInstall(targetDir, pm) {
48
+ try {
49
+ // Remove conflicting lockfiles from the template before installing
50
+ const lockfiles = ["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lockb"];
51
+ for (const lockfile of lockfiles) {
52
+ const lockPath = join(targetDir, lockfile);
53
+ if (existsSync(lockPath)) {
54
+ unlinkSync(lockPath);
55
+ }
56
+ }
57
+ execSync(`${pm} install`, { cwd: targetDir, stdio: "ignore" });
58
+ return true;
59
+ }
60
+ catch {
61
+ return false;
62
+ }
63
+ }
64
+ export function databaseNote(db) {
65
+ switch (db) {
66
+ case "turso":
67
+ return [
68
+ pc.bold("Turso setup (production):"),
69
+ " turso db create my-shop-db",
70
+ " turso db tokens create my-shop-db",
71
+ pc.dim(" Set TURSO_DATABASE_URL + TURSO_AUTH_TOKEN in Vercel."),
72
+ ].join("\n");
73
+ case "postgres":
74
+ return [
75
+ pc.bold("Postgres setup:"),
76
+ " Update DATABASE_URL in .env.local to your Postgres URL.",
77
+ pc.dim(" Prisma schema currently uses 'sqlite' provider — switch to 'postgresql' in prisma/schema.prisma and run a fresh migration."),
78
+ ].join("\n");
79
+ case "sqlite":
80
+ return [
81
+ pc.bold("SQLite (local only):"),
82
+ " No extra setup. dev.db will be created on first migration.",
83
+ ].join("\n");
84
+ }
85
+ }
86
+ //# sourceMappingURL=scaffold.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { titleCase, patchBrandConfigContent } from "./scaffold";
3
+ describe("scaffold helpers", () => {
4
+ it("titleCase laver projektnavn til storeName", () => {
5
+ expect(titleCase("nord-kaffe")).toBe("Nord Kaffe");
6
+ });
7
+ it("patchBrandConfigContent erstatter storeName og storeSlug", () => {
8
+ const input = `{ storeName: "Demo", storeSlug: "demo" }`;
9
+ const out = patchBrandConfigContent(input, "min-shop");
10
+ expect(out).toContain(`storeName: "Min Shop"`);
11
+ expect(out).toContain(`storeSlug: "min-shop"`);
12
+ });
13
+ });
14
+ //# sourceMappingURL=scaffold.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold.test.js","sourceRoot":"","sources":["../src/scaffold.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAEhE,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,KAAK,GAAG,0CAA0C,CAAC;QACzD,MAAM,GAAG,GAAG,uBAAuB,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;QACvD,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,uBAAuB,CAAC,CAAC;QAC/C,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,uBAAuB,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-cartwright",
3
- "version": "0.1.0",
3
+ "version": "2.0.0-beta.1",
4
4
  "description": "Scaffolder for AI-first webshops powered by Cartwright",
5
5
  "keywords": [
6
6
  "cartwright",
@@ -26,7 +26,8 @@
26
26
  "dev": "tsx src/index.ts",
27
27
  "typecheck": "tsc --noEmit",
28
28
  "lint": "eslint src --max-warnings=0",
29
- "test": "echo 'TODO: vitest'",
29
+ "test": "vitest run",
30
+ "test:watch": "vitest",
30
31
  "prepublishOnly": "pnpm build"
31
32
  },
32
33
  "dependencies": {
@@ -39,7 +40,8 @@
39
40
  "@types/node": "^22.10.0",
40
41
  "eslint": "^9.0.0",
41
42
  "tsx": "^4.19.0",
42
- "typescript": "^5.7.0"
43
+ "typescript": "^5.7.0",
44
+ "vitest": "^4.1.7"
43
45
  },
44
46
  "engines": {
45
47
  "node": ">=22"