@anyday/sanity-workspace-manager 0.4.0 → 0.6.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/README.md CHANGED
@@ -6,17 +6,19 @@ the brand, the central Studio entry and the Vercel site. It holds no code for an
6
6
  Each kind of site (a **project type**) is described instead by a `workspace-manager.json` manifest
7
7
  that lives in that site's repo.
8
8
 
9
- This tool writes and saves that manifest:
9
+ This tool makes and saves that manifest, the way `sanity typegen generate` makes types:
10
10
 
11
- - **`init`** drafts `workspace-manager.json` from the site repo, its Sanity schema and, optionally,
12
- the live Sanity project, then lists what it guessed so you can review it.
13
- - **`register`** checks the manifest and saves it to the workspace manager, which then offers the
14
- project type in the Workspaces app.
11
+ - **`generate`** writes `workspace-manager.json` from the repo's schema, the live Sanity project
12
+ and `workspace-manager.config.json`, which holds your answers. Change the schema, run it again.
13
+ - **`check`** fails when the manifest is out of date with the schema or was edited by hand.
14
+ - **`register`** saves the manifest to the workspace manager, which then offers the project type in
15
+ the Workspaces app.
15
16
 
16
17
  ## Requirements
17
18
 
18
19
  - Node.js 20 or later.
19
- - A Sanity login: run `npx sanity login` once, or set `SANITY_AUTH_TOKEN`.
20
+ - A Sanity login for `generate` and `register`: run `npx sanity login` once, or set
21
+ `SANITY_AUTH_TOKEN`. `check` needs none.
20
22
  - For `register`, the **Administrator** role in the manifest's Sanity organisation.
21
23
 
22
24
  ## Quick start
@@ -27,100 +29,157 @@ From the root of the site repo:
27
29
  # 1. Export the Studio's schema (writes sanity.schema.json)
28
30
  cd studio && npx sanity schema extract && cd ..
29
31
 
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
+ # 2. Answer a few questions and pick starter pages; writes the config and the manifest
33
+ npx @anyday/sanity-workspace-manager generate
33
34
 
34
- # 3. Review workspace-manager.json, then commit it next to the schema
35
+ # 3. Review workspace-manager.config.json, commit both files next to the schema
35
36
 
36
37
  # 4. Save it to the workspace manager
37
38
  npx @anyday/sanity-workspace-manager register --api https://anyday-workspace-manager-sanity-api.vercel.app
38
39
  ```
39
40
 
40
- ## init
41
+ After a schema change: extract the schema, run `generate`, commit, `register`.
42
+
43
+ ## The two files
44
+
45
+ | File | Holds | Edited by |
46
+ | ------------------------------- | ------------------------------------------------------------------------------ | ---------- |
47
+ | `workspace-manager.config.json` | Your answers: ids, copy sections, colour role descriptions, starter pages, ... | You |
48
+ | `workspace-manager.json` | Everything else: field mappings, theme defaults, starter page content | `generate` |
49
+
50
+ Never edit `workspace-manager.json`: its `$generated` line holds a hash, and `check` fails after a
51
+ hand edit. Change the config and run `generate` instead. The config has a JSON Schema, so your
52
+ editor completes and checks it once the package is installed.
53
+
54
+ Add this line to the site repo's `.gitattributes`, so GitHub marks the manifest as generated and
55
+ collapses its diff in PRs; `generate` reminds you until it's there:
56
+
57
+ ```text
58
+ workspace-manager.json linguist-generated=true
59
+ ```
60
+
61
+ | Config field | Holds |
62
+ | ---------------- | ----------------------------------------------------------------------------------------------------------- |
63
+ | `id` | Permanent: jobs store it and it prefixes each site's Vercel project |
64
+ | `title` | The name shown in the Workspaces app |
65
+ | `organizationId` | The Sanity organisation new workspaces are created in |
66
+ | `vercelTeamId` | The client's own Vercel team; left out for Anyday's |
67
+ | `site` | The GitHub repo and branch each new site deploys from |
68
+ | `studioOrigins` | The Studios that open new workspaces |
69
+ | `sections` | Each content type with its label in the Copy picker, or `false` to leave it out |
70
+ | `colorRoles` | Each theme colour with a short description of where it's used, shown to whoever sets up a brand |
71
+ | `themeFiles` | Theme fields with uploaded files (fonts) to copy into new sites; fonts are often licensed per brand |
72
+ | `starter` | What **Start fresh** writes: picked live pages, or `[]` for one empty home page (see below) |
73
+ | ... | `singletons`, `homePage`, `settingsFields`: guessed from the schema on the first run, asked when they break |
74
+
75
+ ## generate
41
76
 
42
77
  ```bash
