@getvitops/utils 8.2.0-alpha.0 → 8.3.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/CHANGELOG.md +399 -23
  2. package/dist/{adc-D6aw2qgq.mjs → adc-BiW9BzqA.mjs} +2 -3
  3. package/dist/{adc-D6aw2qgq.mjs.map → adc-BiW9BzqA.mjs.map} +1 -1
  4. package/dist/{adc-BoEoogyo.d.mts → adc-DvX3-P7a.d.mts} +2 -4
  5. package/dist/adc-DvX3-P7a.d.mts.map +1 -0
  6. package/dist/ads/index.d.mts +37 -57
  7. package/dist/ads/index.d.mts.map +1 -1
  8. package/dist/ads/index.mjs +21 -10
  9. package/dist/ads/index.mjs.map +1 -1
  10. package/dist/audit/index.d.mts +180 -0
  11. package/dist/audit/index.d.mts.map +1 -0
  12. package/dist/audit/index.mjs +509 -0
  13. package/dist/audit/index.mjs.map +1 -0
  14. package/dist/color/index.d.mts +7 -5
  15. package/dist/color/index.d.mts.map +1 -1
  16. package/dist/color/index.mjs +10 -5
  17. package/dist/color/index.mjs.map +1 -1
  18. package/dist/content.d.mts +78 -0
  19. package/dist/content.d.mts.map +1 -0
  20. package/dist/content.mjs +45 -0
  21. package/dist/content.mjs.map +1 -0
  22. package/dist/crm/index.d.mts +5 -87
  23. package/dist/crm/index.d.mts.map +1 -1
  24. package/dist/crm/index.mjs +10 -19
  25. package/dist/crm/index.mjs.map +1 -1
  26. package/dist/email/index.d.mts +1 -11
  27. package/dist/email/index.d.mts.map +1 -1
  28. package/dist/email/index.mjs +6 -10
  29. package/dist/email/index.mjs.map +1 -1
  30. package/dist/favicon.d.mts +0 -2
  31. package/dist/favicon.d.mts.map +1 -1
  32. package/dist/favicon.mjs +1 -2
  33. package/dist/git/index.d.mts +0 -2
  34. package/dist/git/index.d.mts.map +1 -1
  35. package/dist/git/index.mjs +1 -2
  36. package/dist/hosting/index.d.mts +0 -8
  37. package/dist/hosting/index.d.mts.map +1 -1
  38. package/dist/hosting/index.mjs +4 -7
  39. package/dist/hosting/index.mjs.map +1 -1
  40. package/dist/{identity-BoRddTx3.mjs → identity-C4yInICW.mjs} +2 -3
  41. package/dist/{identity-BoRddTx3.mjs.map → identity-C4yInICW.mjs.map} +1 -1
  42. package/dist/index.d.mts +2 -88
  43. package/dist/index.d.mts.map +1 -1
  44. package/dist/index.mjs +36 -52
  45. package/dist/index.mjs.map +1 -1
  46. package/dist/indexing/index.d.mts +3 -42
  47. package/dist/indexing/index.d.mts.map +1 -1
  48. package/dist/indexing/index.mjs +8 -13
  49. package/dist/indexing/index.mjs.map +1 -1
  50. package/dist/{json-store-30CFCbZZ.mjs → json-store-VJKEaoVX.mjs} +2 -3
  51. package/dist/{json-store-30CFCbZZ.mjs.map → json-store-VJKEaoVX.mjs.map} +1 -1
  52. package/dist/media/index.d.mts +0 -12
  53. package/dist/media/index.d.mts.map +1 -1
  54. package/dist/media/index.mjs +7 -13
  55. package/dist/media/index.mjs.map +1 -1
  56. package/dist/notify/index.d.mts +64 -44
  57. package/dist/notify/index.d.mts.map +1 -1
  58. package/dist/notify/index.mjs +138 -11
  59. package/dist/notify/index.mjs.map +1 -1
  60. package/dist/onboarding/index.d.mts +26 -6
  61. package/dist/onboarding/index.d.mts.map +1 -1
  62. package/dist/onboarding/index.mjs +92 -5
  63. package/dist/onboarding/index.mjs.map +1 -1
  64. package/dist/{plan-BLKikoOG.mjs → plan-B9USuc17.mjs} +3 -4
  65. package/dist/{plan-BLKikoOG.mjs.map → plan-B9USuc17.mjs.map} +1 -1
  66. package/dist/{plan-Hq8Pb0Uw.d.mts → plan-CH3Wt5ZV.d.mts} +8 -30
  67. package/dist/plan-CH3Wt5ZV.d.mts.map +1 -0
  68. package/dist/plan-CddqsmJJ.d.mts +82 -0
  69. package/dist/plan-CddqsmJJ.d.mts.map +1 -0
  70. package/dist/plan-DpHv6NO8.mjs +93 -0
  71. package/dist/plan-DpHv6NO8.mjs.map +1 -0
  72. package/dist/{plan-format-A18SvJ7C.mjs → plan-format-CIz6Feg2.mjs} +2 -3
  73. package/dist/{plan-format-A18SvJ7C.mjs.map → plan-format-CIz6Feg2.mjs.map} +1 -1
  74. package/dist/{platform-0nlOIy8h.mjs → platform-Cb_RtsF1.mjs} +3 -5
  75. package/dist/{platform-0nlOIy8h.mjs.map → platform-Cb_RtsF1.mjs.map} +1 -1
  76. package/dist/reviews/index.d.mts +4 -120
  77. package/dist/reviews/index.d.mts.map +1 -1
  78. package/dist/reviews/index.mjs +6 -99
  79. package/dist/reviews/index.mjs.map +1 -1
  80. package/dist/reviews/select.d.mts +2 -0
  81. package/dist/reviews/select.mjs +3 -0
  82. package/dist/schema/index.d.mts +0 -12
  83. package/dist/schema/index.d.mts.map +1 -1
  84. package/dist/schema/index.mjs +6 -12
  85. package/dist/schema/index.mjs.map +1 -1
  86. package/dist/security/index.d.mts +0 -2
  87. package/dist/security/index.d.mts.map +1 -1
  88. package/dist/security/index.mjs +1 -2
  89. package/dist/{token-DmmoQ7um.d.mts → token-CHR1O4w5.d.mts} +3 -3
  90. package/dist/token-CHR1O4w5.d.mts.map +1 -0
  91. package/dist/{token-DkKC5m23.mjs → token-CJSl9eIS.mjs} +33 -14
  92. package/dist/token-CJSl9eIS.mjs.map +1 -0
  93. package/dist/tracking/index.d.mts +1 -9
  94. package/dist/tracking/index.d.mts.map +1 -1
  95. package/dist/tracking/index.mjs +3 -4
  96. package/dist/turnstile/index.d.mts +105 -0
  97. package/dist/turnstile/index.d.mts.map +1 -0
  98. package/dist/turnstile/index.mjs +281 -0
  99. package/dist/turnstile/index.mjs.map +1 -0
  100. package/dist/{types-7ELci8RQ.d.mts → types-oac2P5or.d.mts} +3 -23
  101. package/dist/types-oac2P5or.d.mts.map +1 -0
  102. package/dist/url/index.d.mts +18 -0
  103. package/dist/url/index.d.mts.map +1 -0
  104. package/dist/url/index.mjs +34 -0
  105. package/dist/url/index.mjs.map +1 -0
  106. package/package.json +43 -2
  107. package/dist/adc-BoEoogyo.d.mts.map +0 -1
  108. package/dist/cloudflare-BiK61Oq4.d.mts +0 -27
  109. package/dist/cloudflare-BiK61Oq4.d.mts.map +0 -1
  110. package/dist/cloudflare-CD8vwrgj.mjs +0 -92
  111. package/dist/cloudflare-CD8vwrgj.mjs.map +0 -1
  112. package/dist/plan-Hq8Pb0Uw.d.mts.map +0 -1
  113. package/dist/token-DkKC5m23.mjs.map +0 -1
  114. package/dist/token-DmmoQ7um.d.mts.map +0 -1
  115. package/dist/types-7ELci8RQ.d.mts.map +0 -1
@@ -1,4 +1,3 @@
1
- //#region src/hosting/types.d.ts
2
1
  /**
3
2
  * `vitops setup`'s own option surface.
4
3
  *
@@ -47,8 +46,6 @@ interface DomainState {
47
46
  canonical?: string | undefined;
48
47
  robots?: string | undefined;
49
48
  }
50
- //#endregion
51
- //#region src/hosting/plan.d.ts
52
49
  type StepAction = 'create' | 'update' | 'skip' | 'blocked';
53
50
  interface StepPlan {
54
51
  action: StepAction;
@@ -93,8 +90,6 @@ export declare function formatSummary(results: HostingResult[]): string;
93
90
  * human, deliberate action.
94
91
  */
95
92
  export declare function planDomainConsistency(state: DomainState): StepPlan;
96
- //#endregion
97
- //#region src/hosting/wrangler.d.ts
98
93
  interface ExecResult {
99
94
  ok: boolean;
100
95
  stdout: string;
@@ -128,8 +123,6 @@ export declare function createR2Bucket(name: string, execImpl?: ExecImpl): Promi
128
123
  ok: boolean;
129
124
  message?: string | undefined;
130
125
  }>;
131
- //#endregion
132
- //#region src/hosting/github.d.ts
133
126
  export declare function ghAuthed(execImpl?: ExecImpl): Promise<boolean>;
134
127
  export declare function originRemote(execImpl?: ExecImpl): Promise<string | undefined>;
135
128
  export declare function repoNwo(execImpl?: ExecImpl): Promise<string | undefined>;
@@ -179,6 +172,5 @@ export declare function setSecret(name: string, value: string, execImpl?: ExecIm
179
172
  ok: boolean;
180
173
  message?: string | undefined;
181
174
  }>;
182
- //#endregion
183
175
  export type { DomainState, ExecImpl, ExecResult, HostingPlan, HostingResult, HostingSetup, HostingState, StepAction, StepPlan };
