@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.
Files changed (3) hide show
  1. package/README.md +7 -7
  2. package/dist/cli.mjs +73 -40
  3. 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, Studio, theme colours and defaults, fonts named in `themeFiles`, starter pages |
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
- const found = await lookups.studioOriginsOf?.(project).catch(() => void 0);
1245
- studioOrigins = found?.length ? found : studioOrigins;
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 });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anyday/sanity-workspace-manager",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "Generates and checks a site repo's workspace-manager.json for the Anyday workspace manager",
5
5
  "repository": {
6
6
  "type": "git",