43
- npx @anyday/sanity-workspace-manager init [dir] [options]
78
+ npx @anyday/sanity-workspace-manager generate [dir] [options]
44
79
  ```
45
80
 
46
- `dir` is the site repo, and defaults to the current folder. `init` asks five questions, each with a
47
- suggestion you can accept with Enter:
81
+ `dir` is the site repo, and defaults to the current folder. The first run asks who the project type
82
+ is for, each with a suggestion you accept with Enter, then lists the live site's pages to pick
83
+ starter pages from. Answers go in the config. Later runs reuse them and ask only about what changed
84
+ in the schema:
48
85
 
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 |
86
+ ```text
87
+ generalSettings.logo is gone. Which field is the logo? [brandLogo/favicon/none] (none):
88
+ Copy section mediaMention is gone from the schema. Was it renamed? [pressMention/no] (no):
89
+ New content type event: offer it as a copy section? [y/n] (y):
90
+ ```
56
91
 
57
- The id is permanent: jobs store it and it prefixes each site's Vercel project. Registering a
58
- different id later creates a second project type instead of updating this one.
92
+ A renamed field reads as one removed and one added, so `generate` asks rather than guesses.
93
+
94
+ | Option | Does |
95
+ | -------------------- | ---------------------------------------------------------------------------------- |
96
+ | `--yes` | Takes the suggested answers without asking, and lists them |
97
+ | `--offline` | Doesn't read the live project: keeps the live values already in the manifest |
98
+ | `--diff` | Shows what would change in both files, and writes nothing |
99
+ | `--ask-again` | Asks the saved questions again, e.g. to change copy sections or pick starter pages |
100
+ | `--project <id>` | The Sanity project new workspaces copy their schema from |
101
+ | `--org <id>` | The Sanity organisation new workspaces are created in |
102
+ | `--vercel-team <id>` | The client's own Vercel team for the sites |
103
+ | `--studio <origin>` | The Studio that opens new workspaces, e.g. `https://acme.sanity.studio` |
104
+ | `--dataset <name>` | The dataset live values are read from (default `production`) |
105
+ | `--starter <pages>` | Starter pages without the list: `home`, slugs or ids, comma-separated |
59
106
 
60
107
  ### What it reads
61
108
 
62
- | Source | Used for |
63
- | ------------------------------- | ---------------------------------------------------------------------------------------------------- |
64
- | git remote and default branch | `site.repo`, `site.branch`, the suggested id and name |
65
- | `.env.example`, names only | `siteEnv`, the variables each new site needs |
66
- | `sanity.schema.json` | copy sections, singletons, home page, language documents, settings fields, colour and font templates |
67
- | the live project, with `--live` | theme defaults, colour defaults, starter pages, and its deployed Studio for `studioOrigins` |
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 |
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 |
68
115
 
69
116
  `sanity.schema.json` is looked for in the repo root, `frontend`, `web`, `site`, `studio`, `apps`
70
117
  and each folder in `apps`.
71
118
 
72
- ### Options
73
-
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 |
119
+ ### Without access to the live project
120
+
121
+ `generate` says first why it can't read the live project, and what to do:
122
+
123
+ | Situation | What it prints | Fix |
124
+ | ---------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
125
+ | Not signed in | `Not signed in to Sanity: run npx sanity login to read live values` | Run `npx sanity login` |
126
+ | Expired or bad session | `Your Sanity session is missing or expired: run npx sanity login …` | Run `npx sanity login`; if `SANITY_AUTH_TOKEN` is set, it wins over the login, so fix or unset it |
127
+ | No access | `Your Sanity account can't read project <id>: ask one of its Administrators …` | Ask an Administrator of that project to add you (Viewer is enough) |
128
+ | Anything else | `Couldn't read the live project <id>: <reason>` | Follow the reason, e.g. a wrong `--project` or `--dataset` |
129
+
130
+ It then offers to continue from the schema only. Values only the live project holds (colours,
131
+ fonts, starter pages) are kept from the existing manifest, and the last line says so. On a first
132
+ run they start as placeholders: black colours, system fonts and one empty home page, and
133
+ `register` asks before saving them. Sign in and run `generate` again to read the live values.
86
134
 
87
135
  ### Starter pages
88
136
 