184
176
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/hosting/types.ts","../../src/hosting/plan.ts","../../src/hosting/wrangler.ts","../../src/hosting/github.ts"],"mappings":";;;;;;;;;;;UAWiB;EACf;;EAEA;EACA;;EAEA;;EAEA;;;UAIe;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAEA;;EAEA;;;UAIe;EACf;EACA;;EAEA;;EAEA;EACA;;;;KC1BU;UAEK;EACf,QAAQ;EACR;;UAGe;EACf,MAAM;EACN,UAAU;EACV,eAAe;EACf,gBAAgB;EAChB,iBAAiB;EACjB,gBAAgB;EAChB,IAAI;EACJ,IAAI;;EAEJ,SAAS,eAAe;EACxB;;;wBASc,KAAK,OAAO,cAAc,OAAO,eAAe;;wBA6HhD,SAAS,GAAG;;wBAKZ,aAAa,GAAG;EAAgB;EAAc;;;wBAkB9C,WAAW,GAAG;;UAmBb;EACf;EACA;EACA;;;wBAIc,cAAc,SAAS;;;;;;;wBAevB,sBAAsB,OAAO,cAAc;;;UC/N1C;EACf;EACA;EACA;;KAGU,YAAY,aAAa,mBAAmB,QAAQ;;qBAGnD,UAAU;;wBAeD,eACpB,WAAU,WACT;EAAU;EAAiB;;;;;;;;;;wBAeR,OACpB,cACA,WAAU,WACT;;wBAYmB,SACpB,cACA,WAAU,WACT;EAAU;EAAa;EAAyB;;wBAS7B,aAAa,cAAc,WAAU,WAAsB;wBAM3D,eACpB,cACA,WAAU,WACT;EAAU;EAAa;;;;wBC/EJ,SAAS,WAAU,WAAsB;wBAIzC,aAAa,WAAU,WAAsB;wBAK7C,QAAQ,WAAU,WAAsB;wBAUxC,SAAS,WAAU,WAAsB;wBASzC,cACpB,aACA,WAAU,WACT;wBAWmB,cAAc,aAAa,WAAU,WAAsB;wBAI3D,aAAa,aAAa,WAAU,WAAsB;wBAY1D,eACpB,aACA,WAAU,WACT;wBAcmB,WACpB,cACA,kCACA,WAAU,WACT;EAAU;EAAa;;wBAYJ,aACpB,WAAU,WACT;EAAU;EAAa;;wBAKJ,iBACpB,aACA,gBACA,WAAU,WACT;EAAU;EAAa;;;;;;;;;;wBAaJ,YACpB,aACA,WAAU,WACT;EAAU;EAAa;;;;wBA0BJ,qBACpB,aACA,WAAU,WACT;;wBAWmB,sBACpB,aACA,2BACA,WAAU,WACT;EAAU;EAAa;;;wBAqBJ,qBACpB,aACA,WAAU,WACT;EAAU;EAAa;;wBAKJ,UACpB,cACA,eACA,WAAU,WACT;EAAU;EAAa"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/hosting/types.ts","../../src/hosting/plan.ts","../../src/hosting/wrangler.ts","../../src/hosting/github.ts"],"mappings":";;;;;;;;;;UAWiB;EACf;;EAEA;EACA;;EAEA;;EAEA;;;UAIe;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAEA;;EAEA;;;UAIe;EACf;EACA;;EAEA;;EAEA;EACA;;KC1BU;UAEK;EACf,QAAQ;EACR;;UAGe;EACf,MAAM;EACN,UAAU;EACV,eAAe;EACf,gBAAgB;EAChB,iBAAiB;EACjB,gBAAgB;EAChB,IAAI;EACJ,IAAI;;EAEJ,SAAS,eAAe;EACxB;;;wBASc,KAAK,OAAO,cAAc,OAAO,eAAe;;wBA6HhD,SAAS,GAAG;;wBAKZ,aAAa,GAAG;EAAgB;EAAc;;;wBAkB9C,WAAW,GAAG;;UAmBb;EACf;EACA;EACA;;;wBAIc,cAAc,SAAS;;;;;;;wBAevB,sBAAsB,OAAO,cAAc;UC/N1C;EACf;EACA;EACA;;KAGU,YAAY,aAAa,mBAAmB,QAAQ;;qBAGnD,UAAU;;wBAeD,eACpB,WAAU,WACT;EAAU;EAAiB;;;;;;;;;;wBAeR,OACpB,cACA,WAAU,WACT;;wBAYmB,SACpB,cACA,WAAU,WACT;EAAU;EAAa;EAAyB;;wBAS7B,aAAa,cAAc,WAAU,WAAsB;wBAM3D,eACpB,cACA,WAAU,WACT;EAAU;EAAa;;wBC/EJ,SAAS,WAAU,WAAsB;wBAIzC,aAAa,WAAU,WAAsB;wBAK7C,QAAQ,WAAU,WAAsB;wBAUxC,SAAS,WAAU,WAAsB;wBASzC,cACpB,aACA,WAAU,WACT;wBAWmB,cAAc,aAAa,WAAU,WAAsB;wBAI3D,aAAa,aAAa,WAAU,WAAsB;wBAY1D,eACpB,aACA,WAAU,WACT;wBAcmB,WACpB,cACA,kCACA,WAAU,WACT;EAAU;EAAa;;wBAYJ,aACpB,WAAU,WACT;EAAU;EAAa;;wBAKJ,iBACpB,aACA,gBACA,WAAU,WACT;EAAU;EAAa;;;;;;;;;;wBAaJ,YACpB,aACA,WAAU,WACT;EAAU;EAAa;;;;wBA0BJ,qBACpB,aACA,WAAU,WACT;;wBAWmB,sBACpB,aACA,2BACA,WAAU,WACT;EAAU;EAAa;;;wBAqBJ,qBACpB,aACA,WAAU,WACT;EAAU;EAAa;;wBAKJ,UACpB,cACA,eACA,WAAU,WACT;EAAU;EAAa"}
@@ -1,8 +1,8 @@
1
- import { t as table } from "../plan-format-A18SvJ7C.mjs";
1
+ import { t as table } from "../plan-format-CIz6Feg2.mjs";
2
2
  import { promisify } from "node:util";
3
3
  import { execFile } from "node:child_process";
4
4
 
