dropsh 0.5.8 → 0.6.1
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 +156 -3
- package/dist/src/commands/create.d.ts +6 -1
- package/dist/src/commands/create.js +1 -2
- package/dist/src/commands/update.d.ts +13 -1
- package/dist/src/commands/update.js +13 -5
- package/dist/src/core/auth/types.d.ts +17 -4
- package/dist/src/core/jsonapi/client.d.ts +3 -1
- package/dist/src/core/jsonapi/client.js +31 -13
- package/dist/src/core/params/from-parameters.d.ts +12 -0
- package/dist/src/core/params/from-parameters.js +209 -0
- package/dist/src/core/params/help.d.ts +46 -0
- package/dist/src/core/params/help.js +148 -0
- package/dist/src/core/params/parse-args.d.ts +18 -0
- package/dist/src/core/params/parse-args.js +75 -0
- package/dist/src/core/params/schema-fields.d.ts +20 -0
- package/dist/src/core/params/schema-fields.js +65 -0
- package/dist/src/core/plugin.d.ts +8 -1
- package/dist/src/core/request-hooks.d.ts +4 -0
- package/dist/src/core/request-hooks.js +26 -0
- package/dist/src/errors.d.ts +3 -0
- package/dist/src/errors.js +12 -0
- package/dist/src/index.js +138 -44
- package/dist/src/plugin-api.d.ts +8 -3
- package/dist/src/plugin-api.js +3 -1
- package/package.json +3 -3
package/dist/src/index.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { parseArgs } from "node:util";
|
|
3
3
|
import { Command } from "commander";
|
|
4
|
+
import { readDataArg } from "./commands/_data.js";
|
|
4
5
|
import { runAuthLogin, runAuthLogout, runAuthStatus, runAuthUse } from "./commands/auth.js";
|
|
5
6
|
import { runCreate } from "./commands/create.js";
|
|
6
7
|
import { runDelete } from "./commands/delete.js";
|
|
7
8
|
import { runRead } from "./commands/read.js";
|
|
8
9
|
import { applyOperationSchemaPlugins, operationHookPluginIds, runSchema, SCHEMA_PIPELINE_VERSION, schemaCacheMetadataMatches, siteCacheRoot, } from "./commands/schema.js";
|
|
9
10
|
import { runSearch } from "./commands/search.js";
|
|
10
|
-
import { runUpdate } from "./commands/update.js";
|
|
11
|
+
import { assertUpdateTarget, runUpdate, } from "./commands/update.js";
|
|
11
12
|
import { runUploadFile } from "./commands/upload-file.js";
|
|
12
13
|
import { collectProviders, defaultProvider, providerById, sessionlessProvider, } from "./core/auth/registry.js";
|
|
13
14
|
import { readProfiles, writeProfile } from "./core/auth/session-store.js";
|
|
@@ -17,10 +18,14 @@ import { createPrompt } from "./core/cli/prompt.js";
|
|
|
17
18
|
import { loadConfig } from "./core/config.js";
|
|
18
19
|
import { createHttpClient } from "./core/http.js";
|
|
19
20
|
import { createJsonApiClient } from "./core/jsonapi/client.js";
|
|
21
|
+
import { buildPayloadFromParameters } from "./core/params/from-parameters.js";
|
|
22
|
+
import { describeFields, FIELD_PARAMETER_HELP, renderFieldsTable } from "./core/params/help.js";
|
|
23
|
+
import { parseFieldArgs } from "./core/params/parse-args.js";
|
|
24
|
+
import { composeRequestHooks } from "./core/request-hooks.js";
|
|
20
25
|
import { fetchJsonSchema } from "./core/schema/jsonschema-source.js";
|
|
21
26
|
import { toOperationVariant } from "./core/schema/to-jsonschema.js";
|
|
22
27
|
import { validatePayload } from "./core/schema/validate.js";
|
|
23
|
-
import { AuthError, ConfigError, exitCodeFor } from "./errors.js";
|
|
28
|
+
import { AuthError, ConfigError, exitCodeFor, ValidationError } from "./errors.js";
|
|
24
29
|
function resolveConfigPath(override) {
|
|
25
30
|
return override ?? process.env.DROPSH_CONFIG ?? "dropsh.config.js";
|
|
26
31
|
}
|
|
@@ -119,11 +124,18 @@ async function defaultContext(configPath, profile) {
|
|
|
119
124
|
now: Date.now,
|
|
120
125
|
...(profile !== undefined ? { profile } : {}),
|
|
121
126
|
});
|
|
127
|
+
const pluginContext = {
|
|
128
|
+
http,
|
|
129
|
+
auth,
|
|
130
|
+
baseUrl: cfg.site.base_url,
|
|
131
|
+
jsonapiPrefix: cfg.site.jsonapi_prefix,
|
|
132
|
+
};
|
|
122
133
|
const client = createJsonApiClient({
|
|
123
134
|
baseUrl: cfg.site.base_url,
|
|
124
135
|
prefix: cfg.site.jsonapi_prefix,
|
|
125
136
|
http,
|
|
126
137
|
auth,
|
|
138
|
+
alterRequest: composeRequestHooks(cfg.plugins, pluginContext),
|
|
127
139
|
});
|
|
128
140
|
return {
|
|
129
141
|
client,
|
|
@@ -193,6 +205,73 @@ export function buildProgram(opts = {}) {
|
|
|
193
205
|
throw new ConfigError(`Unknown format '${f}'. Available: ${["json", ...renderers.map((r) => r.id)].join(", ")}`);
|
|
194
206
|
}
|
|
195
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Print a resolved schema as its field listing: a table for a human, or the
|
|
210
|
+
* same data verbatim on an explicit `--format json`. Shared by `create`,
|
|
211
|
+
* `update` and `schema`, so the three cannot drift.
|
|
212
|
+
*/
|
|
213
|
+
function emitFieldListing(schema, rctx) {
|
|
214
|
+
const described = describeFields(schema);
|
|
215
|
+
if (program.getOptionValueSource("format") === "default") {
|
|
216
|
+
stdout(`${renderFieldsTable(described)}\n`);
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
output.emit(described, rctx);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* The write-command wiring `create` and `update` share: decide `--data` vs
|
|
224
|
+
* field parameters, fetch the operation schema at most once, build the payload
|
|
225
|
+
* from the parameters, and hand back the args/deps both `runCreate` and
|
|
226
|
+
* `runUpdate` accept. The two commands differ only in how they identify their
|
|
227
|
+
* target, which is why that part stays in each action.
|
|
228
|
+
*/
|
|
229
|
+
async function writeWiring(input) {
|
|
230
|
+
const { ctx, options } = input;
|
|
231
|
+
// --fields is a discovery request, not a write: resolve the schema through
|
|
232
|
+
// the normal fetch-and-cache pipeline, print the parameters, send nothing.
|
|
233
|
+
// It reads as a table because a human asked what the fields are; an explicit
|
|
234
|
+
// --format json hands back the same data verbatim for a caller to consume.
|
|
235
|
+
if (options.fields === true) {
|
|
236
|
+
emitFieldListing(await loadOrFetchSchema(ctx, input.schemaTarget, input.operation), input.rctx);
|
|
237
|
+
return "listed";
|
|
238
|
+
}
|
|
239
|
+
// Exactly one route produces the document, and it is settled here so the
|
|
240
|
+
// command itself only ever receives a finished payload.
|
|
241
|
+
const fields = parseFieldArgs(input.tokens);
|
|
242
|
+
if (options.data !== undefined && fields.length > 0) {
|
|
243
|
+
throw new ValidationError("--data and field parameters are mutually exclusive; use one or the other");
|
|
244
|
+
}
|
|
245
|
+
if (options.data === undefined && fields.length === 0) {
|
|
246
|
+
throw new ValidationError("provide either --data or field parameters (--<field> <value>)");
|
|
247
|
+
}
|
|
248
|
+
let cached;
|
|
249
|
+
const schema = async () => (cached ??= await loadOrFetchSchema(ctx, input.schemaTarget, input.operation));
|
|
250
|
+
let payload;
|
|
251
|
+
if (options.data !== undefined) {
|
|
252
|
+
payload = await readDataArg(options.data);
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
input.guard?.();
|
|
256
|
+
payload = buildPayloadFromParameters({
|
|
257
|
+
schema: await schema(),
|
|
258
|
+
parameters: fields,
|
|
259
|
+
operation: input.operation,
|
|
260
|
+
...(input.id !== undefined ? { id: input.id } : {}),
|
|
261
|
+
resourceType: input.resourceType,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
const args = { payload };
|
|
265
|
+
if (options.dryRun !== undefined)
|
|
266
|
+
args.dryRun = options.dryRun;
|
|
267
|
+
if (options.validate === false)
|
|
268
|
+
args.noValidate = true;
|
|
269
|
+
const deps = { client: ctx.client, emit: (v) => output.emit(v, input.rctx) };
|
|
270
|
+
if (!args.noValidate) {
|
|
271
|
+
deps.validate = async (payload, t) => validatePayload(await schema(), payload, t);
|
|
272
|
+
}
|
|
273
|
+
return { args, deps };
|
|
274
|
+
}
|
|
196
275
|
function assertJsonOnly(command) {
|
|
197
276
|
const f = currentFormat();
|
|
198
277
|
if (f !== "json") {
|
|
@@ -339,54 +418,61 @@ Example:
|
|
|
339
418
|
.command("create <entity_type>")
|
|
340
419
|
.description("Create an entity of given type/bundle")
|
|
341
420
|
.requiredOption("--bundle <bundle>")
|
|
342
|
-
.
|
|
421
|
+
.option("--data <json>", "inline JSON or @path")
|
|
343
422
|
.option("--dry-run")
|
|
344
423
|
.option("--no-validate", "skip client-side schema validation")
|
|
345
|
-
.
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
if (o.validate === false)
|
|
350
|
-
args.noValidate = true;
|
|
424
|
+
.option("--fields", "list this bundle's field parameters and exit")
|
|
425
|
+
.allowUnknownOption()
|
|
426
|
+
.addHelpText("after", FIELD_PARAMETER_HELP)
|
|
427
|
+
.action((entityType, o, cmd) => {
|
|
351
428
|
return run(async (ctx) => {
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
|
|
429
|
+
const wiring = await writeWiring({
|
|
430
|
+
ctx,
|
|
431
|
+
operation: "create",
|
|
432
|
+
schemaTarget: `${entityType}/${o.bundle}`,
|
|
433
|
+
resourceType: `${entityType}--${o.bundle}`,
|
|
434
|
+
tokens: cmd.args.slice(1),
|
|
435
|
+
rctx: { command: "create", entityType, bundle: o.bundle },
|
|
436
|
+
options: o,
|
|
437
|
+
});
|
|
438
|
+
if (wiring === "listed")
|
|
439
|
+
return;
|
|
440
|
+
await runCreate({ entityType, bundle: o.bundle, ...wiring.args }, wiring.deps);
|
|
361
441
|
}, assertRenderable);
|
|
362
442
|
});
|
|
363
443
|
program
|
|
364
444
|
.command("update <target>")
|
|
365
445
|
.description("Update an existing entity")
|
|
366
|
-
.
|
|
446
|
+
.option("--data <json>", "inline JSON or @path")
|
|
367
447
|
.option("--dry-run")
|
|
368
448
|
.option("--no-validate", "skip client-side schema validation")
|
|
369
|
-
.
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
if (o.validate === false)
|
|
374
|
-
args.noValidate = true;
|
|
449
|
+
.option("--fields", "list this bundle's field parameters and exit")
|
|
450
|
+
.allowUnknownOption()
|
|
451
|
+
.addHelpText("after", FIELD_PARAMETER_HELP)
|
|
452
|
+
.action((target, o, cmd) => {
|
|
375
453
|
return run(async (ctx) => {
|
|
376
|
-
const [entityType, bundle] = target.split("/");
|
|
454
|
+
const [entityType, bundle, id] = target.split("/");
|
|
377
455
|
const rctx = { command: "update", target };
|
|
378
456
|
if (entityType !== undefined)
|
|
379
457
|
rctx.entityType = entityType;
|
|
380
458
|
if (bundle !== undefined)
|
|
381
459
|
rctx.bundle = bundle;
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
460
|
+
const wiring = await writeWiring({
|
|
461
|
+
ctx,
|
|
462
|
+
operation: "update",
|
|
463
|
+
schemaTarget: `${entityType}/${bundle}`,
|
|
464
|
+
resourceType: `${entityType}--${bundle}`,
|
|
465
|
+
tokens: cmd.args.slice(1),
|
|
466
|
+
rctx,
|
|
467
|
+
options: o,
|
|
468
|
+
...(id !== undefined ? { id } : {}),
|
|
469
|
+
// Guard the target before building, so a malformed one is reported by
|
|
470
|
+
// its own message rather than as a missing data.id.
|
|
471
|
+
guard: () => assertUpdateTarget(target),
|
|
472
|
+
});
|
|
473
|
+
if (wiring === "listed")
|
|
474
|
+
return;
|
|
475
|
+
await runUpdate({ target, ...wiring.args }, wiring.deps);
|
|
390
476
|
}, assertRenderable);
|
|
391
477
|
});
|
|
392
478
|
program
|
|
@@ -418,21 +504,29 @@ Example:
|
|
|
418
504
|
.description("Catalog (no target) or JSON Schema for <entity>/<bundle>")
|
|
419
505
|
.option("--for <op>", "create|update", "create")
|
|
420
506
|
.option("--refresh", "bypass cache for this call")
|
|
507
|
+
.option("--fields", "list the target's field parameters instead of its schema")
|
|
421
508
|
.action((target, o) => {
|
|
422
509
|
const operation = o.for === "update" ? "update" : "create";
|
|
423
510
|
const schemaArgs = target !== undefined
|
|
424
511
|
? { target, operation, refresh: Boolean(o.refresh) }
|
|
425
512
|
: { operation, refresh: Boolean(o.refresh) };
|
|
426
|
-
return run((ctx) =>
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
513
|
+
return run((ctx) => {
|
|
514
|
+
if (o.fields === true && target === undefined) {
|
|
515
|
+
throw new ValidationError("--fields needs a target: dropsh schema <entity_type>/<bundle> --fields");
|
|
516
|
+
}
|
|
517
|
+
// --fields is the same schema, viewed as its parameters: swapping the
|
|
518
|
+
// emitter inherits runSchema's caching and --refresh untouched.
|
|
519
|
+
return runSchema(schemaArgs, {
|
|
520
|
+
http: ctx.http,
|
|
521
|
+
auth: ctx.auth,
|
|
522
|
+
baseUrl: ctx.baseUrl,
|
|
523
|
+
jsonapiPrefix: ctx.jsonapiPrefix,
|
|
524
|
+
cwd: ctx.cwd,
|
|
525
|
+
emit: o.fields === true ? (schema) => emitFieldListing(schema) : output.emit,
|
|
526
|
+
warn: (m) => stderr(`${m}\n`),
|
|
527
|
+
plugins: ctx.plugins,
|
|
528
|
+
});
|
|
529
|
+
}, () => assertJsonOnly("schema"));
|
|
436
530
|
});
|
|
437
531
|
const auth = program.command("auth").description("Manage authentication");
|
|
438
532
|
auth
|
package/dist/src/plugin-api.d.ts
CHANGED
|
@@ -7,12 +7,17 @@ export type { Config, SiteConfig } from "./core/config.js";
|
|
|
7
7
|
export { loadConfig } from "./core/config.js";
|
|
8
8
|
export type { HttpClient, HttpRequest } from "./core/http.js";
|
|
9
9
|
export { createHttpClient } from "./core/http.js";
|
|
10
|
-
export type { JsonApiClient, JsonApiOptions, ResourceWriteBody, } from "./core/jsonapi/client.js";
|
|
10
|
+
export type { DropSHOperation, JsonApiClient, JsonApiOptions, ResourceWriteBody, } from "./core/jsonapi/client.js";
|
|
11
11
|
export { createJsonApiClient } from "./core/jsonapi/client.js";
|
|
12
12
|
export type { Collection, FilterOp } from "./core/jsonapi/collection.js";
|
|
13
13
|
export type { JsonApiResourceObject, Resource, } from "./core/jsonapi/resource.js";
|
|
14
14
|
export type { JsonApiDocument, JsonApiResource } from "./core/jsonapi/types.js";
|
|
15
|
-
export type {
|
|
15
|
+
export type { BuildPayloadInput } from "./core/params/from-parameters.js";
|
|
16
|
+
export { buildPayloadFromParameters } from "./core/params/from-parameters.js";
|
|
17
|
+
export type { RawParameter } from "./core/params/parse-args.js";
|
|
18
|
+
export type { FieldDescriptor, SchemaFieldIndex } from "./core/params/schema-fields.js";
|
|
19
|
+
export { indexSchemaFields, propertiesOf } from "./core/params/schema-fields.js";
|
|
20
|
+
export type { DropSHPlugin, PluginContext, PluginDescriptor, RequestContext, SchemaOperation, } from "./core/plugin.js";
|
|
16
21
|
export { composePlugins } from "./core/plugin.js";
|
|
17
|
-
export { AuthError, ConfigError, HttpError } from "./errors.js";
|
|
22
|
+
export { AuthError, ConfigError, HttpError, PluginError } from "./errors.js";
|
|
18
23
|
export type { CommandContext } from "./index.js";
|
package/dist/src/plugin-api.js
CHANGED
|
@@ -3,5 +3,7 @@ export { indexIncluded, isInteractive } from "./core/cli/render.js";
|
|
|
3
3
|
export { loadConfig } from "./core/config.js";
|
|
4
4
|
export { createHttpClient } from "./core/http.js";
|
|
5
5
|
export { createJsonApiClient } from "./core/jsonapi/client.js";
|
|
6
|
+
export { buildPayloadFromParameters } from "./core/params/from-parameters.js";
|
|
7
|
+
export { indexSchemaFields, propertiesOf } from "./core/params/schema-fields.js";
|
|
6
8
|
export { composePlugins } from "./core/plugin.js";
|
|
7
|
-
export { AuthError, ConfigError, HttpError } from "./errors.js";
|
|
9
|
+
export { AuthError, ConfigError, HttpError, PluginError } from "./errors.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dropsh",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Entity-agnostic helper CLI for Drupal 11 JSON:API, used by the dropsh Claude skill for editorial publishing workflows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -66,8 +66,8 @@
|
|
|
66
66
|
"test:watch": "vitest",
|
|
67
67
|
"test:integration": "pnpm run build && pnpm run build:packages && vitest run -c vitest.config.integration.ts",
|
|
68
68
|
"typecheck": "pnpm run build:packages && tsc --noEmit && pnpm -r run typecheck",
|
|
69
|
-
"lint": "biome check src/ packages/ plugins/ tests/integrations/helpers/ tests/integrations/schema/ tests/integrations/*.ts",
|
|
70
|
-
"lint:fix": "biome check --write src/ packages/ plugins/ tests/integrations/helpers/ tests/integrations/schema/ tests/integrations/*.ts",
|
|
69
|
+
"lint": "biome check src/ packages/ plugins/ tests/integrations/helpers/ tests/integrations/params/ tests/integrations/schema/ tests/integrations/*.ts",
|
|
70
|
+
"lint:fix": "biome check --write src/ packages/ plugins/ tests/integrations/helpers/ tests/integrations/params/ tests/integrations/schema/ tests/integrations/*.ts",
|
|
71
71
|
"drupal:up": "bash tests/integrations/bin/drupal-up.sh",
|
|
72
72
|
"drupal:down": "bash tests/integrations/bin/drupal-down.sh",
|
|
73
73
|
"init-worktree": "node scripts/init-worktree.mjs",
|