89
- Starter pages are what a new workspace starts with when it doesn't copy another workspace. With
90
- `--live`, `init` lists the live site's pages in the home page's language, home first:
137
+ Starter pages are what a new workspace starts with when it doesn't copy another workspace. They
138
+ always come from the live site, so you update them in the Studio, never by hand in a file. The
139
+ first run lists the live pages in the home page's language, home first:
91
140
 
92
141
  ```text
93
142
  Pages of the live site:
94
143
  1. Home (/home) ← home
95
144
  2. About us (/about-us)
96
- 3. Accessibility (/accessibility)
145
+ 3. Contact (/contact)
97
146
  Starter pages, by number (e.g. 1,3; Enter for one empty home page):
98
147
  ```
99
148
 
100
- Pick by number, or press Enter for one empty home page. To skip the list, name the pages with
101
- `--starter`: `home` means whichever page the settings mark as the home page, and any other entry
102
- is a page's slug or document id, e.g. `--starter home,about-us`. When translations share a
103
- slug, the home page's language wins. With `--yes` and no `--starter`, the starter is one empty
104
- home page.
149
+ The config saves only your pick, e.g. `"starter": ["home", "about-us"]`, and every run reads those
150
+ pages again. `home` means whichever page the settings mark as the home page; anything else is a
151
+ slug or document id. `[]` means one empty home page.
152
+
153
+ **To change the starter pages later**, run `generate --ask-again` and pick again from the list, or
154
+ edit the config's `starter` and run `generate`.
155
+
156
+ A new workspace has only its starter pages, so `generate` removes each link to another document (a
157
+ doctor card, a button to another page) and lists what it removed. Fields the schema no longer has
158
+ are dropped too. Starter pages get `{{siteName}}` and `{{language}}` where the source site's name
159
+ and language were.
105
160
 
106
- Starter pages get `{{siteName}}` and `{{language}}` where the source site's name and language
107
- were. Font files are never copied into theme defaults, since fonts are often licensed per brand.
161
+ ### Moving from a hand-made manifest
162
+
163
+ Run `generate` in a repo that has a `workspace-manager.json` and no config: it moves every choice
164
+ the manifest holds, including copy section labels, colour descriptions and fonts, into the config.
165
+ It then lists the live pages to pick starter pages from; until you pick, it keeps the manifest's
166
+ existing starter pages.
167
+
168
+ ## check
169
+
170
+ ```bash
171
+ npx @anyday/sanity-workspace-manager check [dir]
172
+ ```
108
173
 
109
- ### Review before registering
174
+ Read-only. Fails, naming each difference, when:
110
175
 
111
- The draft is checked against the manifest's JSON Schema before it is written, and every guess is
112
- listed afterwards. Check these in particular:
176
+ - the schema changed in a way the manifest uses (a renamed or removed field, a new content type),
177
+ - `workspace-manager.json` was edited by hand, or
178
+ - the starter pages have fields the schema no longer has.
113
179
 
114
- - **Copy sections** start as every content type. Keep only the ones editors should be able to copy
115
- between workspaces.
116
- - **Starter pages** must not link to documents outside the starter: a new workspace won't have
117
- them, and saving the page fails. `init` names any such links.
118
- - **Colour roles** come with empty descriptions. Add a short one each, saying where the colour is
119
- used, so whoever sets up a brand knows what they are picking.
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.
180
+ Only changes to fields **the manifest uses** matter: the logo, site name, theme colours and fonts,
181
+ home page, language fields, content types, and the fields on starter pages. `check` reads only the
182
+ repo, so it can run in CI without signing in.
124
183
 
125
184
  ## register
126
185
 
