@mccaskie-labs/workbench-config 0.1.15 → 0.1.17

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @mccaskie-labs/workbench-config
2
2
 
3
+ ## 0.1.17
4
+
5
+ ### Patch Changes
6
+
7
+ - Capabilities: the `pdf` component (workbench-pdf) with its `pdf.document` and `pdf.html` probes.
8
+
9
+ ## 0.1.16
10
+
11
+ ### Patch Changes
12
+
13
+ - Capabilities: the `country` component (workbench-country) with its `country.packs` and `country.spelling` probes.
14
+
3
15
  ## 0.1.15
4
16
 
5
17
  ### Patch Changes
@@ -0,0 +1,83 @@
1
+ {
2
+ "$schema": "./schema.json",
3
+ "format": 1,
4
+ "component": "country",
5
+ "title": "Country packs and the one formatter",
6
+ "summary": "workbench-country writes dates, times, numbers and money the way readers in the Philippines, Australia, the UK and the US expect, from one pack per country, and gives each pack's spelling for the Gateway. It runs in the app itself, with no Gateway call and no cost.",
7
+ "status": "built",
8
+ "surfaces": {
9
+ "package": [
10
+ "COUNTRY_PACKS",
11
+ "countryPackFor",
12
+ "audiencePack",
13
+ "checkPack",
14
+ "CountryPackError",
15
+ "formatDate",
16
+ "formatDateTime",
17
+ "calendarDayIn",
18
+ "formatNumber",
19
+ "formatMoney",
20
+ "formattingLocale",
21
+ "currencySymbol",
22
+ "currencySymbolFrom",
23
+ "WORLDWIDE",
24
+ "isWorldwide"
25
+ ]
26
+ },
27
+ "rules": [
28
+ {
29
+ "id": "country.one-formatter",
30
+ "kind": "must",
31
+ "text": "Write every date, time, number and amount readers see through workbench-country, from the app's audiencePack, not hand-built strings or a bare toLocaleString.",
32
+ "enforced_by": "review",
33
+ "probe": "country.packs"
34
+ },
35
+ {
36
+ "id": "country.month-in-words",
37
+ "kind": "must_not",
38
+ "text": "Write a date readers see as digits only (03/04/2026): the month is always in words, so day-first and month-first readers read it the same way.",
39
+ "enforced_by": "types",
40
+ "probe": "country.packs"
41
+ },
42
+ {
43
+ "id": "country.no-bare-dollar",
44
+ "kind": "must_not",
45
+ "text": "Write an amount with a bare \"$\": Australian dollars are A$ and US dollars US$.",
46
+ "enforced_by": "types",
47
+ "probe": "country.packs"
48
+ },
49
+ {
50
+ "id": "country.spelling-from-pack",
51
+ "kind": "must",
52
+ "text": "Pass the pack's spelling as workbench-ai's spelling option, so AI text is spelled for the same readers its dates and money are written for.",
53
+ "enforced_by": "review",
54
+ "probe": "country.spelling"
55
+ }
56
+ ],
57
+ "examples": [
58
+ {
59
+ "id": "country.example.format",
60
+ "title": "Write a date and a price for the app's readers",
61
+ "lang": "ts",
62
+ "probe": "country.packs",
63
+ "code": "import { audiencePack, formatDate, formatMoney } from '@mccaskie-labs/workbench-country'\n\nconst pack = audiencePack({ country: 'PH' })\nconst line = `Due ${formatDate('2026-09-26', { pack })}: ${formatMoney(1400, pack)}`\n// \"Due September 26, 2026: ₱1,400\"\nconst reply = await ai.text({ prompt, spelling: pack.spelling })"
64
+ }
65
+ ],
66
+ "probes": [
67
+ {
68
+ "id": "country.packs",
69
+ "half": "runtime",
70
+ "expect": "pass",
71
+ "smoke": true,
72
+ "cost_hint_usd": 0,
73
+ "note": "In the Worker itself: formats one calendar date, one moment and one amount for PH, AU, GB and US and compares each with the expected text (September 26, 2026 / 26 September 2026; ₱1,400, A$1,400, £1,400, US$1,400)"
74
+ },
75
+ {
76
+ "id": "country.spelling",
77
+ "half": "runtime",
78
+ "expect": "pass",
79
+ "cost_hint_usd": 0,
80
+ "note": "Every pack's spelling is one the Gateway accepts (en-PH, en-AU, en-GB, en-US), checked against the ai manifest's spellings; no Gateway call"
81
+ }
82
+ ]
83
+ }
@@ -0,0 +1,93 @@
1
+ {
2
+ "$schema": "./schema.json",
3
+ "format": 1,
4
+ "component": "pdf",
5
+ "title": "Branded PDFs",
6
+ "summary": "workbench-pdf prints an app's content as a PDF with its brand.json logo, colours and fonts, in the app's own Worker through its Browser Rendering binding (BROWSER). Nothing goes through the Gateway and the app holds no Cloudflare token. The standard layout takes plain-text blocks; html() takes the app's own markup.",
7
+ "status": "built",
8
+ "surfaces": {
9
+ "package": [
10
+ "createPdfMaker",
11
+ "WorkbenchPdfError"
12
+ ]
13
+ },
14
+ "limits": [
15
+ {
16
+ "id": "pdf.blocks",
17
+ "value": 2000,
18
+ "unit": "blocks",
19
+ "per": "document",
20
+ "enforced_by": "types"
21
+ },
22
+ {
23
+ "id": "pdf.text",
24
+ "value": 20000,
25
+ "unit": "characters",
26
+ "per": "text",
27
+ "enforced_by": "types"
28
+ },
29
+ {
30
+ "id": "pdf.logo-size",
31
+ "value": 1048576,
32
+ "unit": "bytes",
33
+ "per": "logo",
34
+ "enforced_by": "types"
35
+ },
36
+ {
37
+ "id": "pdf.time",
38
+ "value": 120000,
39
+ "unit": "ms",
40
+ "per": "document",
41
+ "enforced_by": "types"
42
+ }
43
+ ],
44
+ "rules": [
45
+ {
46
+ "id": "pdf.brand-file",
47
+ "kind": "must",
48
+ "text": "Brand every PDF from the app's brand.json through defineBrand, the same file the account emails use.",
49
+ "enforced_by": "types",
50
+ "probe": "pdf.document"
51
+ },
52
+ {
53
+ "id": "pdf.own-binding",
54
+ "kind": "must_not",
55
+ "text": "Hold a Cloudflare token for Browser Rendering or send PDFs through the Gateway (hard rules 1 and 2): print through the Worker's own BROWSER binding.",
56
+ "enforced_by": "review",
57
+ "probe": "pdf.document"
58
+ },
59
+ {
60
+ "id": "pdf.no-page-network",
61
+ "kind": "must_not",
62
+ "text": "Let a printed page run script or load anything but data: URIs, the brand's font hosts and the origins the app lists in allowOrigins.",
63
+ "enforced_by": "types",
64
+ "probe": "pdf.html"
65
+ }
66
+ ],
67
+ "examples": [
68
+ {
69
+ "id": "pdf.example.document",
70
+ "title": "Print a branded invoice",
71
+ "lang": "ts",
72
+ "probe": "pdf.document",
73
+ "code": "import { defineBrand } from '@mccaskie-labs/workbench-config'\nimport { createPdfMaker } from '@mccaskie-labs/workbench-pdf'\nimport brandFile from '../brand.json'\n\nconst pdf = createPdfMaker({ browser: env.BROWSER, brand: defineBrand(brandFile) })\nconst bytes = await pdf.document({ title: 'Invoice 42', blocks: [{ type: 'table', columns: ['Item', 'Amount'], rows: [['Probe run', '₱112.50']] }] })\nreturn new Response(bytes, { headers: { 'content-type': 'application/pdf' } })"
74
+ }
75
+ ],
76
+ "probes": [
77
+ {
78
+ "id": "pdf.document",
79
+ "half": "runtime",
80
+ "expect": "pass",
81
+ "smoke": true,
82
+ "cost_hint_usd": 0,
83
+ "note": "Prints the standard layout from the app's brand.json through the BROWSER binding; checks the bytes are a PDF and the page carried the brand name"
84
+ },
85
+ {
86
+ "id": "pdf.html",
87
+ "half": "runtime",
88
+ "expect": "pass",
89
+ "cost_hint_usd": 0,
90
+ "note": "Prints the app's own HTML holding an image from a host not in allowOrigins; the PDF is made and the browser reports that image's request as blocked"
91
+ }
92
+ ]
93
+ }
@@ -26,6 +26,8 @@
26
26
  "domains",
