@orderlyshop/web-components 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +495 -0
- package/dist/browser/orderly-web-components.define.global.js +3085 -0
- package/dist/browser/orderly-web-components.define.global.js.map +1 -0
- package/dist/browser/orderly-web-components.global.js +3085 -0
- package/dist/browser/orderly-web-components.global.js.map +1 -0
- package/dist/default-shop-DWdB_MRd.d.ts +220 -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-IAQk8OmQ.d.ts +9 -0
- package/dist/define.d.ts +2 -0
- package/dist/define.js +10266 -0
- package/dist/define.js.map +1 -0
- package/dist/index.d.ts +683 -0
- package/dist/index.js +10589 -0
- package/dist/index.js.map +1 -0
- package/dist/navigation.d.ts +51 -0
- package/dist/navigation.js +818 -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-BnT1C6kG.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 +10359 -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-CCQDd6Nd.d.ts +95 -0
- package/docs/components/README.md +610 -0
- package/docs/components/product-grid.md +176 -0
- package/docs/components/product-rail.md +174 -0
- package/examples/shop/README.md +71 -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 +80 -0
- package/examples/shop/src/includes/body-end.html +1 -0
- package/examples/shop/src/includes/body-start.html +2 -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/shop-footer.html +76 -0
- package/examples/shop/tsconfig.json +32 -0
- package/examples/shop/vite.config.mjs +184 -0
- package/html-custom-data.json +262 -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,342 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { chmodSync, cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { dirname, relative, resolve } from "node:path";
|
|
5
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
6
|
+
import { createInterface } from "node:readline/promises";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { Client } from "basic-ftp";
|
|
9
|
+
|
|
10
|
+
const cwd = process.cwd();
|
|
11
|
+
const args = parseArgs(process.argv.slice(2));
|
|
12
|
+
const target = args.target ?? "local";
|
|
13
|
+
const sourceDir = resolve(cwd, "dist");
|
|
14
|
+
const localConfigFile = resolve(cwd, ".orderly-publish.local.json");
|
|
15
|
+
const hydrateScript = fileURLToPath(new URL("./orderly-hydrate-static-pages.mjs", import.meta.url));
|
|
16
|
+
const buildCategoryScript = fileURLToPath(new URL("./orderly-build-category-pages.mjs", import.meta.url));
|
|
17
|
+
|
|
18
|
+
if (target !== "local" && target !== "ftp") {
|
|
19
|
+
throw new Error(`Unsupported publish target "${target}". Use --target local or --target ftp.`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!args["skip-build"]) {
|
|
23
|
+
if (!args["no-category-pages"]) {
|
|
24
|
+
await runNode([buildCategoryScript, ...passthroughCategoryBuildArgs(process.argv.slice(2))]);
|
|
25
|
+
} else {
|
|
26
|
+
await runNpm(["run", "build"]);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!existsSync(sourceDir)) {
|
|
31
|
+
throw new Error("Build output directory does not exist: dist");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (args.hydrate && args["no-category-pages"]) {
|
|
35
|
+
await runNode([hydrateScript, ...passthroughHydrateArgs(process.argv.slice(2))]);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (target === "local") {
|
|
39
|
+
publishLocal(args.dir ?? args.out ?? "published-site");
|
|
40
|
+
} else {
|
|
41
|
+
await publishFtp();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function publishLocal(outputDirectory) {
|
|
45
|
+
const destination = resolve(cwd, outputDirectory);
|
|
46
|
+
if (destination === sourceDir) {
|
|
47
|
+
throw new Error("Local publish directory cannot be the source directory.");
|
|
48
|
+
}
|
|
49
|
+
rmSync(destination, { recursive: true, force: true });
|
|
50
|
+
mkdirSync(dirname(destination), { recursive: true });
|
|
51
|
+
cpSync(sourceDir, destination, { recursive: true });
|
|
52
|
+
console.log(`Published static site from dist to ${relative(cwd, destination)}.`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function publishFtp() {
|
|
56
|
+
const saved = loadLocalConfig();
|
|
57
|
+
const configured = args.configure ? {} : saved.ftp ?? {};
|
|
58
|
+
const ftp = await resolveFtpSettings(configured);
|
|
59
|
+
saveLocalConfig({ ...saved, ftp });
|
|
60
|
+
|
|
61
|
+
const client = new Client();
|
|
62
|
+
try {
|
|
63
|
+
const connection = ftpConnection(ftp.serverUrl);
|
|
64
|
+
await client.access({
|
|
65
|
+
host: connection.host,
|
|
66
|
+
port: connection.port,
|
|
67
|
+
user: ftp.username,
|
|
68
|
+
password: ftp.password,
|
|
69
|
+
secure: connection.secure
|
|
70
|
+
});
|
|
71
|
+
await client.ensureDir(ftp.folder);
|
|
72
|
+
await client.uploadFromDir(sourceDir);
|
|
73
|
+
console.log(`Published static site from dist to ${ftp.serverUrl}${ftp.folder}.`);
|
|
74
|
+
} finally {
|
|
75
|
+
client.close();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function resolveFtpSettings(configured) {
|
|
80
|
+
const serverUrl = await promptText({
|
|
81
|
+
label: "FTP server URL",
|
|
82
|
+
value: args["server-url"] ?? configured.serverUrl,
|
|
83
|
+
required: true
|
|
84
|
+
});
|
|
85
|
+
const folder = await promptText({
|
|
86
|
+
label: "Remote folder",
|
|
87
|
+
value: args.folder ?? configured.folder,
|
|
88
|
+
required: true
|
|
89
|
+
});
|
|
90
|
+
const username = await promptText({
|
|
91
|
+
label: "Username",
|
|
92
|
+
value: args.username ?? configured.username,
|
|
93
|
+
required: true
|
|
94
|
+
});
|
|
95
|
+
const password = await promptPassword(args.password ?? configured.password);
|
|
96
|
+
return {
|
|
97
|
+
serverUrl,
|
|
98
|
+
folder: normalizeRemoteFolder(folder),
|
|
99
|
+
username,
|
|
100
|
+
password
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function promptText({ label, value, required }) {
|
|
105
|
+
if (value && !args.configure) {
|
|
106
|
+
return value;
|
|
107
|
+
}
|
|
108
|
+
if (!input.isTTY) {
|
|
109
|
+
if (value) {
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
throw new Error(`${label} is required. Pass it as an option or run in an interactive terminal.`);
|
|
113
|
+
}
|
|
114
|
+
const rl = createInterface({ input, output });
|
|
115
|
+
try {
|
|
116
|
+
const suffix = value ? ` [${value}]` : "";
|
|
117
|
+
const answer = (await rl.question(`${label}${suffix}: `)).trim();
|
|
118
|
+
const resolved = answer || value;
|
|
119
|
+
if (required && !resolved) {
|
|
120
|
+
throw new Error(`${label} is required.`);
|
|
121
|
+
}
|
|
122
|
+
return resolved;
|
|
123
|
+
} finally {
|
|
124
|
+
rl.close();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function promptPassword(value) {
|
|
129
|
+
if (value && !args.configure) {
|
|
130
|
+
return value;
|
|
131
|
+
}
|
|
132
|
+
if (!input.isTTY) {
|
|
133
|
+
if (value) {
|
|
134
|
+
return value;
|
|
135
|
+
}
|
|
136
|
+
throw new Error("Password is required. Pass it as --password or run in an interactive terminal.");
|
|
137
|
+
}
|
|
138
|
+
const suffix = value ? " [saved, press Enter to keep]" : "";
|
|
139
|
+
const answer = await promptHidden(`Password${suffix}: `);
|
|
140
|
+
const resolved = answer || value;
|
|
141
|
+
if (!resolved) {
|
|
142
|
+
throw new Error("Password is required.");
|
|
143
|
+
}
|
|
144
|
+
return resolved;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function promptHidden(question) {
|
|
148
|
+
return new Promise((resolve, reject) => {
|
|
149
|
+
let value = "";
|
|
150
|
+
const wasRaw = input.isRaw;
|
|
151
|
+
|
|
152
|
+
function cleanup() {
|
|
153
|
+
input.off("data", onData);
|
|
154
|
+
if (input.isTTY) {
|
|
155
|
+
input.setRawMode(wasRaw);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function onData(buffer) {
|
|
160
|
+
const text = buffer.toString("utf8");
|
|
161
|
+
for (const character of text) {
|
|
162
|
+
if (character === "\u0003") {
|
|
163
|
+
cleanup();
|
|
164
|
+
output.write("\n");
|
|
165
|
+
reject(new Error("Cancelled."));
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (character === "\r" || character === "\n") {
|
|
169
|
+
cleanup();
|
|
170
|
+
output.write("\n");
|
|
171
|
+
resolve(value);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (character === "\u007f") {
|
|
175
|
+
if (value.length > 0) {
|
|
176
|
+
value = value.slice(0, -1);
|
|
177
|
+
output.write("\b \b");
|
|
178
|
+
}
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (character >= " ") {
|
|
182
|
+
value += character;
|
|
183
|
+
output.write("*");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
output.write(question);
|
|
189
|
+
input.setRawMode(true);
|
|
190
|
+
input.resume();
|
|
191
|
+
input.on("data", onData);
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function loadLocalConfig() {
|
|
196
|
+
if (!existsSync(localConfigFile)) {
|
|
197
|
+
return {};
|
|
198
|
+
}
|
|
199
|
+
return JSON.parse(readFileSync(localConfigFile, "utf8"));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function saveLocalConfig(config) {
|
|
203
|
+
mkdirSync(dirname(localConfigFile), { recursive: true });
|
|
204
|
+
writeFileSync(localConfigFile, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
205
|
+
chmodSync(localConfigFile, 0o600);
|
|
206
|
+
console.log(`Saved FTP publish settings to ${relative(cwd, localConfigFile)}.`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function ftpConnection(serverUrl) {
|
|
210
|
+
const url = new URL(serverUrl.includes("://") ? serverUrl : `ftp://${serverUrl}`);
|
|
211
|
+
if (url.protocol !== "ftp:" && url.protocol !== "ftps:") {
|
|
212
|
+
throw new Error("FTP server URL must use ftp:// or ftps://.");
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
host: url.hostname,
|
|
216
|
+
port: url.port ? Number(url.port) : undefined,
|
|
217
|
+
secure: url.protocol === "ftps:"
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function normalizeRemoteFolder(folder) {
|
|
222
|
+
const normalized = folder.trim().replace(/\\/g, "/").replace(/\/+$/g, "");
|
|
223
|
+
if (!normalized || normalized === "/") {
|
|
224
|
+
throw new Error("Remote folder must not be empty or the FTP root.");
|
|
225
|
+
}
|
|
226
|
+
return normalized.startsWith("/") ? normalized : `/${normalized}`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function parseArgs(values) {
|
|
230
|
+
const parsed = {};
|
|
231
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
232
|
+
const value = values[index];
|
|
233
|
+
if (!value.startsWith("--")) {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
const [name, inlineValue] = value.slice(2).split("=", 2);
|
|
237
|
+
if (inlineValue !== undefined) {
|
|
238
|
+
parsed[name] = inlineValue;
|
|
239
|
+
} else if (values[index + 1] && !values[index + 1].startsWith("--")) {
|
|
240
|
+
parsed[name] = values[index + 1];
|
|
241
|
+
index += 1;
|
|
242
|
+
} else {
|
|
243
|
+
parsed[name] = true;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return parsed;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function runNpm(args, env = {}) {
|
|
250
|
+
return new Promise((resolve, reject) => {
|
|
251
|
+
const command = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
252
|
+
const child = spawn(command, args, {
|
|
253
|
+
env: { ...process.env, ...env },
|
|
254
|
+
stdio: "inherit"
|
|
255
|
+
});
|
|
256
|
+
child.on("error", reject);
|
|
257
|
+
child.on("exit", (code) => {
|
|
258
|
+
if (code === 0) {
|
|
259
|
+
resolve();
|
|
260
|
+
} else {
|
|
261
|
+
reject(new Error(`npm ${args.join(" ")} exited with code ${code}.`));
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function runNode(args) {
|
|
268
|
+
return new Promise((resolve, reject) => {
|
|
269
|
+
const child = spawn(process.execPath, args, {
|
|
270
|
+
env: process.env,
|
|
271
|
+
stdio: "inherit"
|
|
272
|
+
});
|
|
273
|
+
child.on("error", reject);
|
|
274
|
+
child.on("exit", (code) => {
|
|
275
|
+
if (code === 0) {
|
|
276
|
+
resolve();
|
|
277
|
+
} else {
|
|
278
|
+
reject(new Error(`${process.execPath} ${args.join(" ")} exited with code ${code}.`));
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function passthroughHydrateArgs(values) {
|
|
285
|
+
const result = [];
|
|
286
|
+
const blocked = new Set([
|
|
287
|
+
"target",
|
|
288
|
+
"dir",
|
|
289
|
+
"out",
|
|
290
|
+
"skip-build",
|
|
291
|
+
"category-pages",
|
|
292
|
+
"no-category-pages",
|
|
293
|
+
"hydrate",
|
|
294
|
+
"server-url",
|
|
295
|
+
"folder",
|
|
296
|
+
"username",
|
|
297
|
+
"password",
|
|
298
|
+
"configure"
|
|
299
|
+
]);
|
|
300
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
301
|
+
const value = values[index];
|
|
302
|
+
const name = value.startsWith("--") ? value.slice(2).split("=", 1)[0] : "";
|
|
303
|
+
if (blocked.has(name)) {
|
|
304
|
+
if (!value.includes("=") && values[index + 1] && !values[index + 1].startsWith("--")) {
|
|
305
|
+
index += 1;
|
|
306
|
+
}
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
result.push(value);
|
|
310
|
+
}
|
|
311
|
+
return result;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function passthroughCategoryBuildArgs(values) {
|
|
315
|
+
const result = [];
|
|
316
|
+
const blocked = new Set([
|
|
317
|
+
"target",
|
|
318
|
+
"dir",
|
|
319
|
+
"out",
|
|
320
|
+
"skip-build",
|
|
321
|
+
"category-pages",
|
|
322
|
+
"no-category-pages",
|
|
323
|
+
"hydrate",
|
|
324
|
+
"server-url",
|
|
325
|
+
"folder",
|
|
326
|
+
"username",
|
|
327
|
+
"password",
|
|
328
|
+
"configure"
|
|
329
|
+
]);
|
|
330
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
331
|
+
const value = values[index];
|
|
332
|
+
const name = value.startsWith("--") ? value.slice(2).split("=", 1)[0] : "";
|
|
333
|
+
if (blocked.has(name)) {
|
|
334
|
+
if (!value.includes("=") && values[index + 1] && !values[index + 1].startsWith("--")) {
|
|
335
|
+
index += 1;
|
|
336
|
+
}
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
result.push(value);
|
|
340
|
+
}
|
|
341
|
+
return result;
|
|
342
|
+
}
|