@@ -135,40 +194,48 @@ workspace manager. The live API is `https://anyday-workspace-manager-sanity-api.
135
194
  - You must be an **Administrator** of the manifest's `organizationId`.
136
195
  - Registering again replaces the saved copy for that `id`, so a changed manifest needs no delete
137
196
  first.
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.
197
+ - It asks before saving placeholder colours or fonts; `--yes` skips the question.
198
+ - `--dry-run` checks the manifest without saving it.
199
+ - It warns when the manifest names a Vercel team the API has no token for yet. A client's own team
200
+ needs its token on the API as `VERCEL_TOKEN_<team id>` (a secret, added on Vercel by Anyday).
140
201
 
141
202
  ## Signing in
142
203
 
143
- Both commands use `SANITY_AUTH_TOKEN` when it is set, and otherwise the token `npx sanity login`
204
+ All commands use `SANITY_AUTH_TOKEN` when it is set, and otherwise the token `npx sanity login`
144
205
  saved. Use your own login: the workspace manager refuses robot tokens for `register`, so that
145
206
  every project type traces back to a person. If a robot token is exported in your shell (for
146
207
  example for a Studio deploy), `unset SANITY_AUTH_TOKEN` before registering.
147
208
 
148
209
  ## Troubleshooting
149
210
 
150
- | Message | Fix |
151
- | ----------------------------------------------------- | ------------------------------------------------------------------ |
152
- | `No sanity.schema.json` | Run `npx sanity schema extract` in the Studio folder |
153
- | `workspace-manager.json exists: pass --force` | Add `--force` to replace it, or `--print` to compare |
154
- | `--starter needs --live` | Add `--live`; starter pages are read from the live project |
155
- | `Pass --api <url> or set WORKSPACE_MANAGER_API_URL` | Add `--api https://anyday-workspace-manager-sanity-api.vercel.app` |
156
- | `Use your own Sanity login, not a robot token` | `unset SANITY_AUTH_TOKEN` and use `npx sanity login` |
157
- | `Register only as an Administrator of organisation …` | Ask an Administrator of that organisation to run `register` |
211
+ | Message | Fix |
212
+ | ----------------------------------------------------- | ----------------------------------------------------------------------- |
213
+ | `No sanity.schema.json` | Run `npx sanity schema extract` in the Studio folder |
214
+ | `workspace-manager.json is out of date` | Run `generate` and commit both files |
215
+ | `was edited by hand` | Put the change in the config, then run `generate` |
216
+ | `No page with the slug or id "…"` | Fix the config's `starter`, or run `generate --ask-again` to pick again |
217
+ | `No GitHub remote found` | Add an `origin` remote, or set `site.repo` in the config |
218
+ | `Not signed in to Sanity` | Run `npx sanity login` |
219
+ | `Your Sanity session is missing or expired` | Run `npx sanity login`, or fix or unset `SANITY_AUTH_TOKEN` |
220
+ | `Your Sanity account can't read project …` | Ask an Administrator of that project to add you (Viewer is enough) |
221
+ | `Couldn't read the live project …` | Follow the reason, e.g. a wrong `--project` or `--dataset` |
222
+ | `Pass --api <url> or set WORKSPACE_MANAGER_API_URL` | Add `--api https://anyday-workspace-manager-sanity-api.vercel.app` |
223
+ | `Use your own Sanity login, not a robot token` | `unset SANITY_AUTH_TOKEN` and use `npx sanity login` |
224
+ | `Register only as an Administrator of organisation …` | Ask an Administrator of that organisation to run `register` |
158
225
 
159
226
  ## How it fits together
160
227
 
161
228
  The full design, including every manifest field, is in