27
27
  "dns",
28
28
  "analytics",
29
+ "country",
30
+ "pdf",
29
31
  "access",
30
32
  "r2",
31
33
  "kv",
@@ -1,4 +1,4 @@
1
- export declare const MANIFEST_VERSION: "f81423629d34";
1
+ export declare const MANIFEST_VERSION: "849b265d6d63";
2
2
  export declare const capabilities: {
3
3
  readonly ai: {
4
4
  readonly $schema: "./schema.json";
@@ -356,6 +356,63 @@ export declare const capabilities: {
356
356
  readonly note: "Manual: run npx workbench-config check in the project";
357
357
  }];
358
358
  };
359
+ readonly country: {
360
+ readonly $schema: "./schema.json";
361
+ readonly format: 1;
362
+ readonly component: "country";
363
+ readonly title: "Country packs and the one formatter";
364
+ readonly summary: "workbench-country writes dates, times, numbers and money the way readers in the Philippines, Australia, the UK and the US expect, from one pack per country, and gives each pack's spelling for the Gateway. It runs in the app itself, with no Gateway call and no cost.";
365
+ readonly status: "built";
366
+ readonly surfaces: {
367
+ readonly package: readonly ["COUNTRY_PACKS", "countryPackFor", "audiencePack", "checkPack", "CountryPackError", "formatDate", "formatDateTime", "calendarDayIn", "formatNumber", "formatMoney", "formattingLocale", "currencySymbol", "currencySymbolFrom", "WORLDWIDE", "isWorldwide"];
368
+ };
369
+ readonly rules: readonly [{
370
+ readonly id: "country.one-formatter";
371
+ readonly kind: "must";
372
+ readonly text: "Write every date, time, number and amount readers see through workbench-country, from the app's audiencePack, not hand-built strings or a bare toLocaleString.";
373
+ readonly enforced_by: "review";
374
+ readonly probe: "country.packs";
375
+ }, {
376
+ readonly id: "country.month-in-words";
377
+ readonly kind: "must_not";
378
+ readonly text: "Write a date readers see as digits only (03/04/2026): the month is always in words, so day-first and month-first readers read it the same way.";
379
+ readonly enforced_by: "types";
380
+ readonly probe: "country.packs";
381
+ }, {
382
+ readonly id: "country.no-bare-dollar";
383
+ readonly kind: "must_not";
384
+ readonly text: "Write an amount with a bare \"$\": Australian dollars are A$ and US dollars US$.";
385
+ readonly enforced_by: "types";
386
+ readonly probe: "country.packs";
387
+ }, {
388
+ readonly id: "country.spelling-from-pack";
389
+ readonly kind: "must";
390
+ readonly text: "Pass the pack's spelling as workbench-ai's spelling option, so AI text is spelled for the same readers its dates and money are written for.";
391
+ readonly enforced_by: "review";
392
+ readonly probe: "country.spelling";
393
+ }];
394
+ readonly examples: readonly [{
395
+ readonly id: "country.example.format";
396
+ readonly title: "Write a date and a price for the app's readers";
397
+ readonly lang: "ts";
398
+ readonly code: "import { audiencePack, formatDate, formatMoney } from '@mccaskie-labs/workbench-country'\n\nconst pack = audiencePack({ country: 'PH' })\nconst line = `Due ${formatDate('2026-09-26', { pack })}: ${formatMoney(1400, pack)}`\n// \"Due September 26, 2026: ₱1,400\"\nconst reply = await ai.text({ prompt, spelling: pack.spelling })";
399
+ readonly probe: "country.packs";
400
+ }];
401
+ readonly probes: readonly [{
402
+ readonly id: "country.packs";
403
+ readonly half: "runtime";
404
+ readonly expect: "pass";
405
+ readonly smoke: true;
406
+ readonly cost_hint_usd: 0;
407
+ readonly note: "In the Worker itself: formats one calendar date, one moment and one amount for PH, AU, GB and US and compares each with the expected text (September 26, 2026 / 26 September 2026; ₱1,400, A$1,400, £1,400, US$1,400)";
408
+ }, {
409
+ readonly id: "country.spelling";
410
+ readonly half: "runtime";
411
+ readonly expect: "pass";
412
+ readonly cost_hint_usd: 0;
413
+ readonly note: "Every pack's spelling is one the Gateway accepts (en-PH, en-AU, en-GB, en-US), checked against the ai manifest's spellings; no Gateway call";
414
+ }];
415
+ };
359
416
  readonly database: {
360
417
  readonly $schema: "./schema.json";
361
418
  readonly format: 1;
@@ -1385,6 +1442,82 @@ export declare const capabilities: {
1385
1442
  readonly cost_hint_usd: 0;
1386
1443
  }];
