@reddoorla/maintenance 0.82.0 → 0.83.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/dist/airtable-5ZPU3MT4.js +9 -0
- package/dist/{announce-7QHQGJPV.js → announce-4H64L2Z6.js} +4 -4
- package/dist/{blux-EATHOHKK.js → blux-ZGDSHI3U.js} +5 -5
- package/dist/{bump-deps-ZREAMK7M.js → bump-deps-2OL6PGTM.js} +5 -5
- package/dist/{chunk-OIDDJLS7.js → chunk-2WN4SNBQ.js} +2 -2
- package/dist/{chunk-HVOOCK6L.js → chunk-4DHQCN5H.js} +2 -1
- package/dist/{chunk-5E4L7WKE.js → chunk-5P2HVNLZ.js} +3 -3
- package/dist/{chunk-ZXRRKKMR.js → chunk-6V7565O2.js} +8 -8
- package/dist/{chunk-YRM6EED7.js → chunk-CLWNAMUX.js} +28 -2
- package/dist/chunk-CLWNAMUX.js.map +1 -0
- package/dist/{chunk-VXXF4Y25.js → chunk-FSRJAF2Q.js} +60 -3
- package/dist/chunk-FSRJAF2Q.js.map +1 -0
- package/dist/{chunk-B5ZUQZFL.js → chunk-ICXV25N7.js} +2 -2
- package/dist/{chunk-UH5YPYS4.js → chunk-JVWJJ36K.js} +5 -4
- package/dist/chunk-JVWJJ36K.js.map +1 -0
- package/dist/{chunk-4PW6ISX7.js → chunk-KV7DIRRO.js} +3 -3
- package/dist/{chunk-ODC4PXZL.js → chunk-MQUOUVQP.js} +2 -2
- package/dist/{chunk-LMCO7CYQ.js → chunk-OQFOMKBP.js} +2 -2
- package/dist/{chunk-FFI7P74K.js → chunk-PBCGXXG6.js} +2 -2
- package/dist/{chunk-T4FQ6TZT.js → chunk-QRDB4NLB.js} +6 -6
- package/dist/{chunk-K5JZ5KLO.js → chunk-R4AB6GCT.js} +3 -3
- package/dist/chunk-R53VL3RI.js +32 -0
- package/dist/chunk-R53VL3RI.js.map +1 -0
- package/dist/{chunk-FX7RAMGC.js → chunk-R6FPFMOS.js} +3 -3
- package/dist/{chunk-MGJ72BA4.js → chunk-RJB3QUSP.js} +2 -2
- package/dist/{chunk-ATJ6ZA2V.js → chunk-SDUJIANJ.js} +2 -2
- package/dist/{chunk-7ADQL5Y3.js → chunk-THEFTZ7F.js} +2 -2
- package/dist/{chunk-NT4WIP7I.js → chunk-UYCAF77E.js} +2 -2
- package/dist/chunk-VNB5XBQ4.js +699 -0
- package/dist/chunk-VNB5XBQ4.js.map +1 -0
- package/dist/{chunk-YAMRVXOD.js → chunk-VRWUL4TR.js} +2 -2
- package/dist/{chunk-2BHSGLWR.js → chunk-XDCRVYFL.js} +3 -3
- package/dist/chunk-XDCRVYFL.js.map +1 -0
- package/dist/{chunk-4NAOR44V.js → chunk-ZMIPG6TR.js} +2 -2
- package/dist/cli/bin.js +51 -23
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/audit.d.ts +1 -1
- package/dist/cli/commands/audit.js +10 -10
- package/dist/{convert-to-pnpm-UT2R6EOW.js → convert-to-pnpm-KCXOYOHW.js} +5 -5
- package/dist/{digest-CLFUWW62.js → digest-WGEPAWMP.js} +12 -7
- package/dist/digest-WGEPAWMP.js.map +1 -0
- package/dist/{ensure-site-LLPOXLWP.js → ensure-site-HYAIVZY2.js} +2 -2
- package/dist/{forms-notify-target-PN6P3WPW.js → forms-notify-target-HSL4ZMT5.js} +2 -2
- package/dist/{github-signals-HTH7474O.js → github-signals-I7XACMCE.js} +6 -6
- package/dist/{header-image-WMHZFU7B.js → header-image-ADYPTGVY.js} +2 -2
- package/dist/{health-endpoint-4E53ZMLE.js → health-endpoint-JNCUILHO.js} +6 -6
- package/dist/index.d.ts +65 -3
- package/dist/index.js +32 -31
- package/dist/index.js.map +1 -1
- package/dist/{init-3ZJ7Z5NG.js → init-FLGLJCMJ.js} +13 -13
- package/dist/{launch-FRCDOB56.js → launch-AVKH2JVF.js} +10 -10
- package/dist/{lighthouse-airtable-I46QCEIS.js → lighthouse-airtable-CHANWVO6.js} +3 -3
- package/dist/{onboard-45NPDMMW.js → onboard-HF4FTJDE.js} +5 -5
- package/dist/{orchestrate-34JYNIXT.js → orchestrate-5FMNYJEA.js} +3 -3
- package/dist/{preflight-WVD32COX.js → preflight-HTWIX3MN.js} +4 -4
- package/dist/prismic-ci-BH225Q4L.js +363 -0
- package/dist/prismic-ci-BH225Q4L.js.map +1 -0
- package/dist/prismic-models-HXCAM4ZL.js +1020 -0
- package/dist/prismic-models-HXCAM4ZL.js.map +1 -0
- package/dist/{protection-audit-2UR45SOC.js → protection-audit-64PLLBLI.js} +3 -3
- package/dist/recipes/sync-configs.d.ts +1 -1
- package/dist/{renovate-dispatch-LOOCNZTW.js → renovate-dispatch-HBZG2UEU.js} +4 -4
- package/dist/{report-CLF2753O.js → report-LV7NY5W3.js} +20 -13
- package/dist/report-LV7NY5W3.js.map +1 -0
- package/dist/{self-updating-5TJ2NB26.js → self-updating-4Q65OVXY.js} +6 -6
- package/dist/{selftest-BTCZLCTL.js → selftest-K5XZKVTE.js} +7 -7
- package/dist/{smoke-suite-GHKXMKIR.js → smoke-suite-J3LL3JZJ.js} +6 -6
- package/dist/{submissions-KUXLKCSQ.js → submissions-GMBUP4LV.js} +2 -2
- package/dist/{svelte-codemods-YTCTLK7G.js → svelte-codemods-JTMX4BSX.js} +4 -4
- package/dist/{sync-configs-KSSQZU5L.js → sync-configs-5DXSL64Q.js} +4 -4
- package/dist/{types-DnEL3NRn.d.ts → types-OIQZ5hWN.d.ts} +1 -1
- package/dist/{upgrade-UDAPY3SN.js → upgrade-HCR25P25.js} +5 -5
- package/dist/{websites-JXG3WWAU.js → websites-VITQ3WXN.js} +6 -2
- package/dist/{write-audits-to-airtable-7D3LV4KP.js → write-audits-to-airtable-3SOXLKJK.js} +4 -4
- package/package.json +1 -1
- package/dist/airtable-ZDHLKK5K.js +0 -9
- package/dist/chunk-2BHSGLWR.js.map +0 -1
- package/dist/chunk-KRDLHAKK.js +0 -17
- package/dist/chunk-KRDLHAKK.js.map +0 -1
- package/dist/chunk-UH5YPYS4.js.map +0 -1
- package/dist/chunk-VXXF4Y25.js.map +0 -1
- package/dist/chunk-YRM6EED7.js.map +0 -1
- package/dist/digest-CLFUWW62.js.map +0 -1
- package/dist/report-CLF2753O.js.map +0 -1
- /package/dist/{airtable-ZDHLKK5K.js.map → airtable-5ZPU3MT4.js.map} +0 -0
- /package/dist/{announce-7QHQGJPV.js.map → announce-4H64L2Z6.js.map} +0 -0
- /package/dist/{blux-EATHOHKK.js.map → blux-ZGDSHI3U.js.map} +0 -0
- /package/dist/{bump-deps-ZREAMK7M.js.map → bump-deps-2OL6PGTM.js.map} +0 -0
- /package/dist/{chunk-OIDDJLS7.js.map → chunk-2WN4SNBQ.js.map} +0 -0
- /package/dist/{chunk-HVOOCK6L.js.map → chunk-4DHQCN5H.js.map} +0 -0
- /package/dist/{chunk-5E4L7WKE.js.map → chunk-5P2HVNLZ.js.map} +0 -0
- /package/dist/{chunk-ZXRRKKMR.js.map → chunk-6V7565O2.js.map} +0 -0
- /package/dist/{chunk-B5ZUQZFL.js.map → chunk-ICXV25N7.js.map} +0 -0
- /package/dist/{chunk-4PW6ISX7.js.map → chunk-KV7DIRRO.js.map} +0 -0
- /package/dist/{chunk-ODC4PXZL.js.map → chunk-MQUOUVQP.js.map} +0 -0
- /package/dist/{chunk-LMCO7CYQ.js.map → chunk-OQFOMKBP.js.map} +0 -0
- /package/dist/{chunk-FFI7P74K.js.map → chunk-PBCGXXG6.js.map} +0 -0
- /package/dist/{chunk-T4FQ6TZT.js.map → chunk-QRDB4NLB.js.map} +0 -0
- /package/dist/{chunk-K5JZ5KLO.js.map → chunk-R4AB6GCT.js.map} +0 -0
- /package/dist/{chunk-FX7RAMGC.js.map → chunk-R6FPFMOS.js.map} +0 -0
- /package/dist/{chunk-MGJ72BA4.js.map → chunk-RJB3QUSP.js.map} +0 -0
- /package/dist/{chunk-ATJ6ZA2V.js.map → chunk-SDUJIANJ.js.map} +0 -0
- /package/dist/{chunk-7ADQL5Y3.js.map → chunk-THEFTZ7F.js.map} +0 -0
- /package/dist/{chunk-NT4WIP7I.js.map → chunk-UYCAF77E.js.map} +0 -0
- /package/dist/{chunk-YAMRVXOD.js.map → chunk-VRWUL4TR.js.map} +0 -0
- /package/dist/{chunk-4NAOR44V.js.map → chunk-ZMIPG6TR.js.map} +0 -0
- /package/dist/{convert-to-pnpm-UT2R6EOW.js.map → convert-to-pnpm-KCXOYOHW.js.map} +0 -0
- /package/dist/{ensure-site-LLPOXLWP.js.map → ensure-site-HYAIVZY2.js.map} +0 -0
- /package/dist/{forms-notify-target-PN6P3WPW.js.map → forms-notify-target-HSL4ZMT5.js.map} +0 -0
- /package/dist/{github-signals-HTH7474O.js.map → github-signals-I7XACMCE.js.map} +0 -0
- /package/dist/{header-image-WMHZFU7B.js.map → header-image-ADYPTGVY.js.map} +0 -0
- /package/dist/{health-endpoint-4E53ZMLE.js.map → health-endpoint-JNCUILHO.js.map} +0 -0
- /package/dist/{init-3ZJ7Z5NG.js.map → init-FLGLJCMJ.js.map} +0 -0
- /package/dist/{launch-FRCDOB56.js.map → launch-AVKH2JVF.js.map} +0 -0
- /package/dist/{lighthouse-airtable-I46QCEIS.js.map → lighthouse-airtable-CHANWVO6.js.map} +0 -0
- /package/dist/{onboard-45NPDMMW.js.map → onboard-HF4FTJDE.js.map} +0 -0
- /package/dist/{orchestrate-34JYNIXT.js.map → orchestrate-5FMNYJEA.js.map} +0 -0
- /package/dist/{preflight-WVD32COX.js.map → preflight-HTWIX3MN.js.map} +0 -0
- /package/dist/{protection-audit-2UR45SOC.js.map → protection-audit-64PLLBLI.js.map} +0 -0
- /package/dist/{renovate-dispatch-LOOCNZTW.js.map → renovate-dispatch-HBZG2UEU.js.map} +0 -0
- /package/dist/{self-updating-5TJ2NB26.js.map → self-updating-4Q65OVXY.js.map} +0 -0
- /package/dist/{selftest-BTCZLCTL.js.map → selftest-K5XZKVTE.js.map} +0 -0
- /package/dist/{smoke-suite-GHKXMKIR.js.map → smoke-suite-J3LL3JZJ.js.map} +0 -0
- /package/dist/{submissions-KUXLKCSQ.js.map → submissions-GMBUP4LV.js.map} +0 -0
- /package/dist/{svelte-codemods-YTCTLK7G.js.map → svelte-codemods-JTMX4BSX.js.map} +0 -0
- /package/dist/{sync-configs-KSSQZU5L.js.map → sync-configs-5DXSL64Q.js.map} +0 -0
- /package/dist/{upgrade-UDAPY3SN.js.map → upgrade-HCR25P25.js.map} +0 -0
- /package/dist/{websites-JXG3WWAU.js.map → websites-VITQ3WXN.js.map} +0 -0
- /package/dist/{write-audits-to-airtable-7D3LV4KP.js.map → write-audits-to-airtable-3SOXLKJK.js.map} +0 -0
|
@@ -0,0 +1,699 @@
|
|
|
1
|
+
// src/prismic/models/canon.ts
|
|
2
|
+
function canon(v) {
|
|
3
|
+
if (Array.isArray(v)) return v.map(canon);
|
|
4
|
+
if (v !== null && typeof v === "object") {
|
|
5
|
+
return Object.fromEntries(
|
|
6
|
+
Object.entries(v).filter(([k, x]) => x !== null && x !== void 0 && x !== "" && k !== "imageUrl").sort(([a], [b]) => a < b ? -1 : 1).map(([k, x]) => [k, canon(x)])
|
|
7
|
+
);
|
|
8
|
+
}
|
|
9
|
+
return v;
|
|
10
|
+
}
|
|
11
|
+
function sameModel(a, b) {
|
|
12
|
+
return JSON.stringify(canon(a)) === JSON.stringify(canon(b));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// src/prismic/models/diff.ts
|
|
16
|
+
var key = (e) => `${e.kind}:${e.id}`;
|
|
17
|
+
function diffModels(local, remote) {
|
|
18
|
+
const remoteByKey = new Map(remote.map((e) => [key(e), e]));
|
|
19
|
+
const localKeys = new Set(local.map(key));
|
|
20
|
+
const diff = { toCreate: [], toUpdate: [], unchanged: [], remoteOnly: [] };
|
|
21
|
+
for (const l of local) {
|
|
22
|
+
const r = remoteByKey.get(key(l));
|
|
23
|
+
if (!r) diff.toCreate.push(l);
|
|
24
|
+
else if (sameModel(l.model, r.model)) diff.unchanged.push(l);
|
|
25
|
+
else diff.toUpdate.push({ local: l, remote: r });
|
|
26
|
+
}
|
|
27
|
+
for (const r of remote) if (!localKeys.has(key(r))) diff.remoteOnly.push(r);
|
|
28
|
+
return diff;
|
|
29
|
+
}
|
|
30
|
+
var asZone = (v) => v !== null && typeof v === "object" ? v : {};
|
|
31
|
+
var MAX_FIELD_DEPTH = 20;
|
|
32
|
+
var compareMeta = (a, b, excluded, format) => {
|
|
33
|
+
const skip = new Set(excluded);
|
|
34
|
+
const out = [];
|
|
35
|
+
for (const k of Object.keys(a)) {
|
|
36
|
+
if (skip.has(k)) continue;
|
|
37
|
+
if (!Object.hasOwn(b, k)) out.push(format(k, "added"));
|
|
38
|
+
}
|
|
39
|
+
for (const k of Object.keys(b)) {
|
|
40
|
+
if (skip.has(k)) continue;
|
|
41
|
+
if (!Object.hasOwn(a, k)) out.push(format(k, "removed"));
|
|
42
|
+
}
|
|
43
|
+
for (const k of Object.keys(a)) {
|
|
44
|
+
if (skip.has(k) || !Object.hasOwn(b, k)) continue;
|
|
45
|
+
if (!sameModel(a[k], b[k])) out.push(format(k, "changed"));
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
};
|
|
49
|
+
var modelMetaLine = (k, verb) => verb === "added" ? `+ (model) ${k}` : verb === "removed" ? `- (model) ${k} (only in Prismic \u2014 pushing DELETES it)` : `~ (model) ${k}`;
|
|
50
|
+
var variationMetaLine = (id, k, verb) => verb === "added" ? `+ variation ${id}: ${k}` : verb === "removed" ? `- variation ${id}: ${k} (only in Prismic \u2014 pushing DELETES it)` : `~ variation ${id}: ${k}`;
|
|
51
|
+
var MODEL_FIELD_KEYS = ["variations", "json"];
|
|
52
|
+
var VARIATION_FIELD_ZONES = ["primary", "items"];
|
|
53
|
+
var VARIATION_META_EXCLUDE = [...VARIATION_FIELD_ZONES, "imageUrl"];
|
|
54
|
+
var containerChildren = (field) => {
|
|
55
|
+
const f = asZone(field);
|
|
56
|
+
if (f.type === "Group") return asZone(asZone(f.config).fields);
|
|
57
|
+
if (f.type === "Slices") return asZone(asZone(f.config).choices);
|
|
58
|
+
return void 0;
|
|
59
|
+
};
|
|
60
|
+
var withoutChildren = (field, childrenKey) => {
|
|
61
|
+
const f = asZone(field);
|
|
62
|
+
const cfg = asZone(f.config);
|
|
63
|
+
const { [childrenKey]: _children, ...restCfg } = cfg;
|
|
64
|
+
return { ...f, config: restCfg };
|
|
65
|
+
};
|
|
66
|
+
var compareZone = (label, lz, rz, depth) => {
|
|
67
|
+
const a = asZone(lz);
|
|
68
|
+
const b = asZone(rz);
|
|
69
|
+
const out = [];
|
|
70
|
+
for (const k of Object.keys(a)) if (!Object.hasOwn(b, k)) out.push(`+ ${label}.${k}`);
|
|
71
|
+
for (const k of Object.keys(b))
|
|
72
|
+
if (!Object.hasOwn(a, k)) out.push(`- ${label}.${k} (only in Prismic \u2014 pushing DELETES it)`);
|
|
73
|
+
for (const k of Object.keys(a)) {
|
|
74
|
+
if (!Object.hasOwn(b, k) || sameModel(a[k], b[k])) continue;
|
|
75
|
+
const childPath = `${label}.${k}`;
|
|
76
|
+
if (depth < MAX_FIELD_DEPTH) {
|
|
77
|
+
const av = asZone(a[k]);
|
|
78
|
+
const bv = asZone(b[k]);
|
|
79
|
+
if (av.type === bv.type && (av.type === "Group" || av.type === "Slices")) {
|
|
80
|
+
const childrenKey = av.type === "Group" ? "fields" : "choices";
|
|
81
|
+
const nested = compareZone(
|
|
82
|
+
childPath,
|
|
83
|
+
containerChildren(a[k]),
|
|
84
|
+
containerChildren(b[k]),
|
|
85
|
+
depth + 1
|
|
86
|
+
);
|
|
87
|
+
if (nested.length > 0) {
|
|
88
|
+
if (!sameModel(withoutChildren(a[k], childrenKey), withoutChildren(b[k], childrenKey)))
|
|
89
|
+
out.push(`~ ${childPath} (changed)`);
|
|
90
|
+
out.push(...nested);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
out.push(`~ ${childPath} (changed)`);
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
};
|
|
99
|
+
function describeDiff(local, remote) {
|
|
100
|
+
const l = asZone(local);
|
|
101
|
+
const r = asZone(remote);
|
|
102
|
+
const lines = [];
|
|
103
|
+
if (remote !== void 0) lines.push(...compareMeta(l, r, MODEL_FIELD_KEYS, modelMetaLine));
|
|
104
|
+
if (Array.isArray(l.variations) || Array.isArray(r.variations)) {
|
|
105
|
+
const variationEntries = (arr) => Array.isArray(arr) ? arr.map((v, i) => {
|
|
106
|
+
const z = asZone(v);
|
|
107
|
+
const id = typeof z.id === "string" ? z.id : `variations[${i}]`;
|
|
108
|
+
return [id, z];
|
|
109
|
+
}) : [];
|
|
110
|
+
const lv = new Map(variationEntries(l.variations));
|
|
111
|
+
const rv = new Map(variationEntries(r.variations));
|
|
112
|
+
for (const [id, v] of lv) {
|
|
113
|
+
const rr = rv.get(id);
|
|
114
|
+
if (!rr) {
|
|
115
|
+
lines.push(`+ variation ${id} (new)`);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
for (const zone of VARIATION_FIELD_ZONES)
|
|
119
|
+
lines.push(...compareZone(`${id}.${zone}`, v[zone], rr[zone], 0));
|
|
120
|
+
lines.push(
|
|
121
|
+
...compareMeta(v, rr, VARIATION_META_EXCLUDE, (k, verb) => variationMetaLine(id, k, verb))
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
for (const id of rv.keys())
|
|
125
|
+
if (!lv.has(id)) lines.push(`- variation ${id} (only in Prismic \u2014 pushing DELETES it)`);
|
|
126
|
+
}
|
|
127
|
+
if (l.json !== void 0 || r.json !== void 0) {
|
|
128
|
+
const lt = asZone(l.json);
|
|
129
|
+
const rt = asZone(r.json);
|
|
130
|
+
for (const tab of Object.keys(lt)) {
|
|
131
|
+
if (!Object.hasOwn(rt, tab)) {
|
|
132
|
+
lines.push(`+ tab ${tab} (new)`);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
lines.push(...compareZone(tab, lt[tab], rt[tab], 0));
|
|
136
|
+
}
|
|
137
|
+
for (const tab of Object.keys(rt))
|
|
138
|
+
if (!Object.hasOwn(lt, tab))
|
|
139
|
+
lines.push(`- tab ${tab} (only in Prismic \u2014 pushing DELETES it)`);
|
|
140
|
+
}
|
|
141
|
+
if (lines.length === 0 && remote !== void 0 && !sameModel(local, remote))
|
|
142
|
+
lines.push("~ (model) changed (no field-level detail)");
|
|
143
|
+
return lines;
|
|
144
|
+
}
|
|
145
|
+
function withRemoteScreenshots(local, remote) {
|
|
146
|
+
if (!Array.isArray(local.variations)) return local;
|
|
147
|
+
if (!remote) {
|
|
148
|
+
return {
|
|
149
|
+
...local,
|
|
150
|
+
variations: local.variations.map((v) => ({ ...asZone(v), imageUrl: "" }))
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const shots = new Map(
|
|
154
|
+
(Array.isArray(remote.variations) ? remote.variations : []).map((v) => asZone(v)).filter((o) => typeof o.id === "string").map((o) => [o.id, o])
|
|
155
|
+
);
|
|
156
|
+
return {
|
|
157
|
+
...local,
|
|
158
|
+
variations: local.variations.map((v) => {
|
|
159
|
+
const o = asZone(v);
|
|
160
|
+
if (typeof o.id !== "string") return v;
|
|
161
|
+
const remoteVariation = shots.get(o.id);
|
|
162
|
+
if (!remoteVariation || !Object.hasOwn(remoteVariation, "imageUrl")) return v;
|
|
163
|
+
return { ...o, imageUrl: remoteVariation.imageUrl };
|
|
164
|
+
})
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/prismic/models/config.ts
|
|
169
|
+
import { readFile } from "fs/promises";
|
|
170
|
+
import { join } from "path";
|
|
171
|
+
var CONFIG_FILES = ["slicemachine.config.json", "prismic.config.json"];
|
|
172
|
+
var PLACEHOLDER_REPOSITORY_NAMES = [
|
|
173
|
+
"your-prismic-repo-name",
|
|
174
|
+
"reddoor-wireframer"
|
|
175
|
+
];
|
|
176
|
+
async function readPrismicConfig(repoRoot) {
|
|
177
|
+
for (const name of CONFIG_FILES) {
|
|
178
|
+
let raw;
|
|
179
|
+
try {
|
|
180
|
+
raw = await readFile(join(repoRoot, name), "utf-8");
|
|
181
|
+
} catch (e) {
|
|
182
|
+
if (e.code === "ENOENT") continue;
|
|
183
|
+
throw new Error(`${name}: present but unreadable (${e.message})`, { cause: e });
|
|
184
|
+
}
|
|
185
|
+
let parsed;
|
|
186
|
+
try {
|
|
187
|
+
parsed = JSON.parse(raw);
|
|
188
|
+
} catch (e) {
|
|
189
|
+
throw new Error(`${name}: invalid JSON (${e.message})`, { cause: e });
|
|
190
|
+
}
|
|
191
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
192
|
+
const got = parsed === null ? "null" : Array.isArray(parsed) ? "an array" : typeof parsed;
|
|
193
|
+
throw new Error(`${name}: not a JSON object (got ${got})`);
|
|
194
|
+
}
|
|
195
|
+
const cfg = parsed;
|
|
196
|
+
if (typeof cfg.repositoryName !== "string" || cfg.repositoryName.trim() === "") {
|
|
197
|
+
throw new Error(`${name}: repositoryName is missing or not a string`);
|
|
198
|
+
}
|
|
199
|
+
const repositoryName = cfg.repositoryName.trim();
|
|
200
|
+
if (PLACEHOLDER_REPOSITORY_NAMES.includes(repositoryName)) continue;
|
|
201
|
+
if (cfg.libraries !== void 0 && !(Array.isArray(cfg.libraries) && cfg.libraries.every((l) => typeof l === "string"))) {
|
|
202
|
+
const got = Array.isArray(cfg.libraries) ? "an array with a non-string entry" : cfg.libraries === null ? "null" : typeof cfg.libraries;
|
|
203
|
+
throw new Error(`${name}: libraries is present but is not an array of strings (got ${got})`);
|
|
204
|
+
}
|
|
205
|
+
const libraries = cfg.libraries === void 0 ? ["./src/lib/slices"] : cfg.libraries;
|
|
206
|
+
return { repositoryName, libraries };
|
|
207
|
+
}
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// src/prismic/models/token.ts
|
|
212
|
+
function prismicTokenEnvName(repositoryName) {
|
|
213
|
+
const slug = repositoryName.toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
214
|
+
if (slug === "") {
|
|
215
|
+
throw new Error(
|
|
216
|
+
`cannot derive a token env var from repositoryName ${JSON.stringify(repositoryName)}: it has no alphanumeric characters`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
return `PRISMIC_TOKEN_${slug}`;
|
|
220
|
+
}
|
|
221
|
+
function resolvePrismicToken(repositoryName, env, opts) {
|
|
222
|
+
const canonical = prismicTokenEnvName(repositoryName);
|
|
223
|
+
const names = opts.allowGeneric ? [canonical, "PRISMIC_WRITE_TOKEN"] : [canonical];
|
|
224
|
+
for (const name of names) {
|
|
225
|
+
const value = env[name]?.trim();
|
|
226
|
+
if (value) return { token: value, source: name };
|
|
227
|
+
}
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// src/prismic/models/local.ts
|
|
232
|
+
import { lstat, readdir, readFile as readFile2, stat } from "fs/promises";
|
|
233
|
+
import { join as join2, posix, relative, sep } from "path";
|
|
234
|
+
var relPath = (repoRoot, full) => relative(repoRoot, full).split(sep).join(posix.sep);
|
|
235
|
+
async function isProvenAbsent(full) {
|
|
236
|
+
try {
|
|
237
|
+
await lstat(full);
|
|
238
|
+
return false;
|
|
239
|
+
} catch (e) {
|
|
240
|
+
return e.code === "ENOENT";
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
async function readModel(repoRoot, full, kind) {
|
|
244
|
+
const rel = relPath(repoRoot, full);
|
|
245
|
+
let raw;
|
|
246
|
+
try {
|
|
247
|
+
raw = await readFile2(full, "utf-8");
|
|
248
|
+
} catch (e) {
|
|
249
|
+
throw new Error(`${rel}: cannot read model file (${e.message})`, { cause: e });
|
|
250
|
+
}
|
|
251
|
+
let parsed;
|
|
252
|
+
try {
|
|
253
|
+
parsed = JSON.parse(raw);
|
|
254
|
+
} catch (e) {
|
|
255
|
+
throw new Error(`${rel}: invalid JSON (${e.message})`, { cause: e });
|
|
256
|
+
}
|
|
257
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
258
|
+
const got = parsed === null ? "null" : Array.isArray(parsed) ? "an array" : typeof parsed;
|
|
259
|
+
throw new Error(`${rel}: model is not a JSON object (got ${got})`);
|
|
260
|
+
}
|
|
261
|
+
const model = parsed;
|
|
262
|
+
if (typeof model.id !== "string" || model.id === "") {
|
|
263
|
+
throw new Error(`${rel}: model has no string "id"`);
|
|
264
|
+
}
|
|
265
|
+
return { kind, id: model.id, model, path: rel };
|
|
266
|
+
}
|
|
267
|
+
async function subdirs(repoRoot, dir) {
|
|
268
|
+
let entries;
|
|
269
|
+
try {
|
|
270
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
271
|
+
} catch (e) {
|
|
272
|
+
if (await isProvenAbsent(dir)) return [];
|
|
273
|
+
throw new Error(`${relPath(repoRoot, dir)}: cannot list directory (${e.message})`, {
|
|
274
|
+
cause: e
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
const names = [];
|
|
278
|
+
for (const d of entries) {
|
|
279
|
+
if (d.isDirectory()) {
|
|
280
|
+
names.push(d.name);
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (!d.isSymbolicLink()) continue;
|
|
284
|
+
const full = join2(dir, d.name);
|
|
285
|
+
let target;
|
|
286
|
+
try {
|
|
287
|
+
target = await stat(full);
|
|
288
|
+
} catch (e) {
|
|
289
|
+
throw new Error(
|
|
290
|
+
`${relPath(repoRoot, full)}: symlink cannot be resolved (${e.message}). A link we cannot follow may be a model directory \u2014 refusing to guess.`,
|
|
291
|
+
{ cause: e }
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
if (target.isDirectory()) names.push(d.name);
|
|
295
|
+
}
|
|
296
|
+
return names.sort();
|
|
297
|
+
}
|
|
298
|
+
async function localModels(repoRoot, libraries) {
|
|
299
|
+
const out = [];
|
|
300
|
+
const ctRoot = join2(repoRoot, "customtypes");
|
|
301
|
+
for (const d of await subdirs(repoRoot, ctRoot)) {
|
|
302
|
+
const full = join2(ctRoot, d, "index.json");
|
|
303
|
+
if (await isProvenAbsent(full)) continue;
|
|
304
|
+
out.push(await readModel(repoRoot, full, "customtype"));
|
|
305
|
+
}
|
|
306
|
+
for (const libRoot of new Set(libraries.map((lib) => join2(repoRoot, lib)))) {
|
|
307
|
+
for (const d of await subdirs(repoRoot, libRoot)) {
|
|
308
|
+
const full = join2(libRoot, d, "model.json");
|
|
309
|
+
if (await isProvenAbsent(full)) continue;
|
|
310
|
+
out.push(await readModel(repoRoot, full, "slice"));
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
assertNoDuplicateIds(out);
|
|
314
|
+
assertVariationsHaveIds(out);
|
|
315
|
+
return out;
|
|
316
|
+
}
|
|
317
|
+
function assertNoDuplicateIds(entries) {
|
|
318
|
+
const seen = /* @__PURE__ */ new Map();
|
|
319
|
+
for (const e of entries) {
|
|
320
|
+
const k = `${e.kind}:${e.id}`;
|
|
321
|
+
const first = seen.get(k);
|
|
322
|
+
if (first !== void 0) {
|
|
323
|
+
throw new Error(
|
|
324
|
+
`duplicate ${e.kind} id "${e.id}": declared by both ${first} and ${e.path}. Two files cannot own one model \u2014 delete or rename one.`
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
seen.set(k, e.path);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
function assertVariationsHaveIds(entries) {
|
|
331
|
+
for (const e of entries) {
|
|
332
|
+
const variations = e.model.variations;
|
|
333
|
+
if (!Array.isArray(variations)) continue;
|
|
334
|
+
variations.forEach((v, i) => {
|
|
335
|
+
const id = v?.id;
|
|
336
|
+
if (typeof id !== "string" || id === "") {
|
|
337
|
+
throw new Error(
|
|
338
|
+
`${e.path}: variation at index ${i} has no string "id". Prismic rejects such a model, and the diff cannot describe it.`
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// src/prismic/models/remote.ts
|
|
346
|
+
var CUSTOM_TYPES_API = "https://customtypes.prismic.io";
|
|
347
|
+
var COLLECTION = { customtype: "customtypes", slice: "slices" };
|
|
348
|
+
var PRISMIC_TIMEOUT_MS = 15e3;
|
|
349
|
+
var authHeaders = (repo, token) => ({
|
|
350
|
+
repository: repo,
|
|
351
|
+
Authorization: `Bearer ${token}`
|
|
352
|
+
});
|
|
353
|
+
var jsonTypeOf = (v) => v === null ? "null" : Array.isArray(v) ? "an array" : typeof v;
|
|
354
|
+
var errorBody = async (res, limit) => res.text().then(
|
|
355
|
+
(t) => t.slice(0, limit),
|
|
356
|
+
(e) => `<response body unreadable: ${String(e)}>`
|
|
357
|
+
);
|
|
358
|
+
async function request(url, init, repo, fetchImpl, timeoutMs) {
|
|
359
|
+
try {
|
|
360
|
+
return await fetchImpl(url, { ...init, signal: AbortSignal.timeout(timeoutMs) });
|
|
361
|
+
} catch (e) {
|
|
362
|
+
const what = `${init.method ?? "GET"} ${url} [repository: ${repo}]`;
|
|
363
|
+
const detail = e?.name === "TimeoutError" ? `timed out after ${timeoutMs}ms` : `failed: ${e instanceof Error ? e.message : String(e)}`;
|
|
364
|
+
throw new Error(`${what} ${detail}`, { cause: e });
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
async function getCollection(repo, token, kind, fetchImpl, timeoutMs) {
|
|
368
|
+
const url = `${CUSTOM_TYPES_API}/${COLLECTION[kind]}`;
|
|
369
|
+
const res = await request(url, { headers: authHeaders(repo, token) }, repo, fetchImpl, timeoutMs);
|
|
370
|
+
if (!res.ok) {
|
|
371
|
+
throw Object.assign(
|
|
372
|
+
new Error(`GET ${url} [repository: ${repo}] -> ${res.status} ${await errorBody(res, 200)}`),
|
|
373
|
+
{ status: res.status }
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
const where = `GET ${url} [repository: ${repo}]`;
|
|
377
|
+
let raw;
|
|
378
|
+
try {
|
|
379
|
+
raw = await res.text();
|
|
380
|
+
} catch (e) {
|
|
381
|
+
throw new Error(`${where}: ${res.status} but the body could not be read`, { cause: e });
|
|
382
|
+
}
|
|
383
|
+
let body;
|
|
384
|
+
try {
|
|
385
|
+
body = JSON.parse(raw);
|
|
386
|
+
} catch (e) {
|
|
387
|
+
throw new Error(
|
|
388
|
+
`${where}: ${res.status} but the body did not parse as JSON: ${raw.slice(0, 200)}`,
|
|
389
|
+
{ cause: e }
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
if (!Array.isArray(body)) {
|
|
393
|
+
throw new Error(`${where}: expected an array, got ${jsonTypeOf(body)}`);
|
|
394
|
+
}
|
|
395
|
+
return body.map((m, i) => {
|
|
396
|
+
const id = m?.id;
|
|
397
|
+
if (typeof id !== "string" || id === "") {
|
|
398
|
+
throw new Error(`${where}: entry at index ${i} has no string "id"`);
|
|
399
|
+
}
|
|
400
|
+
return { kind, id, model: m };
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
async function remoteModels(repo, token, fetchImpl = fetch, timeoutMs = PRISMIC_TIMEOUT_MS) {
|
|
404
|
+
const customtypes = await getCollection(repo, token, "customtype", fetchImpl, timeoutMs);
|
|
405
|
+
const slices = await getCollection(repo, token, "slice", fetchImpl, timeoutMs);
|
|
406
|
+
return [...customtypes, ...slices];
|
|
407
|
+
}
|
|
408
|
+
async function sendModel(repo, token, entry, action, fetchImpl = fetch, timeoutMs = PRISMIC_TIMEOUT_MS) {
|
|
409
|
+
const bodyId = entry.model.id;
|
|
410
|
+
if (typeof bodyId !== "string" || bodyId === "" || bodyId !== entry.id) {
|
|
411
|
+
throw new Error(
|
|
412
|
+
`refusing to ${action} ${entry.kind} "${entry.id}": the model body's id is ${JSON.stringify(bodyId)}. Prismic writes the body's id, not the reported one, so this would modify the wrong model or none at all.`
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
const url = `${CUSTOM_TYPES_API}/${COLLECTION[entry.kind]}/${action}`;
|
|
416
|
+
const res = await request(
|
|
417
|
+
url,
|
|
418
|
+
{
|
|
419
|
+
method: "POST",
|
|
420
|
+
headers: { ...authHeaders(repo, token), "Content-Type": "application/json" },
|
|
421
|
+
body: JSON.stringify(entry.model)
|
|
422
|
+
},
|
|
423
|
+
repo,
|
|
424
|
+
fetchImpl,
|
|
425
|
+
timeoutMs
|
|
426
|
+
);
|
|
427
|
+
if (!res.ok) {
|
|
428
|
+
throw Object.assign(
|
|
429
|
+
new Error(
|
|
430
|
+
`POST ${url} [${entry.kind} ${entry.id}] -> ${res.status} ${await errorBody(res, 300)}`
|
|
431
|
+
),
|
|
432
|
+
{ status: res.status }
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// src/prismic/models/push.ts
|
|
438
|
+
var statusOf = (e) => {
|
|
439
|
+
try {
|
|
440
|
+
const s = e?.status;
|
|
441
|
+
return typeof s === "number" ? s : void 0;
|
|
442
|
+
} catch {
|
|
443
|
+
return void 0;
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
var messageOf = (e) => {
|
|
447
|
+
try {
|
|
448
|
+
if (e instanceof Error) {
|
|
449
|
+
const m = e.message;
|
|
450
|
+
if (typeof m === "string") return m;
|
|
451
|
+
}
|
|
452
|
+
} catch {
|
|
453
|
+
}
|
|
454
|
+
try {
|
|
455
|
+
return String(e);
|
|
456
|
+
} catch {
|
|
457
|
+
return `<a thrown ${typeof e} that cannot be converted to a string>`;
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
async function pushModels(diff, opts) {
|
|
461
|
+
const work = [
|
|
462
|
+
...diff.toCreate.map((entry) => ({ entry, remote: void 0, action: "insert" })),
|
|
463
|
+
...diff.toUpdate.map(({ local, remote }) => ({
|
|
464
|
+
entry: local,
|
|
465
|
+
remote: remote.model,
|
|
466
|
+
action: "update"
|
|
467
|
+
}))
|
|
468
|
+
];
|
|
469
|
+
work.sort((a, b) => a.entry.kind === b.entry.kind ? 0 : a.entry.kind === "slice" ? -1 : 1);
|
|
470
|
+
const report = {
|
|
471
|
+
mode: opts.apply ? "apply" : "dry",
|
|
472
|
+
sent: [],
|
|
473
|
+
failed: [],
|
|
474
|
+
// Filled BEFORE the loop and identically in both modes, because it is a fact
|
|
475
|
+
// about the COMPARISON rather than about what this run did — and because it
|
|
476
|
+
// is the only thing that keeps an empty report from being ambiguous between
|
|
477
|
+
// "in sync" and "this checkout can no longer see its own slices". See
|
|
478
|
+
// `PushReport.remoteOnlyReported` for the failure it exists to name.
|
|
479
|
+
remoteOnlyReported: diff.remoteOnly.map((e) => ({ kind: e.kind, id: e.id }))
|
|
480
|
+
};
|
|
481
|
+
for (const { entry, remote, action } of work) {
|
|
482
|
+
if (!opts.apply) {
|
|
483
|
+
report.sent.push({ kind: entry.kind, id: entry.id, action });
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
try {
|
|
487
|
+
await opts.send(
|
|
488
|
+
{ ...entry, model: withRemoteScreenshots(entry.model, remote) },
|
|
489
|
+
remote,
|
|
490
|
+
action
|
|
491
|
+
);
|
|
492
|
+
report.sent.push({ kind: entry.kind, id: entry.id, action });
|
|
493
|
+
} catch (e) {
|
|
494
|
+
const status = statusOf(e);
|
|
495
|
+
report.failed.push({
|
|
496
|
+
kind: entry.kind,
|
|
497
|
+
id: entry.id,
|
|
498
|
+
error: messageOf(e),
|
|
499
|
+
// Spread, so the key is ABSENT rather than present-and-undefined when
|
|
500
|
+
// there was no status (a network failure has none). `PushReport` is
|
|
501
|
+
// serialised into PR comments and Airtable; a blank `status` key reads
|
|
502
|
+
// as "there was one and it was empty".
|
|
503
|
+
...status === void 0 ? {} : { status }
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return report;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// src/prismic/models/write.ts
|
|
511
|
+
import { mkdir, readFile as readFile3, realpath, rename, writeFile } from "fs/promises";
|
|
512
|
+
import { dirname, join as join3, posix as posix2, relative as relative2, sep as sep2 } from "path";
|
|
513
|
+
var ID_ALLOWED = /^[A-Za-z0-9_-]+$/;
|
|
514
|
+
var SEGMENT_ALLOWED = /^[A-Za-z0-9_][A-Za-z0-9._-]*$/;
|
|
515
|
+
var pascal = (id) => id.split(/[^A-Za-z0-9]+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
516
|
+
function modelFilePath(entry, library) {
|
|
517
|
+
if (!ID_ALLOWED.test(entry.id)) {
|
|
518
|
+
throw new Error(
|
|
519
|
+
`refusing to derive a file path for ${entry.kind} id ${JSON.stringify(entry.id)}: a model id may contain only letters, digits, "_" and "-". Create the file by hand rather than letting this guess where it goes.`
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
let rel;
|
|
523
|
+
if (entry.kind === "customtype") {
|
|
524
|
+
rel = posix2.join("customtypes", entry.id, "index.json");
|
|
525
|
+
} else {
|
|
526
|
+
const lib = library.replace(/^\.\//, "").split(sep2).join(posix2.sep).trim();
|
|
527
|
+
if (lib === "") {
|
|
528
|
+
throw new Error(
|
|
529
|
+
`refusing to place slice "${entry.id}": no slice library was given. An empty library is not the fleet default \u2014 config.ts treats "libraries: []" as "this site has no slice library" \u2014 so there is nowhere to put this model.`
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
const dir = pascal(entry.id);
|
|
533
|
+
if (dir === "") {
|
|
534
|
+
throw new Error(
|
|
535
|
+
`refusing to place slice ${JSON.stringify(entry.id)}: it has no letters or digits, so it yields no directory name.`
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
rel = posix2.join(lib, dir, "model.json");
|
|
539
|
+
}
|
|
540
|
+
const bad = rel.split(posix2.sep).filter((s) => !SEGMENT_ALLOWED.test(s));
|
|
541
|
+
if (bad.length > 0) {
|
|
542
|
+
throw new Error(
|
|
543
|
+
`refusing to write ${entry.kind} "${entry.id}" to ${JSON.stringify(rel)}: the path segment(s) ${JSON.stringify(bad)} are not a plain relative location inside the repo. Check the "libraries" entry in this site's Prismic config.`
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
return rel;
|
|
547
|
+
}
|
|
548
|
+
async function occupantId(rel, full) {
|
|
549
|
+
let raw;
|
|
550
|
+
try {
|
|
551
|
+
raw = await readFile3(full, "utf-8");
|
|
552
|
+
} catch (e) {
|
|
553
|
+
if (e.code === "ENOENT") return null;
|
|
554
|
+
throw new Error(
|
|
555
|
+
`${rel}: cannot be read, so this pipeline cannot tell whether a model is already there (${e.message}). Refusing to write over what it cannot see.`,
|
|
556
|
+
{ cause: e }
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
let parsed;
|
|
560
|
+
try {
|
|
561
|
+
parsed = JSON.parse(raw);
|
|
562
|
+
} catch (e) {
|
|
563
|
+
throw new Error(
|
|
564
|
+
`${rel}: already exists and is not valid JSON (${e.message}). Refusing to overwrite a file this pipeline cannot identify.`,
|
|
565
|
+
{ cause: e }
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
const id = parsed?.id;
|
|
569
|
+
if (typeof id !== "string" || id === "") {
|
|
570
|
+
throw new Error(
|
|
571
|
+
`${rel}: already exists but declares no string "id", so there is no way to tell whether it is the model being pulled down. Refusing to overwrite it.`
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
return id;
|
|
575
|
+
}
|
|
576
|
+
async function realpathOfNearestExisting(p) {
|
|
577
|
+
let cur = p;
|
|
578
|
+
for (; ; ) {
|
|
579
|
+
try {
|
|
580
|
+
return await realpath(cur);
|
|
581
|
+
} catch (e) {
|
|
582
|
+
if (e.code !== "ENOENT") {
|
|
583
|
+
throw new Error(`${cur}: cannot be resolved (${e.message})`, { cause: e });
|
|
584
|
+
}
|
|
585
|
+
const parent = dirname(cur);
|
|
586
|
+
if (parent === cur) throw new Error(`${p}: no ancestor of this path exists`, { cause: e });
|
|
587
|
+
cur = parent;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
async function assertResolvesInsideRepo(repoRoot, rel, dir) {
|
|
592
|
+
const realRoot = await realpathOfNearestExisting(repoRoot);
|
|
593
|
+
const real = await realpathOfNearestExisting(dir);
|
|
594
|
+
if (real !== realRoot && !real.startsWith(realRoot + sep2)) {
|
|
595
|
+
throw new Error(
|
|
596
|
+
`refusing to write ${rel}: it resolves to ${real}, which is outside this repo (${realRoot}). A symlinked path component redirects the write while every segment of the repo-relative path still looks ordinary \u2014 and every sibling of a repo in this fleet is another live client repo.`
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
async function targetPrettierBin(repoRoot) {
|
|
601
|
+
try {
|
|
602
|
+
return await realpath(join3(repoRoot, "node_modules", ".bin", "prettier"));
|
|
603
|
+
} catch {
|
|
604
|
+
return null;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
var PRETTIER_TIMEOUT_MS = 6e4;
|
|
608
|
+
async function writeModelFile(format, repoRoot, entry, library) {
|
|
609
|
+
const bodyId = entry.model.id;
|
|
610
|
+
if (typeof bodyId !== "string" || bodyId === "" || bodyId !== entry.id) {
|
|
611
|
+
throw new Error(
|
|
612
|
+
`refusing to write ${entry.kind} "${entry.id}": the model body's id is ${JSON.stringify(bodyId)}. The path is derived from one and every reader of the file keys on the other, so this model would never be found again.`
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
const rel = modelFilePath(entry, library);
|
|
616
|
+
const full = join3(repoRoot, rel);
|
|
617
|
+
const dir = dirname(full);
|
|
618
|
+
await assertResolvesInsideRepo(repoRoot, rel, dir);
|
|
619
|
+
const occupant = await occupantId(rel, full);
|
|
620
|
+
if (occupant !== null && occupant !== entry.id) {
|
|
621
|
+
throw new Error(
|
|
622
|
+
`refusing to pull ${entry.kind} "${entry.id}" into ${rel}: that file already holds "${occupant}". A slice's directory name and its model id are independent \u2014 6 of the fleet's 132 slice directories differ (measured 2026-08-13) \u2014 so the directory this derives can belong to a different model. Move or rename the existing model, or create this one by hand.`
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
try {
|
|
626
|
+
await mkdir(dir, { recursive: true });
|
|
627
|
+
} catch (e) {
|
|
628
|
+
throw new Error(
|
|
629
|
+
`${posix2.dirname(rel)}: cannot create the directory for this model (${e.message})`,
|
|
630
|
+
{ cause: e }
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
await assertResolvesInsideRepo(repoRoot, rel, dir);
|
|
634
|
+
const body = JSON.stringify(entry.model, null, 2) + "\n";
|
|
635
|
+
if (occupant === null) {
|
|
636
|
+
try {
|
|
637
|
+
await writeFile(full, body, { encoding: "utf-8", flag: "wx" });
|
|
638
|
+
} catch (e) {
|
|
639
|
+
if (e.code === "EEXIST") {
|
|
640
|
+
throw new Error(
|
|
641
|
+
`${rel}: nothing readable was at this path a moment ago and something is there now \u2014 a dangling symlink, or a concurrent write. Refusing to overwrite it.`,
|
|
642
|
+
{ cause: e }
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
throw e;
|
|
646
|
+
}
|
|
647
|
+
} else {
|
|
648
|
+
const tmp = `${full}.tmp`;
|
|
649
|
+
const tmpRel = relative2(repoRoot, tmp);
|
|
650
|
+
try {
|
|
651
|
+
await writeFile(tmp, body, { encoding: "utf-8", flag: "w" });
|
|
652
|
+
} catch (e) {
|
|
653
|
+
throw new Error(
|
|
654
|
+
`${rel}: could not stage the replacement model (${e.message}). The model already in the repo is UNTOUCHED. A partial ${tmpRel} may be left behind \u2014 the next pull-down of this model overwrites it, and nothing reads it.`,
|
|
655
|
+
{ cause: e }
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
try {
|
|
659
|
+
await rename(tmp, full);
|
|
660
|
+
} catch (e) {
|
|
661
|
+
throw new Error(
|
|
662
|
+
`${rel}: could not replace the model with the staged copy (${e.message}). The model already in the repo is UNTOUCHED and the staged copy is at ${tmpRel} \u2014 the next pull-down of this model overwrites it, and nothing reads it.`,
|
|
663
|
+
{ cause: e }
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
const bin = await targetPrettierBin(repoRoot);
|
|
668
|
+
if (bin === null) return { path: rel, formatted: false };
|
|
669
|
+
try {
|
|
670
|
+
return {
|
|
671
|
+
path: rel,
|
|
672
|
+
formatted: await format(repoRoot, [rel], { bin, timeoutMs: PRETTIER_TIMEOUT_MS })
|
|
673
|
+
};
|
|
674
|
+
} catch {
|
|
675
|
+
return { path: rel, formatted: false };
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// src/util/working-tree.ts
|
|
680
|
+
var NOT_A_WORKING_TREE = /* @__PURE__ */ new Set([".git", ".DS_Store"]);
|
|
681
|
+
function noWorkingTreeFailure(repoRoot, entries, noEffect) {
|
|
682
|
+
if (!entries.every((e) => NOT_A_WORKING_TREE.has(e))) return null;
|
|
683
|
+
return `this checkout has no working tree: ${repoRoot} holds ${entries.length === 0 ? "nothing at all" : `only ${entries.join(", ")}`}. ${noEffect} \u2014 this is NOT "no Prismic in this repo"; nothing at all was established about this repo. A git clone killed part-way leaves exactly this (a .git and no files), and a fleet workdir REUSES any non-empty directory as that site's checkout, so it will never re-clone by itself. Delete ${repoRoot} and re-run.`;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
export {
|
|
687
|
+
diffModels,
|
|
688
|
+
describeDiff,
|
|
689
|
+
readPrismicConfig,
|
|
690
|
+
prismicTokenEnvName,
|
|
691
|
+
resolvePrismicToken,
|
|
692
|
+
localModels,
|
|
693
|
+
remoteModels,
|
|
694
|
+
sendModel,
|
|
695
|
+
pushModels,
|
|
696
|
+
writeModelFile,
|
|
697
|
+
noWorkingTreeFailure
|
|
698
|
+
};
|
|
699
|
+
//# sourceMappingURL=chunk-VNB5XBQ4.js.map
|