@anyday/sanity-workspace-manager 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +27 -20
  2. package/dist/cli.mjs +64 -13
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -43,15 +43,16 @@ npx @anyday/sanity-workspace-manager register --api https://anyday-workspace-man
43
43
  npx @anyday/sanity-workspace-manager init [dir] [options]
44
44
  ```
45
45
 
46
- `dir` is the site repo, and defaults to the current folder. `init` asks four questions, each with a
46
+ `dir` is the site repo, and defaults to the current folder. `init` asks five questions, each with a
47
47
  suggestion you can accept with Enter:
48
48
 
49
- | Question | Suggested from |
50
- | ------------------------------------------- | ------------------------------------------------------------------ |
51
- | Project type id, short and lowercase | The repo name in the git remote, e.g. `acme-site` |
52
- | Name shown in the app | The same repo name, capitalised |
53
- | Sanity project to copy schemas from | A `projectId: '…'` written in the Studio's config, if there is one |
54
- | Sanity organisation to create workspaces in | Looked up from that project with your login |
49
+ | Question | Suggested from |
50
+ | ------------------------------------------- | ------------------------------------------------------------------------------------- |
51
+ | Project type id, short and lowercase | The repo name in the git remote, e.g. `acme-site` |
52
+ | Name shown in the app | The same repo name, capitalised |
53
+ | Sanity project to copy schemas from | A `projectId: '…'` written in the Studio's config, if there is one |
54
+ | Sanity organisation to create workspaces in | Looked up from that project with your login |
55
+ | Vercel team for the sites | Nothing: press Enter for Anyday's team; give a team id only for the client's own team |
55
56
 
56
57
  The id is permanent: jobs store it and it prefixes each site's Vercel project. Registering a
57
58
  different id later creates a second project type instead of updating this one.
@@ -63,23 +64,25 @@ different id later creates a second project type instead of updating this one.
63
64
  | git remote and default branch | `site.repo`, `site.branch`, the suggested id and name |
64
65
  | `.env.example`, names only | `siteEnv`, the variables each new site needs |
65
66
  | `sanity.schema.json` | copy sections, singletons, home page, language documents, settings fields, colour and font templates |
66
- | the live project, with `--live` | theme defaults, colour defaults and starter pages |
67
+ | the live project, with `--live` | theme defaults, colour defaults, starter pages, and its deployed Studio for `studioOrigins` |
67
68
 
68
69
  `sanity.schema.json` is looked for in the repo root, `frontend`, `web`, `site`, `studio`, `apps`
69
70
  and each folder in `apps`.
70
71
 
71
72
  ### Options
72
73
 
73
- | Option | Does |
74
- | ------------------- | ----------------------------------------------------------------------------------------------- |
75
- | `--yes` | Accepts the suggested id, name and project without asking |
76
- | `--project <id>` | The Sanity project new workspaces copy their schema from |
77
- | `--org <id>` | The Sanity organisation new workspaces are created in (found with `--live`) |
78
- | `--live` | Reads the current theme, settings and starter pages from that project |
79
- | `--dataset <name>` | The dataset `--live` reads (default `production`) |
80
- | `--starter <pages>` | Starter pages without the list: `home`, slugs or document ids, comma-separated (needs `--live`) |
81
- | `--force` | Replaces an existing `workspace-manager.json` |
82
- | `--print` | Prints the draft instead of writing it |
74
+ | Option | Does |
75
+ | -------------------- | ----------------------------------------------------------------------------------------------- |
76
+ | `--yes` | Accepts the suggested id, name and project without asking |
77
+ | `--project <id>` | The Sanity project new workspaces copy their schema from |
78
+ | `--org <id>` | The Sanity organisation new workspaces are created in (found with `--live`) |
79
+ | `--vercel-team <id>` | The client's own Vercel team for the sites; leave out for Anyday's team |
80
+ | `--studio <origin>` | The Studio that opens new workspaces, e.g. `https://acme.sanity.studio` (found with `--live`) |
81
+ | `--live` | Reads the current theme, settings and starter pages from that project |
82
+ | `--dataset <name>` | The dataset `--live` reads (default `production`) |
83
+ | `--starter <pages>` | Starter pages without the list: `home`, slugs or document ids, comma-separated (needs `--live`) |
84
+ | `--force` | Replaces an existing `workspace-manager.json` |
85
+ | `--print` | Prints the draft instead of writing it |
83
86
 
