@dokebi/cli 0.4.27 → 0.4.28
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/AI.md +1 -1
- package/catalog.json +6 -6
- package/dist/config-secrets.d.ts +8 -0
- package/dist/config-secrets.js +40 -1
- package/dist/deploy.js +1 -29
- package/dist/output.d.ts +1 -1
- package/dist/output.js +1 -1
- package/package.json +1 -1
package/AI.md
CHANGED
|
@@ -75,7 +75,7 @@ The `blog` plan, depending on the answers: `init` (when the folder is not a proj
|
|
|
75
75
|
| `dokebi config secret <id>.<path> [--stdin] [--rotate]` | Writes a random secret (32 bytes; a one-item list when the schema wants a list) into dokebi.yaml, or with `--stdin` a secret you were given (the token of a log receiver: `config secret logging.targets.studio.token --stdin < token.txt`, or typed without echo when stdin is a terminal; one line, at most 64 KiB, nothing or several lines is `INVALID_SET`; the result has `generated: false, stdin: true`). The value never appears in output or logs; an existing value stays (`SECRET_KEPT`, with a `hint` naming `--rotate`); an empty one (`token:`, `''`, `[]`) holds no secret and is filled, and so is the sample's placeholder (`<set the postgres URL here or run npx dokebi dev-infra>`, what `config init` lists in `placeholders`). An address (a key `url` or `uri`, or one ending in `Url` or `Uri`) is never generated: without `--stdin` it is `INVALID_SET` with `details.fix` (`config secret dokebi-db.connections.main.options.url --stdin < url.txt`). Example: `config secret dokebi-websocket-hub.secret`. `logging.<path>` is `options.logging` (the @dokebi/logger options); a list item is named by its `name` (else its `type`) or its index (`logging.targets.studio.token`, `logging.targets.3.token`); a name that is not there is `INVALID_SET` with `details.items`. A secret a plugin declares (such as `dokebi-session.secrets`) is already written by `add`; run it after you removed one. `--rotate` replaces an existing value (AI-M05): a list (`dokebi-session.secrets`, `dokebi-csrf.secrets`, `dokebi-auth.secrets`: the first signs, all verify) gets the new secret **first** and keeps the previous ones after it, at most 2 (`kept`); older ones are dropped (`dropped`, warning `SECRETS_DROPPED` with `count`), and fewer are kept when the schema allows fewer items; when the schema takes no list with a kept one (only a new string or a one-item list), every previous secret is dropped and counted the same way. A string is replaced. The result is `secret{path, list, generated, rotated, kept, dropped}` and a next step to restart the app (`dev` restarts on its own); never a value. What a rotation changes: session cookies signed with a kept secret still verify and are re-signed with the new one as they are used (@dokebi/session); CSRF tokens of a kept secret still verify (@dokebi/csrf); auth stamps signed with a kept key still verify, and remember-me and API tokens are never re-signed, so keep the old key for their longest lifetime (@dokebi/auth AI.md §9). A dropped secret (or a replaced string) invalidates what it signed: sessions end, open forms fail once, logins end. An environment override of the option (`secretsEnv`), where it is set, still wins over dokebi.yaml |
|
|
76
76
|
| `dokebi config set <id>.<path>=<value>...` | Non-secret options into dokebi.yaml (the value is JSON when it parses, otherwise text; checked against the plugin schema, only a missing required value becomes `configRequired`). A plugin id that is not installed is `UNKNOWN_PLUGIN`; any other section nothing declares is `UNDECLARED_SECTION` (D-0155: declare it with `npx dokebi settings add <name>`). `logging.<path>` sets `options.logging` (D-0151): with @dokebi/logger every key of its schema (`config set logging.level=debug logging.targets.file.enabled=true`; list items by `name`, else `type`, or index); without it only `logging.level` and `logging.plugins.<plugin>` (core's levels), anything else is `INVALID_SET` with the add command in `fix`. An old option name (D-0071 alias, `dokebi-web.bodyLimit` of `maxBodyBytes`) and its new name both set is `INVALID_PLUGIN_OPTIONS` (`details.errors[]` with `keyword: "conflict"` and the `field`; `fix`: `config unset` the old name), since the plugin refuses both at start; nothing is written. `add` and `upgrade` write no default beside a set old name for the same reason. The results of `config set`, `unset`, `secret` and `diff --write` carry `paths`: for each file it changed, the option paths it `removed`, `set` and `added` (`{ "dokebi.yaml": { removed: ["logging.redact"], set: [], added: [] } }`; a list is one path; never a value). A path outside the ones asked for (named list items count as their list) is `PATH_MISMATCH`: refused, or a warning in a `--dry-run`, which therefore shows exactly what a run would change. Spell paths as the files do (`npx dokebi config show`; the logger's are `logging.<path>`). |
|
|
77
77
|
| `dokebi config unset <id>[.<path>]...` | Removes options ([D-0153](https://git.nyx-zone.com/dokebi/framework/-/blob/main/docs/decisions/D-0153-config-unset.md)) from dokebi.yaml and from dokebi.example.yaml where the sample has them, in one transaction, with the mappings left empty (the plugin section stays): `{ removed: [{ option, files }], changed, warnings?, configRequired? }`. Values are never printed. A path that is not set is the warning `OPTION_NOT_SET` (a repeated call changes nothing); a secret may be removed (warning `SECRET_REMOVED`: what it signed no longer verifies; to replace one use `config secret --rotate`). The plugin need not be installed (a section `dokebi remove` kept). `logging.<path>` removes from `options.logging`, list items by `name` (else `type`) or index (`config unset logging.targets.studio` removes that target); an item that is not there is `OPTION_NOT_SET`. The removal may only leave missing required values (`configRequired`) or issues the options already had (warning `OPTIONS_STILL_INVALID` with the next `hint`); a new issue is `INVALID_PLUGIN_OPTIONS` and nothing is written. Use it for a key a plugin does not know: `INVALID_PLUGIN_OPTIONS` (from add, config set, doctor) names `npx dokebi config unset <id>.<key>` in its `fix`, and a key that moved to another plugin (`dokebi-web.files` → `dokebi-static.mounts`, D-0147) says where it went and the commands that carry it over A section a file still holds as written is that section, never the plugin the name belongs to: in a sample with the old `options.dokebi-logger` (now `options.logging`), `config unset dokebi-logger.level` removes that old key and warns `SECTION_NOT_READ`, and the live `logging.level` stays (AI-M47 report: it removed the live one before); with no such section the plugin id is `PATH_MISMATCH` (name it `logging.<path>`). List indexes and names are of each file as it is before the call, whichever name reaches the section; one item named twice (`logging.targets.studio` and its index) is removed once. A section alone (`config unset dokebi-logger`) removes a whole section nothing reads; a plugin's, the settings' or a core section (`logging`, `reload`) that way is `INVALID_SET` |
|
|
78
|
-
| `dokebi config secrets` | The secret option paths the app needs, **by name, never values** ([D-0184](https://git.nyx-zone.com/dokebi/framework/-/blob/main/docs/decisions/D-0184-production-deploy.md)): `{ file, secretsFile, listed, secrets[{path, source, listed, required}], missing }` from the settings the app reads (`DOKEBI_CONFIG`, else dokebi.yaml) merged with the secrets file `DOKEBI_SECRETS` names. `source` is `config`, `secrets` or `null` (not set: absent, empty or a `<placeholder>`); `listed` means the document's `secrets:` list names it (the app refuses to start without it), `required` that an installed plugin's install plan generates it (`dokebi-session.secrets`). Exit 1 with `ok: false` while `missing` is not empty. Warnings `SECRET_IN_CONFIG_FILE` (with `DOKEBI_SECRETS` set, the settings file still holds a secret) and `SECRETS_PLAN_UNREADABLE` (a plugin's install module could not run: npm install). A secrets file readable by others, or the same option in both files, fails as the app would (`CONFIG_SECRETS_UNSAFE`, `CONFIG_SECRETS_CONFLICT`) |
|
|
78
|
+
| `dokebi config secrets` | The secret option paths the app needs, **by name, never values** ([D-0184](https://git.nyx-zone.com/dokebi/framework/-/blob/main/docs/decisions/D-0184-production-deploy.md)): `{ file, secretsFile, listed, secrets[{path, source, listed, required, type}], missing }` from the settings the app reads (`DOKEBI_CONFIG`, else dokebi.yaml) merged with the secrets file `DOKEBI_SECRETS` names. `source` is `config`, `secrets` or `null` (not set: absent, empty or a `<placeholder>`); `listed` means the document's `secrets:` list names it (the app refuses to start without it), `required` that an installed plugin's install plan generates it (`dokebi-session.secrets`). `type` is the option's JSON Schema type (`string`; `array` for a list such as `dokebi-session.secrets`, `dokebi-auth.secrets`, `dokebi-csrf.secrets`, `dokebi-mail.secrets`; several as a list; `null` without a schema): a tool that writes the secrets file writes an `array` as a YAML list, even of one value (a text there is `INVALID_PLUGIN_OPTIONS`). Exit 1 with `ok: false` while `missing` is not empty. Warnings `SECRET_IN_CONFIG_FILE` (with `DOKEBI_SECRETS` set, the settings file still holds a secret) and `SECRETS_PLAN_UNREADABLE` (a plugin's install module could not run: npm install). A secrets file readable by others, or the same option in both files, fails as the app would (`CONFIG_SECRETS_UNSAFE`, `CONFIG_SECRETS_CONFLICT`) |
|
|
79
79
|
| `dokebi deploy init [--target <systemd\|compose>] [--instance <name>]... [--port <n>] [--dry-run]` | **An existing project made ready for a server** (D-0184, §11): per instance (`production` port 3000, `staging` 3001, another name needs `--port`; two instances never share a port: `PORT_IN_USE`) `deploy/<instance>/dokebi.example.yaml` (the sample without its secrets, with the instance's web host/port/trustProxy, storage folder and a `secrets:` list), `deploy/<instance>/secrets.example.yaml` (the secret paths with placeholders), `deploy/systemd/<app>-<instance>.service` (or `deploy/compose/<instance>.yaml` with `--target compose`, and Dockerfile/.dockerignore when missing), with @dokebi/db also `deploy/systemd/<app>-<instance>-backup.service`/`.timer`, `deploy/backup.mjs` and `deploy/restore-drill.mjs`, and `DEPLOY.md`. Result `{ app, target, instances[{instance, port, unit, settings, secrets, code, data, logs, backups, database}], secrets, changed, unchanged, kept }`. A second run changes nothing; a file you edited since is kept (`kept`, warning `DEPLOY_FILE_KEPT`); an untouched one is rewritten (DEPLOY.md gains a new instance). Errors `NOT_A_PROJECT`, `APP_NAME_INVALID` (package.json name), `EXAMPLE_MISSING`, `PORT_REQUIRED`, `PORT_IN_USE`, `OPTIONS_SCHEMA_UNREADABLE` (npm install first: without a plugin's schema its secrets cannot be told apart) |
|
|
80
80
|
| `dokebi deploy proxy --caddy\|--nginx --domain <host>... [--instance <name>] [--dry-run]` | The reverse proxy file of an instance (D-0184): `deploy/proxy/<app>-<instance>.Caddyfile` or `.nginx.conf`, from `deploy/<instance>/dokebi.example.yaml` (port, health routes, body limit, request timeout): TLS (Caddy's automatic certificates; nginx with certbot's paths and an http → https redirect), `X-Forwarded-For/Proto/Host`, WebSocket upgrades, unbuffered SSE (the app sends `X-Accel-Buffering: no`) and a long read timeout when the app keeps streams (SSE, WebSocket, MCP), `/health/live` and `/health/ready` passed through uncached, the app's own `Cache-Control` and compression left as they are. Warnings `TRUST_PROXY_MISSING` (the sample's `dokebi-web.trustProxy` lacks 127.0.0.1 or ::1) and `DOMAIN_NOT_MAPPED` (a domain not in `dokebi-app.domains`). Errors `WEB_NOT_INSTALLED`, `INSTANCE_UNKNOWN` (deploy init first) |
|
|
81
81
|
| `dokebi dev-infra [up\|start\|stop\|down\|status] [--file <path>] [--db-port <n>] [--dry-run]` | Without an action: prepare compose.yaml and the database URL in dokebi.yaml; no .env is generated. With an action: run Docker Compose using the YAML password in the child environment only. Repeat --file for additional Compose files. down retains data volumes. --db-port and --dry-run apply only to preparation. On a kept compose.yaml (`COMPOSE_KEPT`, its published port is the URL's) `--db-port <n>` changes only the published host port of `services.db` in it (every other byte of the file stays) and the URL in dokebi.yaml, with the warning `COMPOSE_PORT_CHANGED` (`hint`: `dev-infra up` recreates a running container on the new port and keeps its data volume); a taken port is `PORT_IN_USE`. A port entry the CLI cannot read (a variable, a range) refuses `--db-port` with `COMPOSE_PORT_UNKNOWN` (`details.file`, `details.fix`: set the entry yourself) and nothing is written; without the flag the run keeps the URL's port and warns `COMPOSE_PORT_UNKNOWN`. A MongoDB is a single-node replica set (`rs0`, D-0103): the generated mongod gets `--replSet rs0` and a key file made inside the container at each start (mongod needs one for a replica set with authentication), the healthcheck initiates the set once and passes when the member is the writable primary (so `up` returns when transactions work), and the URL ends with `replicaSet=rs0&directConnection=true`; `service.replicaSet` says so. A kept compose.yaml whose server is standalone (written before) keeps a standalone URL and gives the warning `DEV_INFRA_STANDALONE`, whose `hint` is the switch: `dev-infra down`, move compose.yaml away, `dev-infra --db-port <its port>`, `dev-infra up` (password and data volume kept). `DEV_INFRA_CONFIG_INVALID` means the main database URL is invalid/unset; `DEV_INFRA_FAILED` means the Compose action failed. |
|
package/catalog.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$comment": "Generated by scripts/catalog/build.ts from package.json dokebiCatalog fields and packages/cli/catalog-src (D-0065). Do not edit; run node scripts/catalog/build.ts.",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"cli": "0.4.
|
|
4
|
+
"cli": "0.4.28",
|
|
5
5
|
"core": {
|
|
6
6
|
"package": "@dokebi/core",
|
|
7
|
-
"version": "0.4.
|
|
7
|
+
"version": "0.4.4"
|
|
8
8
|
},
|
|
9
9
|
"capabilities": {
|
|
10
10
|
"admin.ui": {
|
|
@@ -713,7 +713,7 @@
|
|
|
713
713
|
{
|
|
714
714
|
"id": null,
|
|
715
715
|
"package": "@dokebi/cli",
|
|
716
|
-
"version": "0.4.
|
|
716
|
+
"version": "0.4.28",
|
|
717
717
|
"kind": "cli",
|
|
718
718
|
"summary": "Commands to create a project, browse the plugin catalog, run recipes, install plugins and verify (dokebi)",
|
|
719
719
|
"description": "Project scaffolding, plugin catalog, recipes, install and verification CLI for Dokebi.",
|
|
@@ -860,7 +860,7 @@
|
|
|
860
860
|
{
|
|
861
861
|
"id": null,
|
|
862
862
|
"package": "@dokebi/core",
|
|
863
|
-
"version": "0.4.
|
|
863
|
+
"version": "0.4.4",
|
|
864
864
|
"kind": "core",
|
|
865
865
|
"summary": "Application core: settings loading, plugin registration, lifecycle, logger and shared libraries (required)",
|
|
866
866
|
"description": "Minimal composable Node.js application and plugin lifecycle framework.",
|
|
@@ -2111,7 +2111,7 @@
|
|
|
2111
2111
|
{
|
|
2112
2112
|
"id": "dokebi-oauth",
|
|
2113
2113
|
"package": "@dokebi/oauth",
|
|
2114
|
-
"version": "0.1.
|
|
2114
|
+
"version": "0.1.1",
|
|
2115
2115
|
"kind": "plugin",
|
|
2116
2116
|
"summary": "OAuth 2.1 authorization server on @dokebi/auth tokens: authorization code with PKCE S256, refresh rotation and reuse detection, revocation, client ID metadata documents, protected resource metadata and protected routes",
|
|
2117
2117
|
"description": "OAuth 2.1 authorization server for @dokebi/web on @dokebi/auth tokens: authorization code with PKCE, refresh rotation, revocation, client ID metadata documents, protected resource metadata and route protection.",
|
|
@@ -2784,7 +2784,7 @@
|
|
|
2784
2784
|
{
|
|
2785
2785
|
"id": "dokebi-seal",
|
|
2786
2786
|
"package": "@dokebi/seal",
|
|
2787
|
-
"version": "0.4.
|
|
2787
|
+
"version": "0.4.3",
|
|
2788
2788
|
"kind": "plugin",
|
|
2789
2789
|
"summary": "Seals WebSocket frames and HTTP bodies (obfuscation, replay protection) with a browser client (WASM)",
|
|
2790
2790
|
"description": "Payload sealing (obfuscation, replay defence, in-transit encryption) for seal hosts: @dokebi/websocket frames and @dokebi/web HTTP bodies, with browser socket, fetch and Inertia clients and their WASM loader",
|
package/dist/config-secrets.d.ts
CHANGED
|
@@ -8,6 +8,12 @@ export interface SecretEntry {
|
|
|
8
8
|
readonly listed: boolean;
|
|
9
9
|
/** A secret a plugin's install plan generates (dokebi-session.secrets): it needs one. */
|
|
10
10
|
readonly required: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* The JSON Schema type of the option, from its options schema (`string`; `array` for a list
|
|
13
|
+
* such as dokebi-session.secrets, written as a YAML list in the secrets file), several types as
|
|
14
|
+
* a list, null when no schema says (a section without one).
|
|
15
|
+
*/
|
|
16
|
+
readonly type: string | readonly string[] | null;
|
|
11
17
|
}
|
|
12
18
|
export interface ConfigSecretsWarning {
|
|
13
19
|
readonly code: 'SECRET_IN_CONFIG_FILE' | 'SECRETS_PLAN_UNREADABLE';
|
|
@@ -30,6 +36,8 @@ export interface ConfigSecretsResult {
|
|
|
30
36
|
why: string;
|
|
31
37
|
}>;
|
|
32
38
|
}
|
|
39
|
+
/** The JSON Schema types the option at `keys` may have (properties, records, anyOf/oneOf). */
|
|
40
|
+
export declare function schemaTypes(schema: unknown, keys: readonly string[]): Set<string>;
|
|
33
41
|
/**
|
|
34
42
|
* The secrets the installed plugins generate at install (their plans' `configSecrets`, such as
|
|
35
43
|
* `dokebi-session.secrets`): each plugin needs one to start.
|
package/dist/config-secrets.js
CHANGED
|
@@ -13,6 +13,7 @@ import { optionsKey } from './manifest.js';
|
|
|
13
13
|
import { dependencyMap } from './npm-client.js';
|
|
14
14
|
import { planLifecycle } from './plugin-lifecycle.js';
|
|
15
15
|
import { readState } from './project-state.js';
|
|
16
|
+
import { reloadSchemas } from './reloadable.js';
|
|
16
17
|
function valueAt(options, path) {
|
|
17
18
|
let value = options;
|
|
18
19
|
for (const key of path) {
|
|
@@ -33,6 +34,40 @@ function empty(value) {
|
|
|
33
34
|
(Array.isArray(value) && (value.length === 0 || value.every(isPlaceholder))) ||
|
|
34
35
|
(typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 0));
|
|
35
36
|
}
|
|
37
|
+
/** The JSON Schema types the option at `keys` may have (properties, records, anyOf/oneOf). */
|
|
38
|
+
export function schemaTypes(schema, keys) {
|
|
39
|
+
const types = new Set();
|
|
40
|
+
const walk = (node, rest, depth) => {
|
|
41
|
+
if (!node || typeof node !== 'object' || depth > 32)
|
|
42
|
+
return;
|
|
43
|
+
const value = node;
|
|
44
|
+
for (const branch of ['anyOf', 'oneOf', 'allOf'])
|
|
45
|
+
if (Array.isArray(value[branch]))
|
|
46
|
+
for (const item of value[branch])
|
|
47
|
+
walk(item, rest, depth + 1);
|
|
48
|
+
if (!rest.length) {
|
|
49
|
+
if (typeof value.type === 'string')
|
|
50
|
+
types.add(value.type);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const [key, ...tail] = rest;
|
|
54
|
+
const properties = value.properties;
|
|
55
|
+
if (properties && Object.hasOwn(properties, key))
|
|
56
|
+
walk(properties[key], tail, depth + 1);
|
|
57
|
+
else if (value.additionalProperties && typeof value.additionalProperties === 'object')
|
|
58
|
+
walk(value.additionalProperties, tail, depth + 1);
|
|
59
|
+
else if (/^\d+$/.test(key) && value.items)
|
|
60
|
+
walk(value.items, tail, depth + 1);
|
|
61
|
+
};
|
|
62
|
+
walk(schema, keys, 0);
|
|
63
|
+
return types;
|
|
64
|
+
}
|
|
65
|
+
/** The type of the option at `path` as `SecretEntry.type` says it. */
|
|
66
|
+
function typeOf(schemas, path) {
|
|
67
|
+
const [section = '', ...rest] = path.split('.');
|
|
68
|
+
const types = [...schemaTypes(schemas.get(section), rest)].sort();
|
|
69
|
+
return types.length === 0 ? null : types.length === 1 ? types[0] : types;
|
|
70
|
+
}
|
|
36
71
|
/** The same failure as the app's start, as a CLI error with the paths and no values. */
|
|
37
72
|
function configFailure(error) {
|
|
38
73
|
return new CliError(error.code, error.message, {
|
|
@@ -126,6 +161,9 @@ export async function configSecrets(root, env = process.env) {
|
|
|
126
161
|
: (await configShow(root, undefined, { source: secretsText, file: secretsFile }))
|
|
127
162
|
.secrets);
|
|
128
163
|
const planned = await requiredSecretPaths(root, document.options);
|
|
164
|
+
const state = readState(parseObject(await readFile(join(root, '.dokebi/state.json'), 'utf8'), 'state'));
|
|
165
|
+
// The types tell a tool that writes the secrets file a list from a text (Studio v2, 11-01).
|
|
166
|
+
const schemas = await reloadSchemas(root, state.plugins, state.settings ?? []);
|
|
129
167
|
const required = new Set(planned.paths);
|
|
130
168
|
const listed = document.secrets ?? [];
|
|
131
169
|
const paths = [...new Set([...fromConfig, ...fromSecrets, ...listed, ...required])].sort();
|
|
@@ -136,6 +174,7 @@ export async function configSecrets(root, env = process.env) {
|
|
|
136
174
|
source: !set ? null : !empty(valueAt(secretOptions, path.split('.'))) ? 'secrets' : 'config',
|
|
137
175
|
listed: listed.includes(path),
|
|
138
176
|
required: required.has(path),
|
|
177
|
+
type: typeOf(schemas, path),
|
|
139
178
|
};
|
|
140
179
|
});
|
|
141
180
|
const missing = secrets.filter((entry) => entry.source === null).map((entry) => entry.path);
|
|
@@ -180,7 +219,7 @@ export async function configSecrets(root, env = process.env) {
|
|
|
180
219
|
}
|
|
181
220
|
/** The result for people: one line per path. */
|
|
182
221
|
export function configSecretsText(result) {
|
|
183
|
-
const lines = result.secrets.map((entry) => `${entry.source === null ? 'MISSING' : entry.source.padEnd(7)} ${entry.path}${entry.listed ? ' (listed)' : ''}${entry.required ? ' (required)' : ''}`);
|
|
222
|
+
const lines = result.secrets.map((entry) => `${entry.source === null ? 'MISSING' : entry.source.padEnd(7)} ${entry.path}${entry.type === 'array' ? ' (a list)' : ''}${entry.listed ? ' (listed)' : ''}${entry.required ? ' (required)' : ''}`);
|
|
184
223
|
return `${lines.join('\n') || 'no secret options'}\n${result.missing.length ? `missing: ${result.missing.join(', ')}` : 'no secret missing'}${(result.warnings ?? [])
|
|
185
224
|
.map((warning) => `\nwarning: ${warning.code} (${warning.path}): ${warning.hint}`)
|
|
186
225
|
.join('')}\n`;
|
package/dist/deploy.js
CHANGED
|
@@ -6,7 +6,7 @@ import { dirname, join } from 'node:path';
|
|
|
6
6
|
import { parseConfig } from '@dokebi/config';
|
|
7
7
|
import { isMap, isScalar, isSeq, parseDocument } from 'yaml';
|
|
8
8
|
import { isPlaceholder } from './config-init.js';
|
|
9
|
-
import { requiredSecretPaths, unreadableWarning } from './config-secrets.js';
|
|
9
|
+
import { requiredSecretPaths, schemaTypes, unreadableWarning } from './config-secrets.js';
|
|
10
10
|
import { configShow } from './config-show.js';
|
|
11
11
|
import { CONFIG_FILE, renderConfig } from './configuration.js';
|
|
12
12
|
import { BACKUP_SCRIPT, RESTORE_DRILL_SCRIPT } from './deploy-scripts.js';
|
|
@@ -156,34 +156,6 @@ function valueAt(options, path) {
|
|
|
156
156
|
}
|
|
157
157
|
return value;
|
|
158
158
|
}
|
|
159
|
-
/** The JSON Schema types the option at `keys` may have (properties, records, anyOf/oneOf). */
|
|
160
|
-
function schemaTypes(schema, keys) {
|
|
161
|
-
const types = new Set();
|
|
162
|
-
const walk = (node, rest, depth) => {
|
|
163
|
-
if (!node || typeof node !== 'object' || depth > 32)
|
|
164
|
-
return;
|
|
165
|
-
const value = node;
|
|
166
|
-
for (const branch of ['anyOf', 'oneOf', 'allOf'])
|
|
167
|
-
if (Array.isArray(value[branch]))
|
|
168
|
-
for (const item of value[branch])
|
|
169
|
-
walk(item, rest, depth + 1);
|
|
170
|
-
if (!rest.length) {
|
|
171
|
-
if (typeof value.type === 'string')
|
|
172
|
-
types.add(value.type);
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
const [key, ...tail] = rest;
|
|
176
|
-
const properties = value.properties;
|
|
177
|
-
if (properties && Object.hasOwn(properties, key))
|
|
178
|
-
walk(properties[key], tail, depth + 1);
|
|
179
|
-
else if (value.additionalProperties && typeof value.additionalProperties === 'object')
|
|
180
|
-
walk(value.additionalProperties, tail, depth + 1);
|
|
181
|
-
else if (/^\d+$/.test(key) && value.items)
|
|
182
|
-
walk(value.items, tail, depth + 1);
|
|
183
|
-
};
|
|
184
|
-
walk(schema, keys, 0);
|
|
185
|
-
return types;
|
|
186
|
-
}
|
|
187
159
|
/** A placeholder for a secret of the secrets sample, saying what goes there (never a value). */
|
|
188
160
|
function placeholderFor(path, at, current, types) {
|
|
189
161
|
const kind = typeof current === 'string'
|
package/dist/output.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export declare const CLI_VERSION = "0.4.
|
|
1
|
+
export declare const CLI_VERSION = "0.4.28";
|
|
2
2
|
export declare function envelope(command: string, result: unknown, ok?: boolean): object;
|
|
3
3
|
export declare function failure(command: string, error: unknown, mutationStarted: boolean): object;
|
package/dist/output.js
CHANGED