@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
|
-
|
|
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
|
-
|
|
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: {
|
|
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
|
|
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
|
-
|
|
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'))
|
|
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.
|
|
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.
|
|
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
|
|