1387
1444
  };
1445
+ readonly pdf: {
1446
+ readonly $schema: "./schema.json";
1447
+ readonly format: 1;
1448
+ readonly component: "pdf";
1449
+ readonly title: "Branded PDFs";
1450
+ readonly summary: "workbench-pdf prints an app's content as a PDF with its brand.json logo, colours and fonts, in the app's own Worker through its Browser Rendering binding (BROWSER). Nothing goes through the Gateway and the app holds no Cloudflare token. The standard layout takes plain-text blocks; html() takes the app's own markup.";
1451
+ readonly status: "built";
1452
+ readonly surfaces: {
1453
+ readonly package: readonly ["createPdfMaker", "WorkbenchPdfError"];
1454
+ };
1455
+ readonly limits: readonly [{
1456
+ readonly id: "pdf.blocks";
1457
+ readonly value: 2000;
1458
+ readonly unit: "blocks";
1459
+ readonly per: "document";
1460
+ readonly enforced_by: "types";
1461
+ }, {
1462
+ readonly id: "pdf.text";
1463
+ readonly value: 20000;
1464
+ readonly unit: "characters";
1465
+ readonly per: "text";
1466
+ readonly enforced_by: "types";
1467
+ }, {
1468
+ readonly id: "pdf.logo-size";
1469
+ readonly value: 1048576;
1470
+ readonly unit: "bytes";
1471
+ readonly per: "logo";
1472
+ readonly enforced_by: "types";
1473
+ }, {
1474
+ readonly id: "pdf.time";
1475
+ readonly value: 120000;
1476
+ readonly unit: "ms";
1477
+ readonly per: "document";
1478
+ readonly enforced_by: "types";
1479
+ }];
1480
+ readonly rules: readonly [{
1481
+ readonly id: "pdf.brand-file";
1482
+ readonly kind: "must";
1483
+ readonly text: "Brand every PDF from the app's brand.json through defineBrand, the same file the account emails use.";
1484
+ readonly enforced_by: "types";
1485
+ readonly probe: "pdf.document";
1486
+ }, {
1487
+ readonly id: "pdf.own-binding";
1488
+ readonly kind: "must_not";
1489
+ readonly text: "Hold a Cloudflare token for Browser Rendering or send PDFs through the Gateway (hard rules 1 and 2): print through the Worker's own BROWSER binding.";
1490
+ readonly enforced_by: "review";
1491
+ readonly probe: "pdf.document";
1492
+ }, {
1493
+ readonly id: "pdf.no-page-network";
1494
+ readonly kind: "must_not";
1495
+ readonly text: "Let a printed page run script or load anything but data: URIs, the brand's font hosts and the origins the app lists in allowOrigins.";
1496
+ readonly enforced_by: "types";
1497
+ readonly probe: "pdf.html";
1498
+ }];
1499
+ readonly examples: readonly [{
1500
+ readonly id: "pdf.example.document";
1501
+ readonly title: "Print a branded invoice";
1502
+ readonly lang: "ts";
1503
+ readonly code: "import { defineBrand } from '@mccaskie-labs/workbench-config'\nimport { createPdfMaker } from '@mccaskie-labs/workbench-pdf'\nimport brandFile from '../brand.json'\n\nconst pdf = createPdfMaker({ browser: env.BROWSER, brand: defineBrand(brandFile) })\nconst bytes = await pdf.document({ title: 'Invoice 42', blocks: [{ type: 'table', columns: ['Item', 'Amount'], rows: [['Probe run', '₱112.50']] }] })\nreturn new Response(bytes, { headers: { 'content-type': 'application/pdf' } })";
1504
+ readonly probe: "pdf.document";
1505
+ }];
1506
+ readonly probes: readonly [{
1507
+ readonly id: "pdf.document";
1508
+ readonly half: "runtime";
1509
+ readonly expect: "pass";
1510
+ readonly smoke: true;
1511
+ readonly cost_hint_usd: 0;
1512
+ readonly note: "Prints the standard layout from the app's brand.json through the BROWSER binding; checks the bytes are a PDF and the page carried the brand name";
1513
+ }, {
1514
+ readonly id: "pdf.html";
1515
+ readonly half: "runtime";
1516
+ readonly expect: "pass";
1517
+ readonly cost_hint_usd: 0;
1518
+ readonly note: "Prints the app's own HTML holding an image from a host not in allowOrigins; the PDF is made and the browser reports that image's request as blocked";
1519
+ }];
1520
+ };
1388
1521
  };
