@nodii/dev 0.6.1 → 0.6.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.
- package/README.md +36 -3
- package/dist/api.js +10 -2
- package/dist/cli.js +14 -2
- package/dist/core.js +2 -2
- package/dist/devops-openapi.json +1 -1
- package/dist/guidelines.js +223 -0
- package/dist/oauth.js +334 -50
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,9 +24,15 @@ live `devops.*` key except `devops.releases.promote`, which is never granted.
|
|
|
24
24
|
Login uses the RFC 8628 device flow and opens the verification page on an
|
|
25
25
|
interactive terminal unless `--no-browser` is passed. Refresh tokens are
|
|
26
26
|
stored only in macOS Keychain or Linux Secret Service (`secret-tool`); the CLI
|
|
27
|
-
refuses plaintext fallback.
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
refuses plaintext fallback. The current access token is kept there too, so
|
|
28
|
+
every nodii-dev process on the machine shares it instead of refreshing on each
|
|
29
|
+
run; refreshes take turns under a per-user lock. A refresh clears the saved
|
|
30
|
+
sign-in only when nodii-auth answers `invalid_grant` (expired, revoked or
|
|
31
|
+
replayed). A 429, 5xx, timeout, network error or proxy error page keeps it and
|
|
32
|
+
fails with `OAUTH_REFRESH_UNAVAILABLE` (exit 1) after a few retries. Every API
|
|
33
|
+
call goes to the edge's `/devops` prefix with the OAuth bearer and the
|
|
34
|
+
registered `x-agent-id`; signing in again as the same person keeps that
|
|
35
|
+
registration. `logout` revokes the refresh chain before erasing local
|
|
30
36
|
credentials. `--auth-url`, `--edge-url`, and `--tenant-hint` select another real
|
|
31
37
|
deployment.
|
|
32
38
|
|
|
@@ -98,7 +104,34 @@ nodii-dev heartbeat audit [SERVICE]
|
|
|
98
104
|
nodii-dev design entries [--kind KIND] [--group GROUP] [--q TEXT]
|
|
99
105
|
nodii-dev design entry KEY
|
|
100
106
|
nodii-dev design guide PAGE_TYPE
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Design-system guidelines
|
|
110
|
+
|
|
111
|
+
Edit an entry's guideline through Dev, not with signed scripts. A proposal is
|
|
112
|
+
a guideline change set; a person approves or rejects it in Dev (nobody decides
|
|
113
|
+
their own), and the approved edit becomes a design-system PR.
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
nodii-dev design guideline list [--drafted] [--kind KIND] [--q TEXT] [--all]
|
|
117
|
+
nodii-dev design guideline show ENTRY > ENTRY.md # the editable Markdown form
|
|
118
|
+
nodii-dev design guideline propose ENTRY --file ENTRY.md [--summary TEXT] [--dry-run]
|
|
119
|
+
nodii-dev design changeset list [--entry KEY] [--state in_review] [--limit N]
|
|
120
|
+
nodii-dev design changeset show GCS-001
|
|
121
|
+
```
|
|
101
122
|
|
|
123
|
+
The file has one `## <list>` section per statement list (`whenToUse`,
|
|
124
|
+
`whenNotToUse`, `dos`, `donts`, and for an active guideline `anatomy`,
|
|
125
|
+
`states.states`, `states.loading`, `states.empty`, `states.error`, `copy`,
|
|
126
|
+
`composition`, `data.notes`), one `- ` bullet per item, and `(drafted)` after
|
|
127
|
+
the list name when the text still awaits a real source. A line that is an
|
|
128
|
+
HTML comment is ignored (`<!--` inside an item stays in its text), and a line
|
|
129
|
+
break inside an item reads back as a space. The `#` title, as `show` prints it,
|
|
130
|
+
names the entry: `propose` refuses a file titled for another one. `propose` sends
|
|
131
|
+
only the lists that differ from Dev's current guideline, and `--dry-run`
|
|
132
|
+
prints them without creating anything.
|
|
133
|
+
|
|
134
|
+
```bash
|
|
102
135
|
nodii-dev figma lock FILE_KEY --title TEXT
|
|
103
136
|
nodii-dev figma check FILE_KEY
|
|
104
137
|
nodii-dev figma log-write CHANGE_SET --tool TOOL --nodes NODE[,NODE]
|
package/dist/api.js
CHANGED
|
@@ -100,7 +100,7 @@ export const GROUPS = {
|
|
|
100
100
|
decisions: "Decisions", drift: "Drift", requests: "Requests", pipelines: "Pipelines", slices: "Slices", "sql-lookups": "SqlLookups",
|
|
101
101
|
runners: "Runners", routing: "Routing", usage: "Usage", audit: "AuditLog", reconciliation: "Reconciliation", cron: "Cron", health: "Health",
|
|
102
102
|
composite: "Composite", "service-edges": "ServiceEdges", lanes: "Lanes",
|
|
103
|
-
swaps: "Swaps", ideas: "Ideas", terminology: "Terminology",
|
|
103
|
+
swaps: "Swaps", ideas: "Ideas", terminology: "Terminology", credentials: "Credentials",
|
|
104
104
|
rulebook: "ad-documents", "ad-decisions": "ad-decisions", domains: "ad-domains", standardizations: "standardizations",
|
|
105
105
|
archive: "archive", harness: "harness",
|
|
106
106
|
};
|
|
@@ -154,11 +154,19 @@ export const ALIASES = {
|
|
|
154
154
|
"terminology:list": "listTerminology", "terminology:show": "getTerminologyTerm", "terminology:create": "createTerminologyTerm", "terminology:update": "updateTerminologyTerm",
|
|
155
155
|
};
|
|
156
156
|
|
|
157
|
+
// Decisions a person makes in Dev (operator authority): nodii-dev never sends them.
|
|
158
|
+
export const PERSON_ONLY = new Set(["approveDesignGuidelineChangeSet", "rejectDesignGuidelineChangeSet", "mergeDesignRelease"]);
|
|
159
|
+
export function refusePersonOnly(operationId) {
|
|
160
|
+
if (PERSON_ONLY.has(operationId))
|
|
161
|
+
throw new CliError("PERSON_ONLY", `${operationId} is a person's decision in Dev; nodii-dev does not send it.`, 2);
|
|
162
|
+
}
|
|
163
|
+
|
|
157
164
|
export function findFirstClass(operations, group, action) {
|
|
158
165
|
const aliased = ALIASES[`${group}:${action}`];
|
|
159
166
|
if (aliased) return operations.find(x => x.operationId === aliased);
|
|
160
167
|
const tags = Array.isArray(GROUPS[group]) ? GROUPS[group] : [GROUPS[group]];
|
|
161
|
-
|
|
168
|
+
// A person-only operation is never picked by name, so a guessed verb cannot reach it.
|
|
169
|
+
const candidates = operations.filter(x => !PERSON_ONLY.has(x.operationId) && x.tags.some(t => tags.includes(t)));
|
|
162
170
|
const normalized = action?.replace(/-([a-z])/g, (_, c) => c.toUpperCase()).toLowerCase();
|
|
163
171
|
return candidates.find(x => x.operationId.toLowerCase() === normalized || x.operationId.toLowerCase().endsWith(normalized));
|
|
164
172
|
}
|
package/dist/cli.js
CHANGED
|
@@ -26,6 +26,7 @@ import { heartbeatCurrentLane, postHook, preHook, settingsSnippet, workflowSugge
|
|
|
26
26
|
import { setupCommand } from "./setup.js";
|
|
27
27
|
import { harnessCommand } from "./harness.js";
|
|
28
28
|
import { docsCommand, rulebookCommand } from "./upload.js";
|
|
29
|
+
import { changeSetCommand, guidelineCommand } from "./guidelines.js";
|
|
29
30
|
import {
|
|
30
31
|
ALIASES,
|
|
31
32
|
GROUPS,
|
|
@@ -33,6 +34,7 @@ import {
|
|
|
33
34
|
callOperation,
|
|
34
35
|
findFirstClass,
|
|
35
36
|
flagsToOperation,
|
|
37
|
+
refusePersonOnly,
|
|
36
38
|
loadOpenApi,
|
|
37
39
|
operationsFrom,
|
|
38
40
|
parseParams,
|
|
@@ -58,6 +60,9 @@ Usage: nodii-dev <command> [options] [--json]
|
|
|
58
60
|
drift file | request file
|
|
59
61
|
decisions list|get|search
|
|
60
62
|
design entries|entry|guide
|
|
63
|
+
design guideline list [--drafted] [--all] | show ENTRY
|
|
64
|
+
design guideline propose ENTRY --file FILE.md [--summary TEXT] [--dry-run]
|
|
65
|
+
design changeset list [--entry KEY] [--state STATE] | show ID
|
|
61
66
|
figma lock|check|log-write|submit|apply|published|abandon|release
|
|
62
67
|
hook claude-figma-pre|claude-figma-post|print-settings
|
|
63
68
|
setup [--yes|--check] # dry-run by default
|
|
@@ -140,9 +145,12 @@ async function login(flags, json) {
|
|
|
140
145
|
noBrowser: Boolean(flags["no-browser"]),
|
|
141
146
|
scope: flags.scope,
|
|
142
147
|
output: json ? console.error : console.log,
|
|
148
|
+
previous,
|
|
143
149
|
});
|
|
144
|
-
return emit({ loggedIn: true, provider: "oauth", credentialStorage: result.storage }, json,
|
|
145
|
-
|
|
150
|
+
return emit({ loggedIn: true, provider: "oauth", credentialStorage: result.storage, agentId: result.agentId }, json,
|
|
151
|
+
result.agentId
|
|
152
|
+
? `OAuth login complete; refresh credential stored in ${result.storage}. Kept agent registration ${result.agentId}.`
|
|
153
|
+
: `OAuth login complete; refresh credential stored in ${result.storage}. Run \`nodii-dev agent register --name NAME\` next.`);
|
|
146
154
|
}
|
|
147
155
|
|
|
148
156
|
async function issues(action, args, f, json) {
|
|
@@ -237,6 +245,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
237
245
|
if (!action) throw new CliError("USAGE", "api needs an operationId or list", 2);
|
|
238
246
|
const entry = operations.find((x) => x.operationId === action);
|
|
239
247
|
if (!entry) throw new CliError("UNKNOWN_OPERATION", `No OpenAPI operationId: ${action}`, 2);
|
|
248
|
+
refusePersonOnly(entry.operationId);
|
|
240
249
|
const parsed = await readBody(f.body);
|
|
241
250
|
const request = buildOperationRequest(entry, parseParams(f.param), parsed?.value);
|
|
242
251
|
return emit(await devRawRequest(request.method, request.path, parsed?.text), json);
|
|
@@ -270,6 +279,9 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
270
279
|
}
|
|
271
280
|
if (command === "docs") return emitCommand(await docsCommand(action, rest, f));
|
|
272
281
|
if (command === "rulebook" && ["list", "show", "diff"].includes(action)) return emitCommand(await rulebookCommand(action, rest, f));
|
|
282
|
+
// Guideline change sets: approving or rejecting one is a person's action in Dev.
|
|
283
|
+
if (command === "design" && action === "guideline") return emitCommand(await guidelineCommand(rest[0], rest.slice(1), f));
|
|
284
|
+
if (command === "design" && action === "changeset") return emitCommand(await changeSetCommand(rest[0], rest.slice(1), f));
|
|
273
285
|
if (command === "lane") return emitCommand(await laneCommand(action, rest, f));
|
|
274
286
|
if (command === "next") return emitCommand(await nextCommand(action, f));
|
|
275
287
|
if (command === "built") return emitCommand(await builtCommand(action, rest, f));
|
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.3";
|
|
8
8
|
export const configPath = () =>
|
|
9
9
|
process.env.NODII_DEV_CONFIG_PATH ??
|
|
10
10
|
join(homedir(), ".config/nodii-dev/config.json");
|
|
@@ -162,7 +162,7 @@ export async function checkLogin() {
|
|
|
162
162
|
export const encode = (value) => encodeURIComponent(value);
|
|
163
163
|
// Switches never take the next token as their value, so
|
|
164
164
|
// `docs upload --wait rulebook/` keeps rulebook/ as a path.
|
|
165
|
-
const SWITCHES = new Set(["json", "wait", "dry-run", "check", "all", "fleet", "help", "yes", "mine", "no-browser", "include-drafts"]);
|
|
165
|
+
const SWITCHES = new Set(["json", "wait", "dry-run", "check", "all", "fleet", "help", "yes", "mine", "no-browser", "include-drafts", "drafted"]);
|
|
166
166
|
export function parseArgs(argv) {
|
|
167
167
|
const flags = {};
|
|
168
168
|
const args = [];
|