@nodii/dev 0.6.12 → 0.6.14
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 +20 -5
- package/dist/api.js +1 -1
- package/dist/cli.js +9 -6
- package/dist/core.js +32 -10
- package/dist/devops-openapi.json +1 -1
- package/dist/figma.js +74 -5
- package/dist/hooks.js +43 -14
- package/dist/oauth.js +21 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -147,6 +147,7 @@ prints them without creating anything.
|
|
|
147
147
|
nodii-dev figma file add FILE_KEY --name TEXT --kind design-system|surfaces|prototype [--module M]
|
|
148
148
|
nodii-dev figma lock FILE_KEY --title TEXT [--scope KIND:ID ...]
|
|
149
149
|
nodii-dev figma scope add CHANGE_SET --scope KIND:ID [--scope ...]
|
|
150
|
+
nodii-dev figma scope set CHANGE_SET --scope KIND:ID [--scope ...]
|
|
150
151
|
nodii-dev figma publish-plan FILE_KEY
|
|
151
152
|
nodii-dev figma check FILE_KEY
|
|
152
153
|
nodii-dev figma log-write CHANGE_SET --tool TOOL --nodes NODE[,NODE]
|
|
@@ -190,10 +191,21 @@ refuses an overlap with `SCOPE_CONFLICT`; the CLI prints Dev's message, then
|
|
|
190
191
|
the set holding the overlapping scope. `figma lock` without `--scope` locks the
|
|
191
192
|
whole file, as older CLIs do, and that conflicts with every other set.
|
|
192
193
|
`figma scope add` widens a set you hold, with the same check; nothing is ever
|
|
193
|
-
widened silently.
|
|
194
|
+
widened silently. `figma scope set` narrows a set you hold to exactly the
|
|
195
|
+
scopes you give, and releases the rest for other sets: for example, a set
|
|
196
|
+
migrated as a whole-file lock can keep just its page. It only narrows. Each
|
|
197
|
+
scope must sit inside one the set holds (the whole file holds everything, and
|
|
198
|
+
a page holds its components and creates); anything else is
|
|
199
|
+
`SCOPE_NOT_NARROWER`. Dev refuses with `WRITE_OUT_OF_SCOPE` when a logged
|
|
200
|
+
write or a submitted node would fall outside the narrower scopes, so keep a
|
|
201
|
+
scope that holds them.
|
|
194
202
|
|
|
195
203
|
Every write must fall inside the set's scopes (MW1-57.7). The post-hook
|
|
196
|
-
logs each write's node ids
|
|
204
|
+
logs each write's node ids, in colon form. It also logs the variable,
|
|
205
|
+
collection and style ids (`VariableID:…`, `VariableCollectionId:…`, `S:…`)
|
|
206
|
+
that a write returns, including those in a `use_figma` script's JSON
|
|
207
|
+
result, so Dev can tell a foundations write. Dev checks containment at
|
|
208
|
+
`submit`, `resubmit` and `apply`. When Dev answers `WRITE_OUT_OF_SCOPE`,
|
|
197
209
|
the hook exits `2`, so Claude Code shows the agent why: a write that
|
|
198
210
|
returned no node ids (return them from the script, then run `figma log-write`),
|
|
199
211
|
or ids outside the scopes. Dev refuses `figma submit` and `figma resubmit`
|
|
@@ -205,11 +217,14 @@ base version.
|
|
|
205
217
|
Scopes release when a set reaches `awaiting_publish`. One library publish
|
|
206
218
|
then covers every set awaiting publish. Before you publish in Figma,
|
|
207
219
|
`figma publish-plan FILE_KEY` lists the components changed since the last
|
|
208
|
-
publish in two groups
|
|
220
|
+
publish in two groups, and warns about any component that sets from both
|
|
221
|
+
groups changed. **publish** belongs to sets awaiting publish. **leave
|
|
209
222
|
unchecked** belongs to sets still editing or in review: untick those in
|
|
210
223
|
Figma's publish dialog. Then `figma published FILE_KEY --figma-version VERSION`
|
|
211
|
-
marks every
|
|
212
|
-
|
|
224
|
+
marks every set applied before that version as published (pass the
|
|
225
|
+
library publish's version id). Dev holds back a set whose component an
|
|
226
|
+
unfinished set now holds, and flags any published component from an
|
|
227
|
+
unfinished set. `figma published CS-123 ...` keeps the per-set route.
|
|
213
228
|
|
|
214
229
|
`figma withdraw` moves a change set you hold from `in_review` back to
|
|
215
230
|
`editing`, as long as no item has been decided (Dev's
|
package/dist/api.js
CHANGED
|
@@ -102,7 +102,7 @@ export const GROUPS = {
|
|
|
102
102
|
composite: "Composite", "service-edges": "ServiceEdges", lanes: "Lanes",
|
|
103
103
|
swaps: "Swaps", ideas: "Ideas", terminology: "Terminology", credentials: "Credentials",
|
|
104
104
|
rulebook: "ad-documents", "ad-decisions": "ad-decisions", domains: "ad-domains", standardizations: "standardizations",
|
|
105
|
-
archive: "archive", harness: "harness",
|
|
105
|
+
archive: "archive", harness: "harness", utils: "utils",
|
|
106
106
|
// DEC-0020 rule delivery; `rules core|report|cap` are first-class (upload.js).
|
|
107
107
|
rules: "rulebook",
|
|
108
108
|
};
|
package/dist/cli.js
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
figmaChangeSetText,
|
|
32
32
|
figmaFileKey,
|
|
33
33
|
figmaFilePublished,
|
|
34
|
+
figmaFilePublishedText,
|
|
34
35
|
figmaLock,
|
|
35
36
|
figmaLockCheck,
|
|
36
37
|
figmaLockText,
|
|
@@ -39,6 +40,8 @@ import {
|
|
|
39
40
|
figmaPublishPlanText,
|
|
40
41
|
figmaResubmit,
|
|
41
42
|
figmaScopeAdd,
|
|
43
|
+
figmaScopeSet,
|
|
44
|
+
figmaScopeSetText,
|
|
42
45
|
figmaSubmit,
|
|
43
46
|
isChangeSetId,
|
|
44
47
|
scopeText,
|
|
@@ -91,6 +94,7 @@ Usage: nodii-dev <command> [options] [--json]
|
|
|
91
94
|
# scopes: component:NODE page:NODE foundations create:PAGE:NAME;
|
|
92
95
|
# no --scope locks the whole file (DEC-0032)
|
|
93
96
|
figma scope add CHANGE_SET --scope KIND:ID [--scope ...] # widen a held set's scopes
|
|
97
|
+
figma scope set CHANGE_SET --scope KIND:ID [--scope ...] # narrow to exactly these; the rest frees
|
|
94
98
|
figma publish-plan FILE_KEY # what to publish, and what to leave unchecked in Figma's dialog
|
|
95
99
|
figma published FILE_KEY|CHANGE_SET --figma-version V # a file key marks every awaiting set published
|
|
96
100
|
figma check|log-write|submit|apply|abandon|release
|
|
@@ -139,7 +143,7 @@ contracts, sagas, outbox-topics, tables, libraries, workers, projects, issues,
|
|
|
139
143
|
backlogs, builds, changes, deploy-reports, runtime, people, agents, inbox,
|
|
140
144
|
gate, design, runbooks, doctrine, changelog, health-verdicts, approval-kinds,
|
|
141
145
|
notification-registry, skills, open-questions, decisions, drift, requests,
|
|
142
|
-
rulebook, ad-decisions, domains, standardizations, archive, and
|
|
146
|
+
rulebook, ad-decisions, domains, standardizations, archive, harness, and utils.
|
|
143
147
|
|
|
144
148
|
Run a command with --help or see the package README for all flags.`;
|
|
145
149
|
|
|
@@ -605,7 +609,8 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
605
609
|
if (action === "lock")
|
|
606
610
|
return emit(await figmaLock(rest[0], requireFlag(f, "title"), { scopes: scopesFrom(f) }), json, (p) => figmaLockText(p.data));
|
|
607
611
|
if (action === "scope") {
|
|
608
|
-
if (rest[0]
|
|
612
|
+
if (!["add", "set"].includes(rest[0]) || !rest[1] || rest.length > 2) throw new CliError("USAGE", "figma scope add|set CHANGE_SET --scope KIND:ID [--scope ...]", 2);
|
|
613
|
+
if (rest[0] === "set") return emit(await figmaScopeSet(rest[1], scopesFrom(f)), json, (p) => figmaScopeSetText(rest[1], p.data));
|
|
609
614
|
return emit(await figmaScopeAdd(rest[1], scopesFrom(f)), json, (p) =>
|
|
610
615
|
`${rest[1]} holds ${p.data?.scopes?.length ?? "its"} scope(s)${p.data?.scopes?.length ? `: ${p.data.scopes.map(scopeText).join(", ")}` : ""}`);
|
|
611
616
|
}
|
|
@@ -616,9 +621,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
616
621
|
// One publish covers every set awaiting publish on the file (MW1-57.8); a
|
|
617
622
|
// change set id keeps the per-set route.
|
|
618
623
|
if (action === "published" && rest[0] && !isChangeSetId(rest[0]))
|
|
619
|
-
return emit(await figmaFilePublished(rest[0], f.file ? JSON.parse(await readFile(f.file, "utf8")) : { figmaVersion: requireFlag(f, "figma-version") }), json, (p) =>
|
|
620
|
-
`Published ${p.data.published?.length ?? 0} change set(s): ${(p.data.published ?? []).join(", ") || "none awaiting publish"}` +
|
|
621
|
-
(p.data.flagged?.length ? `\nFLAGGED: published components from unfinished sets: ${JSON.stringify(p.data.flagged)}` : ""));
|
|
624
|
+
return emit(await figmaFilePublished(rest[0], f.file ? JSON.parse(await readFile(f.file, "utf8")) : { figmaVersion: requireFlag(f, "figma-version") }), json, (p) => figmaFilePublishedText(p.data));
|
|
622
625
|
if (action === "submit") {
|
|
623
626
|
const payload = f.file ? JSON.parse(await readFile(f.file, "utf8")) : { figmaVersion: requireFlag(f, "figma-version") };
|
|
624
627
|
return emit(await figmaSubmit(rest[0], payload), json);
|
|
@@ -670,7 +673,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
670
673
|
return emit(
|
|
671
674
|
await devRequest(
|
|
672
675
|
"POST",
|
|
673
|
-
`/api/v1/design/files/${encode(rest[0])}/lock/release`,
|
|
676
|
+
`/api/v1/design/files/${encode(figmaFileKey(rest[0]))}/lock/release`,
|
|
674
677
|
{},
|
|
675
678
|
),
|
|
676
679
|
json,
|
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.14";
|
|
8
8
|
export const configPath = () =>
|
|
9
9
|
process.env.NODII_DEV_CONFIG_PATH ??
|
|
10
10
|
join(homedir(), ".config/nodii-dev/config.json");
|
|
@@ -105,20 +105,42 @@ export function requestUrl(config, path) {
|
|
|
105
105
|
return `${config.edgeUrl.replace(/\/$/, "")}/devops${path}`;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
const staleCode = (raw) => {
|
|
109
|
+
try {
|
|
110
|
+
return JSON.parse(raw)?.code === "token_stale";
|
|
111
|
+
} catch {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
108
116
|
async function devFetch(method, path, bodyText, contentType, accept) {
|
|
109
117
|
const config = await readConfig();
|
|
110
118
|
const auth = await authProvider();
|
|
111
|
-
const headers = { accept };
|
|
112
|
-
if (bodyText !== undefined) headers["content-type"] = contentType;
|
|
113
|
-
Object.assign(headers, await auth.headers({ method, path, body: bodyText ?? "" }));
|
|
114
119
|
const url = requestUrl(config, path);
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
+
// The edge refuses a token minted before the principal's claims epoch with
|
|
121
|
+
// 401 token_stale (refresh and retry once). The request never reached Dev,
|
|
122
|
+
// so it is sent again: with a token another process cached since, or with
|
|
123
|
+
// one refresh. A token_stale after that refresh is surfaced.
|
|
124
|
+
let refreshed = false;
|
|
125
|
+
let adopted = false;
|
|
126
|
+
for (;;) {
|
|
127
|
+
const headers = { accept };
|
|
128
|
+
if (bodyText !== undefined) headers["content-type"] = contentType;
|
|
129
|
+
Object.assign(headers, await auth.headers({ method, path, body: bodyText ?? "" }));
|
|
130
|
+
let response;
|
|
131
|
+
try {
|
|
132
|
+
response = await fetch(url, { method, headers, body: bodyText });
|
|
133
|
+
} catch (error) {
|
|
134
|
+
throw new CliError("DEV_UNREACHABLE", `Cannot reach Dev at ${new URL(url).origin}: ${error.message}`);
|
|
135
|
+
}
|
|
136
|
+
const raw = await response.text();
|
|
137
|
+
if (response.status !== 401 || typeof auth.refreshStale !== "function" || !staleCode(raw)) return { response, raw };
|
|
138
|
+
if (refreshed) return { response, raw };
|
|
139
|
+
// A cached token that is stale too is not adopted twice: the next one is refreshed.
|
|
140
|
+
const used = String(headers.authorization ?? "").replace(/^Bearer /, "");
|
|
141
|
+
if ((await auth.refreshStale(used, { force: adopted })) === "refreshed") refreshed = true;
|
|
142
|
+
else adopted = true;
|
|
120
143
|
}
|
|
121
|
-
return { response, raw: await response.text() };
|
|
122
144
|
}
|
|
123
145
|
|
|
124
146
|
export async function devRawRequest(method, path, bodyText, contentType = "application/json") {
|