1389
1522
  export type Alias = (typeof capabilities)["ai"]["allowed"]["aliases"][number]["alias"];
1390
1523
  export type GatewayScope = (typeof capabilities)["gateway"]["allowed"]["scopes"][number];
@@ -1,6 +1,6 @@
1
1
  // Generated by `npm run capabilities:build` from capabilities/*.json. Do not edit.
2
2
  // check:capabilities fails in Checks when this file is stale.
3
- export const MANIFEST_VERSION = "f81423629d34";
3
+ export const MANIFEST_VERSION = "849b265d6d63";
4
4
  export const capabilities = {
5
5
  "ai": {
6
6
  "$schema": "./schema.json",
@@ -523,6 +523,89 @@ export const capabilities = {
523
523
  }
524
524
  ]
525
525
  },
526
+ "country": {
527
+ "$schema": "./schema.json",
528
+ "format": 1,
529
+ "component": "country",
530
+ "title": "Country packs and the one formatter",
531
+ "summary": "workbench-country writes dates, times, numbers and money the way readers in the Philippines, Australia, the UK and the US expect, from one pack per country, and gives each pack's spelling for the Gateway. It runs in the app itself, with no Gateway call and no cost.",
532
+ "status": "built",
533
+ "surfaces": {
534
+ "package": [
535
+ "COUNTRY_PACKS",
536
+ "countryPackFor",
537
+ "audiencePack",
538
+ "checkPack",
539
+ "CountryPackError",
540
+ "formatDate",
541
+ "formatDateTime",
542
+ "calendarDayIn",
543
+ "formatNumber",
544
+ "formatMoney",
545
+ "formattingLocale",
546
+ "currencySymbol",
547
+ "currencySymbolFrom",
548
+ "WORLDWIDE",
549
+ "isWorldwide"
550
+ ]
551
+ },
552
+ "rules": [
553
+ {
554
+ "id": "country.one-formatter",
555
+ "kind": "must",
556
+ "text": "Write every date, time, number and amount readers see through workbench-country, from the app's audiencePack, not hand-built strings or a bare toLocaleString.",
557
+ "enforced_by": "review",
558
+ "probe": "country.packs"
559
+ },
560
+ {
561
+ "id": "country.month-in-words",
562
+ "kind": "must_not",
563
+ "text": "Write a date readers see as digits only (03/04/2026): the month is always in words, so day-first and month-first readers read it the same way.",
564
+ "enforced_by": "types",
565
+ "probe": "country.packs"
566
+ },
567
+ {
568
+ "id": "country.no-bare-dollar",
569
+ "kind": "must_not",
570
+ "text": "Write an amount with a bare \"$\": Australian dollars are A$ and US dollars US$.",
571
+ "enforced_by": "types",
572
+ "probe": "country.packs"
573
+ },
574
+ {
575
+ "id": "country.spelling-from-pack",
576
+ "kind": "must",
577
+ "text": "Pass the pack's spelling as workbench-ai's spelling option, so AI text is spelled for the same readers its dates and money are written for.",
578
+ "enforced_by": "review",
579
+ "probe": "country.spelling"
580
+ }
581
+ ],
582
+ "examples": [
583
+ {
584
+ "id": "country.example.format",
585
+ "title": "Write a date and a price for the app's readers",
586
+ "lang": "ts",
587
+ "code": "import { audiencePack, formatDate, formatMoney } from '@mccaskie-labs/workbench-country'\n\nconst pack = audiencePack({ country: 'PH' })\nconst line = `Due ${formatDate('2026-09-26', { pack })}: ${formatMoney(1400, pack)}`\n// \"Due September 26, 2026: ₱1,400\"\nconst reply = await ai.text({ prompt, spelling: pack.spelling })",
588
+ "probe": "country.packs"
589
+ }
590
+ ],
591
+ "probes": [
592
+ {
593
+ "id": "country.packs",
594
+ "half": "runtime",
595
+ "expect": "pass",
596
+ "smoke": true,
597
+ "cost_hint_usd": 0,
598
+ "note": "In the Worker itself: formats one calendar date, one moment and one amount for PH, AU, GB and US and compares each with the expected text (September 26, 2026 / 26 September 2026; ₱1,400, A$1,400, £1,400, US$1,400)"
599
+ },
600
+ {
601
+ "id": "country.spelling",
602
+ "half": "runtime",
603
+ "expect": "pass",
604
+ "cost_hint_usd": 0,
605
+ "note": "Every pack's spelling is one the Gateway accepts (en-PH, en-AU, en-GB, en-US), checked against the ai manifest's spellings; no Gateway call"
606
+ }
607
+ ]
608
+ },
526
609
  "database": {
527
610
  "$schema": "./schema.json",
528
611
  "format": 1,
@@ -1876,5 +1959,98 @@ export const capabilities = {
1876
1959
  "cost_hint_usd": 0
1877
1960
  }
1878
1961
  ]
