@anyday/sanity-workspace-manager 0.1.1 → 0.2.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 +36 -17
  2. package/dist/cli.mjs +87 -15
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -27,8 +27,9 @@ From the root of the site repo:
27
27
  # 1. Export the Studio's schema (writes sanity.schema.json)
28
28
  cd studio && npx sanity schema extract && cd ..
29
29
 
30
- # 2. Draft the manifest, reading theme defaults and three starter pages from the live project
31
- npx @anyday/sanity-workspace-manager init --live --starter home,about,contact
30
+ # 2. Draft the manifest from the repo and the live project; it lists the site's pages to pick
31
+ # starter content from
32
+ npx @anyday/sanity-workspace-manager init --live
32
33
 
33
34
  # 3. Review workspace-manager.json, then commit it next to the schema
34
35
 
@@ -62,27 +63,45 @@ different id later creates a second project type instead of updating this one.
62
63
  | git remote and default branch | `site.repo`, `site.branch`, the suggested id and name |
63
64
  | `.env.example`, names only | `siteEnv`, the variables each new site needs |
64
65
  | `sanity.schema.json` | copy sections, singletons, home page, language documents, settings fields, colour and font templates |
65
- | the live project, with `--live` | theme defaults, colour defaults and starter pages (`--starter <ids>`) |
66
+ | the live project, with `--live` | theme defaults, colour defaults and starter pages |
66
67
 
67
68
  `sanity.schema.json` is looked for in the repo root, `frontend`, `web`, `site`, `studio`, `apps`
68
69
  and each folder in `apps`.
69
70
 
70
71
  ### Options
71
72
 
72
- | Option | Does |
73
- | ------------------ | --------------------------------------------------------------------------- |
74
- | `--yes` | Accepts the suggested id, name and project without asking |
75
- | `--project <id>` | The Sanity project new workspaces copy their schema from |
76
- | `--org <id>` | The Sanity organisation new workspaces are created in (found with `--live`) |
77
- | `--live` | Reads the current theme, settings and starter pages from that project |
78
- | `--dataset <name>` | The dataset `--live` reads (default `production`) |
79
- | `--starter <ids>` | Comma-separated document ids to use as starter content (needs `--live`) |
80
- | `--force` | Replaces an existing `workspace-manager.json` |
81
- | `--print` | Prints the draft instead of writing it |
82
-
83
- Without `--starter`, new workspaces start from one empty home page. Starter pages get
84
- `{{siteName}}` and `{{language}}` where the source site's name and language were. Font files are
85
- never copied into theme defaults, since fonts are often licensed per brand.
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 |
83
+
84
+ ### Starter pages
85
+
86
+ Starter pages are what a new workspace starts with when it doesn't copy another workspace. With
87
+ `--live`, `init` lists the live site's pages in the home page's language, home first:
88
+
89
+ ```text
90
+ Pages of the live site:
91
+ 1. Home (/home) ← home
92
+ 2. About Olive (/about-olive)
93
+ 3. Accessibility (/accessibility)
94
+ Starter pages, by number (e.g. 1,3; Enter for one empty home page):
95
+ ```
96
+
97
+ Pick by number, or press Enter for one empty home page. To skip the list, name the pages with
98
+ `--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
100
+ slug, the home page's language wins. With `--yes` and no `--starter`, the starter is one empty
101
+ home page.
102
+
103
+ Starter pages get `{{siteName}}` and `{{language}}` where the source site's name and language
104
+ were. Font files are never copied into theme defaults, since fonts are often licensed per brand.
86
105
 
87
106
  ### Review before registering
88
107
 
package/dist/cli.mjs CHANGED
@@ -642,7 +642,7 @@ function draftStarter(schema, home, live, note) {
642
642
  const page = { _id: "starter-home", _type: type };
643
643
  if ("title" in fields) page.title = "{{siteName}}";
644
644
  if ("language" in fields) page.language = "{{language}}";
645
- note("Starter is one empty home page: pass --starter <ids> with --live to copy real pages");
645
+ note("Starter is one empty home page: run with --live to pick real pages instead");
646
646
  return { homeId: "starter-home", documents: [page] };
647
647
  }
648
648
  function draftBrand(schema, home, live, note) {
@@ -792,15 +792,27 @@ async function organizationOf(projectId, token) {
792
792
  if (!response.ok) throw new Error(`Couldn't read project ${projectId}: ${response.status}`);
