@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.
- package/CHANGELOG.md +399 -23
- package/dist/{adc-D6aw2qgq.mjs → adc-BiW9BzqA.mjs} +2 -3
- package/dist/{adc-D6aw2qgq.mjs.map → adc-BiW9BzqA.mjs.map} +1 -1
- package/dist/{adc-BoEoogyo.d.mts → adc-DvX3-P7a.d.mts} +2 -4
- package/dist/adc-DvX3-P7a.d.mts.map +1 -0
- package/dist/ads/index.d.mts +37 -57
- package/dist/ads/index.d.mts.map +1 -1
- package/dist/ads/index.mjs +21 -10
- package/dist/ads/index.mjs.map +1 -1
- package/dist/audit/index.d.mts +180 -0
- package/dist/audit/index.d.mts.map +1 -0
- package/dist/audit/index.mjs +509 -0
- package/dist/audit/index.mjs.map +1 -0
- package/dist/color/index.d.mts +7 -5
- package/dist/color/index.d.mts.map +1 -1
- package/dist/color/index.mjs +10 -5
- package/dist/color/index.mjs.map +1 -1
- package/dist/content.d.mts +78 -0
- package/dist/content.d.mts.map +1 -0
- package/dist/content.mjs +45 -0
- package/dist/content.mjs.map +1 -0
- package/dist/crm/index.d.mts +5 -87
- package/dist/crm/index.d.mts.map +1 -1
- package/dist/crm/index.mjs +10 -19
- package/dist/crm/index.mjs.map +1 -1
- package/dist/email/index.d.mts +1 -11
- package/dist/email/index.d.mts.map +1 -1
- package/dist/email/index.mjs +6 -10
- package/dist/email/index.mjs.map +1 -1
- package/dist/favicon.d.mts +0 -2
- package/dist/favicon.d.mts.map +1 -1
- package/dist/favicon.mjs +1 -2
- package/dist/git/index.d.mts +0 -2
- package/dist/git/index.d.mts.map +1 -1
- package/dist/git/index.mjs +1 -2
- package/dist/hosting/index.d.mts +0 -8
- package/dist/hosting/index.d.mts.map +1 -1
- package/dist/hosting/index.mjs +4 -7
- package/dist/hosting/index.mjs.map +1 -1
- package/dist/{identity-BoRddTx3.mjs → identity-C4yInICW.mjs} +2 -3
- package/dist/{identity-BoRddTx3.mjs.map → identity-C4yInICW.mjs.map} +1 -1
- package/dist/index.d.mts +2 -88
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +36 -52
- package/dist/index.mjs.map +1 -1
- package/dist/indexing/index.d.mts +3 -42
- package/dist/indexing/index.d.mts.map +1 -1
- package/dist/indexing/index.mjs +8 -13
- package/dist/indexing/index.mjs.map +1 -1
- package/dist/{json-store-30CFCbZZ.mjs → json-store-VJKEaoVX.mjs} +2 -3
- package/dist/{json-store-30CFCbZZ.mjs.map → json-store-VJKEaoVX.mjs.map} +1 -1
- package/dist/media/index.d.mts +0 -12
- package/dist/media/index.d.mts.map +1 -1
- package/dist/media/index.mjs +7 -13
- package/dist/media/index.mjs.map +1 -1
- package/dist/notify/index.d.mts +64 -44
- package/dist/notify/index.d.mts.map +1 -1
- package/dist/notify/index.mjs +138 -11
- package/dist/notify/index.mjs.map +1 -1
- package/dist/onboarding/index.d.mts +26 -6
- package/dist/onboarding/index.d.mts.map +1 -1
- package/dist/onboarding/index.mjs +92 -5
- package/dist/onboarding/index.mjs.map +1 -1
- package/dist/{plan-BLKikoOG.mjs → plan-B9USuc17.mjs} +3 -4
- package/dist/{plan-BLKikoOG.mjs.map → plan-B9USuc17.mjs.map} +1 -1
- package/dist/{plan-Hq8Pb0Uw.d.mts → plan-CH3Wt5ZV.d.mts} +8 -30
- package/dist/plan-CH3Wt5ZV.d.mts.map +1 -0
- package/dist/plan-CddqsmJJ.d.mts +82 -0
- package/dist/plan-CddqsmJJ.d.mts.map +1 -0
- package/dist/plan-DpHv6NO8.mjs +93 -0
- package/dist/plan-DpHv6NO8.mjs.map +1 -0
- package/dist/{plan-format-A18SvJ7C.mjs → plan-format-CIz6Feg2.mjs} +2 -3
- package/dist/{plan-format-A18SvJ7C.mjs.map → plan-format-CIz6Feg2.mjs.map} +1 -1
- package/dist/{platform-0nlOIy8h.mjs → platform-Cb_RtsF1.mjs} +3 -5
- package/dist/{platform-0nlOIy8h.mjs.map → platform-Cb_RtsF1.mjs.map} +1 -1
- package/dist/reviews/index.d.mts +4 -120
- package/dist/reviews/index.d.mts.map +1 -1
- package/dist/reviews/index.mjs +6 -99
- package/dist/reviews/index.mjs.map +1 -1
- package/dist/reviews/select.d.mts +2 -0
- package/dist/reviews/select.mjs +3 -0
- package/dist/schema/index.d.mts +0 -12
- package/dist/schema/index.d.mts.map +1 -1
- package/dist/schema/index.mjs +6 -12
- package/dist/schema/index.mjs.map +1 -1
- package/dist/security/index.d.mts +0 -2
- package/dist/security/index.d.mts.map +1 -1
- package/dist/security/index.mjs +1 -2
- package/dist/{token-DmmoQ7um.d.mts → token-CHR1O4w5.d.mts} +3 -3
- package/dist/token-CHR1O4w5.d.mts.map +1 -0
- package/dist/{token-DkKC5m23.mjs → token-CJSl9eIS.mjs} +33 -14
- package/dist/token-CJSl9eIS.mjs.map +1 -0
- package/dist/tracking/index.d.mts +1 -9
- package/dist/tracking/index.d.mts.map +1 -1
- package/dist/tracking/index.mjs +3 -4
- package/dist/turnstile/index.d.mts +105 -0
- package/dist/turnstile/index.d.mts.map +1 -0
- package/dist/turnstile/index.mjs +281 -0
- package/dist/turnstile/index.mjs.map +1 -0
- package/dist/{types-7ELci8RQ.d.mts → types-oac2P5or.d.mts} +3 -23
- package/dist/types-oac2P5or.d.mts.map +1 -0
- package/dist/url/index.d.mts +18 -0
- package/dist/url/index.d.mts.map +1 -0
- package/dist/url/index.mjs +34 -0
- package/dist/url/index.mjs.map +1 -0
- package/package.json +43 -2
- package/dist/adc-BoEoogyo.d.mts.map +0 -1
- package/dist/cloudflare-BiK61Oq4.d.mts +0 -27
- package/dist/cloudflare-BiK61Oq4.d.mts.map +0 -1
- package/dist/cloudflare-CD8vwrgj.mjs +0 -92
- package/dist/cloudflare-CD8vwrgj.mjs.map +0 -1
- package/dist/plan-Hq8Pb0Uw.d.mts.map +0 -1
- package/dist/token-DkKC5m23.mjs.map +0 -1
- package/dist/token-DmmoQ7um.d.mts.map +0 -1
- package/dist/types-7ELci8RQ.d.mts.map +0 -1
package/dist/hosting/index.d.mts
CHANGED
|
@@ -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":"
|
|
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"}
|
package/dist/hosting/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { t as table } from "../plan-format-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
34
|
+
//# sourceMappingURL=identity-C4yInICW.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"identity-
|
|
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
|
-
|
|
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
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/
|
|
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
|
-
|
|
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, "&").replace(/"/g, """)}"`);
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|