@nodii/dev 0.6.16 → 0.6.20
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 +27 -1
- package/dist/cli.js +12 -7
- package/dist/core.js +16 -7
- package/dist/design.js +15 -9
- package/dist/devops-openapi.json +1 -1
- package/dist/figma.js +172 -12
- package/dist/vendor/ds-conformance.mjs +259 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -181,12 +181,15 @@ one `--scope` per scope:
|
|
|
181
181
|
| --- | --- |
|
|
182
182
|
| `component:NODE_ID` | a component set or standalone component, at its top-level node (a variant id is taken as its set) |
|
|
183
183
|
| `page:NODE_ID` | a whole Figma page: new screens, specimens, docs pages |
|
|
184
|
+
| `section:NODE_ID` | a Figma section and every node inside it (its own size and name too) |
|
|
184
185
|
| `foundations` | the file's variables, text styles and effect styles; one holder at a time |
|
|
185
186
|
| `create:PAGE_ID:NAME` | a new component, made on that page |
|
|
186
187
|
|
|
187
188
|
A node id from a figma.com link (`12-34`) is read as Figma's `12:34`. Two
|
|
188
189
|
sets conflict only when their scopes overlap: the same node, a page that holds
|
|
189
|
-
a claimed component (or the reverse),
|
|
190
|
+
a claimed component or section (or the reverse), a section that holds a
|
|
191
|
+
claimed component or section (or the reverse), or foundations already held.
|
|
192
|
+
Sibling sections don't conflict. Dev
|
|
190
193
|
refuses an overlap with `SCOPE_CONFLICT`; the CLI prints Dev's message, then
|
|
191
194
|
the set holding the overlapping scope. `figma lock` without `--scope` locks the
|
|
192
195
|
whole file, as older CLIs do, and that conflicts with every other set.
|
|
@@ -399,6 +402,29 @@ version. A set never submitted needs `--file`: the body `figma submit --file`
|
|
|
399
402
|
would send. A cold run can outlast the edge's gateway; the CLI then says
|
|
400
403
|
`DEV_TIMEOUT` and to run it again (Dev keeps the mains it read for 5 minutes).
|
|
401
404
|
|
|
405
|
+
### Large sets: submit as a Dev job
|
|
406
|
+
|
|
407
|
+
`figma submit`, `figma resubmit` and `design check --change-set` send
|
|
408
|
+
`Prefer: respond-async`. Dev answers 202 with a job, and the CLI polls it
|
|
409
|
+
(`getDesignSubmitJob`) with backoff, printing progress on stderr, until it
|
|
410
|
+
ends. The output and exit code are the synchronous call's: a succeeded job
|
|
411
|
+
prints Dev's 200 body and warnings, a failed one its error and findings. A
|
|
412
|
+
large set's gate outlasts the edge's 30 s timeout, so this is the default.
|
|
413
|
+
|
|
414
|
+
- `a submit is already running: JOB`: Dev already runs a submit (or a dry run)
|
|
415
|
+
for this set (409 `SUBMIT_IN_PROGRESS` / `CONFORMANCE_IN_PROGRESS`). The CLI
|
|
416
|
+
polls that job instead of sending again, then exits 1 with the 409's code
|
|
417
|
+
and that job's outcome (`--json`: `details.result` or `details.error`):
|
|
418
|
+
this command's body was not sent, so read the set and run it again if it
|
|
419
|
+
still needs to go.
|
|
420
|
+
- `SUBMIT_JOB_LOST`: Dev restarted while the job ran. Run the same command again.
|
|
421
|
+
- `SUBMIT_JOB_UNREACHABLE`: polling lost the edge; the job runs on in Dev.
|
|
422
|
+
Read the set with `figma changeset show` before sending it again.
|
|
423
|
+
- `--sync` sends the old single request. If it (or a submit's 202) meets the
|
|
424
|
+
edge's 502 or 504, the CLI reads the set and says whether the submit landed;
|
|
425
|
+
it never resends. `SUBMIT_UNCONFIRMED` means it hasn't yet: check with
|
|
426
|
+
`figma changeset show` before submitting again.
|
|
427
|
+
|
|
402
428
|
When `figma submit`, `figma resubmit` or the dry run hits
|
|
403
429
|
`DS_CONFORMANCE_FAILED`, the CLI prints each finding and Dev's warnings. A
|
|
404
430
|
submit Dev accepts prints its warnings too. On a stale version it also prints
|
package/dist/cli.js
CHANGED
|
@@ -43,12 +43,13 @@ import {
|
|
|
43
43
|
figmaScopeSet,
|
|
44
44
|
figmaScopeSetText,
|
|
45
45
|
figmaSubmit,
|
|
46
|
+
submitMode,
|
|
46
47
|
isChangeSetId,
|
|
47
48
|
scopeText,
|
|
48
49
|
scopesFrom,
|
|
49
50
|
conformanceLines,
|
|
50
51
|
} from "./figma.js";
|
|
51
|
-
export { figmaLock } from "./figma.js";
|
|
52
|
+
export { figmaLock, submitMode } from "./figma.js";
|
|
52
53
|
import { docsCommand, rulebookCommand, rulesCommand } from "./upload.js";
|
|
53
54
|
import { changeSetCommand, guidelineCommand } from "./guidelines.js";
|
|
54
55
|
import { designCheck, designCheckText, designRoles, designRolesText } from "./design.js";
|
|
@@ -96,19 +97,23 @@ Usage: nodii-dev <command> [options] [--json]
|
|
|
96
97
|
design check FILE_KEY|LINK [--nodes A,B] [--roles R,S] [--library]
|
|
97
98
|
# the DS conformance check Dev's submit runs, before you submit;
|
|
98
99
|
# reads Figma with FIGMA_PAT; exit 1 = DS_CONFORMANCE_FAILED or an error (--json: data.passed)
|
|
99
|
-
design check --change-set CS [--file SUBMIT.json] [--figma-version V]
|
|
100
|
+
design check --change-set CS [--file SUBMIT.json] [--figma-version V] [--sync]
|
|
100
101
|
# Dev's dry run of the submit gate on a set, with the image guard;
|
|
101
102
|
# a stale version is a warning that prints when to retry
|
|
102
103
|
figma lock FILE_KEY --title TEXT [--scope KIND:ID ...]
|
|
103
|
-
# scopes: component:NODE page:NODE foundations create:PAGE:NAME;
|
|
104
|
+
# scopes: component:NODE page:NODE section:NODE foundations create:PAGE:NAME;
|
|
104
105
|
# no --scope locks the whole file (DEC-0032)
|
|
105
106
|
figma scope add CHANGE_SET --scope KIND:ID [--scope ...] # widen a held set's scopes
|
|
106
107
|
figma scope set CHANGE_SET --scope KIND:ID [--scope ...] # narrow to exactly these; the rest frees
|
|
107
108
|
figma publish-plan FILE_KEY # what to publish, and what to leave unchecked in Figma's dialog
|
|
108
109
|
figma published FILE_KEY|CHANGE_SET --figma-version V # a file key marks every awaiting set published
|
|
109
110
|
figma check|log-write|submit|apply|abandon|release
|
|
111
|
+
figma submit CHANGE_SET --file SUBMIT.json [--sync]
|
|
112
|
+
# submit, resubmit and design check --change-set run as a job in Dev
|
|
113
|
+
# and poll it (a large set outlasts the edge's timeout); --sync waits
|
|
114
|
+
# on one request instead. A 502/504 is checked against the set, never resent
|
|
110
115
|
figma withdraw CHANGE_SET # holder takes back a submit: in_review -> editing (or a re-submit -> applying)
|
|
111
|
-
figma resubmit CHANGE_SET PAYLOAD.json
|
|
116
|
+
figma resubmit CHANGE_SET PAYLOAD.json [--sync]
|
|
112
117
|
# holder re-submits every rejected item: applying -> in_review, round + 1
|
|
113
118
|
figma changeset show CHANGE_SET # items, variants, round, decision and comment (with the previous round's)
|
|
114
119
|
figma file add FILE_KEY --name TEXT --kind design-system|surfaces|prototype [--module M]
|
|
@@ -428,7 +433,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
428
433
|
if (command === "design" && action === "roles") return emit(await designRoles(rest), json, designRolesText);
|
|
429
434
|
if (command === "design" && action === "check") {
|
|
430
435
|
if ((!rest[0] && f["change-set"] === undefined) || rest.length > 1)
|
|
431
|
-
throw new CliError("USAGE", "design check FILE_KEY|LINK [--nodes A,B] [--roles R,S] [--library] | design check --change-set CS [--file SUBMIT.json] [--figma-version V]", 2);
|
|
436
|
+
throw new CliError("USAGE", "design check FILE_KEY|LINK [--nodes A,B] [--roles R,S] [--library] | design check --change-set CS [--file SUBMIT.json] [--figma-version V] [--sync]", 2);
|
|
432
437
|
const result = await designCheck(rest[0], f);
|
|
433
438
|
emit(result.payload, json, result.human ?? designCheckText);
|
|
434
439
|
if (result.exitCode) {
|
|
@@ -646,7 +651,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
646
651
|
if (action === "submit") {
|
|
647
652
|
const payload = f.file ? JSON.parse(await readFile(f.file, "utf8")) : { figmaVersion: requireFlag(f, "figma-version") };
|
|
648
653
|
// A 200 carries top-level warnings (STALE_FIGMA_VERSION): print them.
|
|
649
|
-
return emit(await figmaSubmit(rest[0], payload), json, (p) =>
|
|
654
|
+
return emit(await figmaSubmit(rest[0], payload, { sync: submitMode(f) }), json, (p) =>
|
|
650
655
|
[JSON.stringify(p.data, null, 2), ...conformanceLines({ warnings: p.warnings })].join("\n"));
|
|
651
656
|
}
|
|
652
657
|
if (action === "file") {
|
|
@@ -686,7 +691,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
686
691
|
if (action === "withdraw")
|
|
687
692
|
return emit(await figmaWithdraw(rest[0]), json, (p) => `Withdrew ${rest[0]}; it is back in ${p.data?.state ?? "editing"}.`);
|
|
688
693
|
if (action === "resubmit")
|
|
689
|
-
return emit(await figmaResubmit(rest[0], rest[1]), json, (p) =>
|
|
694
|
+
return emit(await figmaResubmit(rest[0], rest[1], { sync: submitMode(f) }), json, (p) =>
|
|
690
695
|
[`Re-submitted ${rest[0]}; it is ${p.data.state}.`, figmaChangeSetText(p.data), ...conformanceLines({ warnings: p.warnings })].join("\n"));
|
|
691
696
|
if (action === "changeset") {
|
|
692
697
|
if (rest[0] !== "show" || !rest[1] || rest.length > 2) throw new CliError("USAGE", "figma changeset show CHANGE_SET", 2);
|
package/dist/core.js
CHANGED
|
@@ -4,7 +4,7 @@ import { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
|
4
4
|
import { homedir, platform } from "node:os";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
|
|
7
|
-
export const VERSION = "0.6.
|
|
7
|
+
export const VERSION = "0.6.20";
|
|
8
8
|
export const configPath = () =>
|
|
9
9
|
process.env.NODII_DEV_CONFIG_PATH ??
|
|
10
10
|
join(homedir(), ".config/nodii-dev/config.json");
|
|
@@ -113,7 +113,7 @@ const staleCode = (raw) => {
|
|
|
113
113
|
}
|
|
114
114
|
};
|
|
115
115
|
|
|
116
|
-
async function devFetch(method, path, bodyText, contentType, accept) {
|
|
116
|
+
async function devFetch(method, path, bodyText, contentType, accept, extra = {}) {
|
|
117
117
|
const config = await readConfig();
|
|
118
118
|
const auth = await authProvider();
|
|
119
119
|
const url = requestUrl(config, path);
|
|
@@ -124,7 +124,7 @@ async function devFetch(method, path, bodyText, contentType, accept) {
|
|
|
124
124
|
let refreshed = false;
|
|
125
125
|
let adopted = false;
|
|
126
126
|
for (;;) {
|
|
127
|
-
const headers = { accept };
|
|
127
|
+
const headers = { accept, ...extra };
|
|
128
128
|
if (bodyText !== undefined) headers["content-type"] = contentType;
|
|
129
129
|
Object.assign(headers, await auth.headers({ method, path, body: bodyText ?? "" }));
|
|
130
130
|
let response;
|
|
@@ -143,8 +143,9 @@ async function devFetch(method, path, bodyText, contentType, accept) {
|
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
/** A JSON call that keeps the status: `{ status, payload }`. Errors throw as devRawRequest's do. */
|
|
147
|
+
export async function devExchange(method, path, bodyText, { contentType = "application/json", headers = {} } = {}) {
|
|
148
|
+
const { response, raw } = await devFetch(method, path, bodyText, contentType, "application/json", headers);
|
|
148
149
|
let payload;
|
|
149
150
|
try {
|
|
150
151
|
payload = raw ? JSON.parse(raw) : {};
|
|
@@ -158,7 +159,11 @@ export async function devRawRequest(method, path, bodyText, contentType = "appli
|
|
|
158
159
|
1,
|
|
159
160
|
payload.details,
|
|
160
161
|
);
|
|
161
|
-
return payload;
|
|
162
|
+
return { status: response.status, payload };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export async function devRawRequest(method, path, bodyText, contentType = "application/json") {
|
|
166
|
+
return (await devExchange(method, path, bodyText, { contentType })).payload;
|
|
162
167
|
}
|
|
163
168
|
|
|
164
169
|
/** A Dev route that answers with text (e.g. a document body as text/markdown). Errors are Dev's JSON. */
|
|
@@ -173,6 +178,10 @@ export async function devTextRequest(method, path) {
|
|
|
173
178
|
export const devRequest = (method, path, body) =>
|
|
174
179
|
devRawRequest(method, path, body === undefined ? undefined : JSON.stringify(body));
|
|
175
180
|
|
|
181
|
+
/** A JSON call sent with `Prefer: respond-async` (RFC 7240): `{ status, payload }`, 202 when Dev took it as a job. */
|
|
182
|
+
export const devAsyncRequest = async (method, path, body) =>
|
|
183
|
+
devExchange(method, path, body === undefined ? undefined : JSON.stringify(body), { headers: { prefer: "respond-async" } });
|
|
184
|
+
|
|
176
185
|
/** Proves the stored OAuth login works: a token refresh, then the agent read once registered. */
|
|
177
186
|
export async function checkLogin() {
|
|
178
187
|
const config = await readConfig();
|
|
@@ -184,7 +193,7 @@ export async function checkLogin() {
|
|
|
184
193
|
export const encode = (value) => encodeURIComponent(value);
|
|
185
194
|
// Switches never take the next token as their value, so
|
|
186
195
|
// `docs upload --wait rulebook/` keeps rulebook/ as a path.
|
|
187
|
-
const SWITCHES = new Set(["json", "wait", "dry-run", "check", "all", "fleet", "help", "yes", "mine", "no-browser", "include-drafts", "drafted", "library"]);
|
|
196
|
+
const SWITCHES = new Set(["json", "wait", "dry-run", "check", "all", "fleet", "help", "yes", "mine", "no-browser", "include-drafts", "drafted", "library", "async", "sync"]);
|
|
188
197
|
export function parseArgs(argv) {
|
|
189
198
|
const flags = {};
|
|
190
199
|
const args = [];
|
package/dist/design.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// library main.
|
|
9
9
|
import { readFile } from "node:fs/promises";
|
|
10
10
|
import { CliError, devRequest } from "./core.js";
|
|
11
|
-
import { conformanceLines, figmaConformance, figmaFileKey } from "./figma.js";
|
|
11
|
+
import { conformanceLines, figmaConformance, figmaFileKey, submitMode } from "./figma.js";
|
|
12
12
|
import { checkConformance, neededMains, VERSION as CHECK_VERSION } from "./vendor/ds-conformance.mjs";
|
|
13
13
|
|
|
14
14
|
const FIGMA_API = "https://api.figma.com/v1";
|
|
@@ -159,7 +159,7 @@ export async function lastPublish(figmaGet, fileKey) {
|
|
|
159
159
|
/**
|
|
160
160
|
* The main of each instance, keyed by the instance's componentId (this file's
|
|
161
161
|
* id for it), resolved as Dev's gate resolves it (Dev issue 92, conformance
|
|
162
|
-
* 1.0.
|
|
162
|
+
* 1.0.4), so both give one verdict:
|
|
163
163
|
* - a local component (remote: false) is read from this file;
|
|
164
164
|
* - a remote one is found in the DS file's published listing, else by Figma's
|
|
165
165
|
* lookup of its key, and is read only from a library Dev has registered
|
|
@@ -273,8 +273,10 @@ const nodeId = (x) => (/^\d+-\d+$/.test(x) ? x.replace("-", ":") : x);
|
|
|
273
273
|
* finding fails; stale overrides (a value equal to the main's) are listed but
|
|
274
274
|
* never fail (DEC-0035).
|
|
275
275
|
*/
|
|
276
|
-
export async function designCheck(target, flags = {}, { request = devRequest, env = process.env, figmaGet, read } = {}) {
|
|
277
|
-
|
|
276
|
+
export async function designCheck(target, flags = {}, { request = devRequest, send, progress, env = process.env, figmaGet, read } = {}) {
|
|
277
|
+
submitMode(flags);
|
|
278
|
+
if (flags["change-set"] !== undefined)
|
|
279
|
+
return designCheckChangeSet(target, flags, { request, ...(send ? { send } : {}), ...(progress ? { progress } : {}), ...(read ? { read } : {}) });
|
|
278
280
|
// A --nodes that names nothing must not widen to every page.
|
|
279
281
|
if (flags.nodes !== undefined && ([flags.nodes].flat().includes(true) || !list(flags.nodes).length))
|
|
280
282
|
throw new CliError("USAGE", "--nodes needs node ids, e.g. --nodes 12:34,12:56", 2);
|
|
@@ -345,6 +347,8 @@ export async function designCheck(target, flags = {}, { request = devRequest, en
|
|
|
345
347
|
// Same value through a different variable: the file is behind a library
|
|
346
348
|
// republish. Accept the update in Figma (MW6-17); never a failure.
|
|
347
349
|
libraryUpdatePending: result.libraryUpdatePending ?? [],
|
|
350
|
+
// What the app-UI gate leaves out (email), with its node count (Dev issue 116).
|
|
351
|
+
outOfScope: result.outOfScope ?? [], outOfScopeNodes: result.outOfScopeNodes ?? 0,
|
|
348
352
|
warnings,
|
|
349
353
|
};
|
|
350
354
|
return { payload: { data }, exitCode: data.passed ? 0 : 1 };
|
|
@@ -367,8 +371,9 @@ export function designCheckText({ data }) {
|
|
|
367
371
|
* plus the submit-time guards against the set's items: IMAGE_NOT_TRUECOLOR
|
|
368
372
|
* refuses; STALE_FIGMA_VERSION is a warning. Changes no state.
|
|
369
373
|
*/
|
|
370
|
-
export async function designCheckChangeSet(target, flags, { request = devRequest, read = (f) => readFile(f, "utf8") } = {}) {
|
|
374
|
+
export async function designCheckChangeSet(target, flags, { request = devRequest, send, progress, read = (f) => readFile(f, "utf8") } = {}) {
|
|
371
375
|
const id = flags["change-set"];
|
|
376
|
+
const sync = submitMode(flags);
|
|
372
377
|
if (id === true || !id) throw new CliError("USAGE", "--change-set needs a change set id, e.g. --change-set CS-123", 2);
|
|
373
378
|
if (target !== undefined) throw new CliError("USAGE", "design check --change-set CS takes no file: Dev checks the set's own items", 2);
|
|
374
379
|
if (flags["figma-version"] === true) throw new CliError("USAGE", "--figma-version needs a version id", 2);
|
|
@@ -387,17 +392,18 @@ export async function designCheckChangeSet(target, flags, { request = devRequest
|
|
|
387
392
|
if (flags["figma-version"]) body = { ...body, figmaVersion: String(flags["figma-version"]) };
|
|
388
393
|
let p;
|
|
389
394
|
try {
|
|
390
|
-
p = await figmaConformance(id, body, { request });
|
|
395
|
+
p = await figmaConformance(id, body, { request, sync, ...(send ? { send } : {}), ...(progress ? { progress } : {}) });
|
|
391
396
|
} catch (error) {
|
|
392
397
|
// A cold run reads every root and main from Figma and can outlast the
|
|
393
398
|
// edge's gateway (504, an HTML page). Dev keeps the mains it read for 5
|
|
394
399
|
// minutes, so the same run again usually answers.
|
|
395
|
-
if (error?.code !== "HTTP_504") throw error;
|
|
396
|
-
throw new CliError("DEV_TIMEOUT", `Dev's dry run of ${id} ran past the edge's gateway timeout while reading Figma; run it again (Dev keeps the library mains it read for 5 minutes)`, 1);
|
|
400
|
+
if (error?.code !== "HTTP_504" && error?.code !== "HTTP_502") throw error;
|
|
401
|
+
throw new CliError("DEV_TIMEOUT", `Dev's dry run of ${id} ran past the edge's gateway timeout while reading Figma; run it again${sync ? " without --sync, so Dev runs it as a job" : ""} (Dev keeps the library mains it read for 5 minutes)`, 1);
|
|
397
402
|
}
|
|
398
403
|
const r = p.data ?? {};
|
|
399
404
|
const data = { changeSetId: id, checkVersion: r.version, passed: Boolean(r.passed), checked: r.checked, findings: r.findings ?? [],
|
|
400
|
-
staleOverrides: r.staleOverrides ?? [], libraryUpdatePending: r.libraryUpdatePending ?? [],
|
|
405
|
+
staleOverrides: r.staleOverrides ?? [], libraryUpdatePending: r.libraryUpdatePending ?? [],
|
|
406
|
+
outOfScope: r.outOfScope ?? [], outOfScopeNodes: r.outOfScopeNodes ?? 0, warnings: r.warnings ?? [], staleVersion: r.staleVersion ?? null };
|
|
401
407
|
return { payload: { data }, exitCode: data.passed ? 0 : 1, human: designCheckChangeSetText };
|
|
402
408
|
}
|
|
403
409
|
|