1962
+ },
1963
+ "pdf": {
1964
+ "$schema": "./schema.json",
1965
+ "format": 1,
1966
+ "component": "pdf",
1967
+ "title": "Branded PDFs",
1968
+ "summary": "workbench-pdf prints an app's content as a PDF with its brand.json logo, colours and fonts, in the app's own Worker through its Browser Rendering binding (BROWSER). Nothing goes through the Gateway and the app holds no Cloudflare token. The standard layout takes plain-text blocks; html() takes the app's own markup.",
1969
+ "status": "built",
1970
+ "surfaces": {
1971
+ "package": [
1972
+ "createPdfMaker",
1973
+ "WorkbenchPdfError"
1974
+ ]
1975
+ },
1976
+ "limits": [
1977
+ {
1978
+ "id": "pdf.blocks",
1979
+ "value": 2000,
1980
+ "unit": "blocks",
1981
+ "per": "document",
1982
+ "enforced_by": "types"
1983
+ },
1984
+ {
1985
+ "id": "pdf.text",
1986
+ "value": 20000,
1987
+ "unit": "characters",
1988
+ "per": "text",
1989
+ "enforced_by": "types"
1990
+ },
1991
+ {
1992
+ "id": "pdf.logo-size",
1993
+ "value": 1048576,
1994
+ "unit": "bytes",
1995
+ "per": "logo",
1996
+ "enforced_by": "types"
1997
+ },
1998
+ {
1999
+ "id": "pdf.time",
2000
+ "value": 120000,
2001
+ "unit": "ms",
2002
+ "per": "document",
2003
+ "enforced_by": "types"
2004
+ }
2005
+ ],
2006
+ "rules": [
2007
+ {
2008
+ "id": "pdf.brand-file",
2009
+ "kind": "must",
2010
+ "text": "Brand every PDF from the app's brand.json through defineBrand, the same file the account emails use.",
2011
+ "enforced_by": "types",
2012
+ "probe": "pdf.document"
2013
+ },
2014
+ {
2015
+ "id": "pdf.own-binding",
2016
+ "kind": "must_not",
2017
+ "text": "Hold a Cloudflare token for Browser Rendering or send PDFs through the Gateway (hard rules 1 and 2): print through the Worker's own BROWSER binding.",
2018
+ "enforced_by": "review",
2019
+ "probe": "pdf.document"
2020
+ },
2021
+ {
2022
+ "id": "pdf.no-page-network",
2023
+ "kind": "must_not",
2024
+ "text": "Let a printed page run script or load anything but data: URIs, the brand's font hosts and the origins the app lists in allowOrigins.",
2025
+ "enforced_by": "types",
2026
+ "probe": "pdf.html"
2027
+ }
2028
+ ],
2029
+ "examples": [
2030
+ {
2031
+ "id": "pdf.example.document",
2032
+ "title": "Print a branded invoice",
2033
+ "lang": "ts",
2034
+ "code": "import { defineBrand } from '@mccaskie-labs/workbench-config'\nimport { createPdfMaker } from '@mccaskie-labs/workbench-pdf'\nimport brandFile from '../brand.json'\n\nconst pdf = createPdfMaker({ browser: env.BROWSER, brand: defineBrand(brandFile) })\nconst bytes = await pdf.document({ title: 'Invoice 42', blocks: [{ type: 'table', columns: ['Item', 'Amount'], rows: [['Probe run', '₱112.50']] }] })\nreturn new Response(bytes, { headers: { 'content-type': 'application/pdf' } })",
2035
+ "probe": "pdf.document"
2036
+ }
2037
+ ],
2038
+ "probes": [
2039
+ {
2040
+ "id": "pdf.document",
2041
+ "half": "runtime",
2042
+ "expect": "pass",
2043
+ "smoke": true,
2044
+ "cost_hint_usd": 0,
2045
+ "note": "Prints the standard layout from the app's brand.json through the BROWSER binding; checks the bytes are a PDF and the page carried the brand name"
2046
+ },
2047
+ {
2048
+ "id": "pdf.html",
2049
+ "half": "runtime",
2050
+ "expect": "pass",
2051
+ "cost_hint_usd": 0,
2052
+ "note": "Prints the app's own HTML holding an image from a host not in allowOrigins; the PDF is made and the browser reports that image's request as blocked"
2053
+ }
2054
+ ]
1879
2055
  }
