@zvndev/darkmown 2.2.0 → 2.3.1-next.1.g3a4a8dc

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +34 -2
  3. package/docs/spec-alignment.md +10 -0
  4. package/package.json +16 -11
  5. package/site/pages/docs/index.wd +14 -0
  6. package/site/pages/playground/index.js +47 -46
  7. package/src/catalog.js +16 -1
  8. package/src/compiler/actions.js +42 -22
  9. package/src/compiler/body.js +36 -14
  10. package/src/compiler/collections.js +16 -10
  11. package/src/compiler/context.js +39 -10
  12. package/src/compiler/fetch.js +39 -15
  13. package/src/compiler/format.js +5 -2
  14. package/src/compiler/forms.js +91 -26
  15. package/src/compiler/frontmatter.js +11 -2
  16. package/src/compiler/fs-reader.js +2 -1
  17. package/src/compiler/image-size.js +340 -0
  18. package/src/compiler/includes.js +8 -4
  19. package/src/compiler/interpolation.js +15 -7
  20. package/src/compiler/loops.js +63 -35
  21. package/src/compiler/markdown.js +4 -3
  22. package/src/compiler/media.js +17 -3
  23. package/src/compiler/page.js +8 -6
  24. package/src/compiler/predicates.js +49 -24
  25. package/src/compiler/reader.js +12 -2
  26. package/src/compiler/state.js +16 -2
  27. package/src/compiler/structure.js +24 -14
  28. package/src/deploy.js +8 -4
  29. package/src/errors.js +1156 -0
  30. package/src/router.js +7 -4
  31. package/src/scaffold.js +4 -2
  32. package/src/skin.js +17 -6
  33. package/types/api-runner.d.ts +94 -0
  34. package/types/builder.d.ts +141 -10
  35. package/types/catalog.d.ts +97 -0
  36. package/types/compiler/actions.d.ts +41 -0
  37. package/types/compiler/body.d.ts +11 -0
  38. package/types/compiler/collections.d.ts +76 -0
  39. package/types/compiler/context.d.ts +647 -0
  40. package/types/compiler/directives.d.ts +6 -0
  41. package/types/compiler/expr-ast.d.ts +20 -0
  42. package/types/compiler/fetch.d.ts +29 -0
  43. package/types/compiler/format.d.ts +49 -0
  44. package/types/compiler/forms.d.ts +94 -0
  45. package/types/compiler/frontmatter.d.ts +33 -0
  46. package/types/compiler/fs-reader.d.ts +7 -0
  47. package/types/compiler/image-size.d.ts +14 -0
  48. package/types/compiler/includes.d.ts +100 -0
  49. package/types/compiler/index.d.ts +5 -0
  50. package/types/compiler/interpolation.d.ts +115 -0
  51. package/types/compiler/loops.d.ts +36 -0
  52. package/types/compiler/markdown.d.ts +34 -0
  53. package/types/compiler/media.d.ts +32 -0
  54. package/types/compiler/page.d.ts +109 -0
  55. package/types/compiler/predicates.d.ts +50 -0
  56. package/types/compiler/reader.d.ts +48 -0
  57. package/types/compiler/state.d.ts +68 -0
  58. package/types/compiler/structure.d.ts +56 -0
  59. package/types/compiler.d.ts +35 -223
  60. package/types/dev.d.ts +47 -2
  61. package/types/errors.d.ts +100 -0
  62. package/types/feeds.d.ts +150 -0
  63. package/types/headers.d.ts +44 -0
  64. package/types/highlight.d.ts +24 -0
  65. package/types/router.d.ts +42 -3
  66. package/types/scaffold.d.ts +15 -3
  67. package/types/skin.d.ts +28 -1
  68. package/types/src/api-runner.d.ts +94 -0
  69. package/types/src/builder.d.ts +170 -0
  70. package/types/src/catalog.d.ts +97 -0
  71. package/types/src/compiler/actions.d.ts +41 -0
  72. package/types/src/compiler/body.d.ts +11 -0
  73. package/types/src/compiler/collections.d.ts +76 -0
  74. package/types/src/compiler/context.d.ts +647 -0
  75. package/types/src/compiler/directives.d.ts +6 -0
  76. package/types/src/compiler/expr-ast.d.ts +20 -0
  77. package/types/src/compiler/fetch.d.ts +29 -0
  78. package/types/src/compiler/format.d.ts +49 -0
  79. package/types/src/compiler/forms.d.ts +94 -0
  80. package/types/src/compiler/frontmatter.d.ts +33 -0
  81. package/types/src/compiler/fs-reader.d.ts +7 -0
  82. package/types/src/compiler/image-size.d.ts +14 -0
  83. package/types/src/compiler/includes.d.ts +100 -0
  84. package/types/src/compiler/index.d.ts +5 -0
  85. package/types/src/compiler/interpolation.d.ts +115 -0
  86. package/types/src/compiler/loops.d.ts +36 -0
  87. package/types/src/compiler/markdown.d.ts +34 -0
  88. package/types/src/compiler/media.d.ts +32 -0
  89. package/types/src/compiler/page.d.ts +109 -0
  90. package/types/src/compiler/predicates.d.ts +50 -0
  91. package/types/src/compiler/reader.d.ts +48 -0
  92. package/types/src/compiler/state.d.ts +68 -0
  93. package/types/src/compiler/structure.d.ts +56 -0
  94. package/types/src/compiler.d.ts +48 -0
  95. package/types/src/config.d.ts +45 -0
  96. package/types/src/dev.d.ts +58 -0
  97. package/types/src/errors.d.ts +100 -0
  98. package/types/src/feeds.d.ts +150 -0
  99. package/types/src/headers.d.ts +44 -0
  100. package/types/src/highlight.d.ts +24 -0
  101. package/types/src/router.d.ts +78 -0
  102. package/types/src/scaffold.d.ts +20 -0
  103. package/types/src/skin.d.ts +48 -0
  104. package/types/src/statics.d.ts +24 -0
  105. package/types/statics.d.ts +2 -1
