@anyday/sanity-workspace-manager 0.7.0 → 0.7.1
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/README.md +7 -7
- package/dist/cli.mjs +73 -40
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ workspace-manager.json linguist-generated=true
|
|
|
65
65
|
| `organizationId` | The Sanity organisation new workspaces are created in |
|
|
66
66
|
| `vercelTeamId` | The client's own Vercel team; left out for Anyday's |
|
|
67
67
|
| `site` | The GitHub repo and branch each new site deploys from |
|
|
68
|
-
| `studioOrigins` | The Studios that open new workspaces
|
|
68
|
+
| `studioOrigins` | The Studios that open new workspaces, incl. a site's `/studio`; picked from the live project on first run |
|
|
69
69
|
| `sections` | Each content type with its label in the Copy picker, or `false` to leave it out |
|
|
70
70
|
| `colorRoles` | Each theme colour with a short description of where it's used, shown to whoever sets up a brand |
|
|
71
71
|
| `themeFiles` | Theme fields with uploaded files (fonts) to copy into new sites; fonts are often licensed per brand |
|
|
@@ -106,12 +106,12 @@ A renamed field reads as one removed and one added, so `generate` asks rather th
|
|
|
106
106
|
|
|
107
107
|
### What it reads
|
|
108
108
|
|
|
109
|
-
| Source | Used for
|
|
110
|
-
| ----------------------------- |
|
|
111
|
-
| git remote and default branch | `site`, the suggested id and name
|
|
112
|
-
| `.env.example`, names only | `siteEnv`, the variables each new site needs (`*_WORKSPACES_API_URL` if the site has one)
|
|
113
|
-
| `sanity.schema.json` | copy sections, singletons, home page, language documents, settings fields, colours and fonts
|
|
114
|
-
| the live project | organisation,
|
|
109
|
+
| Source | Used for |
|
|
110
|
+
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
111
|
+
| git remote and default branch | `site`, the suggested id and name |
|
|
112
|
+
| `.env.example`, names only | `siteEnv`, the variables each new site needs (`*_WORKSPACES_API_URL` if the site has one) |
|
|
113
|
+
| `sanity.schema.json` | copy sections, singletons, home page, language documents, settings fields, colours and fonts |
|
|
114
|
+
| the live project | organisation, Studios and CORS origins to pick from, theme colours and defaults, fonts named in `themeFiles`, starter pages |
|
|
115
115
|
|
|
116
116
|
`sanity.schema.json` is looked for in the repo root, `frontend`, `web`, `site`, `studio`, `apps`
|
|
117
117
|
and each folder in `apps`.
|
package/dist/cli.mjs
CHANGED
|
@@ -1208,9 +1208,65 @@ function configFromManifest(manifest, schema) {
|
|
|
1208
1208
|
};
|
|
1209
1209
|
}
|
|
1210
1210
|
|
|
1211
|
+
// src/generate/starter.ts
|
|
1212
|
+
var StarterError = class extends Error {
|
|
1213
|
+
};
|
|
1214
|
+
function resolveStarter(entries, pages, homeId) {
|
|
1215
|
+
const home = pages.find((page) => page._id === homeId);
|
|
1216
|
+
return entries.map((entry) => {
|
|
1217
|
+
if (entry === "home") {
|
|
1218
|
+
if (!homeId)
|
|
1219
|
+
throw new StarterError("The live settings name no home page: pass its slug instead");
|
|
1220
|
+
return homeId;
|
|
1221
|
+
}
|
|
1222
|
+
if (pages.some((page) => page._id === entry)) return entry;
|
|
1223
|
+
let matches = pages.filter((page) => page.slug === entry);
|
|
1224
|
+
if (matches.length > 1 && home?.language)
|
|
1225
|
+
matches = matches.filter((page) => page.language === home.language);
|
|
1226
|
+
if (matches.length === 1) return matches[0]._id;
|
|
1227
|
+
if (!matches.length) throw new StarterError(`No page with the slug or id "${entry}"`);
|
|
1228
|
+
throw new StarterError(
|
|
1229
|
+
`Several pages have the slug "${entry}": pass one of ${matches.map((page) => page._id).join(", ")}`
|
|
1230
|
+
);
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
function pickable(pages, homeId) {
|
|
1234
|
+
const home = pages.find((page) => page._id === homeId);
|
|
1235
|
+
const shown = home?.language ? pages.filter((page) => page.language === home.language) : pages;
|
|
1236
|
+
return [...shown].sort(
|
|
1237
|
+
(a, b) => a._id === homeId ? -1 : b._id === homeId ? 1 : (a.title ?? "").localeCompare(b.title ?? "")
|
|
1238
|
+
);
|
|
1239
|
+
}
|
|
1240
|
+
function parsePick(answer, count) {
|
|
1241
|
+
const picked = answer.split(/[\s,]+/).filter(Boolean).map((part) => Number(part));
|
|
1242
|
+
const wrong = picked.filter((n) => !Number.isInteger(n) || n < 1 || n > count);
|
|
1243
|
+
if (wrong.length) throw new Error(`Pick numbers from 1 to ${count}`);
|
|
1244
|
+
return [...new Set(picked)].map((n) => n - 1);
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1211
1247
|
// src/generate/resolve.ts
|
|
1212
1248
|
var yes = (answer) => /^y(es)?$/i.test(answer.trim());
|
|
1213
1249
|
var ROLE_NAMES = { siteName: "site name", domain: "domain", logo: "logo", favicon: "favicon" };
|
|
1250
|
+
async function pickStudioOrigins(ask, saved, found) {
|
|
1251
|
+
const candidates = [.../* @__PURE__ */ new Set([...saved, ...found.studios, ...found.cors])];
|
|
1252
|
+
if (!candidates.length) return [];
|
|
1253
|
+
const chosen = saved.length ? saved : found.studios;
|
|
1254
|
+
const label = (origin) => saved.includes(origin) ? "saved" : found.studios.includes(origin) ? "Studio" : "CORS list";
|
|
1255
|
+
const list = candidates.map((origin, index) => ` ${index + 1}. ${origin} \u2190 ${label(origin)}`);
|
|
1256
|
+
const suggestion = candidates.flatMap((origin, index) => chosen.includes(origin) ? [index + 1] : []).join(",");
|
|
1257
|
+
let text = `Addresses that run this project's Studio, e.g. a site's /studio:
|
|
1258
|
+
${list.join("\n")}
|
|
1259
|
+
By number`;
|
|
1260
|
+
for (; ; ) {
|
|
1261
|
+
try {
|
|
1262
|
+
return parsePick(await ask({ text, suggestion }), candidates.length).map(
|
|
1263
|
+
(index) => candidates[index]
|
|
1264
|
+
);
|
|
1265
|
+
} catch (error) {
|
|
1266
|
+
text = `${error.message}. ${text}`;
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1214
1270
|
async function resolveIdentity(options) {
|
|
1215
1271
|
const { saved, flags, repo, ask, lookups, note } = options;
|
|
1216
1272
|
const known = { ...saved, ...flags };
|
|
@@ -1240,9 +1296,11 @@ async function resolveIdentity(options) {
|
|
|
1240
1296
|
};
|
|
1241
1297
|
if (!site.repo) throw new Error("No GitHub remote found: add one, or set site.repo in the config");
|
|
1242
1298
|
let studioOrigins = known.studioOrigins;
|
|
1243
|
-
if (!studioOrigins?.length || options.askAgain) {
|
|
1244
|
-
|
|
1245
|
-
|
|
1299
|
+
if (!flags.studioOrigins && (!studioOrigins?.length || options.askAgain)) {
|
|
1300
|
+
studioOrigins = await pickStudioOrigins(ask, saved.studioOrigins ?? [], {
|
|
1301
|
+
studios: await lookups.studioOriginsOf?.(project).catch(() => []) ?? [],
|
|
1302
|
+
cors: await lookups.corsOriginsOf?.(project).catch(() => []) ?? []
|
|
1303
|
+
});
|
|
1246
1304
|
}
|
|
1247
1305
|
if (!studioOrigins?.length)
|
|
1248
1306
|
note(`No Studio found: add studioOrigins to the config, e.g. ["https://acme.sanity.studio"]`);
|
|
@@ -1501,6 +1559,16 @@ async function studioOriginsOf(projectId, token) {
|
|
|
1501
1559
|
if (studioHost) origins.push(`https://${studioHost}.sanity.studio`);
|
|
1502
1560
|
return [...new Set(origins)];
|
|
1503
1561
|
}
|
|
1562
|
+
async function corsOriginsOf(projectId, token) {
|
|
1563
|
+
const response = await fetch(`https://api.sanity.io/v2021-06-07/projects/${projectId}/cors`, {
|
|
1564
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
1565
|
+
});
|
|
1566
|
+
if (!response.ok) throw new Error(`Couldn't list ${projectId}'s CORS origins: ${response.status}`);
|
|
1567
|
+
const entries = await response.json();
|
|
1568
|
+
return entries.filter(
|
|
1569
|
+
({ origin, allowCredentials }) => allowCredentials && origin.startsWith("https://") && !origin.includes("*") && !/^https:\/\/(localhost|127\.0\.0\.1)(:|$)/.test(origin)
|
|
1570
|
+
).map(({ origin }) => origin.replace(/\/+$/, ""));
|
|
1571
|
+
}
|
|
1504
1572
|
var liveClient = ({ projectId, dataset, token }) => createClient({
|
|
1505
1573
|
projectId,
|
|
1506
1574
|
dataset,
|
|
@@ -1582,42 +1650,6 @@ function readRepo(root) {
|
|
|
1582
1650
|
};
|
|
1583
1651
|
}
|
|
1584
1652
|
|
|
1585
|
-
// src/generate/starter.ts
|
|
1586
|
-
var StarterError = class extends Error {
|
|
1587
|
-
};
|
|
1588
|
-
function resolveStarter(entries, pages, homeId) {
|
|
1589
|
-
const home = pages.find((page) => page._id === homeId);
|
|
1590
|
-
return entries.map((entry) => {
|
|
1591
|
-
if (entry === "home") {
|
|
1592
|
-
if (!homeId)
|
|
1593
|
-
throw new StarterError("The live settings name no home page: pass its slug instead");
|
|
1594
|
-
return homeId;
|
|
1595
|
-
}
|
|
1596
|
-
if (pages.some((page) => page._id === entry)) return entry;
|
|
1597
|
-
let matches = pages.filter((page) => page.slug === entry);
|
|
1598
|
-
if (matches.length > 1 && home?.language)
|
|
1599
|
-
matches = matches.filter((page) => page.language === home.language);
|
|
1600
|
-
if (matches.length === 1) return matches[0]._id;
|
|
1601
|
-
if (!matches.length) throw new StarterError(`No page with the slug or id "${entry}"`);
|
|
1602
|
-
throw new StarterError(
|
|
1603
|
-
`Several pages have the slug "${entry}": pass one of ${matches.map((page) => page._id).join(", ")}`
|
|
1604
|
-
);
|
|
1605
|
-
});
|
|
1606
|
-
}
|
|
1607
|
-
function pickable(pages, homeId) {
|
|
1608
|
-
const home = pages.find((page) => page._id === homeId);
|
|
1609
|
-
const shown = home?.language ? pages.filter((page) => page.language === home.language) : pages;
|
|
1610
|
-
return [...shown].sort(
|
|
1611
|
-
(a, b) => a._id === homeId ? -1 : b._id === homeId ? 1 : (a.title ?? "").localeCompare(b.title ?? "")
|
|
1612
|
-
);
|
|
1613
|
-
}
|
|
1614
|
-
function parsePick(answer, count) {
|
|
1615
|
-
const picked = answer.split(/[\s,]+/).filter(Boolean).map((part) => Number(part));
|
|
1616
|
-
const wrong = picked.filter((n) => !Number.isInteger(n) || n < 1 || n > count);
|
|
1617
|
-
if (wrong.length) throw new Error(`Pick numbers from 1 to ${count}`);
|
|
1618
|
-
return [...new Set(picked)].map((n) => n - 1);
|
|
1619
|
-
}
|
|
1620
|
-
|
|
1621
1653
|
// src/register.ts
|
|
1622
1654
|
import { readFileSync as readFileSync4 } from "node:fs";
|
|
1623
1655
|
import { join as join3 } from "node:path";
|
|
@@ -1765,7 +1797,8 @@ async function generate(argv) {
|
|
|
1765
1797
|
note,
|
|
1766
1798
|
lookups: token ? {
|
|
1767
1799
|
organizationOf: (projectId) => organizationOf(projectId, token),
|
|
1768
|
-
studioOriginsOf: (projectId) => studioOriginsOf(projectId, token)
|
|
1800
|
+
studioOriginsOf: (projectId) => studioOriginsOf(projectId, token),
|
|
1801
|
+
corsOriginsOf: (projectId) => corsOriginsOf(projectId, token)
|
|
1769
1802
|
} : {}
|
|
1770
1803
|
});
|
|
1771
1804
|
const choices = await resolveChoices({ saved, schema, ask, askAgain, note });
|