793
793
  return (await response.json()).organizationId ?? "";
794
794
  }
795
+ var liveClient = ({ projectId, dataset, token }) => createClient({
796
+ projectId,
797
+ dataset,
798
+ token,
799
+ apiVersion: "2026-08-15",
800
+ useCdn: false,
801
+ perspective: "published"
802
+ });
803
+ async function listPages(options) {
804
+ const { settings, pages } = await liveClient(options).fetch(
805
+ `{
806
+ "settings": *[_id == $settingsId][0],
807
+ "pages": *[_type == $type && defined(slug.current)]{_id, title, "slug": slug.current, language}
808
+ }`,
809
+ { settingsId: options.home.documentId, type: options.pageType }
810
+ );
811
+ const homeId = settings?.[options.home.field]?._ref ?? null;
812
+ return { pages, homeId };
813
+ }
795
814
  async function readLive(options) {
796
- const client = createClient({
797
- projectId: options.projectId,
798
- dataset: options.dataset,
799
- token: options.token,
800
- apiVersion: "2026-08-15",
801
- useCdn: false,
802
- perspective: "published"
803
- });
815
+ const client = liveClient(options);
804
816
  const docs = await client.fetch("*[_id in $ids]", {
805
817
  ids: [options.settingsId, options.themeId, ...options.starterIds]
806
818
  });
@@ -861,6 +873,39 @@ function readRepo(root) {
861
873
  };
862
874
  }
863
875
 
876
+ // src/init/starter.ts
877
+ function resolveStarter(entries, pages, homeId) {
878
+ const home = pages.find((page) => page._id === homeId);
879
+ return entries.map((entry) => {
880
+ if (entry === "home") {
881
+ if (!homeId) throw new Error("The live settings name no home page: pass its slug instead");
882
+ return homeId;
883
+ }
884
+ if (pages.some((page) => page._id === entry)) return entry;
885
+ let matches = pages.filter((page) => page.slug === entry);
886
+ if (matches.length > 1 && home?.language)
887
+ matches = matches.filter((page) => page.language === home.language);
888
+ if (matches.length === 1) return matches[0]._id;
889
+ if (!matches.length) throw new Error(`No page with the slug or id "${entry}"`);
890
+ throw new Error(
891
+ `Several pages have the slug "${entry}": pass one of ${matches.map((page) => page._id).join(", ")}`
892
+ );
893
+ });
894
+ }
895
+ function pickable(pages, homeId) {
896
+ const home = pages.find((page) => page._id === homeId);
897
+ const shown = home?.language ? pages.filter((page) => page.language === home.language) : pages;
898
+ return [...shown].sort(
899
+ (a, b) => a._id === homeId ? -1 : b._id === homeId ? 1 : (a.title ?? "").localeCompare(b.title ?? "")
900
+ );
901
+ }
902
+ function parsePick(answer, count) {
903
+ const picked = answer.split(/[\s,]+/).filter(Boolean).map((part) => Number(part));
904
+ const wrong = picked.filter((n) => !Number.isInteger(n) || n < 1 || n > count);
905
+ if (wrong.length) throw new Error(`Pick numbers from 1 to ${count}`);
906
+ return [...new Set(picked)].map((n) => n - 1);
907
+ }
908
+
864
909
  // src/register.ts
865
910
  import { readFileSync as readFileSync3 } from "node:fs";