@@ -34,6 +34,7 @@ export function handleState(line, ctx, index) {
34
34
  const match = line.match(/^:state\s+([A-Za-z_$][\w$]*)\s*=\s*(.+?)(\s+persist)?$/);
35
35
  if (!match)
36
36
  throw wdError(`Malformed :state in ${at(ctx, index)}: ${line}. ${STATE_USE}`, {
37
+ code: "WD201",
37
38
  file: ctx.file,
38
39
  line: lineOf(ctx, index),
39
40
  hint: STATE_USE.slice("Use: ".length),
@@ -54,11 +55,15 @@ export function handleState(line, ctx, index) {
54
55
  */
55
56
  export function declareState(name, value, ctx) {
56
57
  if (ctx.loopItem)
57
- throw new Error(`State cannot be declared inside a reactive @loop body (${ctx.file})`);
58
+ throw wdError(`State cannot be declared inside a reactive @loop body (${ctx.file})`, {
59
+ code: "WD202",
60
+ file: ctx.file
61
+ });
58
62
  if (ctx.comp.stores.has(name))
59
63
  throw wdError(
60
64
  `State "${name}" collides with a :store of the same name in ${ctx.file}. Use: :store name = value for the global, or rename one — e.g. ${STORE_EXAMPLE}`,
61
65
  {
66
+ code: "WD203",
62
67
  file: ctx.file,
63
68
  hint: `:store name = value for the global, or rename one — e.g. ${STORE_EXAMPLE}`,
64
69
  example: STORE_EXAMPLE
@@ -66,7 +71,10 @@ export function declareState(name, value, ctx) {
66
71
  );
67
72
  const key = ctx.sections.length ? `${ctx.sections.at(-1)}:${name}` : name;
68
73
  if (ctx.comp.state.has(key))
69
- throw new Error(`State "${name}" is declared twice in the same scope (${ctx.file})`);
74
+ throw wdError(`State "${name}" is declared twice in the same scope (${ctx.file})`, {
75
+ code: "WD204",
76
+ file: ctx.file
77
+ });
70
78
  ctx.comp.state.set(key, value);
71
79
  ctx.comp.assets.runtime = true;
72
80
  return key;
@@ -84,6 +92,7 @@ export function handleStore(line, ctx, index) {
84
92
  throw wdError(
85
93
  `Malformed :store in ${at(ctx, index)}: ${line}. Use: :store name = value [ephemeral] — e.g. ${STORE_EXAMPLE}`,
86
94
  {
95
+ code: "WD205",
87
96
  file: ctx.file,
88
97
  line: lineOf(ctx, index),
89
98
  hint: `:store name = value [ephemeral] — e.g. ${STORE_EXAMPLE}`,
@@ -110,6 +119,7 @@ export function declareStore(name, value, ctx) {
110
119
  throw wdError(
111
120
  `Store "${name}" is declared twice in ${ctx.file}. Use: :store name = value — e.g. ${STORE_EXAMPLE}`,
112
121
  {
122
+ code: "WD206",
113
123
  file: ctx.file,
114
124
  hint: `:store name = value — e.g. ${STORE_EXAMPLE}`,
115
125
  example: STORE_EXAMPLE
@@ -119,6 +129,7 @@ export function declareStore(name, value, ctx) {
119
129
  throw wdError(
120
130
  `Store "${name}" collides with a :state of the same name in ${ctx.file}. Use: :store name = value for the global, or rename one — e.g. ${STORE_EXAMPLE}`,
121
131
  {
132
+ code: "WD207",
122
133
  file: ctx.file,
123
134
  hint: `:store name = value for the global, or rename one — e.g. ${STORE_EXAMPLE}`,
124
135
  example: STORE_EXAMPLE
@@ -152,6 +163,7 @@ export function handleComputed(line, ctx, index) {
152
163
  const match = line.match(/^:computed\s+([A-Za-z_$][\w$]*)\s*=\s*(.+)$/);
153
164
  if (!match)
154
165
  throw wdError(`Malformed :computed in ${at(ctx, index)}: ${line}. Use: ${COMPUTED_EXAMPLE}`, {
166
+ code: "WD208",
155
167
  file: ctx.file,
156
168
  line: lineOf(ctx, index),
157
169
  hint: COMPUTED_EXAMPLE,
@@ -169,6 +181,7 @@ export function handleComputed(line, ctx, index) {
169
181
  throw wdError(
170
182
  `Malformed :computed expression in ${at(ctx, index)}: ${line}. Use: ${COMPUTED_EXAMPLE}`,
171
183
  {
184
+ code: "WD209",
172
185
  file: ctx.file,
173
186
  line: lineOf(ctx, index),
174
187
  hint: COMPUTED_EXAMPLE,
@@ -203,6 +216,7 @@ export function handleTheme(line, ctx, index) {
203
216
  throw wdError(
204
217
  `Malformed :theme in ${at(ctx, index)}: ${line}. Use: :theme (or :theme name = "auto") — e.g. ${THEME_EXAMPLE}`,
205
218
  {
219
+ code: "WD210",
206
220
  file: ctx.file,
207
221
  line: lineOf(ctx, index),
208
222
  hint: `:theme (or :theme name = "auto") — e.g. ${THEME_EXAMPLE}`,
@@ -53,6 +53,7 @@ export function handleInclude(line, ctx, index) {
53
53
  const match = line.match(/^@include\s+(\S+)(?:\s+with\s+(.+))?$/);
54
54
  if (!match)
55
55
  throw wdError(`Malformed @include in ${at(ctx, index)}: ${line}. ${INCLUDE_USE}`, {
56
+ code: "WD603",
56
57
  file: ctx.file,
57
58
  line: lineOf(ctx, index),
58
59
  hint: INCLUDE_USE.slice("Use: ".length),
@@ -107,8 +108,9 @@ function parseIncludeArgs(raw, ctx) {
107
108
  const expr = value.slice(1, -1).trim();
108
109
  const resolved = lookupPath(expr, ctx);
109
110
  if (!resolved.found) {
110
- throw new Error(
111
- `@include argument ${match[1]}={ ${expr} } in ${ctx.file} does not match any value in scope`
111
+ throw wdError(
112
+ `@include argument ${match[1]}={ ${expr} } in ${ctx.file} does not match any value in scope`,
113
+ { code: "WD604", file: ctx.file }
112
114
  );
113
115
  }
114
116
  args[match[1]] = resolved.value;
@@ -165,8 +167,9 @@ export function handleContainer(header, bodyLines, ctx, index) {
165
167
  }
166
168
  const cm = rest.match(/^\.([A-Za-z_][\w-]*)/);
167
169
  if (!cm)
168
- throw new Error(
169
- `Unexpected token "${rest.split(/\s+/)[0]}" in container "::: ${header}" in ${at(ctx, index)}`
170
+ throw wdError(
171
+ `Unexpected token "${rest.split(/\s+/)[0]}" in container "::: ${header}" in ${at(ctx, index)}`,
172
+ { code: "WD605", file: ctx.file, line: lineOf(ctx, index) }
170
173
  );
171
174
  const cls = cm[1];
172
175
  rest = rest.slice(cm[0].length).trim();
@@ -231,6 +234,7 @@ export function handleCarousel(line, bodyLines, ctx, index) {
231
234
  if (!auto) {
232
235
  const hint = `:carousel [autoplay=3000] … :endcarousel — e.g. ${CAROUSEL_EXAMPLE}`;
233
236
  throw wdError(`Malformed :carousel in ${at(ctx, index)}: ${line}. Use: ${hint}`, {
237
+ code: "WD606",
234
238
  file: ctx.file,
235
239
  line: lineOf(ctx, index),
236
240
  hint,
@@ -295,8 +299,9 @@ export function handleIf(line, truthyLines, falsyLines, ctx, index, falsyStart =
295
299
  // Per-row meta vars in :if — only valid inside a loop.
296
300
  if (LOOP_META[head]) {
297
301
  if (!ctx.loopMeta)
298
- throw new Error(
299
- `":if ${match[1]}" uses the loop meta variable "${head}" outside a @loop in ${ctx.file}. Use it inside a loop body.`
302
+ throw wdError(
303
+ `":if ${match[1]}" uses the loop meta variable "${head}" outside a @loop in ${ctx.file}. Use it inside a loop body.`,
304
+ { code: "WD607", file: ctx.file }
300
305
  );
301
306
  if (ctx.loopItem) {
302
307
  const truthy = ctx.compileBody(truthyLines, truthyCtx).trim();
@@ -322,8 +327,9 @@ export function handleIf(line, truthyLines, falsyLines, ctx, index, falsyStart =
322
327
 
323
328
  const key = resolveStateKey(head, ctx);
324
329
  if (!key) {
325
- throw new Error(
326
- `:if ${match[1]} in ${ctx.file} does not match a :state or in-scope value. Declare it first.`
330
+ throw wdError(
331
+ `:if ${match[1]} in ${ctx.file} does not match a :state or in-scope value. Declare it first.`,
332
+ { code: "WD608", file: ctx.file }
327
333
  );
328
334
  }
329
335
  ctx.comp.assets.runtime = true;
@@ -344,6 +350,7 @@ export function handleIf(line, truthyLines, falsyLines, ctx, index, falsyStart =
344
350
  if (!condition) {
345
351
  const hint = `":if name" or ":if a <op> b [and|or|not …]" — e.g. ${IF_EXAMPLE}`;
346
352
  throw wdError(`Malformed :if in ${at(ctx, index)}: ${line}. Use ${hint}`, {
353
+ code: "WD609",
347
354
  file: ctx.file,
348
355
  line: lineOf(ctx, index),
349
356
  hint,
@@ -403,13 +410,15 @@ function validateDemoHref(href, ctx) {
403
410
  const value = href.trim();
404
411
  // biome-ignore lint/suspicious/noControlCharactersInRegex: intentional — rejects control characters in URLs/hrefs.
405
412
  if (value !== href || /[\u0000-\u001F\u007F]/.test(value)) {
406
- throw new Error(
407
- `Unsafe :try href "${escapeHtml(href)}" in ${ctx.file}. Use a relative URL starting with /, ./, ../, or #, or an http:, https:, or mailto: URL.`
413
+ throw wdError(
414
+ `Unsafe :try href "${escapeHtml(href)}" in ${ctx.file}. Use a relative URL starting with /, ./, ../, or #, or an http:, https:, or mailto: URL.`,
415
+ { code: "WD610", file: ctx.file }
408
416
  );
409
417
  }
410
418
  if (value.startsWith("//")) {
411
- throw new Error(
412
- `Unsafe :try href "${escapeHtml(href)}" in ${ctx.file}. Protocol-relative URLs are not allowed; use http: or https: explicitly.`
419
+ throw wdError(
420
+ `Unsafe :try href "${escapeHtml(href)}" in ${ctx.file}. Protocol-relative URLs are not allowed; use http: or https: explicitly.`,
421
+ { code: "WD611", file: ctx.file }
413
422
  );
414
423
  }
415
424
  if (
@@ -424,7 +433,8 @@ function validateDemoHref(href, ctx) {
424
433
  if (scheme && ["http", "https", "mailto"].includes(scheme[1].toLowerCase())) {
425
434
  return value;
426
435
  }
427
- throw new Error(
428
- `Unsafe :try href "${escapeHtml(href)}" in ${ctx.file}. Use a relative URL starting with /, ./, ../, or #, or an http:, https:, or mailto: URL.`
436
+ throw wdError(
437
+ `Unsafe :try href "${escapeHtml(href)}" in ${ctx.file}. Use a relative URL starting with /, ./, ../, or #, or an http:, https:, or mailto: URL.`,
438
+ { code: "WD610", file: ctx.file }
429
439
  );
430
440
  }
package/src/deploy.js CHANGED
@@ -2,6 +2,7 @@ import { spawn } from "node:child_process";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { buildSite } from "./builder.js";
5
+ import { wdError } from "./compiler/context.js";
5
6
  import { BASE_SECURITY_HEADERS, REACTIVE_CSP } from "./headers.js";
6
7
 
7
8
  /** The platforms `darkmown deploy` knows how to drive. */
@@ -25,7 +26,9 @@ export const DEPLOY_TARGETS = ["vercel", "cloudflare"];
25
26
  */
26
27
  export async function deploy({ cwd, target, prod = false, log, run = spawnProcess, projectName }) {
27
28
  if (!DEPLOY_TARGETS.includes(target)) {
28
- throw new Error(`Unknown deploy target "${target}". Use one of: ${DEPLOY_TARGETS.join(", ")}.`);
29
+ throw wdError(`Unknown deploy target "${target}". Use one of: ${DEPLOY_TARGETS.join(", ")}.`, {
30
+ code: "WD904"
31
+ });
29
32
  }
30
33
 
31
34
  ensurePlatformConfig(cwd, target);
@@ -46,11 +49,12 @@ export async function deploy({ cwd, target, prod = false, log, run = spawnProces
46
49
  output
47
50
  )
48
51
  ) {
49
- throw new Error(
50
- `The ${target} CLI is not signed in. Run \`${plan.loginHint}\` (try \`! ${plan.loginHint}\` in this session), then re-run \`darkmown deploy ${target}\`.`
52
+ throw wdError(
53
+ `The ${target} CLI is not signed in. Run \`${plan.loginHint}\` (try \`! ${plan.loginHint}\` in this session), then re-run \`darkmown deploy ${target}\`.`,
54
+ { code: "WD905" }
51
55
  );
52
56
  }
53
- throw new Error(`${target} deploy failed (exit ${result.code}).`);
57
+ throw wdError(`${target} deploy failed (exit ${result.code}).`, { code: "WD906" });
54
58
  }
55
59
 
56
60
  const url = plan.parseUrl(output);