@stacksjs/stx 0.2.342 → 0.2.344
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/binding-manifest.d.ts +32 -0
- package/dist/binding-manifest.js +64 -0
- package/dist/caching.js +1 -0
- package/dist/chunk-020hpw7m.js +2 -0
- package/dist/{chunk-5g9vbhbt.js → chunk-05zfez2g.js} +1 -1
- package/dist/{chunk-6nvm939g.js → chunk-0k1mtwby.js} +1 -1
- package/dist/chunk-1bct7pfj.js +2 -0
- package/dist/{chunk-xec226zb.js → chunk-1c9453w0.js} +1 -1
- package/dist/chunk-1exk54qt.js +2 -0
- package/dist/{chunk-r46cg4qa.js → chunk-1vt1h19d.js} +1 -1
- package/dist/{chunk-cqw3c1xg.js → chunk-1yh6pq6b.js} +1 -1
- package/dist/{chunk-0sq5mdrh.js → chunk-34f8c48f.js} +1 -1
- package/dist/chunk-39qy76ek.js +2 -0
- package/dist/{chunk-hrmxxs0w.js → chunk-3n8avn17.js} +1 -1
- package/dist/chunk-3xpk99z4.js +3 -0
- package/dist/{chunk-4wpxbkbe.js → chunk-4kbaky4w.js} +1 -1
- package/dist/{chunk-c33y19bn.js → chunk-59906baj.js} +1 -1
- package/dist/chunk-5apmxb4e.js +2 -0
- package/dist/{chunk-j82hfkcf.js → chunk-5atmahe4.js} +319 -321
- package/dist/{chunk-mkgbrbr9.js → chunk-5yhv1pe5.js} +1 -1
- package/dist/{chunk-p52cm7ch.js → chunk-64n10kpa.js} +1 -1
- package/dist/{chunk-anf2y2h5.js → chunk-6h3r8v55.js} +1 -1
- package/dist/chunk-6z9pm31g.js +2 -0
- package/dist/{chunk-25f0s8t1.js → chunk-72x5v5an.js} +1 -1
- package/dist/{chunk-wv8h3d68.js → chunk-72x62kch.js} +2 -2
- package/dist/chunk-7d2gfyk8.js +2 -0
- package/dist/{chunk-s4mmmwj4.js → chunk-7h9fajgs.js} +1 -1
- package/dist/{chunk-gwc54hjc.js → chunk-7kcfbc0j.js} +2 -2
- package/dist/{chunk-74r1zw2h.js → chunk-7qp3zphs.js} +1 -1
- package/dist/{chunk-1xwfws06.js → chunk-7t6h8crt.js} +2 -2
- package/dist/{chunk-sytwy0mg.js → chunk-86c461ca.js} +1 -1
- package/dist/chunk-86q2h34b.js +2 -0
- package/dist/{chunk-25b8z7xa.js → chunk-89mqnh67.js} +1 -1
- package/dist/{chunk-k5b9471y.js → chunk-8cc4vdkh.js} +1 -1
- package/dist/chunk-8tjg4wbr.js +2 -0
- package/dist/{chunk-1gwbw81h.js → chunk-8v2k21n4.js} +1 -1
- package/dist/{chunk-rh7mwwb4.js → chunk-8y1ve8tc.js} +2 -2
- package/dist/chunk-94sxzp5x.js +2 -0
- package/dist/{chunk-mznr9nyp.js → chunk-99b5tfxc.js} +1 -1
- package/dist/{chunk-saqbtgqr.js → chunk-9jbssgem.js} +197 -88
- package/dist/{chunk-yr7camcd.js → chunk-9nrgjtah.js} +1 -1
- package/dist/chunk-9p5gbhwp.js +2 -0
- package/dist/chunk-9yqt0nf2.js +2 -0
- package/dist/{chunk-ebd3z4j9.js → chunk-a1ghxrb1.js} +1 -1
- package/dist/chunk-a29x23g7.js +2 -0
- package/dist/chunk-at622hg0.js +2 -0
- package/dist/{chunk-bt8w146r.js → chunk-avwywzsj.js} +2 -2
- package/dist/{chunk-m39hb2a2.js → chunk-b5hank5e.js} +1 -1
- package/dist/{chunk-ts3wfjh9.js → chunk-bk1zb1xp.js} +1 -1
- package/dist/{chunk-ytx17bg2.js → chunk-bpb3xkg4.js} +1 -1
- package/dist/{chunk-01ggk8k8.js → chunk-bqdfbwdf.js} +1 -1
- package/dist/{chunk-yzv702kk.js → chunk-c30qnb6z.js} +1 -1
- package/dist/chunk-cappe4hd.js +2 -0
- package/dist/{chunk-drs93sry.js → chunk-cdq28mek.js} +1 -1
- package/dist/chunk-cmccdayf.js +2 -0
- package/dist/chunk-csv48xrx.js +2 -0
- package/dist/{chunk-e0kzyf1g.js → chunk-d86q27j8.js} +1 -1
- package/dist/{chunk-yhvrq2qd.js → chunk-dgv43ccd.js} +1 -1
- package/dist/{chunk-yq7fc8sa.js → chunk-dsztk2kp.js} +1 -1
- package/dist/{chunk-ys56ecvx.js → chunk-dx2s51rr.js} +1 -1
- package/dist/{chunk-mbstfjnj.js → chunk-dxkjvajv.js} +1 -1
- package/dist/chunk-e4yskgtb.js +2 -0
- package/dist/{chunk-8tsr51hj.js → chunk-e631svvs.js} +1 -1
- package/dist/{chunk-3b0mgxbk.js → chunk-e7np0h2q.js} +1 -1
- package/dist/chunk-efx05ah0.js +2 -0
- package/dist/{chunk-p5d8sb50.js → chunk-ejqpq0wc.js} +1 -1
- package/dist/chunk-erb294sv.js +2 -0
- package/dist/{chunk-k3w3gwm3.js → chunk-evfr2zgg.js} +1 -1
- package/dist/chunk-ewpbxy5m.js +2 -0
- package/dist/chunk-fs2t4rs3.js +33 -0
- package/dist/{chunk-ghd80w8a.js → chunk-g28fs4aa.js} +2 -2
- package/dist/chunk-gmm82dst.js +2 -0
- package/dist/{chunk-kznb3hdj.js → chunk-gsc2vds7.js} +1 -1
- package/dist/chunk-hke0k33t.js +2 -0
- package/dist/{chunk-da0tfpa2.js → chunk-hsbbfp0c.js} +1 -1
- package/dist/chunk-j5qygdy9.js +2 -0
- package/dist/{chunk-c73pyeak.js → chunk-jekrtm2j.js} +1 -1
- package/dist/chunk-jzfk5ect.js +2 -0
- package/dist/{chunk-rr49zrz6.js → chunk-kfkf9v2d.js} +1 -1
- package/dist/chunk-kq30wqp6.js +2 -0
- package/dist/{chunk-ntm1761b.js → chunk-krfx5562.js} +10 -10
- package/dist/{chunk-48x5bsqv.js → chunk-kw9cgyfc.js} +1 -1
- package/dist/{chunk-evtrctj9.js → chunk-m2j5skez.js} +1 -1
- package/dist/{chunk-q4cemvkw.js → chunk-mph46gkj.js} +1 -1
- package/dist/{chunk-k9ep24bd.js → chunk-mpyjnwje.js} +1 -1
- package/dist/{chunk-y956wapy.js → chunk-msywnv5n.js} +1 -1
- package/dist/{chunk-d389vyyz.js → chunk-mvat8js5.js} +2 -2
- package/dist/{chunk-gkq323fg.js → chunk-mz4breg8.js} +1 -1
- package/dist/{chunk-reskzmsb.js → chunk-n5657hm3.js} +1 -1
- package/dist/{chunk-4ecv1e3w.js → chunk-nexpxxsa.js} +1 -1
- package/dist/{chunk-1t3j8a0q.js → chunk-njx93b4z.js} +1 -1
- package/dist/{chunk-fx29n4vc.js → chunk-nm60wh6r.js} +1 -1
- package/dist/{chunk-thrv2nbz.js → chunk-ppzz953j.js} +1 -1
- package/dist/chunk-q5gzpn1n.js +2 -0
- package/dist/{chunk-fm5211ec.js → chunk-rwwfzkz4.js} +1 -1
- package/dist/chunk-san1eq0r.js +7 -0
- package/dist/chunk-tc4pj651.js +64 -0
- package/dist/chunk-tscr6bs8.js +2 -0
- package/dist/{chunk-jcbj3xp4.js → chunk-tthrw91n.js} +1 -1
- package/dist/{chunk-6nxq8rcw.js → chunk-v6e35j4g.js} +1 -1
- package/dist/{chunk-byjy9far.js → chunk-v7aa7v85.js} +1 -1
- package/dist/{chunk-r1rfwm01.js → chunk-vhta1be7.js} +1 -1
- package/dist/{chunk-hnffkdzc.js → chunk-w5j9tbsk.js} +1 -1
- package/dist/{chunk-567g1ttv.js → chunk-wfwj4390.js} +1 -1
- package/dist/{chunk-x7dv5rq7.js → chunk-wj21f1e0.js} +1 -1
- package/dist/{chunk-tmj45xq5.js → chunk-ws0282jr.js} +1 -1
- package/dist/chunk-x054qzma.js +2 -0
- package/dist/{chunk-4vcyf1kh.js → chunk-xh9paa2g.js} +2 -2
- package/dist/{chunk-f2qbjr3z.js → chunk-xj6bcwqa.js} +1 -1
- package/dist/{chunk-hk8j3eps.js → chunk-xvh1k1a4.js} +1 -1
- package/dist/{chunk-1spzqjve.js → chunk-y2f41kaq.js} +1 -1
- package/dist/{chunk-2swnc8kg.js → chunk-y6xzr30x.js} +1 -1
- package/dist/{chunk-beextc84.js → chunk-yg5r7bnm.js} +2 -2
- package/dist/{chunk-gbkq5qnw.js → chunk-zkwk82ss.js} +2 -2
- package/dist/chunk-zkxnarrh.js +2 -0
- package/dist/chunk-zp14ksx1.js +2 -0
- package/dist/{chunk-zv4nf0x2.js → chunk-zwhnkxgy.js} +1 -1
- package/dist/cli.js +14 -14
- package/dist/craft-entry.js +1 -1
- package/dist/craft.js +1 -1
- package/dist/custom-directives.js +21 -8
- package/dist/database.js +1 -1
- package/dist/directive-arguments.d.ts +107 -0
- package/dist/directive-arguments.js +171 -0
- package/dist/expressions.d.ts +1 -1
- package/dist/expressions.js +15 -3
- package/dist/head.d.ts +7 -30
- package/dist/head.js +15 -5
- package/dist/includes.js +2 -2
- package/dist/meta-arguments.d.ts +52 -0
- package/dist/meta-arguments.js +55 -0
- package/dist/process.js +1 -1
- package/dist/pwa.js +2 -2
- package/dist/runtime-globals.d.ts +22 -0
- package/dist/runtime-globals.js +46 -0
- package/dist/seo.d.ts +39 -17
- package/dist/seo.js +221 -255
- package/dist/signals.js +193 -84
- package/dist/slots.js +3 -3
- package/dist/ssg.js +2 -2
- package/dist/theme-tokens.d.ts +59 -0
- package/dist/theme-tokens.js +77 -17
- package/dist/ts-css-config.js +4 -2
- package/dist/types/directive-types.d.ts +12 -0
- package/dist/utils.d.ts +10 -0
- package/dist/utils.js +77 -12
- package/dist/variable-extractor.d.ts +1 -1
- package/dist/variable-extractor.js +23 -3
- package/dist/visual-testing.js +1 -1
- package/package.json +5 -5
- package/dist/chunk-025z2btp.js +0 -2
- package/dist/chunk-19fp0z9s.js +0 -2
- package/dist/chunk-23sv24cm.js +0 -2
- package/dist/chunk-2p68bkza.js +0 -2
- package/dist/chunk-5grrqaqs.js +0 -2
- package/dist/chunk-6jx4r94x.js +0 -2
- package/dist/chunk-6p6b01kp.js +0 -4
- package/dist/chunk-a1rt1fms.js +0 -2
- package/dist/chunk-ahpmjg3f.js +0 -64
- package/dist/chunk-akp89n3w.js +0 -2
- package/dist/chunk-bqhrryqs.js +0 -2
- package/dist/chunk-czvzaa42.js +0 -3
- package/dist/chunk-de2cfcf5.js +0 -2
- package/dist/chunk-exhbc57n.js +0 -2
- package/dist/chunk-fhhp6pmx.js +0 -2
- package/dist/chunk-fqsrft0a.js +0 -2
- package/dist/chunk-gqzccw30.js +0 -2
- package/dist/chunk-h47e1c03.js +0 -2
- package/dist/chunk-kbemcvp1.js +0 -2
- package/dist/chunk-ks4j8fjz.js +0 -2
- package/dist/chunk-pbct6nxk.js +0 -2
- package/dist/chunk-peerbcwj.js +0 -2
- package/dist/chunk-pt1w6vq2.js +0 -2
- package/dist/chunk-rgda0gwf.js +0 -2
- package/dist/chunk-rwa10jm4.js +0 -2
- package/dist/chunk-snsh9ye4.js +0 -2
- package/dist/chunk-td39mtts.js +0 -2
- package/dist/chunk-trncnwem.js +0 -2
- package/dist/chunk-vk2j3vvs.js +0 -2
- package/dist/chunk-vph6zpeq.js +0 -2
- package/dist/chunk-wa3ks5zv.js +0 -2
- package/dist/chunk-xe93ae3r.js +0 -32
- package/dist/chunk-xer5kn26.js +0 -2
- package/dist/chunk-zv4gb3wb.js +0 -2
- package/dist/chunk-zx2krbqf.js +0 -2
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Argument handling for directives whose arguments are expressions.
|
|
3
|
+
*
|
|
4
|
+
* Written for the SEO directives (`@seo`, `@meta`, `@metaTag`,
|
|
5
|
+
* `@structuredData`), and kept free of anything SEO-specific so a custom
|
|
6
|
+
* directive can use it too (`CustomDirective.rawParams`).
|
|
7
|
+
*
|
|
8
|
+
* Every one of these used to fail the same way: an argument that was not the
|
|
9
|
+
* one shape its regex expected was either left in the page as literal text or
|
|
10
|
+
* replaced with nothing, and no one was told. `@seo(seo)` never matched a
|
|
11
|
+
* pattern that insisted on `{`; `@meta('author', post.author)` sent the words
|
|
12
|
+
* "post.author" as the author; `@structuredData(prodcut)` emitted a JSON-LD
|
|
13
|
+
* block holding nothing but `@context`. Each page looked fine and was wrong
|
|
14
|
+
* where it counts, in a link preview or a search result.
|
|
15
|
+
*
|
|
16
|
+
* So the directives share one way to find their call (balanced and quote-aware,
|
|
17
|
+
* so `'Smile :)'` does not end the call early), one way to tell a string literal
|
|
18
|
+
* from an expression, one evaluator that can say WHY a value is missing, and one
|
|
19
|
+
* way to report a failure: an inline comment where the tag would have been, plus
|
|
20
|
+
* a console warning naming the file.
|
|
21
|
+
*/
|
|
22
|
+
import type { ErrorCode } from './errors/codes';
|
|
23
|
+
export interface DirectiveCall {
|
|
24
|
+
/** Index of the `@`. */
|
|
25
|
+
start: number;
|
|
26
|
+
/** Index one past the closing paren, or -1 when the call is never closed. */
|
|
27
|
+
end: number;
|
|
28
|
+
/** The text between the parens, as written. */
|
|
29
|
+
args: string;
|
|
30
|
+
/** The whole call, `@name(...)`, or its first 80 characters when unclosed. */
|
|
31
|
+
call: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Every `@name(...)` call in `template` matched by `pattern`, in order.
|
|
35
|
+
*
|
|
36
|
+
* `pattern` must be global and end at the opening paren. The closing paren is
|
|
37
|
+
* found by balance, skipping parens inside strings, so an argument may hold any
|
|
38
|
+
* expression: `@seo(build({ title: 'Smile :)' }))` is one call.
|
|
39
|
+
*/
|
|
40
|
+
export declare function findDirectiveCalls(template: string, pattern: RegExp): DirectiveCall[];
|
|
41
|
+
/**
|
|
42
|
+
* Replace every call {@link findDirectiveCalls} finds.
|
|
43
|
+
*
|
|
44
|
+
* One left-to-right pass over the original text: a replacement is never
|
|
45
|
+
* rescanned, so markup that happens to contain the directive's name cannot
|
|
46
|
+
* loop. A call with no closing paren keeps its text, preceded by whatever
|
|
47
|
+
* `onUnterminated` returns, so the author sees both the report and the call.
|
|
48
|
+
*/
|
|
49
|
+
export declare function replaceDirectiveCalls(template: string, pattern: RegExp, replace: (args: string, call: string) => string, onUnterminated: (call: string) => string): string;
|
|
50
|
+
/**
|
|
51
|
+
* Split an argument list at its top-level commas.
|
|
52
|
+
*
|
|
53
|
+
* Commas inside strings, template literals, brackets, braces and parens belong
|
|
54
|
+
* to the argument they sit in: `'a, b', fn(x, y)` is two arguments, not four.
|
|
55
|
+
* Arguments come back trimmed and exactly as written, quotes included, so the
|
|
56
|
+
* caller can still tell `'title'` (the word) from `title` (a variable).
|
|
57
|
+
*/
|
|
58
|
+
export declare function splitTopLevelArgs(args: string): string[];
|
|
59
|
+
/**
|
|
60
|
+
* The value of `raw` when it is exactly one string literal, otherwise `null`.
|
|
61
|
+
*
|
|
62
|
+
* `'a' + b`, `'a' || 'b'` and a template literal with `${}` in it are
|
|
63
|
+
* expressions and return `null`. A literal is read here rather than handed to
|
|
64
|
+
* the evaluator because the evaluator's safety screen reads words, and a
|
|
65
|
+
* description is allowed to say "eval".
|
|
66
|
+
*/
|
|
67
|
+
export declare function parseStringLiteral(raw: string): string | null;
|
|
68
|
+
export type EvaluatedArgument = {
|
|
69
|
+
ok: true;
|
|
70
|
+
value: unknown;
|
|
71
|
+
} | {
|
|
72
|
+
ok: false;
|
|
73
|
+
message: string;
|
|
74
|
+
code: ErrorCode;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Evaluate a directive argument against the template's context.
|
|
78
|
+
*
|
|
79
|
+
* The shared evaluator turns every failure into `undefined`, which is right for
|
|
80
|
+
* `{{ }}` and wrong here: a directive whose whole job is to produce a tag has
|
|
81
|
+
* to be able to say why it produced none. So the three ways an expression
|
|
82
|
+
* fails are told apart:
|
|
83
|
+
*
|
|
84
|
+
* - it is not allowed (the safety screen) or not JavaScript (a syntax error);
|
|
85
|
+
* - it names a variable the template does not have, which is almost always a
|
|
86
|
+
* value declared in a plain (client) `<script>`, or a typo;
|
|
87
|
+
* - it is an object or array literal that came back `undefined`, which only
|
|
88
|
+
* happens when something inside it threw.
|
|
89
|
+
*
|
|
90
|
+
* A defined variable that simply holds `null` or `undefined` is not a failure:
|
|
91
|
+
* `@seo(page.seo)` on a page without one is allowed to render nothing.
|
|
92
|
+
*/
|
|
93
|
+
export declare function evaluateDirectiveArgument(expression: string, context: Record<string, unknown>): EvaluatedArgument;
|
|
94
|
+
/** How a value reads in an error message. */
|
|
95
|
+
export declare function describeValue(value: unknown): string;
|
|
96
|
+
/** A plain object: not null, not an array. */
|
|
97
|
+
export declare function isPlainRecord(value: unknown): value is Record<string, unknown>;
|
|
98
|
+
/** The call as it reads in a message: one line, and not the whole object. */
|
|
99
|
+
export declare function shortCall(call: string): string;
|
|
100
|
+
/**
|
|
101
|
+
* Report a directive that produced nothing: a console warning naming the file,
|
|
102
|
+
* and an inline comment where the tag would have been, for whoever reads the
|
|
103
|
+
* built HTML (which is how most of these were found).
|
|
104
|
+
*/
|
|
105
|
+
export declare function reportDirectiveFailure(label: string, call: string, message: string, filePath: string, code?: ErrorCode): string;
|
|
106
|
+
/** Warn without replacing anything: the directive rendered, but not all of it. */
|
|
107
|
+
export declare function warnDirective(call: string, message: string, filePath: string): void;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { ErrorCodes, inlineError } from "./error-handling.js";
|
|
3
|
+
import { findMatchingDelimiter } from "./parser/tokenizer.js";
|
|
4
|
+
import { createSafeContext, createSafeFunction, freeIdentifiers, getExpressionSafetyRule } from "./safe-evaluator.js";
|
|
5
|
+
export function findDirectiveCalls(template, pattern) {
|
|
6
|
+
const calls = [];
|
|
7
|
+
pattern.lastIndex = 0;
|
|
8
|
+
let match;
|
|
9
|
+
while ((match = pattern.exec(template)) !== null) {
|
|
10
|
+
const start = match.index, openParen = start + match[0].length - 1, closeParen = findMatchingDelimiter(template, "(", ")", openParen);
|
|
11
|
+
if (closeParen === -1) {
|
|
12
|
+
calls.push({ start, end: -1, args: "", call: template.slice(start, Math.min(template.length, start + 80)) });
|
|
13
|
+
pattern.lastIndex = openParen + 1;
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
calls.push({
|
|
17
|
+
start,
|
|
18
|
+
end: closeParen + 1,
|
|
19
|
+
args: template.slice(openParen + 1, closeParen),
|
|
20
|
+
call: template.slice(start, closeParen + 1)
|
|
21
|
+
});
|
|
22
|
+
pattern.lastIndex = closeParen + 1;
|
|
23
|
+
}
|
|
24
|
+
return calls;
|
|
25
|
+
}
|
|
26
|
+
export function replaceDirectiveCalls(template, pattern, replace, onUnterminated) {
|
|
27
|
+
let output = "", cursor = 0;
|
|
28
|
+
for (const found of findDirectiveCalls(template, pattern)) {
|
|
29
|
+
output += template.slice(cursor, found.start);
|
|
30
|
+
if (found.end === -1) {
|
|
31
|
+
output += onUnterminated(found.call);
|
|
32
|
+
cursor = found.start;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
output += replace(found.args, found.call);
|
|
36
|
+
cursor = found.end;
|
|
37
|
+
}
|
|
38
|
+
return output + template.slice(cursor);
|
|
39
|
+
}
|
|
40
|
+
export function splitTopLevelArgs(args) {
|
|
41
|
+
const parts = [];
|
|
42
|
+
let depth = 0, quote = null, current = "";
|
|
43
|
+
for (let i = 0;i < args.length; i++) {
|
|
44
|
+
const char = args[i];
|
|
45
|
+
if (quote) {
|
|
46
|
+
current += char;
|
|
47
|
+
if (char === "\\" && i + 1 < args.length) {
|
|
48
|
+
current += args[++i];
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (char === quote)
|
|
52
|
+
quote = null;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (char === "'" || char === '"' || char === "`")
|
|
56
|
+
quote = char;
|
|
57
|
+
else if (char === "(" || char === "[" || char === "{")
|
|
58
|
+
depth++;
|
|
59
|
+
else if (char === ")" || char === "]" || char === "}")
|
|
60
|
+
depth--;
|
|
61
|
+
else if (char === "," && depth === 0) {
|
|
62
|
+
parts.push(current.trim());
|
|
63
|
+
current = "";
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
current += char;
|
|
67
|
+
}
|
|
68
|
+
if (current.trim())
|
|
69
|
+
parts.push(current.trim());
|
|
70
|
+
return parts;
|
|
71
|
+
}
|
|
72
|
+
export function parseStringLiteral(raw) {
|
|
73
|
+
const text = raw.trim(), quote = text[0];
|
|
74
|
+
if (quote !== "'" && quote !== '"' && quote !== "`" || text.length < 2 || text.at(-1) !== quote)
|
|
75
|
+
return null;
|
|
76
|
+
let value = "";
|
|
77
|
+
for (let i = 1;i < text.length - 1; i++) {
|
|
78
|
+
const char = text[i];
|
|
79
|
+
if (char === "\\" && i + 1 < text.length - 1) {
|
|
80
|
+
const next = text[++i];
|
|
81
|
+
value += next === "n" ? `
|
|
82
|
+
` : next === "t" ? "\t" : next;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (char === quote)
|
|
86
|
+
return null;
|
|
87
|
+
if (quote === "`" && char === "$" && text[i + 1] === "{")
|
|
88
|
+
return null;
|
|
89
|
+
value += char;
|
|
90
|
+
}
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
93
|
+
const NOT_VARIABLES = new Set([
|
|
94
|
+
"true",
|
|
95
|
+
"false",
|
|
96
|
+
"null",
|
|
97
|
+
"undefined",
|
|
98
|
+
"typeof",
|
|
99
|
+
"void",
|
|
100
|
+
"new",
|
|
101
|
+
"in",
|
|
102
|
+
"of",
|
|
103
|
+
"instanceof",
|
|
104
|
+
"this",
|
|
105
|
+
"delete",
|
|
106
|
+
"await",
|
|
107
|
+
"async",
|
|
108
|
+
"function",
|
|
109
|
+
"return",
|
|
110
|
+
"NaN",
|
|
111
|
+
"Infinity"
|
|
112
|
+
]);
|
|
113
|
+
export function evaluateDirectiveArgument(expression, context) {
|
|
114
|
+
const source = expression.trim();
|
|
115
|
+
if (!source)
|
|
116
|
+
return { ok: !1, message: "the argument is empty", code: ErrorCodes.INVALID_DIRECTIVE_SYNTAX };
|
|
117
|
+
const rule = getExpressionSafetyRule(source);
|
|
118
|
+
if (rule)
|
|
119
|
+
return { ok: !1, message: `the expression is not allowed (${rule})`, code: ErrorCodes.UNSAFE_EXPRESSION };
|
|
120
|
+
const safeContext = createSafeContext(context);
|
|
121
|
+
let value;
|
|
122
|
+
try {
|
|
123
|
+
value = createSafeFunction(source, Object.keys(safeContext))(...Object.values(safeContext));
|
|
124
|
+
} catch (error) {
|
|
125
|
+
return { ok: !1, message: `the expression could not be evaluated: ${error instanceof Error ? error.message : String(error)}`, code: ErrorCodes.EVALUATION_ERROR };
|
|
126
|
+
}
|
|
127
|
+
if (value !== void 0 && value !== null)
|
|
128
|
+
return { ok: !0, value };
|
|
129
|
+
const missing = [...new Set(freeIdentifiers(source))].filter((name) => !NOT_VARIABLES.has(name) && !(name in safeContext) && !(name in globalThis));
|
|
130
|
+
if (missing.length > 0)
|
|
131
|
+
return {
|
|
132
|
+
ok: !1,
|
|
133
|
+
message: `${missing.map((name) => `"${name}"`).join(", ")} ${missing.length === 1 ? "is" : "are"} not defined here. Values a directive reads must come from <script server> or the render context; a plain <script> runs in the browser. To pass literal text, quote it.`,
|
|
134
|
+
code: ErrorCodes.UNDEFINED_VARIABLE
|
|
135
|
+
};
|
|
136
|
+
if (value === void 0 && (source.startsWith("{") || source.startsWith("[")))
|
|
137
|
+
return { ok: !1, message: "the expression threw while it was evaluated", code: ErrorCodes.EVALUATION_ERROR };
|
|
138
|
+
return { ok: !0, value };
|
|
139
|
+
}
|
|
140
|
+
export function describeValue(value) {
|
|
141
|
+
if (value === null)
|
|
142
|
+
return "null";
|
|
143
|
+
if (Array.isArray(value))
|
|
144
|
+
return "an array";
|
|
145
|
+
if (typeof value === "object")
|
|
146
|
+
return "an object";
|
|
147
|
+
if (typeof value === "string")
|
|
148
|
+
return `a string ("${value.length > 40 ? `${value.slice(0, 40)}...` : value}")`;
|
|
149
|
+
return `${/^[aeiou]/.test(typeof value) ? "an" : "a"} ${typeof value}`;
|
|
150
|
+
}
|
|
151
|
+
export function isPlainRecord(value) {
|
|
152
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
153
|
+
}
|
|
154
|
+
function displayPath(filePath) {
|
|
155
|
+
if (!filePath)
|
|
156
|
+
return "";
|
|
157
|
+
return path.isAbsolute(filePath) ? path.relative(process.cwd(), filePath) || filePath : filePath;
|
|
158
|
+
}
|
|
159
|
+
export function shortCall(call) {
|
|
160
|
+
const flat = call.replace(/\s+/g, " ").trim();
|
|
161
|
+
return flat.length > 80 ? `${flat.slice(0, 77)}...` : flat;
|
|
162
|
+
}
|
|
163
|
+
export function reportDirectiveFailure(label, call, message, filePath, code = ErrorCodes.EVALUATION_ERROR) {
|
|
164
|
+
const where = filePath ? ` in ${displayPath(filePath)}` : "";
|
|
165
|
+
console.warn(`[stx] ${shortCall(call)}${where}: ${message}`);
|
|
166
|
+
return inlineError(label, `${shortCall(call)}: ${message}`, code);
|
|
167
|
+
}
|
|
168
|
+
export function warnDirective(call, message, filePath) {
|
|
169
|
+
const where = filePath ? ` in ${displayPath(filePath)}` : "";
|
|
170
|
+
console.warn(`[stx] ${shortCall(call)}${where}: ${message}`);
|
|
171
|
+
}
|
package/dist/expressions.d.ts
CHANGED
|
@@ -162,7 +162,7 @@ export declare function escapeHtmlValue(unsafe: string): string;
|
|
|
162
162
|
* did before and is still correct for a script that declares its own signals.
|
|
163
163
|
*/
|
|
164
164
|
export declare function usesSignalsInScript(template: string, filePath?: string): boolean;
|
|
165
|
-
export declare function interpolateScriptExpressions(scriptBody: string, context: Record<string, any
|
|
165
|
+
export declare function interpolateScriptExpressions(scriptBody: string, context: Record<string, any>, filePath?: string): string;
|
|
166
166
|
/**
|
|
167
167
|
* Interpolate expressions in a `<script>` tag's ATTRIBUTES.
|
|
168
168
|
*
|
package/dist/expressions.js
CHANGED
|
@@ -371,13 +371,25 @@ function expressionReadsServerBinding(expr, context) {
|
|
|
371
371
|
return !0;
|
|
372
372
|
return !1;
|
|
373
373
|
}
|
|
374
|
-
|
|
374
|
+
const reportedUnresolvedScriptExpressions = new Set;
|
|
375
|
+
function looksLikeServerValue(expression) {
|
|
376
|
+
return /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(expression.trim());
|
|
377
|
+
}
|
|
378
|
+
function unresolvedServerValue(expression, filePath) {
|
|
379
|
+
const key = `${filePath ?? "<unknown>"}\x00${expression}`;
|
|
380
|
+
if (process.env.STX_DEBUG || !reportedUnresolvedScriptExpressions.has(key)) {
|
|
381
|
+
reportedUnresolvedScriptExpressions.add(key);
|
|
382
|
+
console.warn(`[stx] {{ ${expression} }} in a <script> in ${filePath ?? "<unknown>"} has no server value, so it was emitted as undefined. A mustache left in JavaScript is a SyntaxError that takes the whole script down, so give the value a default in <script server>.`);
|
|
383
|
+
}
|
|
384
|
+
return "undefined";
|
|
385
|
+
}
|
|
386
|
+
export function interpolateScriptExpressions(scriptBody, context, filePath) {
|
|
375
387
|
let output = scriptBody;
|
|
376
388
|
output = output.replace(/\{!!([\s\S]*?)!!\}/g, (match, expr) => {
|
|
377
389
|
try {
|
|
378
390
|
const value = evaluateExpression(expr, context);
|
|
379
391
|
if (value === void 0)
|
|
380
|
-
return match;
|
|
392
|
+
return looksLikeServerValue(expr) ? unresolvedServerValue(expr.trim(), filePath) : match;
|
|
381
393
|
return value === null ? "" : String(value);
|
|
382
394
|
} catch {
|
|
383
395
|
return match;
|
|
@@ -390,7 +402,7 @@ export function interpolateScriptExpressions(scriptBody, context) {
|
|
|
390
402
|
try {
|
|
391
403
|
const value = evaluateExpression(expr, context);
|
|
392
404
|
if (value === void 0)
|
|
393
|
-
return match;
|
|
405
|
+
return looksLikeServerValue(trimmed) ? unresolvedServerValue(trimmed, filePath) : match;
|
|
394
406
|
return JSON.stringify(value).replace(/</g, "\\u003c");
|
|
395
407
|
} catch {
|
|
396
408
|
return match;
|
package/dist/head.d.ts
CHANGED
|
@@ -1,32 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* STX Head Management
|
|
3
|
-
*
|
|
4
|
-
* Manage document head (title, meta, links, scripts) with a simple API.
|
|
5
|
-
*
|
|
6
|
-
* @example
|
|
7
|
-
* ```typescript
|
|
8
|
-
* import { useHead, useSeoMeta, definePageMeta } from 'stx'
|
|
9
|
-
*
|
|
10
|
-
* // Full control
|
|
11
|
-
* useHead({
|
|
12
|
-
* title: 'My Page',
|
|
13
|
-
* meta: [{ name: 'description', content: '...' }]
|
|
14
|
-
* })
|
|
15
|
-
*
|
|
16
|
-
* // SEO shorthand
|
|
17
|
-
* useSeoMeta({
|
|
18
|
-
* title: 'My Page',
|
|
19
|
-
* description: 'Page description',
|
|
20
|
-
* ogImage: '/og.png'
|
|
21
|
-
* })
|
|
22
|
-
*
|
|
23
|
-
* // Page configuration
|
|
24
|
-
* definePageMeta({
|
|
25
|
-
* title: 'My Page',
|
|
26
|
-
* layout: 'default'
|
|
27
|
-
* })
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
1
|
export interface MetaTag {
|
|
31
2
|
name?: string;
|
|
32
3
|
property?: string;
|
|
@@ -284,11 +255,17 @@ export declare function processTitleDirective(content: string, context: Record<s
|
|
|
284
255
|
/**
|
|
285
256
|
* Process @meta directive.
|
|
286
257
|
*
|
|
258
|
+
* Stages each tag on the render's head. Reached only when the render has no
|
|
259
|
+
* custom directives, since the registered `metaDirective` (seo.ts) takes every
|
|
260
|
+
* `@meta` first; both resolve their arguments with `resolveMetaArguments`, so
|
|
261
|
+
* a quoted value is text and anything else is an expression.
|
|
262
|
+
*
|
|
287
263
|
* @example
|
|
288
264
|
* ```html
|
|
289
265
|
* @meta('description', 'Page description')
|
|
290
266
|
* @meta('og:image', ogImageUrl)
|
|
267
|
+
* @meta('author', post.author)
|
|
291
268
|
* ```
|
|
292
269
|
*/
|
|
293
|
-
export declare function processMetaDirective(content: string, context: Record<string, unknown
|
|
270
|
+
export declare function processMetaDirective(content: string, context: Record<string, unknown>, filePath?: string): string;
|
|
294
271
|
export { usePageTitle as useTitle };
|
package/dist/head.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { replaceDirectiveCalls, reportDirectiveFailure, splitTopLevelArgs } from "./directive-arguments.js";
|
|
2
|
+
import { ErrorCodes } from "./error-handling.js";
|
|
3
|
+
import { resolveMetaArguments } from "./meta-arguments.js";
|
|
1
4
|
let currentHead = {};
|
|
2
5
|
const headStack = [];
|
|
3
6
|
let currentPageMeta = {};
|
|
@@ -237,12 +240,19 @@ export function processTitleDirective(content, context) {
|
|
|
237
240
|
return "";
|
|
238
241
|
});
|
|
239
242
|
}
|
|
240
|
-
export function processMetaDirective(content, context) {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
243
|
+
export function processMetaDirective(content, context, filePath = "") {
|
|
244
|
+
if (!content.includes("@meta"))
|
|
245
|
+
return content;
|
|
246
|
+
return replaceDirectiveCalls(content, /(?<![\w$@])@meta\s*\(/g, (args, call) => {
|
|
247
|
+
const resolved = resolveMetaArguments(splitTopLevelArgs(args), context);
|
|
248
|
+
if (resolved.kind === "error")
|
|
249
|
+
return reportDirectiveFailure("Meta", call, resolved.message, filePath, resolved.code);
|
|
250
|
+
if (resolved.kind === "tag") {
|
|
251
|
+
const existing = context.__stx_runtime_head ?? {};
|
|
252
|
+
context.__stx_runtime_head = mergeHeadConfigs(existing, { meta: [resolved.tag] });
|
|
253
|
+
}
|
|
244
254
|
return "";
|
|
245
|
-
});
|
|
255
|
+
}, (call) => reportDirectiveFailure("Meta", call, "the call has no closing parenthesis", filePath, ErrorCodes.INVALID_DIRECTIVE_SYNTAX));
|
|
246
256
|
}
|
|
247
257
|
|
|
248
258
|
export {
|
package/dist/includes.js
CHANGED
|
@@ -623,7 +623,7 @@ export async function processIncludes(template, context, filePath, options, depe
|
|
|
623
623
|
const isServerScript = scriptAttrs.includes("server"), shouldTranspile = shouldTranspileTypeScript(scriptAttrs);
|
|
624
624
|
if (!isServerScript && scriptContent.trim()) {
|
|
625
625
|
const { interpolateScriptExpressions } = await import("./expressions.js");
|
|
626
|
-
scriptContent = interpolateScriptExpressions(scriptContent, includeContext);
|
|
626
|
+
scriptContent = interpolateScriptExpressions(scriptContent, includeContext, includePath);
|
|
627
627
|
}
|
|
628
628
|
let vendorStyleTags = "";
|
|
629
629
|
try {
|
|
@@ -684,7 +684,7 @@ export async function processIncludes(template, context, filePath, options, depe
|
|
|
684
684
|
const { interpolateScriptExpressions } = await import("./expressions.js");
|
|
685
685
|
workingContent = workingContent.replace(/__STX_DATA_BLOCK_(\d+)__/g, (_token, index) => {
|
|
686
686
|
const block = dataBlocks[Number(index)];
|
|
687
|
-
return `<script${block.attrs}>${interpolateScriptExpressions(block.body, includeContext)}</script>`;
|
|
687
|
+
return `<script${block.attrs}>${interpolateScriptExpressions(block.body, includeContext, includePath)}</script>`;
|
|
688
688
|
});
|
|
689
689
|
}
|
|
690
690
|
if (signalScopeId) {
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolving `@meta(...)` arguments to a tag.
|
|
3
|
+
*
|
|
4
|
+
* `@meta` has three entry points -- the registered custom directive that stages
|
|
5
|
+
* the tag on the head (seo.ts `metaDirective`), the head pass for renders
|
|
6
|
+
* without custom directives (head.ts `processMetaDirective`), and the inline
|
|
7
|
+
* pass that partials use (seo.ts `processMetaDirectives`) -- and they used to
|
|
8
|
+
* read their arguments three different ways. A quoted value could be looked up
|
|
9
|
+
* in the context as if it were a variable name, and an unquoted one that was
|
|
10
|
+
* not a bare variable (`post.author`) was sent as literal text. One resolver
|
|
11
|
+
* now serves all three. It lives apart from both seo.ts and head.ts because
|
|
12
|
+
* seo.ts already imports head.ts.
|
|
13
|
+
*/
|
|
14
|
+
import type { ErrorCode } from './errors/codes';
|
|
15
|
+
/**
|
|
16
|
+
* Namespaces whose tags are addressed by `property`, not `name`.
|
|
17
|
+
*
|
|
18
|
+
* Open Graph and its relatives are RDFa vocabularies, so the attribute is
|
|
19
|
+
* `property`; a `<meta name="og:title">` is ignored by every scraper that
|
|
20
|
+
* implements the spec. Twitter cards are the exception people expect to be a
|
|
21
|
+
* property and are not: the card spec says `name`.
|
|
22
|
+
*/
|
|
23
|
+
export declare const PROPERTY_NAMESPACES: string[];
|
|
24
|
+
export type MetaTagAttrs = {
|
|
25
|
+
name: string;
|
|
26
|
+
content: string;
|
|
27
|
+
} | {
|
|
28
|
+
property: string;
|
|
29
|
+
content: string;
|
|
30
|
+
};
|
|
31
|
+
export type ResolvedMeta = {
|
|
32
|
+
kind: 'tag';
|
|
33
|
+
tag: MetaTagAttrs;
|
|
34
|
+
} | {
|
|
35
|
+
kind: 'empty';
|
|
36
|
+
} | {
|
|
37
|
+
kind: 'error';
|
|
38
|
+
message: string;
|
|
39
|
+
code: ErrorCode;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Resolve `@meta(...)` arguments, given as written (quotes kept), to a tag.
|
|
43
|
+
*
|
|
44
|
+
* - `@meta('author', 'Jane')`: literal text. A quoted value is never looked up
|
|
45
|
+
* in the context, so `@meta('description', 'title')` means the word.
|
|
46
|
+
* - `@meta('author', author)`, `@meta('og:title', post.title)`,
|
|
47
|
+
* `@meta('keywords', tags)`: the second argument is evaluated. An array joins
|
|
48
|
+
* with ", "; `null`, `undefined`, `false` and `''` render nothing.
|
|
49
|
+
* - `@meta('og:title')`: the one-argument form reads the context by the part
|
|
50
|
+
* after the colon (`title`), then `openGraph.title`.
|
|
51
|
+
*/
|
|
52
|
+
export declare function resolveMetaArguments(args: string[], context: Record<string, unknown>): ResolvedMeta;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { ErrorCodes } from "./error-handling.js";
|
|
2
|
+
import { describeValue, evaluateDirectiveArgument, isPlainRecord, parseStringLiteral } from "./directive-arguments.js";
|
|
3
|
+
export const PROPERTY_NAMESPACES = ["og:", "article:", "book:", "profile:", "fb:", "music:", "video:"];
|
|
4
|
+
const BARE_META_NAME = /^[a-z][\w-]*(?::[\w:-]+)?$/i;
|
|
5
|
+
export function resolveMetaArguments(args, context) {
|
|
6
|
+
if (args.length < 1)
|
|
7
|
+
return { kind: "error", message: "meta directive requires at least the meta name", code: ErrorCodes.INVALID_DIRECTIVE_SYNTAX };
|
|
8
|
+
if (args.length > 2)
|
|
9
|
+
return { kind: "error", message: `@meta takes a name and a value, got ${args.length} arguments`, code: ErrorCodes.INVALID_DIRECTIVE_SYNTAX };
|
|
10
|
+
const name = resolveMetaName(args[0], context);
|
|
11
|
+
if (typeof name !== "string")
|
|
12
|
+
return name;
|
|
13
|
+
let content;
|
|
14
|
+
if (args.length === 2) {
|
|
15
|
+
const value = resolveMetaValue(args[1], context);
|
|
16
|
+
if (typeof value !== "string")
|
|
17
|
+
return value;
|
|
18
|
+
content = value;
|
|
19
|
+
} else {
|
|
20
|
+
const segment = name.includes(":") ? name.slice(name.indexOf(":") + 1) : name, openGraph = context.openGraph, found = context[segment] ?? (isPlainRecord(openGraph) ? openGraph[segment] : void 0);
|
|
21
|
+
content = found === void 0 || found === null || typeof found === "object" ? "" : String(found);
|
|
22
|
+
}
|
|
23
|
+
if (!content)
|
|
24
|
+
return { kind: "empty" };
|
|
25
|
+
return { kind: "tag", tag: PROPERTY_NAMESPACES.some((prefix) => name.startsWith(prefix)) ? { property: name, content } : { name, content } };
|
|
26
|
+
}
|
|
27
|
+
function resolveMetaName(raw, context) {
|
|
28
|
+
const literal = parseStringLiteral(raw);
|
|
29
|
+
if (literal !== null)
|
|
30
|
+
return literal.trim() ? literal : { kind: "error", message: "the meta name is empty", code: ErrorCodes.INVALID_DIRECTIVE_SYNTAX };
|
|
31
|
+
if (BARE_META_NAME.test(raw.trim()))
|
|
32
|
+
return raw.trim();
|
|
33
|
+
const result = evaluateDirectiveArgument(raw, context);
|
|
34
|
+
if (!result.ok)
|
|
35
|
+
return { kind: "error", message: `meta name: ${result.message}`, code: result.code };
|
|
36
|
+
if (typeof result.value !== "string" || !result.value.trim())
|
|
37
|
+
return { kind: "error", message: `the meta name must be a non-empty string, got ${describeValue(result.value)}`, code: ErrorCodes.TYPE_ERROR };
|
|
38
|
+
return result.value;
|
|
39
|
+
}
|
|
40
|
+
function resolveMetaValue(raw, context) {
|
|
41
|
+
const literal = parseStringLiteral(raw);
|
|
42
|
+
if (literal !== null)
|
|
43
|
+
return literal;
|
|
44
|
+
const result = evaluateDirectiveArgument(raw, context);
|
|
45
|
+
if (!result.ok)
|
|
46
|
+
return { kind: "error", message: result.message, code: result.code };
|
|
47
|
+
const value = result.value;
|
|
48
|
+
if (value === void 0 || value === null || value === !1)
|
|
49
|
+
return "";
|
|
50
|
+
if (Array.isArray(value))
|
|
51
|
+
return value.filter((item) => item !== void 0 && item !== null && item !== "").map(String).join(", ");
|
|
52
|
+
if (typeof value === "object" && !(value instanceof Date))
|
|
53
|
+
return { kind: "error", message: `the content must be text, got ${describeValue(value)}`, code: ErrorCodes.TYPE_ERROR };
|
|
54
|
+
return String(value);
|
|
55
|
+
}
|
package/dist/process.js
CHANGED
|
@@ -725,7 +725,7 @@ async function processOtherDirectives(template, context, filePath, options, depe
|
|
|
725
725
|
context.__stx_head_raw = `${context.__stx_head_raw || ""}${headResult.headContent}
|
|
726
726
|
`;
|
|
727
727
|
output = processTitleDirective(output, context);
|
|
728
|
-
output = processMetaDirective(output, context);
|
|
728
|
+
output = processMetaDirective(output, context, filePath);
|
|
729
729
|
output = processRouteDirectives(output);
|
|
730
730
|
output = processAuthDirectives(output, context);
|
|
731
731
|
output = processCsrfDirectives(output);
|
package/dist/pwa.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import{Fk as wq,Gk as dq,Hk as Cq,Ik as mq,Jk as gq}from"./chunk-wvc7t11n.js";import{Kk as S,Lk as Tq,Mk as _q,Nk as z,Ok as D,Pk as P,Qk as kq,Rk as vq,Sk as w,Tk as Iq,Uk as d,Vk as Hq,Wk as Oq,Xk as Mq}from"./chunk-
|
|
3
|
-
Icon generation requires the sharp package for image processing.`),q.success=!1,q}let R=await y.default(Y).metadata();if(R.width&&R.width<512)q.warnings.push(`Source icon is ${R.width}x${R.height}. Recommend using at least 512x512 for best quality.`);for(let $ of X){let x=L.join(Q,`icon-${$}x${$}.png`);if(await y.default(Y).resize($,$,{fit:"contain",background:{r:0,g:0,b:0,alpha:0}}).png().toFile(x),q.generatedFiles.push(x),j){let F=L.join(Q,`icon-${$}x${$}.webp`);await y.default(Y).resize($,$,{fit:"contain",background:{r:0,g:0,b:0,alpha:0}}).webp({quality:90}).toFile(F),q.generatedFiles.push(F)}}if(J){for(let x of v){let F=L.join(Q,`apple-touch-icon-${x}x${x}.png`);await y.default(Y).resize(x,x,{fit:"contain",background:{r:255,g:255,b:255,alpha:1}}).png().toFile(F),q.generatedFiles.push(F)}let $=L.join(Q,"apple-touch-icon.png");await y.default(Y).resize(180,180,{fit:"contain",background:{r:255,g:255,b:255,alpha:1}}).png().toFile($),q.generatedFiles.push($)}for(let $ of I){let x=L.join(Q,`favicon-${$}x${$}.png`);await y.default(Y).resize($,$,{fit:"contain",background:{r:0,g:0,b:0,alpha:0}}).png().toFile(x),q.generatedFiles.push(x)}let U=512,T=L.join(Q,`icon-maskable-${U}x${U}.png`),Z=Math.floor(U*0.1),_=U-Z*2;await y.default(Y).resize(_,_,{fit:"contain",background:{r:0,g:0,b:0,alpha:0}}).extend({top:Z,bottom:Z,left:Z,right:Z,background:{r:255,g:255,b:255,alpha:1}}).png().toFile(T),q.generatedFiles.push(T)}catch(y){let V=y instanceof Error?y.message:String(y);q.errors.push(`Icon generation failed: ${V}`),q.success=!1}return q}function n(G){let B=G.sizes||k,q=G.outputDir?`/${G.outputDir}`:"/pwa-icons",K=G.generateWebP??!0,Y=G.generateAppleIcons??!0,Q=[];for(let X of B)if(Q.push(`${q}/icon-${X}x${X}.png`),K)Q.push(`${q}/icon-${X}x${X}.webp`);if(Y){Q.push(`${q}/apple-touch-icon.png`);for(let X of v)Q.push(`${q}/apple-touch-icon-${X}x${X}.png`)}for(let X of I)Q.push(`${q}/favicon-${X}x${X}.png`);return Q.push(`${q}/icon-maskable-512x512.png`),Q}async function O(){try{return await import("sharp"),!0}catch{return!1}}function m(G,B,q,K){let Y=[],Q=K.join(" ");for(let X of G)if(Y.push({src:`${B}/icon-${X}x${X}.png`,sizes:`${X}x${X}`,type:"image/png",purpose:Q}),q)Y.push({src:`${B}/icon-${X}x${X}.webp`,sizes:`${X}x${X}`,type:"image/webp",purpose:Q});return Y}function g(G){return G.map((B)=>({name:B.name,short_name:B.shortName,description:B.description,url:B.url,icons:B.icons?.map((q)=>({src:q.src,sizes:q.sizes,type:q.type}))}))}function h(G){let B=G.pwa?.shareTarget;if(!B?.enabled)return;let q={action:B.action||"/share",method:B.method||"POST",enctype:B.enctype||"multipart/form-data",params:{title:B.params?.title||"title",text:B.params?.text||"text",url:B.params?.url||"url"}};if(B.acceptFiles&&B.acceptFiles.length>0)q.params.files=B.acceptFiles.map((K)=>({name:K.name,accept:K.accept}));return q}function c(G){let B=G.pwa?.fileHandlers;if(!B?.enabled||!B.accept)return;let q=[{action:B.action||"/",accept:B.accept,launch_type:B.launchType||"single-client"}];if(B.icons)q[0].icons=B.icons.map((K)=>({src:K.src,sizes:K.sizes,type:K.type}));return q}function l(G){let B=G.pwa?.protocolHandlers;if(!B||B.length===0)return;return B.map((q)=>({protocol:q.protocol,url:q.url}))}function u(G){let B=G.pwa;if(!B?.enabled||!B.manifest)return null;let{manifest:q,icons:K}=B,Y=K?.sizes||[72,96,128,144,152,192,384,512],Q=K?.outputDir?`/${K.outputDir}`:"/pwa-icons",X=K?.generateWebP??!0,j=K?.purpose||["any","maskable"],J={name:q.name,short_name:q.shortName||q.name,description:q.description,start_url:q.startUrl||"/",display:q.display||"standalone",display_override:q.displayOverride,orientation:q.orientation,theme_color:q.themeColor,background_color:q.backgroundColor,scope:q.scope||"/",lang:q.lang,dir:q.dir,id:q.id,categories:q.categories,icons:m(Y,Q,X,j)};if(q.shortcuts&&q.shortcuts.length>0)J.shortcuts=g(q.shortcuts);if(q.screenshots&&q.screenshots.length>0)J.screenshots=q.screenshots.map((U)=>({src:U.src,sizes:U.sizes,type:U.type,label:U.label,form_factor:U.platform}));let y=h(G);if(y)J.share_target=y;let V=c(G);if(V)J.file_handlers=V;let R=l(G);if(R)J.protocol_handlers=R;if(q.launchHandler)J.launch_handler={client_mode:q.launchHandler};if(q.handleLinks)J.handle_links=q.handleLinks;if(q.edgeSidePanel?.enabled)J.edge_side_panel={preferred_width:q.edgeSidePanel.preferredWidth};return J}function M(G){let B=u(G);if(!B)return"";let q=JSON.parse(JSON.stringify(B));return JSON.stringify(q,null,2)}function i(G,B){return{...B,...G,shortcuts:G.shortcuts||B.shortcuts,screenshots:G.screenshots||B.screenshots,categories:G.categories||B.categories}}function s(G){let B=[],q=G.pwa;if(!q?.enabled)return B;let K=q.manifest;if(!K)return B.push("PWA manifest configuration is missing"),B;if(!K.name)B.push("Manifest name is required");let Y=["fullscreen","standalone","minimal-ui","browser"];if(K.display&&!Y.includes(K.display))B.push(`Invalid display mode: ${K.display}. Must be one of: ${Y.join(", ")}`);let Q=["any","natural","landscape","landscape-primary","landscape-secondary","portrait","portrait-primary","portrait-secondary"];if(K.orientation&&!Q.includes(K.orientation))B.push(`Invalid orientation: ${K.orientation}`);let X=/^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/;if(K.themeColor&&!X.test(K.themeColor))B.push(`Invalid theme color: ${K.themeColor}. Must be a valid hex color.`);if(K.backgroundColor&&!X.test(K.backgroundColor))B.push(`Invalid background color: ${K.backgroundColor}. Must be a valid hex color.`);if(q.shareTarget?.enabled){if(!q.shareTarget.action)B.push("Share target action URL is required")}if(q.fileHandlers?.enabled){if(!q.fileHandlers.accept||Object.keys(q.fileHandlers.accept).length===0)B.push("File handlers must specify accepted MIME types")}if(q.protocolHandlers)for(let j of q.protocolHandlers){if(!j.protocol)B.push("Protocol handler missing protocol");if(!j.url||!j.url.includes("%s"))B.push(`Protocol handler URL must contain %s placeholder: ${j.protocol}`)}return B}import A from"path";async function b(G){let B=G.pwa?.offline;if(!B?.enabled)return"";if(B.page){let q=A.isAbsolute(B.page)?B.page:A.resolve(process.cwd(),B.page);if(await Bun.file(q).exists())try{let{processDirectives:K}=await import("./chunk-
|
|
2
|
+
import{Fk as wq,Gk as dq,Hk as Cq,Ik as mq,Jk as gq}from"./chunk-wvc7t11n.js";import{Kk as S,Lk as Tq,Mk as _q,Nk as z,Ok as D,Pk as P,Qk as kq,Rk as vq,Sk as w,Tk as Iq,Uk as d,Vk as Hq,Wk as Oq,Xk as Mq}from"./chunk-c30qnb6z.js";import{Ew as Aq,Fw as bq,Gw as Sq,Hw as zq,Iw as Dq,Jw as Pq}from"./chunk-9nrgjtah.js";import{Nw as N}from"./chunk-ejqpq0wc.js";import p from"fs";import E from"path";import C from"fs";import L from"path";var k=[72,96,128,144,152,192,384,512],v=[120,152,167,180],I=[16,32];async function H(G,B){let q={success:!0,generatedFiles:[],errors:[],warnings:[]},K=G.pwa?.icons;if(!K?.src)return q.errors.push("No source icon specified in pwa.icons.src"),q.success=!1,q;let Y=L.isAbsolute(K.src)?K.src:L.resolve(process.cwd(),K.src);if(!await Bun.file(Y).exists())return q.errors.push(`Source icon not found: ${Y}`),q.success=!1,q;let Q=L.resolve(B,K.outputDir||"pwa-icons");try{C.mkdirSync(Q,{recursive:!0})}catch{return q.errors.push(`Failed to create output directory: ${Q}`),q.success=!1,q}let X=K.sizes||k,j=K.generateWebP??!0,J=K.generateAppleIcons??!0;try{let y;try{y=await import("sharp")}catch{return q.errors.push(`sharp is not installed. Install it with: bun add sharp
|
|
3
|
+
Icon generation requires the sharp package for image processing.`),q.success=!1,q}let R=await y.default(Y).metadata();if(R.width&&R.width<512)q.warnings.push(`Source icon is ${R.width}x${R.height}. Recommend using at least 512x512 for best quality.`);for(let $ of X){let x=L.join(Q,`icon-${$}x${$}.png`);if(await y.default(Y).resize($,$,{fit:"contain",background:{r:0,g:0,b:0,alpha:0}}).png().toFile(x),q.generatedFiles.push(x),j){let F=L.join(Q,`icon-${$}x${$}.webp`);await y.default(Y).resize($,$,{fit:"contain",background:{r:0,g:0,b:0,alpha:0}}).webp({quality:90}).toFile(F),q.generatedFiles.push(F)}}if(J){for(let x of v){let F=L.join(Q,`apple-touch-icon-${x}x${x}.png`);await y.default(Y).resize(x,x,{fit:"contain",background:{r:255,g:255,b:255,alpha:1}}).png().toFile(F),q.generatedFiles.push(F)}let $=L.join(Q,"apple-touch-icon.png");await y.default(Y).resize(180,180,{fit:"contain",background:{r:255,g:255,b:255,alpha:1}}).png().toFile($),q.generatedFiles.push($)}for(let $ of I){let x=L.join(Q,`favicon-${$}x${$}.png`);await y.default(Y).resize($,$,{fit:"contain",background:{r:0,g:0,b:0,alpha:0}}).png().toFile(x),q.generatedFiles.push(x)}let U=512,T=L.join(Q,`icon-maskable-${U}x${U}.png`),Z=Math.floor(U*0.1),_=U-Z*2;await y.default(Y).resize(_,_,{fit:"contain",background:{r:0,g:0,b:0,alpha:0}}).extend({top:Z,bottom:Z,left:Z,right:Z,background:{r:255,g:255,b:255,alpha:1}}).png().toFile(T),q.generatedFiles.push(T)}catch(y){let V=y instanceof Error?y.message:String(y);q.errors.push(`Icon generation failed: ${V}`),q.success=!1}return q}function n(G){let B=G.sizes||k,q=G.outputDir?`/${G.outputDir}`:"/pwa-icons",K=G.generateWebP??!0,Y=G.generateAppleIcons??!0,Q=[];for(let X of B)if(Q.push(`${q}/icon-${X}x${X}.png`),K)Q.push(`${q}/icon-${X}x${X}.webp`);if(Y){Q.push(`${q}/apple-touch-icon.png`);for(let X of v)Q.push(`${q}/apple-touch-icon-${X}x${X}.png`)}for(let X of I)Q.push(`${q}/favicon-${X}x${X}.png`);return Q.push(`${q}/icon-maskable-512x512.png`),Q}async function O(){try{return await import("sharp"),!0}catch{return!1}}function m(G,B,q,K){let Y=[],Q=K.join(" ");for(let X of G)if(Y.push({src:`${B}/icon-${X}x${X}.png`,sizes:`${X}x${X}`,type:"image/png",purpose:Q}),q)Y.push({src:`${B}/icon-${X}x${X}.webp`,sizes:`${X}x${X}`,type:"image/webp",purpose:Q});return Y}function g(G){return G.map((B)=>({name:B.name,short_name:B.shortName,description:B.description,url:B.url,icons:B.icons?.map((q)=>({src:q.src,sizes:q.sizes,type:q.type}))}))}function h(G){let B=G.pwa?.shareTarget;if(!B?.enabled)return;let q={action:B.action||"/share",method:B.method||"POST",enctype:B.enctype||"multipart/form-data",params:{title:B.params?.title||"title",text:B.params?.text||"text",url:B.params?.url||"url"}};if(B.acceptFiles&&B.acceptFiles.length>0)q.params.files=B.acceptFiles.map((K)=>({name:K.name,accept:K.accept}));return q}function c(G){let B=G.pwa?.fileHandlers;if(!B?.enabled||!B.accept)return;let q=[{action:B.action||"/",accept:B.accept,launch_type:B.launchType||"single-client"}];if(B.icons)q[0].icons=B.icons.map((K)=>({src:K.src,sizes:K.sizes,type:K.type}));return q}function l(G){let B=G.pwa?.protocolHandlers;if(!B||B.length===0)return;return B.map((q)=>({protocol:q.protocol,url:q.url}))}function u(G){let B=G.pwa;if(!B?.enabled||!B.manifest)return null;let{manifest:q,icons:K}=B,Y=K?.sizes||[72,96,128,144,152,192,384,512],Q=K?.outputDir?`/${K.outputDir}`:"/pwa-icons",X=K?.generateWebP??!0,j=K?.purpose||["any","maskable"],J={name:q.name,short_name:q.shortName||q.name,description:q.description,start_url:q.startUrl||"/",display:q.display||"standalone",display_override:q.displayOverride,orientation:q.orientation,theme_color:q.themeColor,background_color:q.backgroundColor,scope:q.scope||"/",lang:q.lang,dir:q.dir,id:q.id,categories:q.categories,icons:m(Y,Q,X,j)};if(q.shortcuts&&q.shortcuts.length>0)J.shortcuts=g(q.shortcuts);if(q.screenshots&&q.screenshots.length>0)J.screenshots=q.screenshots.map((U)=>({src:U.src,sizes:U.sizes,type:U.type,label:U.label,form_factor:U.platform}));let y=h(G);if(y)J.share_target=y;let V=c(G);if(V)J.file_handlers=V;let R=l(G);if(R)J.protocol_handlers=R;if(q.launchHandler)J.launch_handler={client_mode:q.launchHandler};if(q.handleLinks)J.handle_links=q.handleLinks;if(q.edgeSidePanel?.enabled)J.edge_side_panel={preferred_width:q.edgeSidePanel.preferredWidth};return J}function M(G){let B=u(G);if(!B)return"";let q=JSON.parse(JSON.stringify(B));return JSON.stringify(q,null,2)}function i(G,B){return{...B,...G,shortcuts:G.shortcuts||B.shortcuts,screenshots:G.screenshots||B.screenshots,categories:G.categories||B.categories}}function s(G){let B=[],q=G.pwa;if(!q?.enabled)return B;let K=q.manifest;if(!K)return B.push("PWA manifest configuration is missing"),B;if(!K.name)B.push("Manifest name is required");let Y=["fullscreen","standalone","minimal-ui","browser"];if(K.display&&!Y.includes(K.display))B.push(`Invalid display mode: ${K.display}. Must be one of: ${Y.join(", ")}`);let Q=["any","natural","landscape","landscape-primary","landscape-secondary","portrait","portrait-primary","portrait-secondary"];if(K.orientation&&!Q.includes(K.orientation))B.push(`Invalid orientation: ${K.orientation}`);let X=/^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/;if(K.themeColor&&!X.test(K.themeColor))B.push(`Invalid theme color: ${K.themeColor}. Must be a valid hex color.`);if(K.backgroundColor&&!X.test(K.backgroundColor))B.push(`Invalid background color: ${K.backgroundColor}. Must be a valid hex color.`);if(q.shareTarget?.enabled){if(!q.shareTarget.action)B.push("Share target action URL is required")}if(q.fileHandlers?.enabled){if(!q.fileHandlers.accept||Object.keys(q.fileHandlers.accept).length===0)B.push("File handlers must specify accepted MIME types")}if(q.protocolHandlers)for(let j of q.protocolHandlers){if(!j.protocol)B.push("Protocol handler missing protocol");if(!j.url||!j.url.includes("%s"))B.push(`Protocol handler URL must contain %s placeholder: ${j.protocol}`)}return B}import A from"path";async function b(G){let B=G.pwa?.offline;if(!B?.enabled)return"";if(B.page){let q=A.isAbsolute(B.page)?B.page:A.resolve(process.cwd(),B.page);if(await Bun.file(q).exists())try{let{processDirectives:K}=await import("./chunk-j5qygdy9.js"),Y=await Bun.file(q).text();return await K(Y,{},q,G,new Set)}catch(K){console.warn(`[PWA] Failed to process custom offline page: ${K}`)}else console.warn(`[PWA] Custom offline page not found: ${q}`)}return f(G)}function f(G){let B=G.pwa?.offline,q=G.pwa?.manifest,K=B?.fallbackTitle||"You are offline",Y=B?.fallbackMessage||"Please check your internet connection and try again.",Q=q?.themeColor||"#4f46e5",X=q?.shortName||q?.name||"App";return`<!DOCTYPE html>
|
|
4
4
|
<html lang="en">
|
|
5
5
|
<head>
|
|
6
6
|
<meta charset="UTF-8">
|
|
@@ -224,6 +224,28 @@ export declare const ALPINE_BRIDGE_X_ATTRS: readonly string[];
|
|
|
224
224
|
export declare const COMPONENT_PASSTHROUGH_X_ATTRS: readonly string[];
|
|
225
225
|
/** The `X_HANDLED` lookup the generated runtime embeds, as JS source. */
|
|
226
226
|
export declare function runtimeHandledXAttrsLiteral(): string;
|
|
227
|
+
/**
|
|
228
|
+
* The colon names that route to their own binder rather than to the generic
|
|
229
|
+
* attribute binding: `:text` is a text binding, `:title` is an attribute.
|
|
230
|
+
*
|
|
231
|
+
* Shared for the same reason the `x-` table is. Anything that has to decide
|
|
232
|
+
* what an attribute *means* -- the runtime's dispatch, and the compile-time
|
|
233
|
+
* binding manifest that has to find every binding the runtime would -- needs
|
|
234
|
+
* this list, and two copies of it disagree eventually.
|
|
235
|
+
*/
|
|
236
|
+
export declare const RUNTIME_DIRECTIVE_NAMES: readonly string[];
|
|
237
|
+
/** The `DIRECTIVE_NAMES` lookup the generated runtime embeds, as JS source. */
|
|
238
|
+
export declare function runtimeDirectiveNamesLiteral(): string;
|
|
239
|
+
/**
|
|
240
|
+
* Event names a bare `:name` or `@name` is read as a listener for.
|
|
241
|
+
*
|
|
242
|
+
* Prefix-matched, so `click` also covers `click.stop` and `click.prevent`.
|
|
243
|
+
*/
|
|
244
|
+
export declare const RUNTIME_EVENT_NAMES: readonly string[];
|
|
245
|
+
/** The `EVENT_RE` the generated runtime embeds, as JS source. */
|
|
246
|
+
export declare function runtimeEventRegexLiteral(): string;
|
|
247
|
+
/** Does this name begin with an event the runtime binds as a listener? */
|
|
248
|
+
export declare function isRuntimeEventName(name: string): boolean;
|
|
227
249
|
/**
|
|
228
250
|
* Drop cached blanked documents.
|
|
229
251
|
*
|