866
911
  import { join as join3 } from "node:path";
@@ -896,7 +941,8 @@ Options:
896
941
  --org <id> The Sanity organisation new workspaces are created in (found with --live)
897
942
  --live Read the current theme, settings and starter pages from that project
898
943
  --dataset <name> Dataset to read with --live (default: production)
899
- --starter <ids> Comma-separated document ids to use as starter content (needs --live)
944
+ --starter <pages> Comma-separated pages to copy as starter content: \`home\`, slugs or
945
+ document ids (needs --live; without it, --live offers a list to pick from)
900
946
  --force Overwrite an existing workspace-manager.json
901
947
  --print Print the draft instead of writing it
902
948
 
@@ -957,13 +1003,22 @@ async function init(argv) {
957
1003
  };
958
1004
  let draft = draftManifest(repo, base);
959
1005
  if (token) {
1006
+ const project = { projectId: answers.project, dataset: values.dataset, token };
1007
+ const { pages, homeId } = await listPages({
1008
+ ...project,
1009
+ pageType: draft.manifest.content.starter.documents[0]._type,
1010
+ home: draft.manifest.content.homePage
1011
+ });
1012
+ const starterIds = values.starter ? resolveStarter(
1013
+ values.starter.split(",").map((entry) => entry.trim()).filter(Boolean),
1014
+ pages,
1015
+ homeId
1016
+ ) : values.yes ? [] : await pickStarter(pickable(pages, homeId), homeId);
960
1017
  const live = await readLive({
961
- projectId: answers.project,
962
- dataset: values.dataset,
963
- token,
1018
+ ...project,
964
1019
  settingsId: draft.manifest.brand.settings.documentId,
965
1020
  themeId: draft.manifest.brand.theme.documentId,
966
- starterIds: values.starter?.split(",").map((id) => id.trim()).filter(Boolean) ?? []
1021
+ starterIds
967
1022
  });
968
1023
  draft = draftManifest(repo, base, live);
969
1024
  }
@@ -978,7 +1033,7 @@ Check before registering:`);
978
1033
  for (const note of draft.notes) console.error(` - ${note}`);
979
1034
  }
980
1035
  async function ask(suggested, orgFor) {
981
- const prompt = createInterface({ input: process.stdin, output: process.stdout });
1036
+ const prompt = createInterface({ input: process.stdin, output: process.stderr });
982
1037
  const question = async (label, fallback) => (await prompt.question(`${label}${fallback ? ` (${fallback})` : ""}: `)).trim() || fallback;
983
1038
  try {
984
1039
  const id = await question("Project type id, short and lowercase", suggested.id);
@@ -990,6 +1045,23 @@ async function ask(suggested, orgFor) {
990
1045
  prompt.close();
991
1046
  }
992
1047
  }
1048
+ async function pickStarter(pages, homeId) {
1049
+ if (!pages.length) return [];
1050
+ console.error("\nPages of the live site:");
1051
+ pages.forEach((page, index) => {
1052
+ const label = `${page.title ?? page._id} (/${page.slug})`;
1053
+ console.error(` ${index + 1}. ${label}${page._id === homeId ? " \u2190 home" : ""}`);
1054
+ });
1055
+ const prompt = createInterface({ input: process.stdin, output: process.stderr });
1056
+ try {
1057
+ const answer = await prompt.question(
1058
+ "Starter pages, by number (e.g. 1,3; Enter for one empty home page): "
1059
+ );
1060
+ return parsePick(answer, pages.length).map((index) => pages[index]._id);
1061
+ } finally {
1062
+ prompt.close();
1063
+ }
1064
+ }
993
1065
  var commands = {
994
1066
  init,
995
1067
  register: registerCommand
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anyday/sanity-workspace-manager",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Drafts a site repo's workspace-manager.json for the Anyday workspace manager",
5
5
  "repository": {
6
6
  "type": "git",