5
- //#region src/hosting/plan.ts
5
+ // src/hosting/plan.ts
6
6
  /**
7
7
  * Every decision `vitops setup` makes, as one pure function.
8
8
  *
@@ -233,8 +233,7 @@ function planDomainConsistency(state) {
233
233
  };
234
234
  }
235
235
 
236
- //#endregion
237
- //#region src/hosting/wrangler.ts
236
+ // src/hosting/wrangler.ts
238
237
  /**
239
238
  * Cloudflare resources — dev D1 + R2, plus the account id. Executes a plan and
240
239
  * decides nothing; every observation and mutation is a structured result, never a
@@ -342,8 +341,7 @@ async function createR2Bucket(name, execImpl = realExec) {
342
341
  };
343
342
  }
344
343
 
345
- //#endregion
346
- //#region src/hosting/github.ts
344
+ // src/hosting/github.ts
347
345
  async function ghAuthed(execImpl = realExec) {
348
346
  return (await execImpl("gh", ["auth", "status"])).ok;
349
347
  }
@@ -548,6 +546,5 @@ async function setSecret(name, value, execImpl = realExec) {
548
546
  };
549
547
  }
550
548
 
551
- //#endregion
552
549
  export { adminCollaboratorIds, blockedSteps, branches, createD1, createDevEnvironment, createProdEnvironment, createR2Bucket, createRepo, defaultBranch, environments, findD1, findR2Bucket, formatPlan, formatSummary, ghAuthed, hasDrift, mainProtected, originRemote, plan, planDomainConsistency, protectMain, pushBranches, realExec, repoNwo, secretsPresent, setDefaultBranch, setSecret, wranglerWhoami };
553
550
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/hosting/plan.ts","../../src/hosting/wrangler.ts","../../src/hosting/github.ts"],"sourcesContent":["/**\n * Every decision `vitops setup` makes, as one pure function.\n *\n * Same split as `onboarding/plan.ts` and `ads/plan.ts`: this module decides, from\n * observed live state, what's left to do; `wrangler.ts`/`github.ts` execute a plan\n * and decide nothing. That is what makes `--dry` a complete account of a run and\n * `--check` an honest drift report with no network beyond the observation step.\n *\n * Idempotency lives here: a step whose desired state already holds resolves to\n * `skip`, so a re-run of a fully-provisioned project is all skips by construction.\n *\n * `blocked` (ads' addition to onboarding's three-state `StepAction`) covers the one\n * thing this command genuinely cannot do: mint a Cloudflare API token. That is\n * config nothing here can supply — surfaced as blocked, carrying the manual path,\n * never as a failure.\n *\n * Never destructive: no step here deletes a database, a bucket, a branch, a\n * protection rule, or a DNS record — matching `onboarding/cloudflare.ts`'s DNS\n * verbs, which have no update or delete either.\n */\nimport { table } from '../plan-format.ts';\nimport type { DomainState, HostingSetup, HostingState } from './types.ts';\n\nexport type StepAction = 'create' | 'update' | 'skip' | 'blocked';\n\nexport interface StepPlan {\n action: StepAction;\n detail: string;\n}\n\nexport interface HostingPlan {\n repo: StepPlan;\n branches: StepPlan;\n defaultBranch: StepPlan;\n mainProtection: StepPlan;\n prodEnvironment: StepPlan;\n devEnvironment: StepPlan;\n d1: StepPlan;\n r2: StepPlan;\n /** One step per repo secret this command sets. */\n secrets: Record<string, StepPlan>;\n notes: string[];\n}\n\nconst SECRETS_FOR = (setup: HostingSetup): string[] =>\n setup.platform === 'cloudflare'\n ? ['DEV_D1_ID', 'CLOUDFLARE_ACCOUNT_ID', 'CLOUDFLARE_API_TOKEN']\n : [];\n\n/** Plan the whole hosting sequence from setup + observed state. */\nexport function plan(setup: HostingSetup, state: HostingState): HostingPlan {\n const notes: string[] = [];\n\n const repo: StepPlan =\n state.originRemote && state.repoNwo\n ? { action: 'skip', detail: `repo already exists (${state.repoNwo})` }\n : {\n action: 'create',\n detail: `create GitHub repo \"${setup.projectName}\" (${setup.repoVisibility})`,\n };\n\n const branches: StepPlan =\n state.branches.includes('main') && state.branches.includes('dev')\n ? { action: 'skip', detail: 'main + dev already exist' }\n : { action: 'create', detail: 'push main + dev' };\n\n const defaultBranch: StepPlan =\n state.defaultBranch === 'dev'\n ? { action: 'skip', detail: 'dev is already the default branch' }\n : { action: 'update', detail: 'make dev the default branch' };\n\n const mainProtection: StepPlan = state.mainProtected\n ? { action: 'skip', detail: 'main is already protected' }\n : {\n action: 'blocked',\n detail:\n 'requires GitHub Pro/Team on a private repo — set it manually in Settings → Branches ' +\n '(PR + review + the \"ci\" check required) if this stays blocked',\n };\n\n const prodEnvironment: StepPlan = state.environments.includes('production')\n ? { action: 'skip', detail: 'production environment already exists' }\n : {\n action: 'blocked',\n detail:\n 'requires GitHub Pro/Team on a private repo — create a \"production\" environment ' +\n 'gated on your admin collaborators manually if this stays blocked',\n };\n\n const devEnvironment: StepPlan = state.environments.includes('dev')\n ? { action: 'skip', detail: 'dev environment already exists' }\n : {\n action: 'create',\n detail: 'create \"dev\" environment with no required reviewers (stays open)',\n };\n\n const d1: StepPlan =\n setup.platform !== 'cloudflare' || !setup.devResources\n ? { action: 'skip', detail: 'not a cloudflare project, or dev resources not requested' }\n : state.d1Present\n ? { action: 'skip', detail: `dev D1 already exists (${state.d1Id ?? 'id unknown'})` }\n : { action: 'create', detail: `create D1 database \"${setup.projectName}-dev\"` };\n\n const r2: StepPlan =\n setup.platform !== 'cloudflare' || !setup.devResources\n ? { action: 'skip', detail: 'not a cloudflare project, or dev resources not requested' }\n : state.r2Present\n ? { action: 'skip', detail: `dev R2 bucket already exists` }\n : { action: 'create', detail: `create R2 bucket \"${setup.projectName}-media-dev\"` };\n\n const secrets: Record<string, StepPlan> = {};\n for (const name of SECRETS_FOR(setup)) {\n if (!state.ghAuthed || !state.originRemote) {\n secrets[name] = {\n action: 'blocked',\n detail: 'no GitHub repo wired up yet — run the repo step first',\n };\n continue;\n }\n if (state.secretsPresent.includes(name)) {\n secrets[name] = { action: 'skip', detail: 'already set' };\n continue;\n }\n if (name === 'CLOUDFLARE_API_TOKEN') {\n secrets[name] = {\n action: 'blocked',\n detail:\n 'tokens cannot be created via any API — create one at ' +\n 'https://dash.cloudflare.com/profile/api-tokens (Workers + D1 + R2 edit), then ' +\n '`gh secret set CLOUDFLARE_API_TOKEN`',\n };\n continue;\n }\n const value =\n name === 'DEV_D1_ID'\n ? state.d1Id\n : name === 'CLOUDFLARE_ACCOUNT_ID'\n ? state.accountId\n : undefined;\n secrets[name] = value\n ? { action: 'create', detail: `set from provisioned/observed value` }\n : { action: 'blocked', detail: 'value not yet known — provision the resource first' };\n }\n\n if (!state.wranglerAuthed && setup.platform === 'cloudflare' && setup.devResources)\n notes.push('wrangler is not logged in — run `npx wrangler login` first');\n if (!state.ghAuthed) notes.push('gh is not logged in — run `gh auth login` first');\n\n return {\n repo,\n branches,\n defaultBranch,\n mainProtection,\n prodEnvironment,\n devEnvironment,\n d1,\n r2,\n secrets,\n notes,\n };\n}\n\nconst allSteps = (p: HostingPlan): StepPlan[] => [\n p.repo,\n p.branches,\n p.defaultBranch,\n p.mainProtection,\n p.prodEnvironment,\n p.devEnvironment,\n p.d1,\n p.r2,\n ...Object.values(p.secrets),\n];\n\n/** Any step that isn't a skip — i.e. the live state has drifted from desired. */\nexport function hasDrift(p: HostingPlan): boolean {\n return allSteps(p).some((s) => s.action !== 'skip');\n}\n\n/** Every blocked step, with a name — what `--check` reports and the CLI surfaces. */\nexport function blockedSteps(p: HostingPlan): { name: string; detail: string }[] {\n const named: [string, StepPlan][] = [\n ['repo', p.repo],\n ['branches', p.branches],\n ['defaultBranch', p.defaultBranch],\n ['mainProtection', p.mainProtection],\n ['prodEnvironment', p.prodEnvironment],\n ['devEnvironment', p.devEnvironment],\n ['d1', p.d1],\n ['r2', p.r2],\n ...Object.entries(p.secrets).map(([name, s]) => [`secret:${name}`, s] as [string, StepPlan]),\n ];\n return named\n .filter(([, s]) => s.action === 'blocked')\n .map(([name, s]) => ({ name, detail: s.detail }));\n}\n\n/** Render a plan as the block `--dry` and `--check` print. */\nexport function formatPlan(p: HostingPlan): string {\n const glyph = (s: StepPlan) =>\n s.action === 'skip' ? '·' : s.action === 'blocked' ? '?' : s.action === 'update' ? '~' : '+';\n const lines: string[] = [];\n lines.push(`${glyph(p.repo)} repo ${p.repo.detail}`);\n lines.push(`${glyph(p.branches)} branches ${p.branches.detail}`);\n lines.push(`${glyph(p.defaultBranch)} default branch ${p.defaultBranch.detail}`);\n lines.push(`${glyph(p.mainProtection)} main protection ${p.mainProtection.detail}`);\n lines.push(`${glyph(p.prodEnvironment)} production env ${p.prodEnvironment.detail}`);\n lines.push(`${glyph(p.devEnvironment)} dev env ${p.devEnvironment.detail}`);\n lines.push(`${glyph(p.d1)} dev D1 ${p.d1.detail}`);\n lines.push(`${glyph(p.r2)} dev R2 ${p.r2.detail}`);\n for (const [name, s] of Object.entries(p.secrets))\n lines.push(`${glyph(s)} secret ${name}${' '.repeat(Math.max(1, 11 - name.length))}${s.detail}`);\n for (const n of p.notes) lines.push(`! ${n}`);\n return lines.join('\\n');\n}\n\n/** The outcome of one run, for the summary table. */\nexport interface HostingResult {\n step: string;\n status: 'created' | 'updated' | 'present' | 'blocked' | 'failed';\n detail: string;\n}\n\n/** Render the end-of-run summary: one row per step, plus blocked reminders. */\nexport function formatSummary(results: HostingResult[]): string {\n const headers = ['STEP', 'STATUS', 'DETAIL'];\n const rows = results.map((r) => [r.step, r.status, r.detail]);\n const reminders = results\n .filter((r) => r.status === 'blocked')\n .map((r) => ` ! ${r.step}: ${r.detail}`);\n return table(headers, rows, reminders);\n}\n\n/**\n * Plan the domain-consistency check on its own — it has no executor, only an\n * observation (`wrangler.jsonc` + the config), and it is never applied by this\n * command. See `SETUP.html`'s \"Domain strategy\" stage: the actual cutover is a\n * human, deliberate action.\n */\nexport function planDomainConsistency(state: DomainState): StepPlan {\n const cutover = Boolean(state.customDomain);\n if (cutover) {\n if (!state.canonical || state.canonical !== `https://${state.customDomain}`)\n return {\n action: 'blocked',\n detail: `wrangler.jsonc names a custom domain (${state.customDomain}) but site.domains.canonical doesn't match — a visitor sees the new site while <link rel=canonical> still points at the old one`,\n };\n if (state.robots?.includes('noindex'))\n return {\n action: 'blocked',\n detail:\n 'custom domain is live but robots is still \"noindex,nofollow\" — flip it back after cutover',\n };\n return { action: 'skip', detail: 'custom domain configured and consistent' };\n }\n if (!state.robots?.includes('noindex'))\n return {\n action: 'blocked',\n detail:\n 'still on *.workers.dev but robots does not say \"noindex,nofollow\" — a workers.dev URL is indexable, and this is what stops search engines treating it as a duplicate of the real site',\n };\n return { action: 'skip', detail: 'workers.dev phase, correctly noindexed' };\n}\n","/**\n * Cloudflare resources — dev D1 + R2, plus the account id. Executes a plan and\n * decides nothing; every observation and mutation is a structured result, never a\n * throw, so the CLI decides what a failure means.\n *\n * Shells out to `wrangler` and reads `gh`'s account-id output — there is no REST\n * alternative that doesn't also need the same credential wrangler already holds\n * from `wrangler login`. Same class of executor as `@getvitops/utils/media`\n * shelling to `ffmpeg`: an external tool, not an npm dependency, with `execImpl`\n * injected so the parsing is testable without a real Cloudflare account.\n */\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nconst exec = promisify(execFile);\n\nexport interface ExecResult {\n ok: boolean;\n stdout: string;\n message?: string | undefined;\n}\n\nexport type ExecImpl = (cmd: string, args: string[]) => Promise<ExecResult>;\n\n/** The real executor — `execFile` with an argv array, never a shell string. */\nexport const realExec: ExecImpl = async (cmd, args) => {\n try {\n const { stdout } = await exec(cmd, args, { maxBuffer: 16 * 1024 * 1024 });\n return { ok: true, stdout };\n } catch (err) {\n const e = err as { stdout?: string; stderr?: string; message?: string };\n return {\n ok: false,\n stdout: e.stdout ?? '',\n message: e.stderr || e.message || 'command failed',\n };\n }\n};\n\n/** `wrangler whoami` — auth check + the 32-hex account id, from one call. */\nexport async function wranglerWhoami(\n execImpl: ExecImpl = realExec,\n): Promise<{ authed: boolean; accountId?: string }> {\n const res = await execImpl('npx', ['wrangler', 'whoami']);\n if (!res.ok) return { authed: false };\n const accountId = /\\b([0-9a-f]{32})\\b/.exec(res.stdout)?.[1];\n return { authed: true, ...(accountId ? { accountId } : {}) };\n}\n\n/**\n * Find an existing dev D1 database's id by name.\n *\n * `d1 create`'s own stdout carries the id, but its format has drifted across\n * wrangler versions before — a re-list is the one thing guaranteed to still hold\n * the answer, so both the \"does it exist\" check and the post-create fallback go\n * through this.\n */\nexport async function findD1(\n name: string,\n execImpl: ExecImpl = realExec,\n): Promise<string | undefined> {\n const res = await execImpl('npx', ['wrangler', 'd1', 'list', '--json']);\n if (!res.ok) return undefined;\n try {\n const list = JSON.parse(res.stdout) as { name: string; uuid: string }[];\n return list.find((d) => d.name === name)?.uuid;\n } catch {\n return undefined;\n }\n}\n\n/** Create a dev D1 database, then resolve its id via `findD1` (see its docblock). */\nexport async function createD1(\n name: string,\n execImpl: ExecImpl = realExec,\n): Promise<{ ok: boolean; id?: string | undefined; message?: string | undefined }> {\n const res = await execImpl('npx', ['wrangler', 'd1', 'create', name]);\n if (!res.ok) return { ok: false, message: res.message };\n const inline = /\"database_id\":\\s*\"([0-9a-f-]{36})\"/.exec(res.stdout)?.[1];\n const id = inline ?? (await findD1(name, execImpl));\n if (!id) return { ok: false, message: 'created, but could not determine the database id' };\n return { ok: true, id };\n}\n\nexport async function findR2Bucket(name: string, execImpl: ExecImpl = realExec): Promise<boolean> {\n const res = await execImpl('npx', ['wrangler', 'r2', 'bucket', 'list']);\n if (!res.ok) return false;\n return new RegExp(`name:\\\\s+${name}\\\\s*$`, 'm').test(res.stdout);\n}\n\nexport async function createR2Bucket(\n name: string,\n execImpl: ExecImpl = realExec,\n): Promise<{ ok: boolean; message?: string | undefined }> {\n const res = await execImpl('npx', ['wrangler', 'r2', 'bucket', 'create', name]);\n return res.ok ? { ok: true } : { ok: false, message: res.message };\n}\n","/**\n * GitHub repo, branches, environments and secrets. Executes a plan and decides\n * nothing — see `wrangler.ts`'s docblock for why this shells out (`gh` carries the\n * auth; there is no token-free REST alternative) and for the `execImpl` seam.\n *\n * Absorbs the behaviour of the templates' old `scripts/init-github.mjs`:\n * `dev` is left with **no required reviewers**, deliberately — that is what keeps\n * it the unprotected integration branch. `main` protection and the `production`\n * environment's reviewer policy both 403 on a private repo without GitHub\n * Pro/Team; that is reported as `blocked`, not `failed` — see `plan.ts`.\n */\nimport type { ExecImpl } from './wrangler.ts';\nimport { realExec } from './wrangler.ts';\n\nexport async function ghAuthed(execImpl: ExecImpl = realExec): Promise<boolean> {\n return (await execImpl('gh', ['auth', 'status'])).ok;\n}\n\nexport async function originRemote(execImpl: ExecImpl = realExec): Promise<string | undefined> {\n const res = await execImpl('git', ['remote', 'get-url', 'origin']);\n return res.ok ? res.stdout.trim() : undefined;\n}\n\nexport async function repoNwo(execImpl: ExecImpl = realExec): Promise<string | undefined> {\n const res = await execImpl('gh', ['repo', 'view', '--json', 'nameWithOwner']);\n if (!res.ok) return undefined;\n try {\n return (JSON.parse(res.stdout) as { nameWithOwner: string }).nameWithOwner;\n } catch {\n return undefined;\n }\n}\n\nexport async function branches(execImpl: ExecImpl = realExec): Promise<string[]> {\n const res = await execImpl('git', ['branch', '--list', '--format=%(refname:short)']);\n if (!res.ok) return [];\n return res.stdout\n .split('\\n')\n .map((l) => l.trim())\n .filter(Boolean);\n}\n\nexport async function defaultBranch(\n nwo: string,\n execImpl: ExecImpl = realExec,\n): Promise<string | undefined> {\n const res = await execImpl('gh', ['repo', 'view', nwo, '--json', 'defaultBranchRef']);\n if (!res.ok) return undefined;\n try {\n return (JSON.parse(res.stdout) as { defaultBranchRef?: { name: string } }).defaultBranchRef\n ?.name;\n } catch {\n return undefined;\n }\n}\n\nexport async function mainProtected(nwo: string, execImpl: ExecImpl = realExec): Promise<boolean> {\n return (await execImpl('gh', ['api', `repos/${nwo}/branches/main/protection`])).ok;\n}\n\nexport async function environments(nwo: string, execImpl: ExecImpl = realExec): Promise<string[]> {\n const res = await execImpl('gh', ['api', `repos/${nwo}/environments`]);\n if (!res.ok) return [];\n try {\n return (\n (JSON.parse(res.stdout) as { environments?: { name: string }[] }).environments ?? []\n ).map((e) => e.name);\n } catch {\n return [];\n }\n}\n\nexport async function secretsPresent(\n nwo: string,\n execImpl: ExecImpl = realExec,\n): Promise<string[]> {\n const res = await execImpl('gh', ['api', `repos/${nwo}/actions/secrets`]);\n if (!res.ok) return [];\n try {\n return ((JSON.parse(res.stdout) as { secrets?: { name: string }[] }).secrets ?? []).map(\n (s) => s.name,\n );\n } catch {\n return [];\n }\n}\n\n// ── Mutations ─────────────────────────────────────────────────────────────────\n\nexport async function createRepo(\n name: string,\n visibility: 'public' | 'private',\n execImpl: ExecImpl = realExec,\n): Promise<{ ok: boolean; message?: string | undefined }> {\n const res = await execImpl('gh', [\n 'repo',\n 'create',\n name,\n `--${visibility}`,\n '--source=.',\n '--remote=origin',\n ]);\n return res.ok ? { ok: true } : { ok: false, message: res.message };\n}\n\nexport async function pushBranches(\n execImpl: ExecImpl = realExec,\n): Promise<{ ok: boolean; message?: string | undefined }> {\n const res = await execImpl('git', ['push', '-u', 'origin', 'main', 'dev']);\n return res.ok ? { ok: true } : { ok: false, message: res.message };\n}\n\nexport async function setDefaultBranch(\n nwo: string,\n branch: string,\n execImpl: ExecImpl = realExec,\n): Promise<{ ok: boolean; message?: string | undefined }> {\n const res = await execImpl('gh', ['repo', 'edit', nwo, `--default-branch=${branch}`]);\n return res.ok ? { ok: true } : { ok: false, message: res.message };\n}\n\n/**\n * `PUT .../branches/main/protection`. A 403 on Pro/Team-gated repos is expected.\n *\n * Every field goes through `gh api`'s `-f`/`-F` form flags rather than a JSON body\n * on stdin — `gh api` supports nested keys (`a[b]=c`) and repeated array keys\n * (`a[]=x`) this way, so no request needs a second I/O channel `ExecImpl` would\n * have to carry just for this one call.\n */\nexport async function protectMain(\n nwo: string,\n execImpl: ExecImpl = realExec,\n): Promise<{ ok: boolean; message?: string | undefined }> {\n const res = await execImpl('gh', [\n 'api',\n '--method',\n 'PUT',\n `repos/${nwo}/branches/main/protection`,\n '-F',\n 'required_status_checks[strict]=true',\n '-f',\n 'required_status_checks[contexts][]=ci',\n '-F',\n 'enforce_admins=false',\n '-F',\n 'required_pull_request_reviews[required_approving_review_count]=1',\n '-F',\n 'restrictions=null',\n '-F',\n 'allow_force_pushes=false',\n '-F',\n 'allow_deletions=false',\n ]);\n return res.ok ? { ok: true } : { ok: false, message: res.message };\n}\n\n/** The repo's admin collaborators — numeric user ids, which is what the\n * environment-reviewers API wants (not logins). */\nexport async function adminCollaboratorIds(\n nwo: string,\n execImpl: ExecImpl = realExec,\n): Promise<number[]> {\n const res = await execImpl('gh', ['api', `repos/${nwo}/collaborators?permission=admin`]);\n if (!res.ok) return [];\n try {\n return (JSON.parse(res.stdout) as { id: number }[]).map((c) => c.id);\n } catch {\n return [];\n }\n}\n\n/** `PUT .../environments/production`, reviewers = current admin collaborators. */\nexport async function createProdEnvironment(\n nwo: string,\n reviewerUserIds: number[],\n execImpl: ExecImpl = realExec,\n): Promise<{ ok: boolean; message?: string | undefined }> {\n const res = await execImpl('gh', [\n 'api',\n '--method',\n 'PUT',\n `repos/${nwo}/environments/production`,\n '-f',\n `deployment_branch_policy[protected_branches]=true`,\n '-f',\n `deployment_branch_policy[custom_branch_policies]=false`,\n ...reviewerUserIds.flatMap((id) => [\n '-F',\n `reviewers[][id]=${id}`,\n '-f',\n 'reviewers[][type]=User',\n ]),\n ]);\n return res.ok ? { ok: true } : { ok: false, message: res.message };\n}\n\n/** `PUT .../environments/dev` with no reviewers — deliberately left open. */\nexport async function createDevEnvironment(\n nwo: string,\n execImpl: ExecImpl = realExec,\n): Promise<{ ok: boolean; message?: string | undefined }> {\n const res = await execImpl('gh', ['api', '--method', 'PUT', `repos/${nwo}/environments/dev`]);\n return res.ok ? { ok: true } : { ok: false, message: res.message };\n}\n\nexport async function setSecret(\n name: string,\n value: string,\n execImpl: ExecImpl = realExec,\n): Promise<{ ok: boolean; message?: string | undefined }> {\n const res = await execImpl('gh', ['secret', 'set', name, '--body', value]);\n return res.ok ? { ok: true } : { ok: false, message: res.message };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/hosting/plan.ts","../../src/hosting/wrangler.ts","../../src/hosting/github.ts"],"sourcesContent":["/**\n * Every decision `vitops setup` makes, as one pure function.\n *\n * Same split as `onboarding/plan.ts` and `ads/plan.ts`: this module decides, from\n * observed live state, what's left to do; `wrangler.ts`/`github.ts` execute a plan\n * and decide nothing. That is what makes `--dry` a complete account of a run and\n * `--check` an honest drift report with no network beyond the observation step.\n *\n * Idempotency lives here: a step whose desired state already holds resolves to\n * `skip`, so a re-run of a fully-provisioned project is all skips by construction.\n *\n * `blocked` (ads' addition to onboarding's three-state `StepAction`) covers the one\n * thing this command genuinely cannot do: mint a Cloudflare API token. That is\n * config nothing here can supply — surfaced as blocked, carrying the manual path,\n * never as a failure.\n *\n * Never destructive: no step here deletes a database, a bucket, a branch, a\n * protection rule, or a DNS record — matching `onboarding/cloudflare.ts`'s DNS\n * verbs, which have no update or delete either.\n */\nimport { table } from '../plan-format.ts';\nimport type { DomainState, HostingSetup, HostingState } from './types.ts';\n\nexport type StepAction = 'create' | 'update' | 'skip' | 'blocked';\n\nexport interface StepPlan {\n action: StepAction;\n detail: string;\n}\n\nexport interface HostingPlan {\n repo: StepPlan;\n branches: StepPlan;\n defaultBranch: StepPlan;\n mainProtection: StepPlan;\n prodEnvironment: StepPlan;\n devEnvironment: StepPlan;\n d1: StepPlan;\n r2: StepPlan;\n /** One step per repo secret this command sets. */\n secrets: Record<string, StepPlan>;\n notes: string[];\n}\n\nconst SECRETS_FOR = (setup: HostingSetup): string[] =>\n setup.platform === 'cloudflare'\n ? ['DEV_D1_ID', 'CLOUDFLARE_ACCOUNT_ID', 'CLOUDFLARE_API_TOKEN']\n : [];\n\n/** Plan the whole hosting sequence from setup + observed state. */\nexport function plan(setup: HostingSetup, state: HostingState): HostingPlan {\n const notes: string[] = [];\n\n const repo: StepPlan =\n state.originRemote && state.repoNwo\n ? { action: 'skip', detail: `repo already exists (${state.repoNwo})` }\n : {\n action: 'create',\n detail: `create GitHub repo \"${setup.projectName}\" (${setup.repoVisibility})`,\n };\n\n const branches: StepPlan =\n state.branches.includes('main') && state.branches.includes('dev')\n ? { action: 'skip', detail: 'main + dev already exist' }\n : { action: 'create', detail: 'push main + dev' };\n\n const defaultBranch: StepPlan =\n state.defaultBranch === 'dev'\n ? { action: 'skip', detail: 'dev is already the default branch' }\n : { action: 'update', detail: 'make dev the default branch' };\n\n const mainProtection: StepPlan = state.mainProtected\n ? { action: 'skip', detail: 'main is already protected' }\n : {\n action: 'blocked',\n detail:\n 'requires GitHub Pro/Team on a private repo — set it manually in Settings → Branches ' +\n '(PR + review + the \"ci\" check required) if this stays blocked',\n };\n\n const prodEnvironment: StepPlan = state.environments.includes('production')\n ? { action: 'skip', detail: 'production environment already exists' }\n : {\n action: 'blocked',\n detail:\n 'requires GitHub Pro/Team on a private repo — create a \"production\" environment ' +\n 'gated on your admin collaborators manually if this stays blocked',\n };\n\n const devEnvironment: StepPlan = state.environments.includes('dev')\n ? { action: 'skip', detail: 'dev environment already exists' }\n : {\n action: 'create',\n detail: 'create \"dev\" environment with no required reviewers (stays open)',\n };\n\n const d1: StepPlan =\n setup.platform !== 'cloudflare' || !setup.devResources\n ? { action: 'skip', detail: 'not a cloudflare project, or dev resources not requested' }\n : state.d1Present\n ? { action: 'skip', detail: `dev D1 already exists (${state.d1Id ?? 'id unknown'})` }\n : { action: 'create', detail: `create D1 database \"${setup.projectName}-dev\"` };\n\n const r2: StepPlan =\n setup.platform !== 'cloudflare' || !setup.devResources\n ? { action: 'skip', detail: 'not a cloudflare project, or dev resources not requested' }\n : state.r2Present\n ? { action: 'skip', detail: `dev R2 bucket already exists` }\n : { action: 'create', detail: `create R2 bucket \"${setup.projectName}-media-dev\"` };\n\n const secrets: Record<string, StepPlan> = {};\n for (const name of SECRETS_FOR(setup)) {\n if (!state.ghAuthed || !state.originRemote) {\n secrets[name] = {\n action: 'blocked',\n detail: 'no GitHub repo wired up yet — run the repo step first',\n };\n continue;\n }\n if (state.secretsPresent.includes(name)) {\n secrets[name] = { action: 'skip', detail: 'already set' };\n continue;\n }\n if (name === 'CLOUDFLARE_API_TOKEN') {\n secrets[name] = {\n action: 'blocked',\n detail:\n 'tokens cannot be created via any API — create one at ' +\n 'https://dash.cloudflare.com/profile/api-tokens (Workers + D1 + R2 edit), then ' +\n '`gh secret set CLOUDFLARE_API_TOKEN`',\n };\n continue;\n }\n const value =\n name === 'DEV_D1_ID'\n ? state.d1Id\n : name === 'CLOUDFLARE_ACCOUNT_ID'\n ? state.accountId\n : undefined;\n secrets[name] = value\n ? { action: 'create', detail: `set from provisioned/observed value` }\n : { action: 'blocked', detail: 'value not yet known — provision the resource first' };\n }\n\n if (!state.wranglerAuthed && setup.platform === 'cloudflare' && setup.devResources)\n notes.push('wrangler is not logged in — run `npx wrangler login` first');\n if (!state.ghAuthed) notes.push('gh is not logged in — run `gh auth login` first');\n\n return {\n repo,\n branches,\n defaultBranch,\n mainProtection,\n prodEnvironment,\n devEnvironment,\n d1,\n r2,\n secrets,\n notes,\n };\n}\n\nconst allSteps = (p: HostingPlan): StepPlan[] => [\n p.repo,\n p.branches,\n p.defaultBranch,\n p.mainProtection,\n p.prodEnvironment,\n p.devEnvironment,\n p.d1,\n p.r2,\n ...Object.values(p.secrets),\n];\n\n/** Any step that isn't a skip — i.e. the live state has drifted from desired. */\nexport function hasDrift(p: HostingPlan): boolean {\n return allSteps(p).some((s) => s.action !== 'skip');\n}\n\n/** Every blocked step, with a name — what `--check` reports and the CLI surfaces. */\nexport function blockedSteps(p: HostingPlan): { name: string; detail: string }[] {\n const named: [string, StepPlan][] = [\n ['repo', p.repo],\n ['branches', p.branches],\n ['defaultBranch', p.defaultBranch],\n ['mainProtection', p.mainProtection],\n ['prodEnvironment', p.prodEnvironment],\n ['devEnvironment', p.devEnvironment],\n ['d1', p.d1],\n ['r2', p.r2],\n ...Object.entries(p.secrets).map(([name, s]) => [`secret:${name}`, s] as [string, StepPlan]),\n ];\n return named\n .filter(([, s]) => s.action === 'blocked')\n .map(([name, s]) => ({ name, detail: s.detail }));\n}\n\n/** Render a plan as the block `--dry` and `--check` print. */\nexport function formatPlan(p: HostingPlan): string {\n const glyph = (s: StepPlan) =>\n s.action === 'skip' ? '·' : s.action === 'blocked' ? '?' : s.action === 'update' ? '~' : '+';\n const lines: string[] = [];\n lines.push(`${glyph(p.repo)} repo ${p.repo.detail}`);\n lines.push(`${glyph(p.branches)} branches ${p.branches.detail}`);\n lines.push(`${glyph(p.defaultBranch)} default branch ${p.defaultBranch.detail}`);\n lines.push(`${glyph(p.mainProtection)} main protection ${p.mainProtection.detail}`);\n lines.push(`${glyph(p.prodEnvironment)} production env ${p.prodEnvironment.detail}`);\n lines.push(`${glyph(p.devEnvironment)} dev env ${p.devEnvironment.detail}`);\n lines.push(`${glyph(p.d1)} dev D1 ${p.d1.detail}`);\n lines.push(`${glyph(p.r2)} dev R2 ${p.r2.detail}`);\n for (const [name, s] of Object.entries(p.secrets))\n lines.push(`${glyph(s)} secret ${name}${' '.repeat(Math.max(1, 11 - name.length))}${s.detail}`);\n for (const n of p.notes) lines.push(`! ${n}`);\n return lines.join('\\n');\n}\n\n/** The outcome of one run, for the summary table. */\nexport interface HostingResult {\n step: string;\n status: 'created' | 'updated' | 'present' | 'blocked' | 'failed';\n detail: string;\n}\n\n/** Render the end-of-run summary: one row per step, plus blocked reminders. */\nexport function formatSummary(results: HostingResult[]): string {\n const headers = ['STEP', 'STATUS', 'DETAIL'];\n const rows = results.map((r) => [r.step, r.status, r.detail]);\n const reminders = results\n .filter((r) => r.status === 'blocked')\n .map((r) => ` ! ${r.step}: ${r.detail}`);\n return table(headers, rows, reminders);\n}\n\n/**\n * Plan the domain-consistency check on its own — it has no executor, only an\n * observation (`wrangler.jsonc` + the config), and it is never applied by this\n * command. See `SETUP.html`'s \"Domain strategy\" stage: the actual cutover is a\n * human, deliberate action.\n */\nexport function planDomainConsistency(state: DomainState): StepPlan {\n const cutover = Boolean(state.customDomain);\n if (cutover) {\n if (!state.canonical || state.canonical !== `https://${state.customDomain}`)\n return {\n action: 'blocked',\n detail: `wrangler.jsonc names a custom domain (${state.customDomain}) but site.domains.canonical doesn't match — a visitor sees the new site while <link rel=canonical> still points at the old one`,\n };\n if (state.robots?.includes('noindex'))\n return {\n action: 'blocked',\n detail:\n 'custom domain is live but robots is still \"noindex,nofollow\" — flip it back after cutover',\n };\n return { action: 'skip', detail: 'custom domain configured and consistent' };\n }\n if (!state.robots?.includes('noindex'))\n return {\n action: 'blocked',\n detail:\n 'still on *.workers.dev but robots does not say \"noindex,nofollow\" — a workers.dev URL is indexable, and this is what stops search engines treating it as a duplicate of the real site',\n };\n return { action: 'skip', detail: 'workers.dev phase, correctly noindexed' };\n}\n","/**\n * Cloudflare resources — dev D1 + R2, plus the account id. Executes a plan and\n * decides nothing; every observation and mutation is a structured result, never a\n * throw, so the CLI decides what a failure means.\n *\n * Shells out to `wrangler` and reads `gh`'s account-id output — there is no REST\n * alternative that doesn't also need the same credential wrangler already holds\n * from `wrangler login`. Same class of executor as `@getvitops/utils/media`\n * shelling to `ffmpeg`: an external tool, not an npm dependency, with `execImpl`\n * injected so the parsing is testable without a real Cloudflare account.\n */\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nconst exec = promisify(execFile);\n\nexport interface ExecResult {\n ok: boolean;\n stdout: string;\n message?: string | undefined;\n}\n\nexport type ExecImpl = (cmd: string, args: string[]) => Promise<ExecResult>;\n\n/** The real executor — `execFile` with an argv array, never a shell string. */\nexport const realExec: ExecImpl = async (cmd, args) => {\n try {\n const { stdout } = await exec(cmd, args, { maxBuffer: 16 * 1024 * 1024 });\n return { ok: true, stdout };\n } catch (err) {\n const e = err as { stdout?: string; stderr?: string; message?: string };\n return {\n ok: false,\n stdout: e.stdout ?? '',\n message: e.stderr || e.message || 'command failed',\n };\n }\n};\n\n/** `wrangler whoami` — auth check + the 32-hex account id, from one call. */\nexport async function wranglerWhoami(\n execImpl: ExecImpl = realExec,\n): Promise<{ authed: boolean; accountId?: string }> {\n const res = await execImpl('npx', ['wrangler', 'whoami']);\n if (!res.ok) return { authed: false };\n const accountId = /\\b([0-9a-f]{32})\\b/.exec(res.stdout)?.[1];\n return { authed: true, ...(accountId ? { accountId } : {}) };\n}\n\n/**\n * Find an existing dev D1 database's id by name.\n *\n * `d1 create`'s own stdout carries the id, but its format has drifted across\n * wrangler versions before — a re-list is the one thing guaranteed to still hold\n * the answer, so both the \"does it exist\" check and the post-create fallback go\n * through this.\n */\nexport async function findD1(\n name: string,\n execImpl: ExecImpl = realExec,\n): Promise<string | undefined> {\n const res = await execImpl('npx', ['wrangler', 'd1', 'list', '--json']);\n if (!res.ok) return undefined;\n try {\n const list = JSON.parse(res.stdout) as { name: string; uuid: string }[];\n return list.find((d) => d.name === name)?.uuid;\n } catch {\n return undefined;\n }\n}\n\n/** Create a dev D1 database, then resolve its id via `findD1` (see its docblock). */\nexport async function createD1(\n name: string,\n execImpl: ExecImpl = realExec,\n): Promise<{ ok: boolean; id?: string | undefined; message?: string | undefined }> {\n const res = await execImpl('npx', ['wrangler', 'd1', 'create', name]);\n if (!res.ok) return { ok: false, message: res.message };\n const inline = /\"database_id\":\\s*\"([0-9a-f-]{36})\"/.exec(res.stdout)?.[1];\n const id = inline ?? (await findD1(name, execImpl));\n if (!id) return { ok: false, message: 'created, but could not determine the database id' };\n return { ok: true, id };\n}\n\nexport async function findR2Bucket(name: string, execImpl: ExecImpl = realExec): Promise<boolean> {\n const res = await execImpl('npx', ['wrangler', 'r2', 'bucket', 'list']);\n if (!res.ok) return false;\n return new RegExp(`name:\\\\s+${name}\\\\s*$`, 'm').test(res.stdout);\n}\n\nexport async function createR2Bucket(\n name: string,\n execImpl: ExecImpl = realExec,\n): Promise<{ ok: boolean; message?: string | undefined }> {\n const res = await execImpl('npx', ['wrangler', 'r2', 'bucket', 'create', name]);\n return res.ok ? { ok: true } : { ok: false, message: res.message };\n}\n","/**\n * GitHub repo, branches, environments and secrets. Executes a plan and decides\n * nothing — see `wrangler.ts`'s docblock for why this shells out (`gh` carries the\n * auth; there is no token-free REST alternative) and for the `execImpl` seam.\n *\n * Absorbs the behaviour of the templates' old `scripts/init-github.mjs`:\n * `dev` is left with **no required reviewers**, deliberately — that is what keeps\n * it the unprotected integration branch. `main` protection and the `production`\n * environment's reviewer policy both 403 on a private repo without GitHub\n * Pro/Team; that is reported as `blocked`, not `failed` — see `plan.ts`.\n */\nimport type { ExecImpl } from './wrangler.ts';\nimport { realExec } from './wrangler.ts';\n\nexport async function ghAuthed(execImpl: ExecImpl = realExec): Promise<boolean> {\n return (await execImpl('gh', ['auth', 'status'])).ok;\n}\n\nexport async function originRemote(execImpl: ExecImpl = realExec): Promise<string | undefined> {\n const res = await execImpl('git', ['remote', 'get-url', 'origin']);\n return res.ok ? res.stdout.trim() : undefined;\n}\n\nexport async function repoNwo(execImpl: ExecImpl = realExec): Promise<string | undefined> {\n const res = await execImpl('gh', ['repo', 'view', '--json', 'nameWithOwner']);\n if (!res.ok) return undefined;\n try {\n return (JSON.parse(res.stdout) as { nameWithOwner: string }).nameWithOwner;\n } catch {\n return undefined;\n }\n}\n\nexport async function branches(execImpl: ExecImpl = realExec): Promise<string[]> {\n const res = await execImpl('git', ['branch', '--list', '--format=%(refname:short)']);\n if (!res.ok) return [];\n return res.stdout\n .split('\\n')\n .map((l) => l.trim())\n .filter(Boolean);\n}\n\nexport async function defaultBranch(\n nwo: string,\n execImpl: ExecImpl = realExec,\n): Promise<string | undefined> {\n const res = await execImpl('gh', ['repo', 'view', nwo, '--json', 'defaultBranchRef']);\n if (!res.ok) return undefined;\n try {\n return (JSON.parse(res.stdout) as { defaultBranchRef?: { name: string } }).defaultBranchRef\n ?.name;\n } catch {\n return undefined;\n }\n}\n\nexport async function mainProtected(nwo: string, execImpl: ExecImpl = realExec): Promise<boolean> {\n return (await execImpl('gh', ['api', `repos/${nwo}/branches/main/protection`])).ok;\n}\n\nexport async function environments(nwo: string, execImpl: ExecImpl = realExec): Promise<string[]> {\n const res = await execImpl('gh', ['api', `repos/${nwo}/environments`]);\n if (!res.ok) return [];\n try {\n return (\n (JSON.parse(res.stdout) as { environments?: { name: string }[] }).environments ?? []\n ).map((e) => e.name);\n } catch {\n return [];\n }\n}\n\nexport async function secretsPresent(\n nwo: string,\n execImpl: ExecImpl = realExec,\n): Promise<string[]> {\n const res = await execImpl('gh', ['api', `repos/${nwo}/actions/secrets`]);\n if (!res.ok) return [];\n try {\n return ((JSON.parse(res.stdout) as { secrets?: { name: string }[] }).secrets ?? []).map(\n (s) => s.name,\n );\n } catch {\n return [];\n }\n}\n\n// ── Mutations ─────────────────────────────────────────────────────────────────\n\nexport async function createRepo(\n name: string,\n visibility: 'public' | 'private',\n execImpl: ExecImpl = realExec,\n): Promise<{ ok: boolean; message?: string | undefined }> {\n const res = await execImpl('gh', [\n 'repo',\n 'create',\n name,\n `--${visibility}`,\n '--source=.',\n '--remote=origin',\n ]);\n return res.ok ? { ok: true } : { ok: false, message: res.message };\n}\n\nexport async function pushBranches(\n execImpl: ExecImpl = realExec,\n): Promise<{ ok: boolean; message?: string | undefined }> {\n const res = await execImpl('git', ['push', '-u', 'origin', 'main', 'dev']);\n return res.ok ? { ok: true } : { ok: false, message: res.message };\n}\n\nexport async function setDefaultBranch(\n nwo: string,\n branch: string,\n execImpl: ExecImpl = realExec,\n): Promise<{ ok: boolean; message?: string | undefined }> {\n const res = await execImpl('gh', ['repo', 'edit', nwo, `--default-branch=${branch}`]);\n return res.ok ? { ok: true } : { ok: false, message: res.message };\n}\n\n/**\n * `PUT .../branches/main/protection`. A 403 on Pro/Team-gated repos is expected.\n *\n * Every field goes through `gh api`'s `-f`/`-F` form flags rather than a JSON body\n * on stdin — `gh api` supports nested keys (`a[b]=c`) and repeated array keys\n * (`a[]=x`) this way, so no request needs a second I/O channel `ExecImpl` would\n * have to carry just for this one call.\n */\nexport async function protectMain(\n nwo: string,\n execImpl: ExecImpl = realExec,\n): Promise<{ ok: boolean; message?: string | undefined }> {\n const res = await execImpl('gh', [\n 'api',\n '--method',\n 'PUT',\n `repos/${nwo}/branches/main/protection`,\n '-F',\n 'required_status_checks[strict]=true',\n '-f',\n 'required_status_checks[contexts][]=ci',\n '-F',\n 'enforce_admins=false',\n '-F',\n 'required_pull_request_reviews[required_approving_review_count]=1',\n '-F',\n 'restrictions=null',\n '-F',\n 'allow_force_pushes=false',\n '-F',\n 'allow_deletions=false',\n ]);\n return res.ok ? { ok: true } : { ok: false, message: res.message };\n}\n\n/** The repo's admin collaborators — numeric user ids, which is what the\n * environment-reviewers API wants (not logins). */\nexport async function adminCollaboratorIds(\n nwo: string,\n execImpl: ExecImpl = realExec,\n): Promise<number[]> {\n const res = await execImpl('gh', ['api', `repos/${nwo}/collaborators?permission=admin`]);\n if (!res.ok) return [];\n try {\n return (JSON.parse(res.stdout) as { id: number }[]).map((c) => c.id);\n } catch {\n return [];\n }\n}\n\n/** `PUT .../environments/production`, reviewers = current admin collaborators. */\nexport async function createProdEnvironment(\n nwo: string,\n reviewerUserIds: number[],\n execImpl: ExecImpl = realExec,\n): Promise<{ ok: boolean; message?: string | undefined }> {\n const res = await execImpl('gh', [\n 'api',\n '--method',\n 'PUT',\n `repos/${nwo}/environments/production`,\n '-f',\n `deployment_branch_policy[protected_branches]=true`,\n '-f',\n `deployment_branch_policy[custom_branch_policies]=false`,\n ...reviewerUserIds.flatMap((id) => [\n '-F',\n `reviewers[][id]=${id}`,\n '-f',\n 'reviewers[][type]=User',\n ]),\n ]);\n return res.ok ? { ok: true } : { ok: false, message: res.message };\n}\n\n/** `PUT .../environments/dev` with no reviewers — deliberately left open. */\nexport async function createDevEnvironment(\n nwo: string,\n execImpl: ExecImpl = realExec,\n): Promise<{ ok: boolean; message?: string | undefined }> {\n const res = await execImpl('gh', ['api', '--method', 'PUT', `repos/${nwo}/environments/dev`]);\n return res.ok ? { ok: true } : { ok: false, message: res.message };\n}\n\nexport async function setSecret(\n name: string,\n value: string,\n execImpl: ExecImpl = realExec,\n): Promise<{ ok: boolean; message?: string | undefined }> {\n const res = await execImpl('gh', ['secret', 'set', name, '--body', value]);\n return res.ok ? { ok: true } : { ok: false, message: res.message };\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- //#region src/tracking/identity.ts
1
+ // src/tracking/identity.ts
2
2
  function first(formData, ...keys) {
3
3
  for (const k of keys) {
4
4
  const v = formData[k]?.trim();
@@ -30,6 +30,5 @@ function identifySubmitter(formData) {
30
30
  };
31
31
  }
32
32
 
33
- //#endregion
34
33
  export { identifySubmitter as t };
35
- //# sourceMappingURL=identity-BoRddTx3.mjs.map
34
+ //# sourceMappingURL=identity-C4yInICW.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"identity-BoRddTx3.mjs","names":[],"sources":["../src/tracking/identity.ts"],"sourcesContent":["/**\n * Pulling a submitter's identity out of a form's fields.\n *\n * Extracted from `notify/render.ts`'s `submitterName()`, which needed only the\n * display name for a subject line. `crm/map.ts` needs the whole set — first\n * name, last name, email, phone, company — to fill a CRM record, and a form's\n * field names vary the same way `submitterName()` already accounted for\n * (`name` vs `first_name`/`last_name` vs neither). Two independent cascades\n * over the same handful of aliases is exactly the failure `render.ts`'s own\n * comment records: a downstream site had five of six forms carrying\n * `first_name`/`last_name`, and a cascade that only checked `name` read every\n * one of them as \"Unknown\". Keep this the one place that knows the aliases;\n * `submitterName()` and `crm/map.ts` both delegate to it.\n */\n\nexport interface SubmitterIdentity {\n firstName?: string | undefined;\n lastName?: string | undefined;\n /** The best available display name — a submitted full name, first + last\n * joined, or the email address as a last resort. Never empty. */\n fullName: string;\n email?: string | undefined;\n phone?: string | undefined;\n company?: string | undefined;\n}\n\nfunction first(formData: Record<string, string>, ...keys: string[]): string | undefined {\n for (const k of keys) {\n const v = formData[k]?.trim();\n if (v) return v;\n }\n return undefined;\n}\n\nexport function identifySubmitter(formData: Record<string, string>): SubmitterIdentity {\n const fullNameField = first(formData, 'name', 'full_name', 'fullName', 'fullname', 'your-name');\n const firstName = first(formData, 'first_name', 'firstName', 'firstname', 'given_name');\n const lastName = first(formData, 'last_name', 'lastName', 'lastname', 'family_name', 'surname');\n const email = first(formData, 'email', 'email_address', 'emailAddress');\n const phone = first(formData, 'phone', 'phone_number', 'phoneNumber', 'tel');\n const company = first(formData, 'company', 'company_name', 'organization', 'business');\n\n let derivedFirst = firstName;\n let derivedLast = lastName;\n if (!derivedFirst && !derivedLast && fullNameField) {\n const parts = fullNameField.split(/\\s+/);\n derivedFirst = parts[0];\n derivedLast = parts.slice(1).join(' ') || undefined;\n }\n\n const joined = [firstName, lastName].filter(Boolean).join(' ');\n const fullName = fullNameField || joined || email || 'Unknown';\n\n return {\n firstName: derivedFirst,\n lastName: derivedLast,\n fullName,\n email,\n phone,\n company,\n };\n}\n"],"mappings":";AA0BA,SAAS,MAAM,UAAkC,GAAG,MAAoC;CACtF,KAAK,MAAM,KAAK,MAAM;EACpB,MAAM,IAAI,SAAS,EAAE,EAAE,KAAK;EAC5B,IAAI,GAAG,OAAO;CAChB;AAEF;AAEA,SAAgB,kBAAkB,UAAqD;CACrF,MAAM,gBAAgB,MAAM,UAAU,QAAQ,aAAa,YAAY,YAAY,WAAW;CAC9F,MAAM,YAAY,MAAM,UAAU,cAAc,aAAa,aAAa,YAAY;CACtF,MAAM,WAAW,MAAM,UAAU,aAAa,YAAY,YAAY,eAAe,SAAS;CAC9F,MAAM,QAAQ,MAAM,UAAU,SAAS,iBAAiB,cAAc;CACtE,MAAM,QAAQ,MAAM,UAAU,SAAS,gBAAgB,eAAe,KAAK;CAC3E,MAAM,UAAU,MAAM,UAAU,WAAW,gBAAgB,gBAAgB,UAAU;CAErF,IAAI,eAAe;CACnB,IAAI,cAAc;CAClB,IAAI,CAAC,gBAAgB,CAAC,eAAe,eAAe;EAClD,MAAM,QAAQ,cAAc,MAAM,KAAK;EACvC,eAAe,MAAM;EACrB,cAAc,MAAM,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,KAAK;CAC5C;CAEA,MAAM,SAAS,CAAC,WAAW,QAAQ,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;CAG7D,OAAO;EACL,WAAW;EACX,UAAU;EACV,UALe,iBAAiB,UAAU,SAAS;EAMnD;EACA;EACA;CACF;AACF"}
1
+ {"version":3,"file":"identity-C4yInICW.mjs","names":[],"sources":["../src/tracking/identity.ts"],"sourcesContent":["/**\n * Pulling a submitter's identity out of a form's fields.\n *\n * Extracted from `notify/render.ts`'s `submitterName()`, which needed only the\n * display name for a subject line. `crm/map.ts` needs the whole set — first\n * name, last name, email, phone, company — to fill a CRM record, and a form's\n * field names vary the same way `submitterName()` already accounted for\n * (`name` vs `first_name`/`last_name` vs neither). Two independent cascades\n * over the same handful of aliases is exactly the failure `render.ts`'s own\n * comment records: a downstream site had five of six forms carrying\n * `first_name`/`last_name`, and a cascade that only checked `name` read every\n * one of them as \"Unknown\". Keep this the one place that knows the aliases;\n * `submitterName()` and `crm/map.ts` both delegate to it.\n */\n\nexport interface SubmitterIdentity {\n firstName?: string | undefined;\n lastName?: string | undefined;\n /** The best available display name — a submitted full name, first + last\n * joined, or the email address as a last resort. Never empty. */\n fullName: string;\n email?: string | undefined;\n phone?: string | undefined;\n company?: string | undefined;\n}\n\nfunction first(formData: Record<string, string>, ...keys: string[]): string | undefined {\n for (const k of keys) {\n const v = formData[k]?.trim();\n if (v) return v;\n }\n return undefined;\n}\n\nexport function identifySubmitter(formData: Record<string, string>): SubmitterIdentity {\n const fullNameField = first(formData, 'name', 'full_name', 'fullName', 'fullname', 'your-name');\n const firstName = first(formData, 'first_name', 'firstName', 'firstname', 'given_name');\n const lastName = first(formData, 'last_name', 'lastName', 'lastname', 'family_name', 'surname');\n const email = first(formData, 'email', 'email_address', 'emailAddress');\n const phone = first(formData, 'phone', 'phone_number', 'phoneNumber', 'tel');\n const company = first(formData, 'company', 'company_name', 'organization', 'business');\n\n let derivedFirst = firstName;\n let derivedLast = lastName;\n if (!derivedFirst && !derivedLast && fullNameField) {\n const parts = fullNameField.split(/\\s+/);\n derivedFirst = parts[0];\n derivedLast = parts.slice(1).join(' ') || undefined;\n }\n\n const joined = [firstName, lastName].filter(Boolean).join(' ');\n const fullName = fullNameField || joined || email || 'Unknown';\n\n return {\n firstName: derivedFirst,\n lastName: derivedLast,\n fullName,\n email,\n phone,\n company,\n };\n}\n"],"mappings":";AA0BA,SAAS,MAAM,UAAkC,GAAG,MAAoC;CACtF,KAAK,MAAM,KAAK,MAAM;EACpB,MAAM,IAAI,SAAS,EAAE,EAAE,KAAK;EAC5B,IAAI,GAAG,OAAO;CAChB;AAEF;AAEA,SAAgB,kBAAkB,UAAqD;CACrF,MAAM,gBAAgB,MAAM,UAAU,QAAQ,aAAa,YAAY,YAAY,WAAW;CAC9F,MAAM,YAAY,MAAM,UAAU,cAAc,aAAa,aAAa,YAAY;CACtF,MAAM,WAAW,MAAM,UAAU,aAAa,YAAY,YAAY,eAAe,SAAS;CAC9F,MAAM,QAAQ,MAAM,UAAU,SAAS,iBAAiB,cAAc;CACtE,MAAM,QAAQ,MAAM,UAAU,SAAS,gBAAgB,eAAe,KAAK;CAC3E,MAAM,UAAU,MAAM,UAAU,WAAW,gBAAgB,gBAAgB,UAAU;CAErF,IAAI,eAAe;CACnB,IAAI,cAAc;CAClB,IAAI,CAAC,gBAAgB,CAAC,eAAe,eAAe;EAClD,MAAM,QAAQ,cAAc,MAAM,KAAK;EACvC,eAAe,MAAM;EACrB,cAAc,MAAM,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,KAAK;CAC5C;CAEA,MAAM,SAAS,CAAC,WAAW,QAAQ,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;CAG7D,OAAO;EACL,WAAW;EACX,UAAU;EACV,UALe,iBAAiB,UAAU,SAAS;EAMnD;EACA;EACA;CACF;AACF"}
package/dist/index.d.mts CHANGED
@@ -1,85 +1,6 @@
1
+ import { Attrs, ContentNode, Elmnt, ImageLink, ImageRef, Link, Localizable, TextLink, isElmnt, isImageLink, isLink, isTextLink, partAttrs, t } from "./content.mjs";
1
2
  import { FaviconLink, FaviconOptions, FaviconTagOptions, WebManifestOptions, faviconLinks, faviconManifest, generateFavicons, writeFaviconManifest } from "./favicon.mjs";
2
3
  import { DefaultTreeAdapterTypes } from "parse5";
3
- //#region src/content.d.ts
4
- /**
5
- * Framework-agnostic content-model types, guards, i18n, and part-attr resolution.
6
- *
7
- * Extracted from @getvitops/astro so the pure-TS helpers live in one place (no
8
- * Astro dependency). The Astro-typed element tag was `astro/types`' `HTMLTag`;
9
- * here it's a plain `string` (any tag name), keeping these fully framework-neutral.
10
- */
11
- /** A translatable value: a plain string, or a locale → string map. */
12
- type Localizable = string | Record<string, string>;
13
- /**
14
- * An HTML attribute bag. `class` may be a string or a class:list-style array
15
- * (falsy entries dropped); everything else is passed through to the element.
16
- */
17
- interface Attrs {
18
- class?: string | (string | false | null | undefined)[];
19
- [attr: string]: unknown;
20
- }
21
- /** An image reference used by image links. */
22
- interface ImageRef {
23
- src: string;
24
- alt?: Localizable;
25
- [key: string]: unknown;
26
- }
27
- /** A link with a text label. */
28
- interface TextLink {
29
- label: Localizable;
30
- href: string;
31
- attrs?: Attrs;
32
- [key: string]: unknown;
33
- }
34
- /** A link whose content is an image. */
35
- interface ImageLink {
36
- image: ImageRef;
37
- href: string;
38
- attrs?: Attrs;
39
- [key: string]: unknown;
40
- }
41
- /** Any link — text or image. */
42
- type Link = TextLink | ImageLink;
43
- /**
44
- * A generic content element wrapper (discriminated by `type: 'element'`),
45
- * carrying a payload `T` rendered as `Tag` (any HTML tag name).
46
- */
47
- interface Elmnt<T = unknown, Tag extends string = 'div'> {
48
- type: 'element';
49
- tag?: Tag;
50
- attrs?: Attrs;
51
- content?: T;
52
- [key: string]: unknown;
53
- }
54
- /**
55
- * A renderable node tree — a text string (Localizable) or an element with
56
- * optional attrs and either `text` shorthand or `children`.
57
- */
58
- type ContentNode = string | {
59
- tag: string;
60
- attrs?: Attrs;
61
- text?: Localizable;
62
- children?: ContentNode[];
63
- };
64
- export declare function isElmnt<T, Tag extends string = 'div'>(item: any): item is Elmnt<T, Tag>;
65
- export declare function isLink(item: any): item is Link;
66
- export declare function isTextLink(item: any): item is TextLink;
67
- export declare function isImageLink(item: any): item is ImageLink;
68
- /**
69
- * Resolve a Localizable value to a string for the given locale. A plain string
70
- * is returned as-is; a locale map returns the best match (locale → default → '').
71
- */
72
- export declare function t(value: Localizable | undefined, locale: string, defaultLocale: string): string;
73
- /**
74
- * Resolve per-part attrs from a presentation-type config. For the `root` part,
75
- * merges legacy `attrs` with `parts.root` (backward compat).
76
- */
77
- export declare function partAttrs(present: {
78
- attrs?: Attrs;
79
- parts?: Record<string, Attrs>;
80
- } | undefined, part: string): Attrs;
81
- //#endregion
82
- //#region src/html.d.ts
83
4
  type ChildNode = DefaultTreeAdapterTypes.ChildNode;
84
5
  export declare function parseRenderedSlots(html: string): ChildNode[];
85
6
  export declare function getAttribute(node: ChildNode, attrName: string): string | null;
@@ -100,8 +21,6 @@ interface El<Tag extends string = string> {
100
21
  export declare function toHtml<Tag extends string>(content: string | El<Tag>): string;
101
22
  /** Render an array of `ContentNode`s to an HTML string. */
102
23
  export declare function nodesToHtml(nodes: ContentNode[], resolveText?: (value: Localizable) => string): string;
103
- //#endregion
104
- //#region src/icons.d.ts
105
24
  /**
106
25
  * Semantic icon name mapping across icon sets.
107
26
  *
@@ -620,8 +539,6 @@ export declare function generateIconInclude(iconsConfig?: {
620
539
  semantic?: string[];
621
540
  [prefix: string]: string | string[] | undefined;
622
541
  }): Record<string, string[]>;
623
- //#endregion
624
- //#region src/scan.d.ts
625
542
  /** A file the scanner found, with its contents already read. */
626
543
  interface SourceFile {
627
544
  path: string;
@@ -647,8 +564,6 @@ export declare const SCAN_SKIP: ReadonlySet<string>;
647
564
  * not templates, and `.astro`/`.git` would otherwise be walked twice over.
648
565
  */
649
566
  export declare function scanFiles(dir: string, opts?: ScanOptions): SourceFile[];
650
- //#endregion
651
- //#region src/icons-scan.d.ts
652
567
  /**
653
568
  * Find the icons a consumer's source actually uses.
654
569
  *
@@ -701,6 +616,5 @@ export declare function collectIconRefs(files: readonly {
701
616
  path: string;
702
617
  text: string;
703
618
  }[], opts?: IconScanOptions): IconScanResult;
704
- //#endregion
705
- export { type Attrs, type BrandIcon, type ChildNode, type ContentNode, type DynamicIconRef, type El, type Elmnt, type FaviconLink, type FaviconOptions, type FaviconTagOptions, type IconRef, type IconRefKind, type IconScanOptions, type IconScanResult, type IconSet, type ImageLink, type ImageRef, type Link, type Localizable, type ScanOptions, type SemanticIcon, type SourceFile, type StyleList, type StyleValue, type TextLink, type WebManifestOptions, faviconLinks, faviconManifest, generateFavicons, writeFaviconManifest };
619
+ export { type Attrs, type BrandIcon, type ChildNode, type ContentNode, type DynamicIconRef, type El, type Elmnt, type FaviconLink, type FaviconOptions, type FaviconTagOptions, type IconRef, type IconRefKind, type IconScanOptions, type IconScanResult, type IconSet, type ImageLink, type ImageRef, type Link, type Localizable, type ScanOptions, type SemanticIcon, type SourceFile, type StyleList, type StyleValue, type TextLink, type WebManifestOptions, faviconLinks, faviconManifest, generateFavicons, isElmnt, isImageLink, isLink, isTextLink, partAttrs, t, writeFaviconManifest };
706
620
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/content.ts","../src/html.ts","../src/icons.ts","../src/scan.ts","../src/icons-scan.ts"],"mappings":";;;;;;;;;;;KAWY,uBAAuB;;;;;UAMlB;EACf;GACC;;;UAIc;EACf;EACA,MAAM;GACL;;;UAIc;EACf,OAAO;EACP;EACA,QAAQ;GACP;;;UAIc;EACf,OAAO;EACP;EACA,QAAQ;GACP;;;KAIS,OAAO,WAAW;;;;;UAMb,MAAM,aAAa;EAClC;EACA,MAAM;EACN,QAAQ;EACR,UAAU;GACT;;;;;;KAOS;EAGN;EACA,QAAQ;EACR,OAAO;EACP,WAAW;;wBAKD,QAAQ,GAAG,4BAA4B,YAAY,QAAQ,MAAM,GAAG;wBAIpE,OAAO,YAAY,QAAQ;wBAI3B,WAAW,YAAY,QAAQ;wBAI/B,YAAY,YAAY,QAAQ;;;;;wBAUhC,EAAE,OAAO,yBAAyB,gBAAgB;;;;;wBAYlD,UACd;EAAW,QAAQ;EAAO,QAAQ,eAAe;eACjD,eACC;;;KCxGS,YAAY,wBAAwB;wBAEhC,mBAAmB,eAAe;wBAIlC,aAAa,MAAM,WAAW;wBAQ9B,UAAU,OAAO,YAAY;;KAOxC,cAAc;KACP,sBAAsB;KACtB,YAAY,aAAa;;wBAerB,aAAa,QAAQ;;UAmBpB,GAAG;EAClB;EACA,KAAK;EACL,aAAa;;;wBA4BC,OAAO,oBAAoB,kBAAkB,GAAG;;wBAkDhD,YACd,OAAO,eACP,eAAc,OAAO;;;;;;;;;;;;;;;;;;;;;;;;qBC9HV,gBAAgB;;;;;;;;;;qBAqBhB,eAAe;qBAIf;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAgiBD,uBAAuB;KACvB,6BAA6B;KAC7B,0BAA0B;;;;;;;;;;;;wBAatB,YAAY,cAAc,gBAAgB;EAAS;;;;;;;;;wBAiCnD,iBAAiB,cAAc;;;;;;;;;;;;;;wBAwB/B,oBAAoB;EAClC;EACA;EACA;EACA;GACC;IACC;;;;UC5oBa;EACf;EACA;;UAGe;;EAEf,OAAO;;EAEP,WAAW;;;;;;qBAOA,UAAU;;qBAgBV,WAAW;;;;;;;wBAeR,UAAU,aAAa,OAAO,cAAc;;;;;;;;;;;;;;;;;;;KC/ChD;;UAGK;EACf;EACA,MAAM;EACN;EACA;EACA;;;UAIe;EACf;EACA;EACA;EACA;;UAGe;EACf,MAAM;EACN,SAAS;;EAET;;UAGe;;EAEf;;;wBAmEc,gBACd,aACA,eACA,OAAO;EACJ,MAAM;EAAW,SAAS;;;wBA2Cf,gBACd;EAAkB;EAAc;KAChC,OAAO,kBACN"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/html.ts","../src/icons.ts","../src/scan.ts","../src/icons-scan.ts"],"mappings":";;;KASY,YAAY,wBAAwB;wBAEhC,mBAAmB,eAAe;wBAIlC,aAAa,MAAM,WAAW;wBAQ9B,UAAU,OAAO,YAAY;;KAOxC,cAAc;KACP,sBAAsB;KACtB,YAAY,aAAa;;wBAerB,aAAa,QAAQ;;UAmBpB,GAAG;EAClB;EACA,KAAK;EACL,aAAa;;;wBAqDC,OAAO,oBAAoB,kBAAkB,GAAG;;wBAoDhD,YACd,OAAO,eACP,eAAc,OAAO;;;;;;;;;;;;;;;;;;;;;;qBC1JV,gBAAgB;;;;;;;;;;qBAqBhB,eAAe;qBAIf;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAgiBD,uBAAuB;KACvB,6BAA6B;KAC7B,0BAA0B;;;;;;;;;;;;wBAatB,YAAY,cAAc,gBAAgB;EAAS;;;;;;;;;wBAiCnD,iBAAiB,cAAc;;;;;;;;;;;;;;wBAwB/B,oBAAoB;EAClC;EACA;EACA;EACA;GACC;IACC;;UC5oBa;EACf;EACA;;UAGe;;EAEf,OAAO;;EAEP,WAAW;;;;;;qBAOA,UAAU;;qBAgBV,WAAW;;;;;;;wBAeR,UAAU,aAAa,OAAO,cAAc;;;;;;;;;;;;;;;;;KC/ChD;;UAGK;EACf;EACA,MAAM;EACN;EACA;EACA;;;UAIe;EACf;EACA;EACA;EACA;;UAGe;EACf,MAAM;EACN,SAAS;;EAET;;UAGe;;EAEf;;;wBAmEc,gBACd,aACA,eACA,OAAO;EACJ,MAAM;EAAW,SAAS;;;wBA2Cf,gBACd;EAAkB;EAAc;KAChC,OAAO,kBACN"}
package/dist/index.mjs CHANGED
@@ -1,53 +1,11 @@
1
1
  import { faviconLinks, faviconManifest, generateFavicons, writeFaviconManifest } from "./favicon.mjs";
2
+ import { isElmnt, isImageLink, isLink, isTextLink, partAttrs, t } from "./content.mjs";
3
+ import { safeHref } from "./url/index.mjs";
2
4
  import { readFileSync, readdirSync } from "node:fs";
3
5
  import { extname, join } from "node:path";
4
6
  import { parseFragment, serialize as serialize$1 } from "parse5";
5
7
 
6
- //#region src/content.ts
7
- function isElmnt(item) {
8
- return item && item.type === "element";
9
- }
10
- function isLink(item) {
11
- return item && ("label" in item || "image" in item) && "href" in item;
12
- }
13
- function isTextLink(item) {
14
- return item && "label" in item && "href" in item;
15
- }
16
- function isImageLink(item) {
17
- return item && "image" in item && "href" in item;
18
- }
19
- /**
20
- * Resolve a Localizable value to a string for the given locale. A plain string
21
- * is returned as-is; a locale map returns the best match (locale → default → '').
22
- */
23
- function t(value, locale, defaultLocale) {
24
- if (value == null) return "";
25
- if (typeof value === "string") return value;
26
- return value[locale] ?? value[defaultLocale] ?? "";
27
- }
28
- /**
29
- * Resolve per-part attrs from a presentation-type config. For the `root` part,
30
- * merges legacy `attrs` with `parts.root` (backward compat).
31
- */
32
- function partAttrs(present, part) {
33
- const fromParts = present?.parts?.[part];
34
- if (part === "root") {
35
- const legacy = present?.attrs;
36
- if (!legacy && !fromParts) return {};
37
- const { class: lc, ...lr } = legacy ?? {};
38
- const { class: pc, ...pr } = fromParts ?? {};
39
- const classes = [lc, pc].flat().filter((c) => typeof c === "string" && c !== "");
40
- return {
41
- ...classes.length ? { class: classes } : {},
42
- ...lr,
43
- ...pr
44
- };
45
- }
46
- return fromParts ?? {};
47
- }
48
-
49
- //#endregion
50
- //#region src/html.ts
8
+ // src/html.ts
51
9
  /**
52
10
  * Framework-agnostic HTML/markup helpers (parse5-based). Extracted from
53
11
  * @getvitops/astro — no Astro runtime dependency; the Astro-typed `HTMLTag` /
@@ -94,10 +52,38 @@ function styleList(...styles) {
94
52
  function isEl(value) {
95
53
  return typeof value === "object" && value !== null && "tag" in value && "text" in value;
96
54
  }
55
+ const TAG_NAME = /^[a-z][a-z0-9-]*$/i;
56
+ const ATTR_NAME = /^[a-z_:][a-z0-9_:.-]*$/i;
57
+ /** Attributes whose value is a URL the browser will follow or load. */
58
+ const URL_ATTRS = /* @__PURE__ */ new Set([
59
+ "href",
60
+ "src",
61
+ "action",
62
+ "formaction",
63
+ "poster",
64
+ "xlink:href",
65
+ "data"
66
+ ]);
67
+ /**
68
+ * Names are interpolated into markup unescaped, so a hostile one (`x onload=…`, `img src=x
69
+ * onerror=…`) would break out of the tag. They are validated, not escaped — there is no escaping
70
+ * for a name. Callers pass authored data today; this keeps that from becoming load-bearing.
71
+ */
72
+ function assertTag(tag) {
73
+ if (!TAG_NAME.test(tag)) throw new Error(`nodesToHtml: invalid tag name ${JSON.stringify(tag)}`);
74
+ }
97
75
  function attributesToString(attrs) {
98
76
  const parts = [];
99
77
  for (const [key, value] of Object.entries(attrs)) {
100
78
  if (value == null || value === false) continue;
79
+ if (!ATTR_NAME.test(key)) throw new Error(`nodesToHtml: invalid attribute name ${JSON.stringify(key)}`);
80
+ if (/^on/i.test(key)) throw new Error(`nodesToHtml: event-handler attribute "${key}" is not allowed`);
81
+ if (URL_ATTRS.has(key.toLowerCase()) && typeof value === "string") {
82
+ const safe = safeHref(value);
83
+ if (safe === void 0) continue;
84
+ parts.push(`${key}="${safe.replace(/&/g, "&amp;").replace(/"/g, "&quot;")}"`);
85
+ continue;
86
+ }
101
87
  if (value === true) parts.push(key);
102
88
  else if (key === "style" && typeof value === "object") parts.push(`style="${styleList(value)}"`);
103
89
  else {
@@ -113,6 +99,7 @@ function toHtml(content) {
113
99
  const { tag, text, attributes } = content;
114
100
  const attrStr = attributes ? attributesToString(attributes) : "";
115
101
  const t = String(tag);
102
+ assertTag(t);
116
103
  return attrStr ? `<${t} ${attrStr}>${text}</${t}>` : `<${t}>${text}</${t}>`;
117
104
  }
118
105
  const VOID_ELEMENTS = /* @__PURE__ */ new Set([
@@ -141,6 +128,7 @@ function escapeHtml(text) {
141
128
  function nodeToHtml(node, resolveText) {
142
129
  if (typeof node === "string") return escapeHtml(resolveText(node));
143
130
  const { tag, attrs, text, children } = node;
131
+ assertTag(tag);
144
132
  const attrStr = attrs ? attributesToString(attrs) : "";
145
133
  const open = attrStr ? `<${tag} ${attrStr}>` : `<${tag}>`;
146
134
  if (VOID_ELEMENTS.has(tag)) return open;
@@ -154,8 +142,7 @@ function nodesToHtml(nodes, resolveText = defaultResolveText) {
154
142
  return nodes.map((node) => nodeToHtml(node, resolveText)).join("");
155
143
  }
156
144
 
157
- //#endregion
158
- //#region src/icons.ts
145
+ // src/icons.ts
159
146
  /**
160
147
  * Semantic icon name mapping across icon sets.
161
148
  *
@@ -727,8 +714,7 @@ function generateIconInclude(iconsConfig) {
727
714
  return include;
728
715
  }
729
716
 
730
- //#endregion
731
- //#region src/scan.ts
717
+ // src/scan.ts
732
718
  /**
733
719
  * Consumer source discovery.
734
720
  *
@@ -795,8 +781,7 @@ function walk(dir, exts, skip, acc) {
795
781
  }
796
782
  }
797
783
 
798
- //#endregion
799
- //#region src/icons-scan.ts
784
+ // src/icons-scan.ts
800
785
  /**
801
786
  * Props that carry an icon name. `icon` covers the component convention
802
787
  * (`Popover`/`Drawer`/`Details` triggers); the start/end pair covers adornments
@@ -938,6 +923,5 @@ function collectIconRefs(files, opts) {
938
923
  };
939
924
  }
940
925
 
941
- //#endregion
942
926
  export { SCAN_EXT, SCAN_SKIP, WEIGHTED_SETS, collectIconRefs, extractIconRefs, faviconLinks, faviconManifest, generateFavicons, generateIconInclude, getAttribute, iconMap, isElmnt, isImageLink, isLink, isTextLink, nodesToHtml, parseRenderedSlots, partAttrs, prefixToMapKey, resolveBrandIcon, resolveIcon, scanFiles, serialize, styleList, t, toHtml, writeFaviconManifest };
943
927
  //# sourceMappingURL=index.mjs.map