1880
2056
  };
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- export declare const COMPONENTS: readonly ["gateway", "ai", "images", "image-search", "config", "email", "email-inbound", "email-sending", "database", "workers", "domains", "dns", "analytics", "access", "r2", "kv", "github"];
2
+ export declare const COMPONENTS: readonly ["gateway", "ai", "images", "image-search", "config", "email", "email-inbound", "email-sending", "database", "workers", "domains", "dns", "analytics", "country", "pdf", "access", "r2", "kv", "github"];
3
3
  export type Component = (typeof COMPONENTS)[number];
4
4
  export declare const allowedSchemas: {
5
5
  gateway: z.ZodObject<{
@@ -96,6 +96,7 @@ export declare const capabilitySchema: z.ZodObject<{
96
96
  ai: "ai";
97
97
  analytics: "analytics";
98
98
  config: "config";
99
+ country: "country";
99
100
  database: "database";
100
101
  dns: "dns";
101
102
  domains: "domains";
@@ -107,6 +108,7 @@ export declare const capabilitySchema: z.ZodObject<{
107
108
  "image-search": "image-search";
108
109
  images: "images";
109
110
  kv: "kv";
111
+ pdf: "pdf";
110
112
  r2: "r2";
111
113
  workers: "workers";
112
114
  }>;
@@ -16,6 +16,8 @@ export const COMPONENTS = [
16
16
  "domains",
17
17
  "dns",
18
18
  "analytics",
19
+ "country",
20
+ "pdf",
19
21
  "access",
20
22
  "r2",
21
23
  "kv",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mccaskie-labs/workbench-config",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "Workbench app configuration: WORKBENCH_ENV, typed variables that fail by name, secrets that are never printed, and the capabilities manifest.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",