@orderlyshop/web-components 0.1.0-build.7045
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/AGENTS.md +110 -0
- package/README.md +685 -0
- package/bin/orderly-build-category-pages.mjs +160 -0
- package/bin/orderly-generate-category-pages.mjs +308 -0
- package/bin/orderly-hydrate-static-pages.mjs +595 -0
- package/bin/orderly-init-navigation.mjs +327 -0
- package/bin/orderly-init-shop.mjs +876 -0
- package/bin/orderly-init-taxonomy.mjs +2 -0
- package/bin/orderly-publish-site.mjs +342 -0
- package/custom-elements.json +505 -0
- package/dist/browser/orderly-web-components.define.global.js +3551 -0
- package/dist/browser/orderly-web-components.define.global.js.map +1 -0
- package/dist/browser/orderly-web-components.global.js +3551 -0
- package/dist/browser/orderly-web-components.global.js.map +1 -0
- package/dist/default-shop-DgX6uy10.d.ts +221 -0
- package/dist/default-shop.d.ts +6 -0
- package/dist/default-shop.js +762 -0
- package/dist/default-shop.js.map +1 -0
- package/dist/define-BNMhl19n.d.ts +9 -0
- package/dist/define.d.ts +2 -0
- package/dist/define.js +11094 -0
- package/dist/define.js.map +1 -0
- package/dist/index.d.ts +683 -0
- package/dist/index.js +11417 -0
- package/dist/index.js.map +1 -0
- package/dist/navigation.d.ts +61 -0
- package/dist/navigation.js +1125 -0
- package/dist/navigation.js.map +1 -0
- package/dist/query.d.ts +31 -0
- package/dist/query.js +115 -0
- package/dist/query.js.map +1 -0
- package/dist/registry-CPDecU3g.d.ts +6 -0
- package/dist/shop-BgQhGRzS.d.ts +173 -0
- package/dist/shop-query.d.ts +8 -0
- package/dist/shop-query.js +100 -0
- package/dist/shop-query.js.map +1 -0
- package/dist/shop.d.ts +8 -0
- package/dist/shop.js +11187 -0
- package/dist/shop.js.map +1 -0
- package/dist/stores.d.ts +46 -0
- package/dist/stores.js +145 -0
- package/dist/stores.js.map +1 -0
- package/dist/taxonomy.d.ts +35 -0
- package/dist/taxonomy.js +247 -0
- package/dist/taxonomy.js.map +1 -0
- package/dist/types-Bjez59Hr.d.ts +96 -0
- package/docs/components/README.md +708 -0
- package/docs/components/product-grid.md +182 -0
- package/docs/components/product-rail.md +174 -0
- package/examples/shop/README.md +72 -0
- package/examples/shop/package.json +28 -0
- package/examples/shop/src/category.html +20 -0
- package/examples/shop/src/checkout.html +21 -0
- package/examples/shop/src/forretningsbetingelser.html +81 -0
- package/examples/shop/src/includes/body-end.html +1 -0
- package/examples/shop/src/includes/body-start.html +3 -0
- package/examples/shop/src/includes/head.html +32 -0
- package/examples/shop/src/index.html +25 -0
- package/examples/shop/src/navigation.ts +154 -0
- package/examples/shop/src/product.html +24 -0
- package/examples/shop/src/templates/page-layouts.html +162 -0
- package/examples/shop/src/templates/shop-footer.html +76 -0
- package/examples/shop/tsconfig.json +32 -0
- package/examples/shop/vite.config.mjs +190 -0
- package/html-custom-data.json +279 -0
- package/package.json +118 -0
- package/server/README.md +111 -0
- package/server/apache/.htaccess +18 -0
- package/server/nginx/orderly-products.conf +24 -0
- package/server/node/product-snapshot-server.mjs +133 -0
- package/server/php/orderly-product.php +204 -0
|
@@ -0,0 +1,876 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, relative, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const cwd = process.cwd();
|
|
7
|
+
const args = parseArgs(process.argv.slice(2));
|
|
8
|
+
const packageRoot = resolve(fileURLToPath(new URL("..", import.meta.url)));
|
|
9
|
+
const ownPackage = readJson(resolve(packageRoot, "package.json")) ?? {};
|
|
10
|
+
const defaultBackendUrl = "https://service.orderly.shop";
|
|
11
|
+
const shopName = args.name ?? "Orderly Butik";
|
|
12
|
+
const targetRoot = resolve(cwd, args.path ?? ".");
|
|
13
|
+
const force = Boolean(args.force);
|
|
14
|
+
const dryRun = Boolean(args["dry-run"]);
|
|
15
|
+
const skipPackageJson = Boolean(args["skip-package-json"]);
|
|
16
|
+
|
|
17
|
+
if (args.help || args.h) {
|
|
18
|
+
printHelp();
|
|
19
|
+
process.exit(0);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const config = {
|
|
23
|
+
accountId: args["account-id"],
|
|
24
|
+
backendUrl: args["base-url"] ?? defaultBackendUrl,
|
|
25
|
+
packageName: args["package-name"] ?? slugPackageName(shopName),
|
|
26
|
+
shopName
|
|
27
|
+
};
|
|
28
|
+
const files = scaffoldFiles(config);
|
|
29
|
+
const packageJsonPath = resolve(targetRoot, "package.json");
|
|
30
|
+
const planned = [...files.map((file) => file.path)];
|
|
31
|
+
if (!skipPackageJson) {
|
|
32
|
+
planned.push("package.json");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const existing = files
|
|
36
|
+
.filter((file) => !file.skipIfExists)
|
|
37
|
+
.map((file) => file.path)
|
|
38
|
+
.filter((file) => existsSync(resolve(targetRoot, file)));
|
|
39
|
+
if (existing.length && !force) {
|
|
40
|
+
throw new Error(`Refusing to overwrite existing files: ${existing.join(", ")}. Re-run with --force to overwrite scaffold files.`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (dryRun) {
|
|
44
|
+
console.log(`Would initialize ${shopName} in ${relative(cwd, targetRoot) || "."}.`);
|
|
45
|
+
for (const file of planned) {
|
|
46
|
+
console.log(`- ${file}`);
|
|
47
|
+
}
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
mkdirSync(targetRoot, { recursive: true });
|
|
52
|
+
for (const file of files) {
|
|
53
|
+
const target = resolve(targetRoot, file.path);
|
|
54
|
+
if (file.skipIfExists && existsSync(target) && !force) {
|
|
55
|
+
console.log(`Skipped existing ${relative(cwd, target)}.`);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
59
|
+
writeFileSync(target, file.content, "utf8");
|
|
60
|
+
console.log(`Created ${relative(cwd, target)}.`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!skipPackageJson) {
|
|
64
|
+
const packageJsonExists = existsSync(packageJsonPath);
|
|
65
|
+
const nextPackageJson = packageJsonSource(packageJsonPath, config);
|
|
66
|
+
writeFileSync(packageJsonPath, `${JSON.stringify(nextPackageJson, null, 2)}\n`, "utf8");
|
|
67
|
+
console.log(`${packageJsonExists ? "Updated" : "Created"} ${relative(cwd, packageJsonPath)}.`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.log(`\nNext steps:
|
|
71
|
+
npm install
|
|
72
|
+
npm run dev
|
|
73
|
+
|
|
74
|
+
Then open the local Vite URL and start customizing src/navigation.ts, src/style.css, and src/templates/*.html.`);
|
|
75
|
+
|
|
76
|
+
function scaffoldFiles(options) {
|
|
77
|
+
return [
|
|
78
|
+
{ path: ".gitignore", content: gitignoreSource(), skipIfExists: true },
|
|
79
|
+
{ path: "AGENTS.md", content: agentsSource(), skipIfExists: true },
|
|
80
|
+
{ path: "README.md", content: readmeSource(options), skipIfExists: true },
|
|
81
|
+
{ path: "tsconfig.json", content: tsconfigSource() },
|
|
82
|
+
{ path: "vite.config.mjs", content: viteConfigSource() },
|
|
83
|
+
{ path: "src/index.html", content: pageSource({ title: options.shopName, element: homeElementSource() }) },
|
|
84
|
+
{ path: "src/category.html", content: pageSource({ title: `Kategori | ${options.shopName}`, element: "<orderly-category-page></orderly-category-page>" }) },
|
|
85
|
+
{ path: "src/product.html", content: pageSource({ title: `Produkt | ${options.shopName}`, element: productDetailElementSource() }) },
|
|
86
|
+
{ path: "src/checkout.html", content: pageSource({ title: `Betaling | ${options.shopName}`, element: checkoutElementSource() }) },
|
|
87
|
+
{ path: "src/includes/head.html", content: headIncludeSource(options) },
|
|
88
|
+
{ path: "src/includes/body-start.html", content: bodyStartSource() },
|
|
89
|
+
{ path: "src/includes/body-end.html", content: "<!-- Add shared body-end snippets here. -->\n" },
|
|
90
|
+
{ path: "src/navigation.ts", content: navigationSource() },
|
|
91
|
+
{ path: "src/shop-query.ts", content: shopQuerySource(options) },
|
|
92
|
+
{ path: "src/style.css", content: styleSource() },
|
|
93
|
+
{ path: "src/templates/shop-footer.html", content: shopFooterTemplateSource(options) },
|
|
94
|
+
{ path: "src/templates/product-tile.html", content: productTileTemplateSource() },
|
|
95
|
+
{ path: "src/templates/product-page.html", content: productPageTemplateSource() },
|
|
96
|
+
{ path: "src/templates/basket.html", content: basketTemplateSource() }
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function packageJsonSource(file, options) {
|
|
101
|
+
const existing = readJson(file);
|
|
102
|
+
const packageJson = existing ?? {
|
|
103
|
+
name: options.packageName,
|
|
104
|
+
version: "0.1.0",
|
|
105
|
+
private: true,
|
|
106
|
+
type: "module"
|
|
107
|
+
};
|
|
108
|
+
packageJson.type ??= "module";
|
|
109
|
+
packageJson.scripts = {
|
|
110
|
+
dev: "vite",
|
|
111
|
+
build: "orderly-build-category-pages --build-command \"tsc --noEmit && vite build\"",
|
|
112
|
+
preview: "vite preview",
|
|
113
|
+
"generate:categories": "orderly-generate-category-pages",
|
|
114
|
+
"clean:categories": "orderly-generate-category-pages --clean",
|
|
115
|
+
"build:category-pages": "orderly-build-category-pages --build-command \"tsc --noEmit && vite build\"",
|
|
116
|
+
...(packageJson.scripts ?? {})
|
|
117
|
+
};
|
|
118
|
+
packageJson.dependencies = {
|
|
119
|
+
"@orderlyshop/core-client": ownPackage.dependencies?.["@orderlyshop/core-client"] ?? "^0.1.0",
|
|
120
|
+
"@orderlyshop/web-components": `^${ownPackage.version ?? "0.1.0"}`,
|
|
121
|
+
...(packageJson.dependencies ?? {})
|
|
122
|
+
};
|
|
123
|
+
packageJson.devDependencies = {
|
|
124
|
+
typescript: ownPackage.devDependencies?.typescript ?? "^5.9.3",
|
|
125
|
+
vite: "^7.3.2",
|
|
126
|
+
...(packageJson.devDependencies ?? {})
|
|
127
|
+
};
|
|
128
|
+
return packageJson;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function gitignoreSource() {
|
|
132
|
+
return `node_modules
|
|
133
|
+
dist
|
|
134
|
+
.env
|
|
135
|
+
.DS_Store
|
|
136
|
+
`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function agentsSource() {
|
|
140
|
+
return `# Agent Guide
|
|
141
|
+
|
|
142
|
+
- This shop was scaffolded with \`npx orderly-init-shop\` from \`@orderlyshop/web-components\`.
|
|
143
|
+
- Keep storefront logic in Orderly web components. Customize navigation, content, templates, and CSS instead of replacing package components.
|
|
144
|
+
- Configure shop-wide backend, default query, navigation, page layout, and component registration in \`src/includes/head.html\` through \`configureShop(...)\`.
|
|
145
|
+
- Use \`src/navigation.ts\` for category/navigation structure. Every category needs a stable globally unique \`slug\`.
|
|
146
|
+
- Use \`src/shop-query.ts\` for the shop-wide Core \`SearchQuery\`, such as tenant/account scoping.
|
|
147
|
+
- Put shop-specific component templates in \`src/templates/*.html\` and include them from \`src/includes/body-start.html\`.
|
|
148
|
+
- \`npm run build\` generates real category URL files and hydrates built homepage/category pages with server-rendered fallback HTML.
|
|
149
|
+
- Use \`npm run generate:categories\` for local source inspection and \`npm run clean:categories\` to remove generated source pages.
|
|
150
|
+
`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function readmeSource(options) {
|
|
154
|
+
return `# ${options.shopName}
|
|
155
|
+
|
|
156
|
+
Vanilla storefront scaffolded with \`@orderlyshop/web-components\`.
|
|
157
|
+
|
|
158
|
+
## Commands
|
|
159
|
+
|
|
160
|
+
\`\`\`sh
|
|
161
|
+
npm install
|
|
162
|
+
npm run dev
|
|
163
|
+
npm run build
|
|
164
|
+
npm run preview
|
|
165
|
+
\`\`\`
|
|
166
|
+
|
|
167
|
+
Override the backend during local development:
|
|
168
|
+
|
|
169
|
+
\`\`\`sh
|
|
170
|
+
VITE_ORDERLY_BASE_URL=${options.backendUrl} npm run dev
|
|
171
|
+
\`\`\`
|
|
172
|
+
|
|
173
|
+
Production builds generate real category URL pages from \`src/navigation.ts\` and hydrate the built homepage/category pages with server-rendered fallback HTML:
|
|
174
|
+
|
|
175
|
+
\`\`\`sh
|
|
176
|
+
npm run build
|
|
177
|
+
\`\`\`
|
|
178
|
+
|
|
179
|
+
Use \`npm run generate:categories\` when you want to inspect generated source pages during development. Generated category source pages are removed automatically after \`npm run build\`.
|
|
180
|
+
|
|
181
|
+
## Customize
|
|
182
|
+
|
|
183
|
+
- \`src/navigation.ts\` defines storefront navigation and category queries.
|
|
184
|
+
- \`src/shop-query.ts\` defines the shop-wide Core \`SearchQuery\`.
|
|
185
|
+
- \`src/includes/head.html\` calls \`configureShop(...)\`.
|
|
186
|
+
- \`src/templates/*.html\` contains global component templates.
|
|
187
|
+
- \`src/style.css\` owns the shop look and feel.
|
|
188
|
+
`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function tsconfigSource() {
|
|
192
|
+
return `{
|
|
193
|
+
"compilerOptions": {
|
|
194
|
+
"target": "ES2022",
|
|
195
|
+
"useDefineForClassFields": true,
|
|
196
|
+
"module": "ES2022",
|
|
197
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
198
|
+
"types": ["vite/client"],
|
|
199
|
+
"skipLibCheck": true,
|
|
200
|
+
"moduleResolution": "bundler",
|
|
201
|
+
"allowImportingTsExtensions": true,
|
|
202
|
+
"isolatedModules": true,
|
|
203
|
+
"moduleDetection": "force",
|
|
204
|
+
"noEmit": true,
|
|
205
|
+
"strict": true
|
|
206
|
+
},
|
|
207
|
+
"include": ["src"]
|
|
208
|
+
}
|
|
209
|
+
`;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function viteConfigSource() {
|
|
213
|
+
return `import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
214
|
+
import { dirname, relative, resolve } from "node:path";
|
|
215
|
+
import { defineConfig } from "vite";
|
|
216
|
+
|
|
217
|
+
export default defineConfig({
|
|
218
|
+
server: {
|
|
219
|
+
host: "localhost",
|
|
220
|
+
port: 61677,
|
|
221
|
+
strictPort: true
|
|
222
|
+
},
|
|
223
|
+
preview: {
|
|
224
|
+
host: "localhost",
|
|
225
|
+
port: 61677,
|
|
226
|
+
strictPort: true
|
|
227
|
+
},
|
|
228
|
+
plugins: [htmlIncludes(), sourceHtmlDevServer(), rootHtmlOutput()],
|
|
229
|
+
build: {
|
|
230
|
+
rollupOptions: {
|
|
231
|
+
input: htmlInputs()
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
function htmlIncludes() {
|
|
237
|
+
return {
|
|
238
|
+
name: "orderly-html-includes",
|
|
239
|
+
transformIndexHtml: {
|
|
240
|
+
order: "pre",
|
|
241
|
+
handler(html) {
|
|
242
|
+
return html
|
|
243
|
+
.replace("<!-- orderly-head-includes -->", include("head.html"))
|
|
244
|
+
.replace("<!-- orderly-body-start-includes -->", include("body-start.html"))
|
|
245
|
+
.replace("<!-- orderly-body-end-includes -->", include("body-end.html"));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function sourceHtmlDevServer() {
|
|
252
|
+
return {
|
|
253
|
+
name: "orderly-source-html-dev-server",
|
|
254
|
+
configureServer(server) {
|
|
255
|
+
server.middlewares.use(async (request, response, next) => {
|
|
256
|
+
const file = sourceHtmlFileForUrl(request.url ?? "/");
|
|
257
|
+
if (!file) {
|
|
258
|
+
next();
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
try {
|
|
263
|
+
const html = await server.transformIndexHtml(request.url ?? "/", readFileSync(file, "utf8"));
|
|
264
|
+
response.statusCode = 200;
|
|
265
|
+
response.setHeader("Content-Type", "text/html");
|
|
266
|
+
response.end(html);
|
|
267
|
+
} catch (error) {
|
|
268
|
+
next(error);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function rootHtmlOutput() {
|
|
276
|
+
return {
|
|
277
|
+
name: "orderly-root-html-output",
|
|
278
|
+
enforce: "post",
|
|
279
|
+
generateBundle(_, bundle) {
|
|
280
|
+
for (const [fileName, output] of Object.entries(bundle)) {
|
|
281
|
+
if (!fileName.startsWith("src/") || !fileName.endsWith(".html")) {
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
delete bundle[fileName];
|
|
285
|
+
output.fileName = fileName.slice("src/".length);
|
|
286
|
+
bundle[output.fileName] = output;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function include(fileName) {
|
|
293
|
+
return includeFile(resolve(process.cwd(), "src/includes", fileName));
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function includeFile(filePath) {
|
|
297
|
+
return expandTemplateIncludes(readFileSync(filePath, "utf8").trim(), dirname(filePath));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function expandTemplateIncludes(html, baseDir) {
|
|
301
|
+
return html.replace(/<!--\\s*orderly-include-template:\\s*([^>]+?)\\s*-->/g, (_, templatePath) => {
|
|
302
|
+
return includeFile(resolve(baseDir, templatePath.trim()));
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function htmlInputs(root = process.cwd()) {
|
|
307
|
+
const inputs = {};
|
|
308
|
+
for (const file of listHtmlFiles(root)) {
|
|
309
|
+
const name = htmlInputName(root, file);
|
|
310
|
+
inputs[name] = file;
|
|
311
|
+
}
|
|
312
|
+
return inputs;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function htmlInputName(root, file) {
|
|
316
|
+
const name = relative(root, file).replace(/\\\\/g, "/").replace(/\\.html$/, "");
|
|
317
|
+
return name.startsWith("src/") ? name.slice("src/".length) : name;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function listHtmlFiles(dir) {
|
|
321
|
+
const files = [];
|
|
322
|
+
for (const entry of readdirSync(dir)) {
|
|
323
|
+
if (entry === "dist" || entry === "node_modules") {
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
const fullPath = resolve(dir, entry);
|
|
327
|
+
if (isHtmlPartial(fullPath)) {
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
const stat = statSync(fullPath);
|
|
331
|
+
if (stat.isDirectory()) {
|
|
332
|
+
files.push(...listHtmlFiles(fullPath));
|
|
333
|
+
} else if (entry.endsWith(".html")) {
|
|
334
|
+
files.push(fullPath);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return files;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function isHtmlPartial(file) {
|
|
341
|
+
const path = relative(process.cwd(), file).replace(/\\\\/g, "/");
|
|
342
|
+
return path.startsWith("src/includes") || path.startsWith("src/templates");
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function sourceHtmlFileForUrl(url) {
|
|
346
|
+
const requestPath = safeHtmlRequestPath(url);
|
|
347
|
+
if (!requestPath) {
|
|
348
|
+
return undefined;
|
|
349
|
+
}
|
|
350
|
+
const srcRoot = resolve(process.cwd(), "src");
|
|
351
|
+
const categoryTemplate = resolve(srcRoot, "category.html");
|
|
352
|
+
if (requestPath.startsWith("categories/") && requestPath.endsWith("index.html") && existsSync(categoryTemplate)) {
|
|
353
|
+
return categoryTemplate;
|
|
354
|
+
}
|
|
355
|
+
const file = resolve(srcRoot, requestPath);
|
|
356
|
+
if (!file.startsWith(srcRoot) || isHtmlPartial(file) || !existsSync(file)) {
|
|
357
|
+
return undefined;
|
|
358
|
+
}
|
|
359
|
+
return statSync(file).isFile() ? file : undefined;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function safeHtmlRequestPath(url) {
|
|
363
|
+
const [rawPath] = url.split(/[?#]/, 1);
|
|
364
|
+
let path;
|
|
365
|
+
try {
|
|
366
|
+
path = decodeURIComponent(rawPath || "/");
|
|
367
|
+
} catch {
|
|
368
|
+
return undefined;
|
|
369
|
+
}
|
|
370
|
+
if (path.includes("..")) {
|
|
371
|
+
return undefined;
|
|
372
|
+
}
|
|
373
|
+
if (path === "/") {
|
|
374
|
+
return "index.html";
|
|
375
|
+
}
|
|
376
|
+
if (path.endsWith("/")) {
|
|
377
|
+
return path.slice(1) + "index.html";
|
|
378
|
+
}
|
|
379
|
+
if (path.startsWith("/categories/") && !path.includes(".")) {
|
|
380
|
+
return path.slice(1) + "/index.html";
|
|
381
|
+
}
|
|
382
|
+
if (path.endsWith(".html")) {
|
|
383
|
+
return path.slice(1);
|
|
384
|
+
}
|
|
385
|
+
return undefined;
|
|
386
|
+
}
|
|
387
|
+
`;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function pageSource({ title, element }) {
|
|
391
|
+
return `<!doctype html>
|
|
392
|
+
<html lang="da">
|
|
393
|
+
<head>
|
|
394
|
+
<meta charset="UTF-8">
|
|
395
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
396
|
+
<title>${escapeHtml(title)}</title>
|
|
397
|
+
<!-- orderly-head-includes -->
|
|
398
|
+
</head>
|
|
399
|
+
<body>
|
|
400
|
+
<!-- orderly-body-start-includes -->
|
|
401
|
+
${element}
|
|
402
|
+
<!-- orderly-body-end-includes -->
|
|
403
|
+
</body>
|
|
404
|
+
</html>
|
|
405
|
+
`;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function homeElementSource() {
|
|
409
|
+
return `<orderly-home-page
|
|
410
|
+
eyebrow="Nye varer"
|
|
411
|
+
title="Find næste favorit"
|
|
412
|
+
rail-cta-label="Se alle"
|
|
413
|
+
empty-label="Der blev ikke fundet varer."
|
|
414
|
+
checkout-label="Gå til betaling">
|
|
415
|
+
<div slot="utility" class="shop-utility-strip" aria-label="Kundefordele">
|
|
416
|
+
<span>Hurtig levering</span>
|
|
417
|
+
<span>Nem retur</span>
|
|
418
|
+
<span>Sikker betaling</span>
|
|
419
|
+
</div>
|
|
420
|
+
</orderly-home-page>`;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function productDetailElementSource() {
|
|
424
|
+
return `<orderly-product-detail-page
|
|
425
|
+
add-label="Læg i kurven"
|
|
426
|
+
loading-label="Indlæser produkt..."
|
|
427
|
+
not-found-label="Produktet blev ikke fundet."
|
|
428
|
+
error-label="Produktet kunne ikke indlæses.">
|
|
429
|
+
<div slot="utility" class="shop-utility-strip" aria-label="Kundefordele">
|
|
430
|
+
<span>Hurtig levering</span>
|
|
431
|
+
<span>Nem retur</span>
|
|
432
|
+
<span>Sikker betaling</span>
|
|
433
|
+
</div>
|
|
434
|
+
</orderly-product-detail-page>`;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function checkoutElementSource() {
|
|
438
|
+
return `<orderly-checkout-page>
|
|
439
|
+
<div slot="utility" class="shop-utility-strip" aria-label="Kundefordele">
|
|
440
|
+
<span>Hurtig levering</span>
|
|
441
|
+
<span>Nem retur</span>
|
|
442
|
+
<span>Sikker betaling</span>
|
|
443
|
+
</div>
|
|
444
|
+
</orderly-checkout-page>`;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function headIncludeSource(options) {
|
|
448
|
+
return `<!-- Add shared head snippets here, for example analytics, consent, or preconnect tags. -->
|
|
449
|
+
<link rel="icon" href="https://orderly.shop/home/App_Icon.svg" type="image/svg+xml">
|
|
450
|
+
<link rel="stylesheet" href="/src/style.css">
|
|
451
|
+
<script type="module">
|
|
452
|
+
import { configureShop } from "@orderlyshop/web-components";
|
|
453
|
+
import { navigationDefinitions } from "/src/navigation.ts";
|
|
454
|
+
import { defaultQuery } from "/src/shop-query.ts";
|
|
455
|
+
|
|
456
|
+
const categoryUrlMode = import.meta.env.VITE_ORDERLY_CATEGORY_URL_MODE === "hash" ? "hash" : "path";
|
|
457
|
+
|
|
458
|
+
configureShop({
|
|
459
|
+
uiLanguage: "DA",
|
|
460
|
+
defaultShop: {
|
|
461
|
+
baseUrl: import.meta.env.VITE_ORDERLY_BASE_URL ?? ${JSON.stringify(options.backendUrl)},
|
|
462
|
+
defaultQuery,
|
|
463
|
+
brandLabel: ${JSON.stringify(options.shopName)},
|
|
464
|
+
homeHref: "/",
|
|
465
|
+
checkoutHref: "/checkout.html",
|
|
466
|
+
navigationDefinitions,
|
|
467
|
+
categoryPageRoot: categoryUrlMode === "hash" ? "/category.html" : "/categories/",
|
|
468
|
+
categoryUrlMode
|
|
469
|
+
},
|
|
470
|
+
pageLayout: {
|
|
471
|
+
logoSrc: "https://orderly.shop/home/App_Icon.svg",
|
|
472
|
+
logoAlt: ${JSON.stringify(options.shopName)},
|
|
473
|
+
logoHref: "/"
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
</script>
|
|
477
|
+
`;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function bodyStartSource() {
|
|
481
|
+
return `<!-- Add shared body-start snippets here, for example Google Tag Manager noscript fallback. -->
|
|
482
|
+
<!-- orderly-include-template: ../templates/shop-footer.html -->
|
|
483
|
+
<!-- orderly-include-template: ../templates/product-tile.html -->
|
|
484
|
+
<!-- orderly-include-template: ../templates/product-page.html -->
|
|
485
|
+
<!-- orderly-include-template: ../templates/basket.html -->
|
|
486
|
+
`;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function navigationSource() {
|
|
490
|
+
return `import type { NavigationDefinition } from "@orderlyshop/web-components";
|
|
491
|
+
|
|
492
|
+
export const navigationDefinitions: NavigationDefinition[] = [
|
|
493
|
+
{
|
|
494
|
+
label: "Nyheder",
|
|
495
|
+
slug: "nyheder",
|
|
496
|
+
heroImage: "https://images.unsplash.com/photo-1496747611176-843222e1e57c?auto=format&fit=crop&w=1800&q=80",
|
|
497
|
+
query: { query: "nyheder" },
|
|
498
|
+
children: [
|
|
499
|
+
{ label: "Dame", slug: "dame", query: { query: "dame mode" } },
|
|
500
|
+
{ label: "Herre", slug: "herre", query: { query: "herre mode" } },
|
|
501
|
+
{ label: "Børn", slug: "boern", query: { query: "børn baby" } }
|
|
502
|
+
]
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
label: "Sko",
|
|
506
|
+
slug: "sko",
|
|
507
|
+
heroImage: "https://images.unsplash.com/photo-1542291026-7eec264c27ff?auto=format&fit=crop&w=1800&q=80",
|
|
508
|
+
query: { query: "sko sneakers" },
|
|
509
|
+
children: [
|
|
510
|
+
{ label: "Damesko", slug: "damesko", query: { query: "damesko sneakers" } },
|
|
511
|
+
{ label: "Herresko", slug: "herresko", query: { query: "herresko sneakers" } },
|
|
512
|
+
{ label: "Sportssko", slug: "sportssko", query: { query: "sportssko løb" } }
|
|
513
|
+
]
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
label: "Bolig",
|
|
517
|
+
slug: "bolig",
|
|
518
|
+
heroImage: "https://images.unsplash.com/photo-1618221195710-dd6b41faaea6?auto=format&fit=crop&w=1800&q=80",
|
|
519
|
+
query: { query: "bolig indretning" },
|
|
520
|
+
children: [
|
|
521
|
+
{ label: "Møbler", slug: "moebler", query: { query: "møbler sofa stol bord" } },
|
|
522
|
+
{ label: "Belysning", slug: "belysning", query: { query: "belysning lampe" } },
|
|
523
|
+
{ label: "Dekoration", slug: "dekoration", query: { query: "dekoration kunst bolig" } }
|
|
524
|
+
]
|
|
525
|
+
}
|
|
526
|
+
];
|
|
527
|
+
`;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function shopQuerySource(options) {
|
|
531
|
+
if (!options.accountId) {
|
|
532
|
+
return `import type { SearchQuery } from "@orderlyshop/core-client";
|
|
533
|
+
|
|
534
|
+
// Add shop-wide SearchQuery constraints here, for example SourceAccountId.
|
|
535
|
+
// This query is merged into every category, rail, and product grid search.
|
|
536
|
+
export const defaultQuery: SearchQuery | undefined = undefined;
|
|
537
|
+
`;
|
|
538
|
+
}
|
|
539
|
+
return `import { create } from "@bufbuild/protobuf";
|
|
540
|
+
import { AccountIdSchema, SearchQuerySchema, UUIDSchema } from "@orderlyshop/core-client";
|
|
541
|
+
|
|
542
|
+
export const defaultQuery = create(SearchQuerySchema, {
|
|
543
|
+
SourceAccountId: create(AccountIdSchema, {
|
|
544
|
+
Id: create(UUIDSchema, { Value: ${JSON.stringify(options.accountId)} })
|
|
545
|
+
})
|
|
546
|
+
});
|
|
547
|
+
`;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function styleSource() {
|
|
551
|
+
return `* {
|
|
552
|
+
box-sizing: border-box;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
:root {
|
|
556
|
+
--shop-accent: #ef5a24;
|
|
557
|
+
--shop-border: #e2e2e2;
|
|
558
|
+
--shop-muted: #5f5f5f;
|
|
559
|
+
color: #141414;
|
|
560
|
+
background: #eeeeee;
|
|
561
|
+
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
body {
|
|
565
|
+
min-width: 320px;
|
|
566
|
+
margin: 0;
|
|
567
|
+
overflow-x: hidden;
|
|
568
|
+
background: #eeeeee;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
body.orderly-navigation-menu-open {
|
|
572
|
+
overflow: hidden;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
orderly-home-page:not(:defined),
|
|
576
|
+
orderly-category-page:not(:defined),
|
|
577
|
+
orderly-product-detail-page:not(:defined),
|
|
578
|
+
orderly-checkout-page:not(:defined) {
|
|
579
|
+
display: block;
|
|
580
|
+
min-height: 100vh;
|
|
581
|
+
background: #f6f8f9;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
orderly-home-page:not(:defined)::before,
|
|
585
|
+
orderly-category-page:not(:defined)::before,
|
|
586
|
+
orderly-product-detail-page:not(:defined)::before,
|
|
587
|
+
orderly-checkout-page:not(:defined)::before {
|
|
588
|
+
content: "";
|
|
589
|
+
display: block;
|
|
590
|
+
height: 62px;
|
|
591
|
+
border-bottom: 1px solid #d8dee3;
|
|
592
|
+
background: #ffffff;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
button,
|
|
596
|
+
input,
|
|
597
|
+
select {
|
|
598
|
+
font: inherit;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
.shop-utility-strip {
|
|
602
|
+
display: flex;
|
|
603
|
+
justify-content: center;
|
|
604
|
+
gap: clamp(14px, 4vw, 64px);
|
|
605
|
+
padding: 8px 16px;
|
|
606
|
+
background: #141414;
|
|
607
|
+
color: #ffffff;
|
|
608
|
+
font-size: 0.8rem;
|
|
609
|
+
font-weight: 700;
|
|
610
|
+
text-transform: uppercase;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
.orderly-page-layout__header {
|
|
614
|
+
position: sticky;
|
|
615
|
+
top: 0;
|
|
616
|
+
z-index: 20;
|
|
617
|
+
display: flex;
|
|
618
|
+
align-items: center;
|
|
619
|
+
justify-content: space-between;
|
|
620
|
+
gap: 16px;
|
|
621
|
+
padding: 14px 24px;
|
|
622
|
+
border-bottom: 1px solid var(--shop-border);
|
|
623
|
+
background: #ffffff;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
.orderly-page-layout__primary-nav,
|
|
627
|
+
.orderly-page-layout__footer {
|
|
628
|
+
background: #ffffff;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
.orderly-product-grid__items,
|
|
632
|
+
.orderly-product-rail__items {
|
|
633
|
+
display: grid;
|
|
634
|
+
grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
|
|
635
|
+
gap: 18px;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
.orderly-product-rail__items {
|
|
639
|
+
display: flex;
|
|
640
|
+
overflow-x: auto;
|
|
641
|
+
padding-bottom: 12px;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
.orderly-product-rail__item {
|
|
645
|
+
flex: 0 0 min(240px, 70vw);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
.orderly-product-tile {
|
|
649
|
+
display: grid;
|
|
650
|
+
grid-template-rows: auto minmax(0, 1fr);
|
|
651
|
+
height: 100%;
|
|
652
|
+
border: 1px solid var(--shop-border);
|
|
653
|
+
border-radius: 8px;
|
|
654
|
+
background: #ffffff;
|
|
655
|
+
overflow: hidden;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
.orderly-product-tile__image {
|
|
659
|
+
width: 100%;
|
|
660
|
+
aspect-ratio: 1;
|
|
661
|
+
background: #ffffff;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
.orderly-product-tile__body {
|
|
665
|
+
display: flex;
|
|
666
|
+
flex-direction: column;
|
|
667
|
+
gap: 8px;
|
|
668
|
+
min-width: 0;
|
|
669
|
+
padding: 12px;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
.orderly-product-tile__footer {
|
|
673
|
+
display: flex;
|
|
674
|
+
align-items: center;
|
|
675
|
+
justify-content: space-between;
|
|
676
|
+
gap: 12px;
|
|
677
|
+
margin-top: auto;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
.orderly-product-tile__add,
|
|
681
|
+
.orderly-basket-icon__button {
|
|
682
|
+
min-width: 44px;
|
|
683
|
+
min-height: 44px;
|
|
684
|
+
border: 1px solid #141414;
|
|
685
|
+
border-radius: 999px;
|
|
686
|
+
background: #141414;
|
|
687
|
+
color: #ffffff;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
.orderly-product-tile__add-icon,
|
|
691
|
+
.orderly-product-tile__add-svg,
|
|
692
|
+
.orderly-product-tile__remove-svg {
|
|
693
|
+
display: block;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
.orderly-shop-footer {
|
|
697
|
+
display: grid;
|
|
698
|
+
gap: 24px;
|
|
699
|
+
padding: 40px 24px;
|
|
700
|
+
border-top: 1px solid var(--shop-border);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
@media (min-width: 760px) {
|
|
704
|
+
.orderly-shop-footer {
|
|
705
|
+
grid-template-columns: 2fr repeat(3, 1fr);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
`;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
function shopFooterTemplateSource(options) {
|
|
712
|
+
return `<template data-orderly-template="footer" data-orderly-for="shop-footer">
|
|
713
|
+
<div class="orderly-shop-footer">
|
|
714
|
+
<div class="orderly-shop-footer__brand">
|
|
715
|
+
<a class="orderly-shop-footer__logo" href="/" aria-label="${escapeAttribute(options.shopName)}">
|
|
716
|
+
<img class="orderly-shop-footer__logo-image" src="https://orderly.shop/home/App_Icon.svg" alt="${escapeAttribute(options.shopName)}">
|
|
717
|
+
</a>
|
|
718
|
+
<p class="orderly-shop-footer__about">A storefront built with Orderly web components.</p>
|
|
719
|
+
</div>
|
|
720
|
+
<section class="orderly-shop-footer__section">
|
|
721
|
+
<h2 class="orderly-shop-footer__heading">Contact</h2>
|
|
722
|
+
<p>hello@example.com</p>
|
|
723
|
+
<p>+45 12 34 56 78</p>
|
|
724
|
+
</section>
|
|
725
|
+
<section class="orderly-shop-footer__section">
|
|
726
|
+
<h2 class="orderly-shop-footer__heading">Opening hours</h2>
|
|
727
|
+
<p>Monday - Friday: 09.00 - 17.00</p>
|
|
728
|
+
<p>Saturday: 10.00 - 14.00</p>
|
|
729
|
+
</section>
|
|
730
|
+
<section class="orderly-shop-footer__section">
|
|
731
|
+
<h2 class="orderly-shop-footer__heading">Information</h2>
|
|
732
|
+
<ul class="orderly-shop-footer__list">
|
|
733
|
+
<li><a href="/information/terms/">Terms</a></li>
|
|
734
|
+
<li><a href="/information/delivery/">Delivery and returns</a></li>
|
|
735
|
+
<li><a href="/information/privacy/">Privacy policy</a></li>
|
|
736
|
+
</ul>
|
|
737
|
+
</section>
|
|
738
|
+
</div>
|
|
739
|
+
</template>
|
|
740
|
+
`;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function productTileTemplateSource() {
|
|
744
|
+
return `<template data-orderly-template="product" data-orderly-for="product-tile">
|
|
745
|
+
<article class="orderly-product-tile">
|
|
746
|
+
<orderly-stored-image class="orderly-product-tile__image" data-orderly-bind="image" fit="contain"></orderly-stored-image>
|
|
747
|
+
<div class="orderly-product-tile__body">
|
|
748
|
+
<h3 class="orderly-product-tile__title">
|
|
749
|
+
<a class="orderly-product-tile__link" href="/product.html" data-orderly-bind="share-url">
|
|
750
|
+
<span data-orderly-bind="title"></span>
|
|
751
|
+
</a>
|
|
752
|
+
</h3>
|
|
753
|
+
<p class="orderly-product-tile__brand" data-orderly-bind="brand"></p>
|
|
754
|
+
<div class="orderly-product-tile__footer">
|
|
755
|
+
<p class="orderly-product-tile__price" data-orderly-bind="price"></p>
|
|
756
|
+
<button class="orderly-product-tile__add" type="button" data-orderly-action="add-to-basket">
|
|
757
|
+
<span class="orderly-product-tile__add-icon" data-orderly-bind="basket-action-icon" aria-hidden="true"></span>
|
|
758
|
+
</button>
|
|
759
|
+
</div>
|
|
760
|
+
</div>
|
|
761
|
+
</article>
|
|
762
|
+
</template>
|
|
763
|
+
`;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
function productPageTemplateSource() {
|
|
767
|
+
return `<template data-orderly-template="product" data-orderly-for="product-page">
|
|
768
|
+
<section class="orderly-product-page">
|
|
769
|
+
<div class="orderly-product-page__media" style="display: grid; gap: 12px; align-content: start; justify-self: center; width: 100%; max-width: 520px; min-width: 0;">
|
|
770
|
+
<orderly-stored-image class="orderly-product-page__image" data-orderly-bind="image" fit="contain" variant="object" style="display: block; width: 100%; max-width: 520px; min-width: 0; aspect-ratio: 1 / 1; overflow: hidden; background: #ffffff;"></orderly-stored-image>
|
|
771
|
+
<div class="orderly-product-page__thumbnails" data-orderly-slot="thumbnails"></div>
|
|
772
|
+
</div>
|
|
773
|
+
<div class="orderly-product-page__details" style="min-width: 0;">
|
|
774
|
+
<p data-orderly-bind="brand"></p>
|
|
775
|
+
<h1 data-orderly-bind="title"></h1>
|
|
776
|
+
<p data-orderly-bind="price"></p>
|
|
777
|
+
<p data-orderly-bind="description"></p>
|
|
778
|
+
<button type="button" data-orderly-action="add-to-basket">
|
|
779
|
+
<span data-orderly-bind="basket-action-label"></span>
|
|
780
|
+
</button>
|
|
781
|
+
</div>
|
|
782
|
+
</section>
|
|
783
|
+
</template>
|
|
784
|
+
`;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function basketTemplateSource() {
|
|
788
|
+
return `<template data-orderly-template="basket" data-orderly-for="basket">
|
|
789
|
+
<section class="orderly-basket">
|
|
790
|
+
<header class="orderly-basket__header">
|
|
791
|
+
<h2 data-orderly-bind="title"></h2>
|
|
792
|
+
<button type="button" data-orderly-action="clear"><span data-orderly-bind="clearLabel"></span></button>
|
|
793
|
+
</header>
|
|
794
|
+
<p class="orderly-basket__empty" data-orderly-bind="emptyLabel"></p>
|
|
795
|
+
<ul class="orderly-basket__lines" data-orderly-slot="lines"></ul>
|
|
796
|
+
<footer class="orderly-basket__summary">
|
|
797
|
+
<p><span data-orderly-bind="subtotalLabel"></span> <strong data-orderly-bind="subtotal"></strong></p>
|
|
798
|
+
<p><span data-orderly-bind="totalLabel"></span> <strong data-orderly-bind="total"></strong></p>
|
|
799
|
+
<a class="orderly-basket__checkout" data-orderly-action="checkout" data-orderly-bind="checkoutHref">
|
|
800
|
+
<span data-orderly-bind="checkoutLabel"></span>
|
|
801
|
+
</a>
|
|
802
|
+
</footer>
|
|
803
|
+
</section>
|
|
804
|
+
</template>
|
|
805
|
+
`;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function parseArgs(values) {
|
|
809
|
+
const parsed = {};
|
|
810
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
811
|
+
const value = values[index];
|
|
812
|
+
if (!value.startsWith("--")) {
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
const [name, inlineValue] = value.slice(2).split("=", 2);
|
|
816
|
+
if (inlineValue !== undefined) {
|
|
817
|
+
parsed[name] = inlineValue;
|
|
818
|
+
} else if (values[index + 1] && !values[index + 1].startsWith("--")) {
|
|
819
|
+
parsed[name] = values[index + 1];
|
|
820
|
+
index += 1;
|
|
821
|
+
} else {
|
|
822
|
+
parsed[name] = true;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
return parsed;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
function printHelp() {
|
|
829
|
+
console.log(`Usage: orderly-init-shop [options]
|
|
830
|
+
|
|
831
|
+
Create a vanilla TypeScript storefront using @orderlyshop/web-components.
|
|
832
|
+
|
|
833
|
+
Options:
|
|
834
|
+
--path <dir> Target directory. Defaults to the current directory.
|
|
835
|
+
--name <name> Shop display name. Defaults to "Orderly Butik".
|
|
836
|
+
--package-name <name> package.json name when package.json does not exist.
|
|
837
|
+
--base-url <url> Orderly backend URL. Defaults to ${defaultBackendUrl}.
|
|
838
|
+
--account-id <id> Optional tenant/account id written into src/shop-query.ts.
|
|
839
|
+
--skip-package-json Do not create or update package.json.
|
|
840
|
+
--force Overwrite scaffold files that already exist.
|
|
841
|
+
--dry-run Print files that would be written without mutating the project.
|
|
842
|
+
--help, -h Show this help.
|
|
843
|
+
|
|
844
|
+
Agent workflow:
|
|
845
|
+
1. If no shop scaffold exists, run: npx orderly-init-shop
|
|
846
|
+
2. Ask the user for the shop account id. Re-run with --account-id <id> or edit src/shop-query.ts.
|
|
847
|
+
3. Customize src/navigation.ts with categories and SearchQuery values.
|
|
848
|
+
4. Customize src/templates/*.html and src/style.css for the shop look and feel.
|
|
849
|
+
`);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
function readJson(file) {
|
|
853
|
+
if (!existsSync(file)) {
|
|
854
|
+
return undefined;
|
|
855
|
+
}
|
|
856
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
function slugPackageName(value) {
|
|
860
|
+
const slug = value
|
|
861
|
+
.toLowerCase()
|
|
862
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
863
|
+
.replace(/^-+|-+$/g, "");
|
|
864
|
+
return slug || "orderly-shop";
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
function escapeHtml(value) {
|
|
868
|
+
return String(value)
|
|
869
|
+
.replaceAll("&", "&")
|
|
870
|
+
.replaceAll("<", "<")
|
|
871
|
+
.replaceAll(">", ">");
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
function escapeAttribute(value) {
|
|
875
|
+
return escapeHtml(value).replaceAll("\"", """);
|
|
876
|
+
}
|