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.
- package/dist/approve.d.ts +2 -0
- package/dist/approve.js +16 -0
- package/dist/approve.js.map +1 -0
- package/dist/approve.test.d.ts +1 -0
- package/dist/approve.test.js +23 -0
- package/dist/approve.test.js.map +1 -0
- package/dist/brief.d.ts +26 -0
- package/dist/brief.js +25 -0
- package/dist/brief.js.map +1 -0
- package/dist/brief.test.d.ts +1 -0
- package/dist/brief.test.js +23 -0
- package/dist/brief.test.js.map +1 -0
- package/dist/generate/index.d.ts +4 -0
- package/dist/generate/index.js +42 -0
- package/dist/generate/index.js.map +1 -0
- package/dist/generate/index.test.d.ts +1 -0
- package/dist/generate/index.test.js +47 -0
- package/dist/generate/index.test.js.map +1 -0
- package/dist/index.js +56 -78
- package/dist/index.js.map +1 -1
- package/dist/inject.d.ts +2 -0
- package/dist/inject.js +21 -0
- package/dist/inject.js.map +1 -0
- package/dist/inject.test.d.ts +1 -0
- package/dist/inject.test.js +37 -0
- package/dist/inject.test.js.map +1 -0
- package/dist/interview.d.ts +8 -0
- package/dist/interview.js +73 -0
- package/dist/interview.js.map +1 -0
- package/dist/interview.test.d.ts +1 -0
- package/dist/interview.test.js +50 -0
- package/dist/interview.test.js.map +1 -0
- package/dist/key-step.d.ts +12 -0
- package/dist/key-step.js +22 -0
- package/dist/key-step.js.map +1 -0
- package/dist/key-step.test.d.ts +1 -0
- package/dist/key-step.test.js +56 -0
- package/dist/key-step.test.js.map +1 -0
- package/dist/llm.d.ts +2 -0
- package/dist/llm.js +51 -0
- package/dist/llm.js.map +1 -0
- package/dist/llm.test.d.ts +1 -0
- package/dist/llm.test.js +32 -0
- package/dist/llm.test.js.map +1 -0
- package/dist/scaffold.d.ts +10 -0
- package/dist/scaffold.js +86 -0
- package/dist/scaffold.js.map +1 -0
- package/dist/scaffold.test.d.ts +1 -0
- package/dist/scaffold.test.js +14 -0
- package/dist/scaffold.test.js.map +1 -0
- package/package.json +5 -3
package/dist/approve.js
ADDED
|
@@ -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"}
|
package/dist/brief.d.ts
ADDED
|
@@ -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,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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
|
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("
|
|
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
|
|
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 "${
|
|
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
|
-
|
|
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(
|
|
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 ${
|
|
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":";AACA;;;;;;;;;;;;;GAaG;AACH,OAAO,EACL,KAAK,EACL,KAAK,EACL,IAAI,EACJ,MAAM,EACN,OAAO,EACP,MAAM,EACN,QAAQ,EACR,OAAO,EACP,IAAI,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/inject.d.ts
ADDED
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>;
|
package/dist/key-step.js
ADDED
|
@@ -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
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
|
package/dist/llm.js.map
ADDED
|
@@ -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 {};
|
package/dist/llm.test.js
ADDED
|
@@ -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;
|
package/dist/scaffold.js
ADDED
|
@@ -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
|
|
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": "
|
|
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"
|