162
229
  [docs/project-config.md](https://github.com/anydayagency/anyday-workspace-manager-sanity/blob/main/docs/project-config.md),
163
- and an overview in
164
- [docs/how-it-works.md](https://github.com/anydayagency/anyday-workspace-manager-sanity/blob/main/docs/how-it-works.md)
230
+ and how generate works in
231
+ [docs/manifest-generate.md](https://github.com/anydayagency/anyday-workspace-manager-sanity/blob/main/docs/manifest-generate.md)
165
232
  (Anyday's GitHub organisation only).
166
233
 
167
234
  ## Developing and publishing
168
235
 
169
236
  From a clone of the workspace manager repo, run the CLI without npm:
170
- `npm run cli -- init ../my-site`. `npm run build` bundles `src/cli.ts`, with the manifest code it
171
- borrows from the API, into `dist/cli.mjs`; only `@sanity/client` and `ajv` stay dependencies.
237
+ `npm run cli -- generate ../my-site`. `npm run build` bundles `src/cli.ts`, with the manifest code
238
+ it borrows from the API, into `dist/cli.mjs`; only `@sanity/client` and `ajv` stay dependencies.
172
239
 
173
240
  Publish from `main`: bump `version`, then run
174
241
  `npm publish --workspace=@anyday/sanity-workspace-manager --access public --auth-type=web` from the
@@ -0,0 +1,157 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://github.com/anydayagency/anyday-workspace-manager-sanity/workspace-manager.config.schema.json",
4
+ "title": "Workspace manager config",
5
+ "description": "The decisions behind a site's workspace-manager.json. Edit this file, then run `sanity-workspace-manager generate`.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": [
9
+ "id",
10
+ "title",
11
+ "organizationId",
12
+ "schemaSourceProjectId",
13
+ "site",
14
+ "singletons",
15
+ "homePage",
16
+ "settingsFields",
17
+ "sections",
18
+ "colorRoles"
19
+ ],
20
+ "definitions": {
21
+ "nullableField": {
22
+ "type": ["string", "null"]
23
+ }
24
+ },
25
+ "properties": {
26
+ "$schema": {
27
+ "type": "string"
28
+ },
29
+ "id": {
30
+ "description": "Permanent: jobs store it and it prefixes each site's Vercel project.",
31
+ "type": "string",
32
+ "pattern": "^[a-z0-9][a-z0-9-]{0,31}$"
33
+ },
34
+ "title": {
35
+ "description": "The name shown in the Workspaces app.",
36
+ "type": "string"
37
+ },
38
+ "organizationId": {
39
+ "description": "The Sanity organisation new workspaces are created in.",
40
+ "type": "string"
41
+ },
42
+ "vercelTeamId": {
43
+ "description": "The client's own Vercel team; leave out for Anyday's.",
44
+ "type": "string"
45
+ },
46
+ "schemaSourceProjectId": {
47
+ "description": "The Sanity project new workspaces copy their schema from.",
48
+ "type": "string"
49
+ },
50
+ "dataset": {
51
+ "description": "The dataset live values are read from. Default: production.",
52
+ "type": "string"
53
+ },
54
+ "site": {
55
+ "type": "object",
56
+ "additionalProperties": false,
57
+ "required": ["repo", "branch"],
58
+ "properties": {
59
+ "repo": {
60
+ "description": "owner/name on GitHub.",
61
+ "type": "string",
62
+ "pattern": "^[\\w.-]+/[\\w.-]+$"
63
+ },
64
+ "branch": {
65
+ "type": "string"
66
+ },
67
+ "revalidatePath": {
68
+ "type": "string"
69
+ }
70
+ }
71
+ },
72
+ "studioOrigins": {
73
+ "description": "The Studios that open new workspaces, e.g. https://acme.sanity.studio.",
74
+ "type": "array",
75
+ "items": {
76
+ "type": "string",
77
+ "pattern": "^https://"
78
+ }
79
+ },
80
+ "singletons": {
81
+ "description": "Settings documents: one each, never offered for copy.",
82
+ "type": "array",
83
+ "items": {
84
+ "type": "string"
85
+ }
86
+ },
87
+ "homePage": {
88
+ "description": "The settings field that references the home page.",
89
+ "type": "object",
90
+ "additionalProperties": false,
91
+ "required": ["documentId", "field"],
92
+ "properties": {
93
+ "documentId": {
94
+ "type": "string"
95
+ },
96
+ "field": {
97
+ "type": "string"
98
+ }
99
+ }
100
+ },
101
+ "settingsFields": {
102
+ "description": "The field on the home page's settings document that holds each brand value; null for none.",
103
+ "type": "object",
104
+ "additionalProperties": false,
105
+ "properties": {
106
+ "siteName": {
107
+ "$ref": "#/definitions/nullableField"
108
+ },
109
+ "domain": {
110
+ "$ref": "#/definitions/nullableField"
111
+ },
112
+ "logo": {
113
+ "$ref": "#/definitions/nullableField"
114
+ },
115
+ "favicon": {
116
+ "$ref": "#/definitions/nullableField"
117
+ }
118
+ }
119
+ },
120
+ "sections": {
121
+ "description": "Each content type, with its label in the Copy picker, or false to leave it out.",
122
+ "type": "object",
123
+ "additionalProperties": {
124
+ "anyOf": [
125
+ {
126
+ "type": "string",
127
+ "minLength": 1
128
+ },
129
+ {
130
+ "const": false
131
+ }
132
+ ]
133
+ }
134
+ },
135
+ "colorRoles": {
136
+ "description": "Each colour role on the theme, with where it is used, shown to whoever sets up a brand.",
137
+ "type": "object",
138
+ "additionalProperties": {
139
+ "type": "string"
140
+ }
141
+ },
142
+ "themeFiles": {
143
+ "description": "Theme fields with uploaded files, e.g. fonts, to copy into new sites. Fonts are often licensed per brand.",
144
+ "type": "array",
145
+ "items": {
146
+ "type": "string"
147
+ }
148
+ },
149
+ "starter": {
150
+ "description": "What Start fresh writes: live pages (`home`, slugs or ids), or [] for one empty home page.",
151
+ "type": "array",
152
+ "items": {
153
+ "type": "string"
154
+ }
155
+ }
156
+ }
157
+ }