@milesroxas/design-hub 0.1.2 → 0.1.3

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.
@@ -29,6 +29,10 @@ export declare const spec: {
29
29
  readonly type: 'string';
30
30
  readonly description: 'Link this Vercel project and set the password gate env on it.';
31
31
  };
32
+ readonly team: {
33
+ readonly type: 'string';
34
+ readonly description: 'The Vercel team (its slug) the project lives in or is created in. Absent, the CLI’s current team.';
35
+ };
32
36
  readonly 'gate-password': {
33
37
  readonly type: 'string';
34
38
  readonly description: 'The share password (8+ characters). Given again, it rotates.';
@@ -12,7 +12,7 @@ import { readEnv, writeEnv } from '../server/env.js';
12
12
  import { ROOT, TEMPLATES } from '../server/paths.js';
13
13
  import { CliError } from './lib/cli.js';
14
14
  import { holderOf, pickPort, readClaims, writeClaim } from './lib/ports.js';
15
- import { ENVIRONMENTS, envNames, linkedProject, STORE_ENV, setEnv, vercel, } from './lib/vercel.js';
15
+ import { ENVIRONMENTS, envNames, linkedOrg, linkedProject, STORE_ENV, setEnv, vercel, } from './lib/vercel.js';
16
16
  export const spec = {
17
17
  name: 'bootstrap',
18
18
  summary: 'Set the project up on this machine, and optionally on Vercel. Safe to run again.',
@@ -26,6 +26,10 @@ export const spec = {
26
26
  conductor: { type: 'boolean', description: 'Write .conductor/settings.toml for Conductor workspaces.' },
27
27
  branch: { type: 'string', default: 'main', description: 'The branch Conductor workspaces land on.' },
28
28
  vercel: { type: 'string', description: 'Link this Vercel project and set the password gate env on it.' },
29
+ team: {
30
+ type: 'string',
31
+ description: 'The Vercel team (its slug) the project lives in or is created in. Absent, the CLI’s current team.',
32
+ },
29
33
  'gate-password': {
30
34
  type: 'string',
31
35
  description: 'The share password (8+ characters). Given again, it rotates.',
@@ -120,13 +124,15 @@ function cli(args, input) {
120
124
  });
121
125
  return out.stdout;
122
126
  }
123
- function link(project, warnings) {
127
+ function link(project, team, warnings) {
124
128
  const current = linkedProject(ROOT);
125
129
  if (current === project)
126
130
  return 'kept';
127
131
  if (current)
128
132
  warnings.push(`.vercel/project.json pointed at ${current}; relinked to ${project}.`);
129
- cli(['link', '--yes', '--project', project]);
133
+ // A new project is created in the scope given; without one the CLI's current team wins, which
134
+ // is whichever team the user last switched to, not necessarily where this project belongs.
135
+ cli(['link', '--yes', '--project', project, ...(team ? ['--scope', team] : [])]);
130
136
  return 'ran';
131
137
  }
132
138
  /** Sets each value where it is missing; `rotate` names the ones given again, which replace what is there. */
@@ -169,7 +175,21 @@ function pullValues(target, names) {
169
175
  rmSync(dir, { recursive: true, force: true });
170
176
  }
171
177
  }
172
- function provisionReview(project, warnings) {
178
+ /** `--scope` for the commands that name the project instead of reading the link. */
179
+ const scopeArgs = (team) => {
180
+ const scope = team ?? linkedOrg(ROOT);
181
+ return scope ? ['--scope', scope] : [];
182
+ };
183
+ /**
184
+ * Vercel Authentication is on for every new project on a team, and it sends each
185
+ * visitor to a Vercel login before the gate. The gate is the protection, so it goes
186
+ * off. Idempotent: disabling twice is fine.
187
+ */
188
+ function protection(project, team) {
189
+ cli(['project', 'protection', 'disable', project, '--sso', ...scopeArgs(team)]);
190
+ return 'off';
191
+ }
192
+ function provisionReview(project, team, warnings) {
173
193
  const names = envNames(ROOT, 'production');
174
194
  let store = 'kept';
175
195
  if (!names || !STORE_ENV.every((n) => names.has(n))) {
@@ -186,6 +206,8 @@ function provisionReview(project, warnings) {
186
206
  '-e',
187
207
  'development',
188
208
  '--no-env-pull',
209
+ // Unlike env and deploy, this command ignores the project link and takes the CLI's current team.
210
+ ...scopeArgs(team),
189
211
  ]);
190
212
  if (out?.status !== 0)
191
213
  throw new CliError('Could not add the Upstash for Redis store.', {
@@ -226,7 +248,7 @@ export function gateValues(given, env) {
226
248
  return { password, secret, rotated: given !== undefined && given !== env.GATE_PASSWORD };
227
249
  }
228
250
  function setUpVercel(project, flags, env, typesafeKey, warnings) {
229
- const linked = link(project, warnings);
251
+ const linked = link(project, flags.team, warnings);
230
252
  const { password, secret, rotated } = gateValues(flags['gate-password'], env);
231
253
  writeEnv(ROOT, { GATE_PASSWORD: password, GATE_SECRET: secret });
232
254
  const values = { GATE_PASSWORD: password, GATE_SECRET: secret };
@@ -236,13 +258,18 @@ function setUpVercel(project, flags, env, typesafeKey, warnings) {
236
258
  if (rotated)
237
259
  warnings.push('The password changed: every visitor signs in again.');
238
260
  const result = {
239
- vercel: { project, linked, env: ensureEnv(values, new Set(rotated ? ['GATE_PASSWORD'] : [])) },
261
+ vercel: {
262
+ project,
263
+ linked,
264
+ protection: protection(project, flags.team),
265
+ env: ensureEnv(values, new Set(rotated ? ['GATE_PASSWORD'] : [])),
266
+ },
240
267
  share: { password },
241
268
  };
242
269
  if (!flags.review)
243
270
  return result;
244
271
  warnings.push('Redeploy so the new env reaches the site; then design-hub people invite --name "<you>" --role editor.');
245
- return { ...result, review: provisionReview(project, warnings) };
272
+ return { ...result, review: provisionReview(project, flags.team, warnings) };
246
273
  }
247
274
  function envIgnored(warnings) {
248
275
  try {
@@ -8,7 +8,9 @@ export type Run = {
8
8
  stderr: string;
9
9
  };
10
10
  export declare function vercel(cwd: string, args: string[], input?: string): Run | null;
11
- export declare function linkedProject(cwd: string): string | null;
11
+ export declare const linkedProject: (cwd: string) => string | null;
12
+ /** The team the linked project belongs to. The commands that take a project name instead of the link need it as `--scope`. */
13
+ export declare const linkedOrg: (cwd: string) => string | null;
12
14
  /** The variable names set for a target, read from `vercel env ls`'s table. */
13
15
  export declare function envNames(cwd: string, target: Target): Set<string> | null;
14
16
  /** Sets one variable for one target; `force` replaces a value that is already there. */
@@ -15,17 +15,20 @@ export function vercel(cwd, args, input) {
15
15
  return null;
16
16
  return { status: out.status ?? 1, stdout: out.stdout ?? '', stderr: out.stderr ?? '' };
17
17
  }
18
- export function linkedProject(cwd) {
18
+ function readLink(cwd) {
19
19
  const file = join(cwd, '.vercel', 'project.json');
20
20
  if (!existsSync(file))
21
21
  return null;
22
22
  try {
23
- return JSON.parse(readFileSync(file, 'utf8')).projectName ?? null;
23
+ return JSON.parse(readFileSync(file, 'utf8'));
24
24
  }
25
25
  catch {
26
26
  return null;
27
27
  }
28
28
  }
29
+ export const linkedProject = (cwd) => readLink(cwd)?.projectName ?? null;
30
+ /** The team the linked project belongs to. The commands that take a project name instead of the link need it as `--scope`. */
31
+ export const linkedOrg = (cwd) => readLink(cwd)?.orgId ?? null;
29
32
  /** The variable names set for a target, read from `vercel env ls`'s table. */
30
33
  export function envNames(cwd, target) {
31
34
  const out = vercel(cwd, ['env', 'ls', target]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@milesroxas/design-hub",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "The Design Hub product: the hub that lists and serves prototypes, the review panel and its API, the password gate, the kit, and the design-hub CLI. A project installs it and keeps only its prototypes, kit materials and docs.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -34,11 +34,12 @@ Never send the link with a failed step, and never skip one with `--skip` without
34
34
  1. The user signs in once, in their own terminal: `vercel login`. It is interactive; never run it for them.
35
35
  2. From the project root:
36
36
  ```bash
37
- pnpm bootstrap --vercel <project-name>
37
+ pnpm bootstrap --vercel <project-name> --team <team-slug>
38
38
  ```
39
- It links the project, writes `GATE_PASSWORD` (12 readable characters, or `--gate-password <word>`) and a fresh `GATE_SECRET` to `.env` and to Production and Preview, and prints the password in `share.password`. A second run keeps what is set.
39
+ `--team` is the Vercel team the project is created in (`vercel teams ls` lists them); without it the CLI's current team wins, which is often not the one the user means. It links the project, writes `GATE_PASSWORD` (12 readable characters, or `--gate-password <word>`) and a fresh `GATE_SECRET` to `.env` and to Production and Preview, and prints the password in `share.password`. A second run keeps what is set.
40
40
  3. `pnpm preflight`, then push `main` (or `vercel --prod` from the project root).
41
- 4. The handover message, in one message: the URL, the password, which prototype to open (the sprint's slug), the walkthrough at `/guide/<feature>/`, and that Controls is the round button at the top of the page. A reviewer who is not told about Controls reviews one state. Never send `GATE_SECRET`.
41
+ 4. On a Pro team Vercel turns Vercel Authentication on for every new project, which sends each visitor to a Vercel login before the gate. Turn it off for Production once: Project Settings, Deployment Protection, Vercel Authentication, set to Only Preview Deployments or Disabled. The hub's gate is the protection. Test by opening the production URL in a private window: the password page must appear, not vercel.com.
42
+ 5. The handover message, in one message: the URL, the password, which prototype to open (the sprint's slug), the walkthrough at `/guide/<feature>/`, and that Controls is the round button at the top of the page. A reviewer who is not told about Controls reviews one state. Never send `GATE_SECRET`.
42
43
 
43
44
  ## Comments
44
45