84
87
  ### Starter pages
85
88
 
@@ -89,14 +92,14 @@ Starter pages are what a new workspace starts with when it doesn't copy another
89
92
  ```text
90
93
  Pages of the live site:
91
94
  1. Home (/home) ← home
92
- 2. About Olive (/about-olive)
95
+ 2. About us (/about-us)
93
96
  3. Accessibility (/accessibility)
94
97
  Starter pages, by number (e.g. 1,3; Enter for one empty home page):
95
98
  ```
96
99
 
97
100
  Pick by number, or press Enter for one empty home page. To skip the list, name the pages with
98
101
  `--starter`: `home` means whichever page the settings mark as the home page, and any other entry
99
- is a page's slug or document id, e.g. `--starter home,about-olive`. When translations share a
102
+ is a page's slug or document id, e.g. `--starter home,about-us`. When translations share a
100
103
  slug, the home page's language wins. With `--yes` and no `--starter`, the starter is one empty
101
104
  home page.
102
105
 
@@ -115,6 +118,9 @@ listed afterwards. Check these in particular:
115
118
  - **Colour roles** come with empty descriptions. Add a short one each, saying where the colour is
116
119
  used, so whoever sets up a brand knows what they are picking.
117
120
  - **Language documents and singletons** are guessed from type names (`…Settings`, `l10n.locale`).
121
+ - **Vercel team:** sites go to Anyday's team unless you gave a team id. A client's own team needs
122
+ its token on the API as `VERCEL_TOKEN_<team id>` (a secret, added on Vercel by Anyday, never in
123
+ the manifest); `register` warns while it is missing.
118
124
 
119
125
  ## register
120
126
 
@@ -130,6 +136,7 @@ workspace manager. The live API is `https://anyday-workspace-manager-sanity-api.
130
136
  - Registering again replaces the saved copy for that `id`, so a changed manifest needs no delete
131
137
  first.
132
138
  - It prints `Registered <name> (<id>)` when saved.
139
+ - It warns when the manifest names a Vercel team the API has no token for yet.
133
140
 
134
141
  ## Signing in
135
142
 
package/dist/cli.mjs CHANGED
@@ -236,6 +236,14 @@ var workspace_manager_schema_default = {
236
236
  description: "The Sanity project whose latest deployed Studio schema each workspace gets.",
237
237
  $ref: "#/definitions/name"
238
238
  },
239
+ studioOrigins: {
240
+ description: "Origins of the Studios that open these workspaces, e.g. https://acme.sanity.studio; every new workspace allows them.",
241
+ type: "array",
242
+ items: {
243
+ type: "string",
244
+ pattern: "^https?://[^/]+$"
245
+ }
246
+ },
239
247
  languages: {
240
248
  type: "object",
241
249
  required: ["document", "setupTypes"],
@@ -411,6 +419,7 @@ function configFrom(manifest) {
411
419
  site: manifest.site,
412
420
  siteEnv: manifest.siteEnv,
413
421
  schemaSourceProjectId: manifest.schemaSourceProjectId,
422
+ studioOrigins: manifest.studioOrigins ?? [],
414
423
  languageDocument: languages && ((code) => fill(languages.document, {
415
424
  code,
416
425
  codeUpper: code.toUpperCase(),
@@ -431,6 +440,7 @@ function configFrom(manifest) {
431
440
  },
432
441
  brand: {
433
442
  colorRoles: brand.colorRoles,
443
+ fonts: brand.theme.font !== void 0,
434
444
  documents: (values, homePageId) => brandDocuments(brand, values, homePageId)
435
445
  }
436
446
  };
@@ -558,11 +568,18 @@ function draftManifest(repo, answers, live = {}) {
558
568
  );
559
569
  if (!repo.repo) note("No GitHub remote found: set site.repo to owner/name");
560
570
  if (!repo.hasRevalidateRoute) note("No /api/revalidate route found: check site.revalidatePath");
571
+ if (answers.vercelTeamId)
572
+ note(
573
+ `Sites go to Vercel team ${answers.vercelTeamId}: the API needs VERCEL_TOKEN_${answers.vercelTeamId} before setups reach Vercel`
574
+ );
575
+ if (answers.studioOrigins?.length) note(`Studio: ${answers.studioOrigins.join(", ")}`);
576
+ else note('No Studio found: add studioOrigins, e.g. ["https://acme.sanity.studio"]');
561
577
  return {
562
578
  manifest: {
563
579
  id: answers.id,
564
580
  title: answers.title,
565
581
  organizationId: answers.organizationId,
582
+ ...answers.vercelTeamId ? { vercelTeamId: answers.vercelTeamId } : {},
566
583
  site: {
567
584
  repo: repo.repo ?? "owner/name",
568
585
  branch: repo.branch ?? "main",
@@ -570,6 +587,7 @@ function draftManifest(repo, answers, live = {}) {
570
587
  },
571
588
  siteEnv,
572
589
  schemaSourceProjectId: answers.schemaSourceProjectId,
590
+ ...answers.studioOrigins?.length ? { studioOrigins: answers.studioOrigins } : {},
573
591
  ...languages ? { languages } : {},
574
592
  content: {
575
593
  sections,
@@ -785,12 +803,29 @@ function sanityToken() {
785
803
  if (!existsSync(config)) return null;
786
804
  return JSON.parse(readFileSync(config, "utf8")).authToken ?? null;
787
805
  }
788
- async function organizationOf(projectId, token) {
806
+ async function projectInfo(projectId, token) {
789
807
  const response = await fetch(`https://api.sanity.io/v2021-06-07/projects/${projectId}`, {
