@drbaher/draft-cli 0.6.0 → 0.7.0
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/CHANGELOG.md +49 -0
- package/PARAM_SCHEMA.md +64 -0
- package/draft-cli.mjs +247 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,55 @@ All notable changes to this project will be documented in this file. The
|
|
|
4
4
|
format is loosely based on [Keep a Changelog](https://keepachangelog.com/),
|
|
5
5
|
and the project adheres to semantic versioning once it leaves 0.x.
|
|
6
6
|
|
|
7
|
+
## 0.7.0 — 2026-05-17
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **Multi-document bundles.** `draft --bundle <bundle.json>` reads a
|
|
12
|
+
bundle definition and fills multiple templates with the same set of
|
|
13
|
+
parameter values in one invocation:
|
|
14
|
+
```json
|
|
15
|
+
{
|
|
16
|
+
"_meta": { "schema_version": 1 },
|
|
17
|
+
"outputs": [
|
|
18
|
+
{ "template": "msa/v3.md", "output": "out/msa.md" },
|
|
19
|
+
{ "template": "order-form/v3.md", "output": "out/order-form.md" }
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
Each template runs through detection independently. Placeholders
|
|
24
|
+
across templates are unioned by key (so a key declared in any
|
|
25
|
+
template's schema applies to all — Q3.3 locked). Resolution,
|
|
26
|
+
typed-parameter normalization, and computed values all run once on
|
|
27
|
+
the union. Each output is then substituted using its own
|
|
28
|
+
template/tier and written to its own path. `parties.json` refs
|
|
29
|
+
(v0.6.0) resolve inside bundle entries too.
|
|
30
|
+
- **Schema-union semantics.** A key declared/detected in any bundle
|
|
31
|
+
template applies to every template in the bundle. First-occurrence
|
|
32
|
+
metadata wins; resolved values flow to all templates that reference
|
|
33
|
+
the same key.
|
|
34
|
+
- **`.docx` bundle entries** round-trip through `substituteDocxXml`
|
|
35
|
+
when the entry's `output` path has the `.docx` extension. Same
|
|
36
|
+
runs/styles preservation as single-doc `.docx` mode.
|
|
37
|
+
- **New public API:** `loadBundle(path)`, `cmdBundle(opts, bundle,
|
|
38
|
+
paramsObj, envObj, io)`.
|
|
39
|
+
|
|
40
|
+
### Decisions locked (V2_BRIEFS_REMAINING Q3.1–Q3.3)
|
|
41
|
+
|
|
42
|
+
- **Q3.1 Bundle file format:** JSON object with `outputs` array of
|
|
43
|
+
`{template, output}` pairs. Each entry has its own output path,
|
|
44
|
+
enabling per-doc overrides without inventing a custom DSL.
|
|
45
|
+
- **Q3.2 Partial-failure policy:** abort-all. Any pre-write error
|
|
46
|
+
(no detection in an entry, missing required param across the
|
|
47
|
+
union, type / computed / ref failure, positional mismatch, schema
|
|
48
|
+
orphan) exits 4 before any file is written. Write failures
|
|
49
|
+
mid-bundle exit 1; earlier successful writes are not rolled back
|
|
50
|
+
(best-effort atomicity at the filesystem boundary).
|
|
51
|
+
- **Q3.3 Schema union semantics:** keys declared in any template's
|
|
52
|
+
schema (or detected as canonical-key matches without a schema)
|
|
53
|
+
apply across the bundle. Same value resolves into every template
|
|
54
|
+
that references the key.
|
|
55
|
+
|
|
7
56
|
## 0.6.0 — 2026-05-16
|
|
8
57
|
|
|
9
58
|
### Added
|
package/PARAM_SCHEMA.md
CHANGED
|
@@ -451,6 +451,70 @@ substitution always uses string output.
|
|
|
451
451
|
Programmatic API: `loadParties(path)`, `resolveRef(value, parties)`,
|
|
452
452
|
`resolveRefs(resolved, sources, parties)`.
|
|
453
453
|
|
|
454
|
+
### Multi-document bundles (v0.7.0, opt-in)
|
|
455
|
+
|
|
456
|
+
`draft --bundle <bundle.json>` reads a bundle definition and fills
|
|
457
|
+
multiple templates with one shared set of parameter values:
|
|
458
|
+
|
|
459
|
+
```json
|
|
460
|
+
{
|
|
461
|
+
"_meta": { "schema_version": 1 },
|
|
462
|
+
"outputs": [
|
|
463
|
+
{ "template": "msa/v3.md", "output": "out/msa.md" },
|
|
464
|
+
{ "template": "order-form/v3.md", "output": "out/order-form.md" },
|
|
465
|
+
{ "template": "dpa/v2.docx", "output": "out/dpa.docx" }
|
|
466
|
+
]
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
```sh
|
|
471
|
+
draft --bundle deal.bundle.json --params deal.json --parties parties.json
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
**Q3.1 locked:** the bundle file is a JSON object with an `outputs`
|
|
475
|
+
array of `{template, output}` pairs. Each entry has its own
|
|
476
|
+
`template` (filesystem path or `template-vault get` ref) and own
|
|
477
|
+
`output` path. No alternative shorter DSL — JSON is unambiguous and
|
|
478
|
+
extensible.
|
|
479
|
+
|
|
480
|
+
**Q3.2 locked:** abort-all. Any pre-write error (no detection in an
|
|
481
|
+
entry, missing required param across the union, type validation
|
|
482
|
+
failure, computed-value failure, ref-resolution failure, positional
|
|
483
|
+
mismatch, schema orphan) returns exit 4 **before any file is
|
|
484
|
+
written**. The bundle either writes all `outputs` or writes none.
|
|
485
|
+
Filesystem write errors mid-bundle exit 1; earlier successful
|
|
486
|
+
writes are not rolled back (best-effort atomicity at the filesystem
|
|
487
|
+
boundary).
|
|
488
|
+
|
|
489
|
+
**Q3.3 locked:** schema union. A key declared in any template's
|
|
490
|
+
schema, or detected as a canonical-key match without a schema,
|
|
491
|
+
applies across the entire bundle. The same resolved value flows to
|
|
492
|
+
every template that references the key. First-occurrence metadata
|
|
493
|
+
wins (`type`, `format`, `currency`, `computed`, `positions`, etc.);
|
|
494
|
+
templates with richer aliases for the same key contribute their
|
|
495
|
+
aliases for detection in their own body but don't redefine the key.
|
|
496
|
+
|
|
497
|
+
**Per-template detection independence:** each bundle entry runs the
|
|
498
|
+
full T1–T5 cascade against its own body. Different entries can land
|
|
499
|
+
on different tiers (e.g. MSA on T1 brackets, DPA on T3 highlights).
|
|
500
|
+
Positional addressing on T1/T2 still works per entry.
|
|
501
|
+
|
|
502
|
+
**`.docx` entries** with a `.docx` output path round-trip via
|
|
503
|
+
`substituteDocxXml` + `writeDocxBuffer`, preserving runs/styles.
|
|
504
|
+
Mixing text and `.docx` entries in the same bundle works.
|
|
505
|
+
|
|
506
|
+
**`parties.json` refs** (v0.6.0) resolve inside bundles too — load
|
|
507
|
+
the same parties file once via `--parties PATH` (or the CWD
|
|
508
|
+
default), and ref strings in any bundle template's schema default
|
|
509
|
+
or in shared `--params` expand against it.
|
|
510
|
+
|
|
511
|
+
**`--json`** for bundles emits a structured result listing each
|
|
512
|
+
entry's template, output path, and tier, plus the union of resolved
|
|
513
|
+
keys and their sources.
|
|
514
|
+
|
|
515
|
+
Programmatic API: `loadBundle(path)` parses + validates; `cmdBundle`
|
|
516
|
+
runs the orchestration with the same IO contract as `cmdDraft`.
|
|
517
|
+
|
|
454
518
|
### Orphan handling (Q4 locked)
|
|
455
519
|
|
|
456
520
|
Schema declares a key whose alias list matches no detected phrase →
|
package/draft-cli.mjs
CHANGED
|
@@ -70,7 +70,7 @@ import { fileURLToPath } from "node:url";
|
|
|
70
70
|
*/
|
|
71
71
|
|
|
72
72
|
/** @type {string} */
|
|
73
|
-
export const VERSION = "0.
|
|
73
|
+
export const VERSION = "0.7.0";
|
|
74
74
|
|
|
75
75
|
// ─── EXIT CODES ─────────────────────────────────────────────────────────────
|
|
76
76
|
/**
|
|
@@ -278,6 +278,7 @@ export function parseArgs(argv) {
|
|
|
278
278
|
if (a === "--diff") { opts.diff = true; continue; }
|
|
279
279
|
if (a === "--params") { opts.params = argv[++i]; continue; }
|
|
280
280
|
if (a === "--parties") { opts.parties = argv[++i]; continue; }
|
|
281
|
+
if (a === "--bundle") { opts.bundle = argv[++i]; continue; }
|
|
281
282
|
if (a === "--output" || a === "-o") { opts.output = argv[++i]; continue; }
|
|
282
283
|
if (a === "--syntax") {
|
|
283
284
|
const v = argv[++i];
|
|
@@ -1375,6 +1376,79 @@ export function resolveRefs(resolved, sources, parties) {
|
|
|
1375
1376
|
return { ok: errors.length === 0, errors };
|
|
1376
1377
|
}
|
|
1377
1378
|
|
|
1379
|
+
/**
|
|
1380
|
+
* Load and validate a bundle definition (v2 #6). Bundles describe
|
|
1381
|
+
* multiple templates that should be filled with the same set of
|
|
1382
|
+
* parameter values in one invocation:
|
|
1383
|
+
*
|
|
1384
|
+
* {
|
|
1385
|
+
* "_meta": { "schema_version": 1 },
|
|
1386
|
+
* "outputs": [
|
|
1387
|
+
* { "template": "msa/v3.md", "output": "out/msa.md" },
|
|
1388
|
+
* { "template": "order-form/v3.md", "output": "out/order-form.md" }
|
|
1389
|
+
* ]
|
|
1390
|
+
* }
|
|
1391
|
+
*
|
|
1392
|
+
* Returns the parsed bundle. Throws on missing file, invalid JSON, no
|
|
1393
|
+
* `outputs` array, empty `outputs`, missing `template`/`output` on an
|
|
1394
|
+
* entry, or duplicate output paths.
|
|
1395
|
+
*
|
|
1396
|
+
* @param {string} path
|
|
1397
|
+
* @returns {{ outputs: Array<{ template: string, output: string }> }}
|
|
1398
|
+
* @throws {Error} with `.exitCode = EXIT.IO`
|
|
1399
|
+
*/
|
|
1400
|
+
export function loadBundle(path) {
|
|
1401
|
+
if (!existsSync(path)) {
|
|
1402
|
+
const e = new Error(`bundle file not found: ${path}`);
|
|
1403
|
+
e.exitCode = EXIT.IO;
|
|
1404
|
+
throw e;
|
|
1405
|
+
}
|
|
1406
|
+
let parsed;
|
|
1407
|
+
try {
|
|
1408
|
+
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
1409
|
+
} catch (err) {
|
|
1410
|
+
const e = new Error(`could not parse bundle ${path}: ${err.message}`);
|
|
1411
|
+
e.exitCode = EXIT.IO;
|
|
1412
|
+
throw e;
|
|
1413
|
+
}
|
|
1414
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1415
|
+
const e = new Error(`bundle ${path} must be a JSON object`);
|
|
1416
|
+
e.exitCode = EXIT.IO;
|
|
1417
|
+
throw e;
|
|
1418
|
+
}
|
|
1419
|
+
if (!Array.isArray(parsed.outputs) || parsed.outputs.length === 0) {
|
|
1420
|
+
const e = new Error(`bundle ${path}: missing or empty "outputs" array`);
|
|
1421
|
+
e.exitCode = EXIT.IO;
|
|
1422
|
+
throw e;
|
|
1423
|
+
}
|
|
1424
|
+
const seenOutputs = new Set();
|
|
1425
|
+
for (let i = 0; i < parsed.outputs.length; i++) {
|
|
1426
|
+
const o = parsed.outputs[i];
|
|
1427
|
+
if (!o || typeof o !== "object" || Array.isArray(o)) {
|
|
1428
|
+
const e = new Error(`bundle ${path}: outputs[${i}] must be an object`);
|
|
1429
|
+
e.exitCode = EXIT.IO;
|
|
1430
|
+
throw e;
|
|
1431
|
+
}
|
|
1432
|
+
if (typeof o.template !== "string" || !o.template) {
|
|
1433
|
+
const e = new Error(`bundle ${path}: outputs[${i}].template must be a non-empty string`);
|
|
1434
|
+
e.exitCode = EXIT.IO;
|
|
1435
|
+
throw e;
|
|
1436
|
+
}
|
|
1437
|
+
if (typeof o.output !== "string" || !o.output) {
|
|
1438
|
+
const e = new Error(`bundle ${path}: outputs[${i}].output must be a non-empty string`);
|
|
1439
|
+
e.exitCode = EXIT.IO;
|
|
1440
|
+
throw e;
|
|
1441
|
+
}
|
|
1442
|
+
if (seenOutputs.has(o.output)) {
|
|
1443
|
+
const e = new Error(`bundle ${path}: outputs[${i}].output "${o.output}" is duplicated`);
|
|
1444
|
+
e.exitCode = EXIT.IO;
|
|
1445
|
+
throw e;
|
|
1446
|
+
}
|
|
1447
|
+
seenOutputs.add(o.output);
|
|
1448
|
+
}
|
|
1449
|
+
return { outputs: parsed.outputs.map(o => ({ template: o.template, output: o.output })) };
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1378
1452
|
/**
|
|
1379
1453
|
* Resolve a value for every placeholder using the locked precedence chain:
|
|
1380
1454
|
* CLI flag > `--params` JSON > `--interactive` prompt > schema default >
|
|
@@ -2282,6 +2356,151 @@ export async function cmdDraft(opts, input, schema, paramsObj, envObj, { fetcher
|
|
|
2282
2356
|
return EXIT.OK;
|
|
2283
2357
|
}
|
|
2284
2358
|
|
|
2359
|
+
/**
|
|
2360
|
+
* cmdBundle — orchestrate filling multiple templates with one shared
|
|
2361
|
+
* parameter set (v2 #6). For each bundle entry:
|
|
2362
|
+
* 1. resolveInput + loadSchema (per template)
|
|
2363
|
+
* 2. runCascade (per template)
|
|
2364
|
+
* 3. union placeholders by key
|
|
2365
|
+
* Then resolve values once across the union (CLI/--params/interactive/
|
|
2366
|
+
* default), run typed-param normalization + computed values, and write
|
|
2367
|
+
* each output. Q3.2 locked: any pre-write error (no-detection in an
|
|
2368
|
+
* entry, missing required param across the union, type / computed
|
|
2369
|
+
* failure) aborts the whole bundle before any file is written.
|
|
2370
|
+
*
|
|
2371
|
+
* @param {Object} opts
|
|
2372
|
+
* @param {{outputs: Array<{template: string, output: string}>}} bundle
|
|
2373
|
+
* @param {Object} paramsObj
|
|
2374
|
+
* @param {Object} envObj
|
|
2375
|
+
* @returns {Promise<number>} exit code
|
|
2376
|
+
*/
|
|
2377
|
+
export async function cmdBundle(opts, bundle, paramsObj, envObj, { fetcher, out, err, spawner, stdinReader, parties = null } = {}) {
|
|
2378
|
+
// Phase 1: load each template + schema, run detection.
|
|
2379
|
+
const entries = [];
|
|
2380
|
+
for (let i = 0; i < bundle.outputs.length; i++) {
|
|
2381
|
+
const o = bundle.outputs[i];
|
|
2382
|
+
let input, schema, cascade;
|
|
2383
|
+
try {
|
|
2384
|
+
input = await resolveInput(o.template, { spawner, stdinReader });
|
|
2385
|
+
schema = loadSchema(input.path);
|
|
2386
|
+
} catch (e) {
|
|
2387
|
+
err.write(paint(`error: bundle entry ${i} "${o.template}": ${e.message}\n`, "red", err));
|
|
2388
|
+
return e.exitCode || EXIT.IO;
|
|
2389
|
+
}
|
|
2390
|
+
cascade = await runCascade(input, opts, schema, envObj, { fetcher });
|
|
2391
|
+
if (cascade.tier === "none") {
|
|
2392
|
+
err.write(paint(`error: bundle entry ${i} "${o.template}": no placeholders detected by any tier\n`, "red", err));
|
|
2393
|
+
return EXIT.VALIDATION;
|
|
2394
|
+
}
|
|
2395
|
+
// v2 #7 positional errors per template — abort early.
|
|
2396
|
+
if (cascade.positional_errors && cascade.positional_errors.length > 0) {
|
|
2397
|
+
for (const pe of cascade.positional_errors) {
|
|
2398
|
+
err.write(paint(`error: bundle entry ${i} "${o.template}" positional placeholder "${pe.key}": ${pe.reason}\n`, "red", err));
|
|
2399
|
+
}
|
|
2400
|
+
return EXIT.VALIDATION;
|
|
2401
|
+
}
|
|
2402
|
+
// Orphan check per template (schema declares something not detected here).
|
|
2403
|
+
const orphans = findOrphans(schema, cascade.placeholders, cascade.detected_schema_keys);
|
|
2404
|
+
if (orphans.length > 0) {
|
|
2405
|
+
for (const oo of orphans) {
|
|
2406
|
+
err.write(paint(`error: bundle entry ${i} "${o.template}" schema declares "${oo.key}" but no matching phrase was detected by tier '${cascade.tier}'.\n`, "red", err));
|
|
2407
|
+
}
|
|
2408
|
+
return EXIT.VALIDATION;
|
|
2409
|
+
}
|
|
2410
|
+
entries.push({ output: o.output, input, schema, cascade });
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
// Phase 2: union placeholders by key. Q3.3 locked: union semantics —
|
|
2414
|
+
// a key declared/detected in any template applies to all. First
|
|
2415
|
+
// occurrence's metadata wins (required, default, type, format, etc.);
|
|
2416
|
+
// a per-template later occurrence may have richer aliases but we keep
|
|
2417
|
+
// the first canonical entry.
|
|
2418
|
+
const unionPlaceholders = [];
|
|
2419
|
+
const seenKeys = new Set();
|
|
2420
|
+
for (const e of entries) {
|
|
2421
|
+
for (const p of e.cascade.placeholders) {
|
|
2422
|
+
if (seenKeys.has(p.key)) continue;
|
|
2423
|
+
seenKeys.add(p.key);
|
|
2424
|
+
unionPlaceholders.push(p);
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
// Phase 3: shared value resolution + footgun guard.
|
|
2429
|
+
const { resolved, missing, sources } = await resolveValues(unionPlaceholders, opts, paramsObj);
|
|
2430
|
+
const declaredKeys = new Set(unionPlaceholders.map((p) => p.key));
|
|
2431
|
+
const unusedFlags = Object.keys(opts.paramFlags).filter((k) => !declaredKeys.has(k));
|
|
2432
|
+
for (const u of unusedFlags) {
|
|
2433
|
+
err.write(paint(`warning: flag --${u.replace(/_/g, "-")} did not match any placeholder in any bundle template (possible typo?)\n`, "yellow", err));
|
|
2434
|
+
}
|
|
2435
|
+
if (missing.length > 0) {
|
|
2436
|
+
printMissing(missing, err);
|
|
2437
|
+
return EXIT.VALIDATION;
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
// v2 #5: parties.json refs resolve across the union before typed
|
|
2441
|
+
// normalization (same order as cmdDraft / cmdValidate).
|
|
2442
|
+
const refCheck = resolveRefs(resolved, sources, parties);
|
|
2443
|
+
if (!refCheck.ok) {
|
|
2444
|
+
for (const re of refCheck.errors) {
|
|
2445
|
+
err.write(paint(`error: parties reference failed for "${re.key}": ${re.message}\n`, "red", err));
|
|
2446
|
+
}
|
|
2447
|
+
return EXIT.VALIDATION;
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
// Phase 4: typed-parameter + computed pipelines (same as cmdDraft).
|
|
2451
|
+
const typeCheck = normalizeTypedValues(unionPlaceholders, resolved);
|
|
2452
|
+
if (!typeCheck.ok) {
|
|
2453
|
+
for (const te of typeCheck.errors) {
|
|
2454
|
+
err.write(paint(`error: type validation failed for "${te.key}": ${te.message}\n`, "red", err));
|
|
2455
|
+
}
|
|
2456
|
+
return EXIT.VALIDATION;
|
|
2457
|
+
}
|
|
2458
|
+
const computeCheck = computeValues(unionPlaceholders, resolved);
|
|
2459
|
+
if (!computeCheck.ok) {
|
|
2460
|
+
for (const ce of computeCheck.errors) {
|
|
2461
|
+
err.write(paint(`error: computed value failed for "${ce.key}": ${ce.message}\n`, "red", err));
|
|
2462
|
+
}
|
|
2463
|
+
return EXIT.VALIDATION;
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
// Phase 5: substitute per template + write. Q3.2: any write failure
|
|
2467
|
+
// exits with EXIT.IO; earlier successful writes are NOT rolled back
|
|
2468
|
+
// (atomicity at the filesystem is best-effort).
|
|
2469
|
+
for (let i = 0; i < entries.length; i++) {
|
|
2470
|
+
const e = entries[i];
|
|
2471
|
+
const outputText = substitute(e.input.body, e.cascade.placeholders, resolved, e.cascade.tier);
|
|
2472
|
+
try {
|
|
2473
|
+
// For .docx input with .docx output: round-trip via substituteDocxXml.
|
|
2474
|
+
if (e.input.kind === "docx" && extname(e.output) === ".docx") {
|
|
2475
|
+
const { xml: newXml, warnings: dw } = substituteDocxXml(
|
|
2476
|
+
e.input.docxXml, e.cascade.placeholders, resolved, e.cascade.tier
|
|
2477
|
+
);
|
|
2478
|
+
if (dw.length) for (const w of dw) err.write(paint(`warning (entry ${i}): ${w}\n`, "yellow", err));
|
|
2479
|
+
const buf = await writeDocxBuffer(e.input.path, newXml);
|
|
2480
|
+
writeFileSync(e.output, buf);
|
|
2481
|
+
} else {
|
|
2482
|
+
writeFileSync(e.output, outputText, "utf8");
|
|
2483
|
+
}
|
|
2484
|
+
} catch (writeErr) {
|
|
2485
|
+
err.write(paint(`error: bundle entry ${i} could not write ${e.output}: ${writeErr.message}\n`, "red", err));
|
|
2486
|
+
return EXIT.IO;
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2490
|
+
if (!opts.silent && !opts.json) {
|
|
2491
|
+
err.write(paint(`ok: wrote ${entries.length} document(s) — ${entries.map(e => e.output).join(", ")}\n`, "green", err));
|
|
2492
|
+
}
|
|
2493
|
+
if (opts.json) {
|
|
2494
|
+
out.write(JSON.stringify({
|
|
2495
|
+
ok: true,
|
|
2496
|
+
outputs: entries.map(e => ({ template: e.input.path, output: e.output, tier: e.cascade.tier })),
|
|
2497
|
+
resolved_keys: Object.keys(resolved),
|
|
2498
|
+
sources,
|
|
2499
|
+
}, null, 2) + "\n");
|
|
2500
|
+
}
|
|
2501
|
+
return EXIT.OK;
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2285
2504
|
function describeInput(input) {
|
|
2286
2505
|
if (input.path) return input.path;
|
|
2287
2506
|
if (input.kind === "text") return "stdin";
|
|
@@ -2583,6 +2802,33 @@ export async function main(argv, io = {}) {
|
|
|
2583
2802
|
return await runCheckLlm(envObj, out, err, { fetcher });
|
|
2584
2803
|
}
|
|
2585
2804
|
|
|
2805
|
+
// v2 #6: bundle mode. `--bundle PATH` reads a bundle definition and
|
|
2806
|
+
// orchestrates filling each entry's template with shared parameters.
|
|
2807
|
+
// In bundle mode, no positional template arg is required (the bundle
|
|
2808
|
+
// declares them).
|
|
2809
|
+
if (opts.bundle) {
|
|
2810
|
+
if (opts.positional.length > 0) {
|
|
2811
|
+
err.write(paint(`error: --bundle does not take a positional template arg (the bundle declares them)\n`, "red", err));
|
|
2812
|
+
return EXIT.IO;
|
|
2813
|
+
}
|
|
2814
|
+
let bundle, paramsObj, envObj, parties;
|
|
2815
|
+
try {
|
|
2816
|
+
bundle = loadBundle(opts.bundle);
|
|
2817
|
+
paramsObj = loadParamsFile(opts.params);
|
|
2818
|
+
envObj = effectiveEnv(cwd, processEnv);
|
|
2819
|
+
parties = loadParties(opts.parties || null);
|
|
2820
|
+
} catch (e) {
|
|
2821
|
+
err.write(paint(`error: ${e.message}\n`, "red", err));
|
|
2822
|
+
return e.exitCode || EXIT.IO;
|
|
2823
|
+
}
|
|
2824
|
+
try {
|
|
2825
|
+
return await cmdBundle(opts, bundle, paramsObj, envObj, { fetcher, out, err, spawner, stdinReader, parties });
|
|
2826
|
+
} catch (e) {
|
|
2827
|
+
err.write(paint(`error: ${e.message}\n`, "red", err));
|
|
2828
|
+
return e.exitCode || EXIT.IO;
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
|
|
2586
2832
|
if (opts.positional.length === 0) {
|
|
2587
2833
|
err.write(paint(`error: no template given\n`, "red", err));
|
|
2588
2834
|
err.write(`run \`draft --help\` for usage.\n`);
|