790
808
  headers: { Authorization: `Bearer ${token}` }
791
809
  });
792
810
  if (!response.ok) throw new Error(`Couldn't read project ${projectId}: ${response.status}`);
793
- return (await response.json()).organizationId ?? "";
811
+ return await response.json();
812
+ }
813
+ async function organizationOf(projectId, token) {
814
+ return (await projectInfo(projectId, token)).organizationId ?? "";
815
+ }
816
+ async function studioOriginsOf(projectId, token) {
817
+ const response = await fetch(
818
+ `https://api.sanity.io/v2024-08-01/projects/${projectId}/user-applications`,
819
+ { headers: { Authorization: `Bearer ${token}` } }
820
+ );
821
+ if (!response.ok) throw new Error(`Couldn't list ${projectId}'s Studios: ${response.status}`);
822
+ const apps = await response.json();
823
+ const origins = apps.filter((app) => app.type === "studio").map(
824
+ (app) => app.urlType === "external" ? new URL(app.appHost).origin : `https://${app.appHost}.sanity.studio`
825
+ );
826
+ const { studioHost } = await projectInfo(projectId, token);
827
+ if (studioHost) origins.push(`https://${studioHost}.sanity.studio`);
828
+ return [...new Set(origins)];
794
829
  }
795
830
  var liveClient = ({ projectId, dataset, token }) => createClient({
796
831
  projectId,
@@ -939,6 +974,9 @@ Options:
939
974
  --yes Accept the suggested id, title and project without asking
940
975
  --project <id> The Sanity project new workspaces copy their schema from
941
976
  --org <id> The Sanity organisation new workspaces are created in (found with --live)
977
+ --vercel-team <id> The client's own Vercel team for the sites (default: Anyday's team)
978
+ --studio <origin> The Studio that opens new workspaces, e.g. https://acme.sanity.studio
979
+ (found with --live from the project's deployed Studio)
942
980
  --live Read the current theme, settings and starter pages from that project
943
981
  --dataset <name> Dataset to read with --live (default: production)
944
982
  --starter <pages> Comma-separated pages to copy as starter content: \`home\`, slugs or
@@ -965,20 +1003,23 @@ async function registerCommand(argv) {
965
1003
  if (!token) throw new Error("register needs SANITY_AUTH_TOKEN or `npx sanity login`");
966
1004
  const saved = await register({ dir: dirOf(positionals[0]), api, token });
967
1005
  console.log(`Registered ${saved.title} (${saved.id})`);
1006
+ for (const warning of saved.warnings ?? []) console.error(`Warning: ${warning}`);
968
1007
  }
969
1008
  async function init(argv) {
970
1009
  const { values, positionals } = parseArgs({
971
1010
  args: argv,
972
1011
  allowPositionals: true,
973
1012
  options: {
974
- yes: { type: "boolean", default: false },
975
- project: { type: "string" },
976
- org: { type: "string" },
977
- live: { type: "boolean", default: false },
978
- dataset: { type: "string", default: "production" },
979
- starter: { type: "string" },
980
- force: { type: "boolean", default: false },
981
- print: { type: "boolean", default: false }
1013
+ "yes": { type: "boolean", default: false },
1014
+ "project": { type: "string" },
1015
+ "org": { type: "string" },
1016
+ "studio": { type: "string" },
1017
+ "vercel-team": { type: "string" },
1018
+ "live": { type: "boolean", default: false },
1019
+ "dataset": { type: "string", default: "production" },
1020
+ "starter": { type: "string" },
1021
+ "force": { type: "boolean", default: false },
1022
+ "print": { type: "boolean", default: false }
982
1023
  }
983
1024
  });
984
1025
  const root = dirOf(positionals[0]);
@@ -991,7 +1032,11 @@ async function init(argv) {
991
1032
  const orgFor = async (projectId) => values.org ?? (token && projectId ? await organizationOf(projectId, token) : "");
992
1033
  const repo = readRepo(root);
993
1034
  const suggested = { ...suggestNames(repo.repo), project: values.project ?? repo.projectId ?? "" };
994
- const answers = values.yes ? { ...suggested, org: await orgFor(suggested.project) } : await ask(suggested, orgFor);
1035
+ const answers = values.yes ? {
1036
+ ...suggested,
1037
+ org: await orgFor(suggested.project),
1038
+ vercelTeam: values["vercel-team"] ?? ""
1039
+ } : await ask({ ...suggested, vercelTeam: values["vercel-team"] ?? "" }, orgFor);
995
1040
  if (!answers.project)
996
1041
  throw new Error("Pass --project <id>: the Sanity project to copy schemas from");
997
1042
  if (!answers.org) throw new Error("Pass --org <id>, or --live to read it from the project");
@@ -999,7 +1044,9 @@ async function init(argv) {
999
1044
  id: answers.id,
1000
1045
  title: answers.title,
1001
1046
  organizationId: answers.org,
1002
- schemaSourceProjectId: answers.project
1047
+ ...answers.vercelTeam ? { vercelTeamId: answers.vercelTeam } : {},
1048
+ schemaSourceProjectId: answers.project,
1049
+ studioOrigins: values.studio ? [values.studio.replace(/\/+$/, "")] : token ? await studioOriginsOf(answers.project, token) : []
1003
1050
  };
1004
1051
  let draft = draftManifest(repo, base);
1005
1052
  if (token) {
@@ -1040,7 +1087,11 @@ async function ask(suggested, orgFor) {
1040
1087
  const title = await question("Name shown in the app", suggested.title);
1041
1088
  const project = await question("Sanity project to copy schemas from", suggested.project);
1042
1089
  const org = await question("Sanity organisation to create workspaces in", await orgFor(project));
1043
- return { id, title, project, org };
1090
+ const vercelTeam = await question(
1091
+ "Vercel team for the sites, only if it's the client's own (Enter for Anyday's)",
1092
+ suggested.vercelTeam
1093
+ );
1094
+ return { id, title, project, org, vercelTeam };
1044
1095
  } finally {
1045
1096
  prompt.close();
1046
1097
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anyday/sanity-workspace-manager",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Drafts a site repo's workspace-manager.json for the Anyday workspace manager",
5
5
  "repository": {
6
6
  "type": "git",