@nowline/cli 0.8.6 → 0.10.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/README.md +32 -9
- package/dist/cli/args.d.ts +20 -4
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +20 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +31 -3
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/options.d.ts +74 -0
- package/dist/cli/options.d.ts.map +1 -0
- package/dist/cli/options.js +209 -0
- package/dist/cli/options.js.map +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +2 -3
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/mcp-install.d.ts +18 -0
- package/dist/commands/mcp-install.d.ts.map +1 -0
- package/dist/commands/mcp-install.js +151 -0
- package/dist/commands/mcp-install.js.map +1 -0
- package/dist/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js +7 -12
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/render.d.ts +7 -0
- package/dist/commands/render.d.ts.map +1 -1
- package/dist/commands/render.js +161 -155
- package/dist/commands/render.js.map +1 -1
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +87 -60
- package/dist/commands/serve.js.map +1 -1
- package/dist/diagnostics/adapt.d.ts +12 -1
- package/dist/diagnostics/adapt.d.ts.map +1 -1
- package/dist/diagnostics/adapt.js +53 -1
- package/dist/diagnostics/adapt.js.map +1 -1
- package/dist/diagnostics/model.d.ts +1 -1
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.d.ts.map +1 -1
- package/dist/generated/version.js +2 -2
- package/dist/generated/version.js.map +1 -1
- package/dist/i18n/locale.d.ts.map +1 -1
- package/dist/i18n/locale.js +2 -5
- package/dist/i18n/locale.js.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/io/config.d.ts +1 -1
- package/dist/io/config.d.ts.map +1 -1
- package/dist/io/config.js +1 -1
- package/dist/io/config.js.map +1 -1
- package/dist/io/log.d.ts +17 -0
- package/dist/io/log.d.ts.map +1 -0
- package/dist/io/log.js +16 -0
- package/dist/io/log.js.map +1 -0
- package/dist/mcp-install/clients.d.ts +58 -0
- package/dist/mcp-install/clients.d.ts.map +1 -0
- package/dist/mcp-install/clients.js +304 -0
- package/dist/mcp-install/clients.js.map +1 -0
- package/dist/mcp-install/host.d.ts +29 -0
- package/dist/mcp-install/host.d.ts.map +1 -0
- package/dist/mcp-install/host.js +72 -0
- package/dist/mcp-install/host.js.map +1 -0
- package/dist/mcp-install/json-config.d.ts +29 -0
- package/dist/mcp-install/json-config.d.ts.map +1 -0
- package/dist/mcp-install/json-config.js +139 -0
- package/dist/mcp-install/json-config.js.map +1 -0
- package/dist/mcp-install/server-command.d.ts +26 -0
- package/dist/mcp-install/server-command.d.ts.map +1 -0
- package/dist/mcp-install/server-command.js +52 -0
- package/dist/mcp-install/server-command.js.map +1 -0
- package/man/fr/nowline.1 +248 -19
- package/man/fr/nowline.5 +819 -45
- package/man/nowline.1 +193 -18
- package/man/nowline.5 +764 -36
- package/package.json +19 -19
- package/src/cli/args.ts +55 -5
- package/src/cli/help.ts +31 -3
- package/src/cli/options.ts +286 -0
- package/src/commands/init.ts +2 -3
- package/src/commands/mcp-install.ts +211 -0
- package/src/commands/mcp.ts +10 -14
- package/src/commands/render.ts +198 -195
- package/src/commands/serve.ts +102 -74
- package/src/diagnostics/adapt.ts +62 -0
- package/src/diagnostics/model.ts +1 -1
- package/src/generated/version.ts +2 -2
- package/src/i18n/locale.ts +2 -6
- package/src/index.ts +3 -0
- package/src/io/config.ts +0 -1
- package/src/io/log.ts +33 -0
- package/src/mcp-install/clients.ts +443 -0
- package/src/mcp-install/host.ts +106 -0
- package/src/mcp-install/json-config.ts +179 -0
- package/src/mcp-install/server-command.ts +75 -0
package/src/commands/render.ts
CHANGED
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
import { promises as fs } from 'node:fs';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
|
+
import type { ResolveDiagnostic } from '@nowline/core';
|
|
3
4
|
import {
|
|
5
|
+
acceptStagedRoadmap,
|
|
4
6
|
type ExportFormat,
|
|
5
|
-
|
|
7
|
+
exportStaged,
|
|
6
8
|
type HostEnv,
|
|
7
9
|
type RenderInputs,
|
|
10
|
+
type StageResult,
|
|
11
|
+
stageForExport,
|
|
8
12
|
} from '@nowline/export';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
type NormalizedZone,
|
|
12
|
-
normalizeThemeName,
|
|
13
|
-
normalizeZone,
|
|
14
|
-
resolveToday,
|
|
15
|
-
type ThemeName,
|
|
16
|
-
TimezoneError,
|
|
17
|
-
} from '@nowline/layout';
|
|
13
|
+
import type { PdfPageSize } from '@nowline/export-core';
|
|
14
|
+
import type { NonWorkingDisplay, ThemeName } from '@nowline/layout';
|
|
18
15
|
import type { AssetResolver } from '@nowline/renderer';
|
|
19
16
|
import type { ParsedArgs } from '../cli/args.js';
|
|
20
17
|
import {
|
|
@@ -23,12 +20,15 @@ import {
|
|
|
23
20
|
type OutputFormat,
|
|
24
21
|
resolveFormat,
|
|
25
22
|
} from '../cli/formats.js';
|
|
23
|
+
import { type PdfOrientation, resolveCliOptions } from '../cli/options.js';
|
|
26
24
|
import { resolveRenderOutputPath } from '../cli/output-path.js';
|
|
27
25
|
import { parseNowlineJson } from '../convert/parse-json.js';
|
|
28
26
|
import { printNowlineFile } from '../convert/printer.js';
|
|
29
27
|
import { serializeToJson } from '../convert/schema.js';
|
|
30
|
-
import { parseSource } from '../core/parse.js';
|
|
28
|
+
import { getServices, parseSource } from '../core/parse.js';
|
|
31
29
|
import {
|
|
30
|
+
adaptResolveDiagnostic,
|
|
31
|
+
type CliDiagnostic,
|
|
32
32
|
type DiagnosticFormat,
|
|
33
33
|
type DiagnosticSource,
|
|
34
34
|
formatDiagnostics,
|
|
@@ -39,8 +39,9 @@ import {
|
|
|
39
39
|
readDirectiveLocale,
|
|
40
40
|
resolveLocaleOverride,
|
|
41
41
|
} from '../i18n/locale.js';
|
|
42
|
-
import { loadConfig } from '../io/config.js';
|
|
42
|
+
import { type LoadConfigResult, loadConfig } from '../io/config.js';
|
|
43
43
|
import { CliError, ExitCode } from '../io/exit-codes.js';
|
|
44
|
+
import { createLog, type Log } from '../io/log.js';
|
|
44
45
|
import { readInput } from '../io/read.js';
|
|
45
46
|
import { writeOutput } from '../io/write.js';
|
|
46
47
|
|
|
@@ -67,18 +68,20 @@ export async function renderHandler(options: RenderHandlerOptions): Promise<void
|
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
const isStdoutOutput = args.output === '-';
|
|
70
|
-
const
|
|
71
|
+
const log = createLog(args.logLevel);
|
|
72
|
+
const { config, path: rcPath } = await loadConfigFor(args.positional, cwd);
|
|
73
|
+
// Every rc-backed setting resolves flag > .nowlinerc > default; a bad
|
|
74
|
+
// value is reported by `assertValid` below, where it always has been.
|
|
75
|
+
const cliOptions = resolveCliOptions(args, config, { rcPath });
|
|
71
76
|
|
|
72
77
|
const format = resolveFormatOrThrow({
|
|
73
78
|
flag: args.format,
|
|
74
79
|
outputPath: args.output,
|
|
75
|
-
configFormat:
|
|
80
|
+
configFormat: cliOptions.defaultFormat,
|
|
76
81
|
isStdout: isStdoutOutput,
|
|
77
82
|
});
|
|
78
83
|
|
|
79
|
-
|
|
80
|
-
process.stderr.write(`nowline: format=${format} (resolved)\n`);
|
|
81
|
-
}
|
|
84
|
+
log.verbose(`nowline: format=${format} (resolved)`);
|
|
82
85
|
|
|
83
86
|
const resolvedOutput = resolveRenderOutputPath({
|
|
84
87
|
outputArg: args.output,
|
|
@@ -96,10 +99,11 @@ export async function renderHandler(options: RenderHandlerOptions): Promise<void
|
|
|
96
99
|
const resolved = resolveLocaleOverride({
|
|
97
100
|
flag: args.locale,
|
|
98
101
|
env: process.env,
|
|
99
|
-
rc:
|
|
102
|
+
rc: cliOptions.rcLocale,
|
|
100
103
|
});
|
|
101
104
|
const locale = resolved.tag;
|
|
102
105
|
const opLocale = operatorLocale(resolved);
|
|
106
|
+
cliOptions.assertValid();
|
|
103
107
|
|
|
104
108
|
const { rendered, isBinary } = await produce({
|
|
105
109
|
format,
|
|
@@ -108,34 +112,34 @@ export async function renderHandler(options: RenderHandlerOptions): Promise<void
|
|
|
108
112
|
displayPath: input.displayPath,
|
|
109
113
|
absInputPath: input.isStdin ? path.resolve(cwd, 'stdin.nowline') : input.path,
|
|
110
114
|
isStdin: input.isStdin,
|
|
111
|
-
theme:
|
|
112
|
-
today:
|
|
113
|
-
width:
|
|
115
|
+
theme: cliOptions.surface.theme,
|
|
116
|
+
today: cliOptions.surface.today,
|
|
117
|
+
width: cliOptions.surface.width,
|
|
118
|
+
nonWorking: cliOptions.surface.nonWorking,
|
|
114
119
|
noLinks: args.noLinks,
|
|
115
120
|
strict: args.strict,
|
|
116
|
-
assetRoot:
|
|
117
|
-
// m2c format-specific options.
|
|
118
|
-
//
|
|
119
|
-
pageSize:
|
|
120
|
-
orientation:
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
121
|
+
assetRoot: cliOptions.assetRoot,
|
|
122
|
+
// m2c format-specific options. The PDF settings parse in `produce`,
|
|
123
|
+
// after validation, for every format.
|
|
124
|
+
pageSize: cliOptions.surface.pageSize,
|
|
125
|
+
orientation: cliOptions.orientation,
|
|
126
|
+
marginPt: cliOptions.margin,
|
|
127
|
+
assertPdfSettingsValid: () => cliOptions.assertPdfSettingsValid(),
|
|
128
|
+
fontSans: cliOptions.fontSans,
|
|
129
|
+
fontMono: cliOptions.fontMono,
|
|
130
|
+
headless: cliOptions.headless,
|
|
131
|
+
useSystemFonts: cliOptions.useSystemFonts,
|
|
126
132
|
scale: args.scale,
|
|
127
133
|
start: args.start,
|
|
128
134
|
locale,
|
|
129
135
|
operatorLocale: opLocale,
|
|
130
136
|
resolvedLocale: resolved,
|
|
131
|
-
|
|
137
|
+
log,
|
|
132
138
|
diagnosticFormat: resolveDiagnosticFormat(args),
|
|
133
139
|
});
|
|
134
140
|
|
|
135
141
|
if (args.dryRun) {
|
|
136
|
-
|
|
137
|
-
process.stderr.write('nowline: --dry-run; skipping write\n');
|
|
138
|
-
}
|
|
142
|
+
log.verbose('nowline: --dry-run; skipping write');
|
|
139
143
|
return;
|
|
140
144
|
}
|
|
141
145
|
|
|
@@ -146,8 +150,8 @@ export async function renderHandler(options: RenderHandlerOptions): Promise<void
|
|
|
146
150
|
{ cwd },
|
|
147
151
|
);
|
|
148
152
|
|
|
149
|
-
if (
|
|
150
|
-
|
|
153
|
+
if (!resolvedOutput.isStdout) {
|
|
154
|
+
log.verbose(`nowline: wrote ${resolvedOutput.path}`);
|
|
151
155
|
}
|
|
152
156
|
}
|
|
153
157
|
|
|
@@ -199,13 +203,17 @@ interface ProduceArgs {
|
|
|
199
203
|
theme: ThemeName;
|
|
200
204
|
today?: Date;
|
|
201
205
|
width?: number;
|
|
206
|
+
nonWorking?: NonWorkingDisplay;
|
|
202
207
|
noLinks: boolean;
|
|
203
208
|
strict: boolean;
|
|
204
209
|
assetRoot?: string;
|
|
205
210
|
// m2c format-specific
|
|
206
|
-
pageSize?:
|
|
207
|
-
orientation?:
|
|
208
|
-
margin
|
|
211
|
+
pageSize?: PdfPageSize;
|
|
212
|
+
orientation?: PdfOrientation;
|
|
213
|
+
/** Page margin in points. */
|
|
214
|
+
marginPt?: number;
|
|
215
|
+
/** Reports a bad PDF setting; run after validation so its error follows any diagnostics. */
|
|
216
|
+
assertPdfSettingsValid: () => void;
|
|
209
217
|
fontSans?: string;
|
|
210
218
|
fontMono?: string;
|
|
211
219
|
headless: boolean;
|
|
@@ -219,8 +227,8 @@ interface ProduceArgs {
|
|
|
219
227
|
operatorLocale: string;
|
|
220
228
|
/** Resolved locale override metadata, used for the verbose-mode source line. */
|
|
221
229
|
resolvedLocale: import('../i18n/locale.js').ResolvedLocale;
|
|
222
|
-
/**
|
|
223
|
-
|
|
230
|
+
/** `-v` / `-q` gate: verbose lines, warnings, and text-mode warning diagnostics. */
|
|
231
|
+
log: Log;
|
|
224
232
|
/** How diagnostics are written to stderr. See `resolveDiagnosticFormat`. */
|
|
225
233
|
diagnosticFormat: DiagnosticFormat;
|
|
226
234
|
}
|
|
@@ -245,17 +253,38 @@ async function produce(args: ProduceArgs): Promise<ProduceResult> {
|
|
|
245
253
|
? jsonToNowlineText(args.contents, args.displayPath)
|
|
246
254
|
: args.contents;
|
|
247
255
|
|
|
248
|
-
// Pre-validate with locale-aware diagnostic formatting so validation
|
|
249
|
-
// errors reach the operator in their locale. The kernel will re-parse
|
|
250
|
-
// the same source below — double work accepted in exchange for faithful
|
|
251
|
-
// stderr output.
|
|
252
|
-
await parseAndValidate(sourceText, args);
|
|
253
|
-
|
|
254
256
|
const assetRoot = args.assetRoot
|
|
255
257
|
? path.resolve(args.assetRoot)
|
|
256
258
|
: path.dirname(args.absInputPath);
|
|
257
259
|
|
|
258
|
-
|
|
260
|
+
// Included files' text, kept for the code frames of routed diagnostics.
|
|
261
|
+
const included = new Map<string, string>();
|
|
262
|
+
const nodeHost = createNodeHostEnv(assetRoot);
|
|
263
|
+
const host: HostEnv = {
|
|
264
|
+
...nodeHost,
|
|
265
|
+
readSource: async (absPath) => {
|
|
266
|
+
const text = await nodeHost.readSource(absPath);
|
|
267
|
+
included.set(absPath, text);
|
|
268
|
+
return text;
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
// What the stage lays out with; the export below must ask for the same.
|
|
273
|
+
const layoutInputs: RenderInputs = {
|
|
274
|
+
sourcePath: args.absInputPath,
|
|
275
|
+
today: args.today,
|
|
276
|
+
locale: args.locale ?? 'und',
|
|
277
|
+
theme: args.theme,
|
|
278
|
+
width: args.width,
|
|
279
|
+
nonWorking: args.nonWorking,
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// Parse and stage once. Validation diagnostics come out in the operator's
|
|
283
|
+
// locale, and routed include-resolver diagnostics join the validator's
|
|
284
|
+
// here, so they are reported at the same point and in the same report.
|
|
285
|
+
const { stage, stageFailure } = await parseAndValidate(sourceText, args, {
|
|
286
|
+
stage: { inputs: layoutInputs, host, included },
|
|
287
|
+
});
|
|
259
288
|
|
|
260
289
|
// Resolve fonts lazily — only for formats that rasterize or embed them.
|
|
261
290
|
let fonts: RenderInputs['fonts'];
|
|
@@ -263,29 +292,28 @@ async function produce(args: ProduceArgs): Promise<ProduceResult> {
|
|
|
263
292
|
fonts = await resolveNodeFonts(args);
|
|
264
293
|
}
|
|
265
294
|
|
|
295
|
+
args.assertPdfSettingsValid();
|
|
296
|
+
|
|
266
297
|
const kernelInputs: RenderInputs = {
|
|
267
|
-
|
|
268
|
-
today: args.today,
|
|
269
|
-
locale: args.locale ?? 'und',
|
|
270
|
-
theme: args.theme,
|
|
298
|
+
...layoutInputs,
|
|
271
299
|
fonts,
|
|
272
|
-
width: args.width,
|
|
273
300
|
noLinks: args.noLinks,
|
|
274
301
|
strict: args.strict,
|
|
275
302
|
pageSize: args.pageSize,
|
|
276
|
-
orientation:
|
|
277
|
-
marginPt:
|
|
303
|
+
orientation: args.orientation,
|
|
304
|
+
marginPt: args.marginPt,
|
|
278
305
|
pngScale: parseScale(args.scale),
|
|
279
306
|
msprojStart: args.start,
|
|
307
|
+
onNotice: (message) => args.log.notice(message),
|
|
308
|
+
onWarning: (message) => args.log.notice(`warning: ${message}`),
|
|
280
309
|
};
|
|
281
310
|
|
|
282
311
|
try {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
);
|
|
312
|
+
// A stage failure or an uncoded include error was held back until
|
|
313
|
+
// here, so a bad font or PDF setting is still reported first.
|
|
314
|
+
if (!stage) throw stageFailure;
|
|
315
|
+
const staged = acceptStagedRoadmap(stage, kernelInputs);
|
|
316
|
+
const bytes = await exportStaged(staged, args.format as ExportFormat, kernelInputs, host);
|
|
289
317
|
const isBinary = BINARY_FORMATS.has(args.format);
|
|
290
318
|
const rendered: string | Uint8Array = isBinary
|
|
291
319
|
? bytes
|
|
@@ -301,6 +329,18 @@ async function produce(args: ProduceArgs): Promise<ProduceResult> {
|
|
|
301
329
|
}
|
|
302
330
|
}
|
|
303
331
|
|
|
332
|
+
/**
|
|
333
|
+
* How an included file is named in diagnostics: relative to the input's
|
|
334
|
+
* display path, so `examples/a.nowline` including `./teams/b.nowline` prints
|
|
335
|
+
* `examples/teams/b.nowline` (absolute when the input was given absolute).
|
|
336
|
+
*/
|
|
337
|
+
function includeDisplayPath(absPath: string, args: ProduceArgs): string {
|
|
338
|
+
const rel = path.relative(path.dirname(args.absInputPath), absPath);
|
|
339
|
+
if (path.isAbsolute(rel)) return rel;
|
|
340
|
+
const base = args.isStdin ? '' : path.dirname(args.displayPath);
|
|
341
|
+
return path.join(base, rel);
|
|
342
|
+
}
|
|
343
|
+
|
|
304
344
|
// ---- Node HostEnv -----------------------------------------------------------
|
|
305
345
|
|
|
306
346
|
function createNodeHostEnv(assetRoot: string): HostEnv {
|
|
@@ -371,20 +411,20 @@ async function resolveNodeFonts(
|
|
|
371
411
|
if (args.strict) {
|
|
372
412
|
throw new CliError(ExitCode.InputError, `nowline: ${message}`);
|
|
373
413
|
}
|
|
374
|
-
|
|
414
|
+
args.log.notice(`warning: ${message}`);
|
|
375
415
|
}
|
|
376
416
|
|
|
377
417
|
// Bundled fallback after an opted-in probe found no usable system font.
|
|
378
418
|
// Only meaningful when --use-system-fonts is set; warn under --strict.
|
|
379
419
|
if (args.strict) {
|
|
380
420
|
if (result.sansFellBackToBundled) {
|
|
381
|
-
|
|
382
|
-
'warning: sans font fell back to bundled DejaVu (no platform font found)
|
|
421
|
+
args.log.notice(
|
|
422
|
+
'warning: sans font fell back to bundled DejaVu (no platform font found)',
|
|
383
423
|
);
|
|
384
424
|
}
|
|
385
425
|
if (result.monoFellBackToBundled) {
|
|
386
|
-
|
|
387
|
-
'warning: mono font fell back to bundled DejaVu (no platform font found)
|
|
426
|
+
args.log.notice(
|
|
427
|
+
'warning: mono font fell back to bundled DejaVu (no platform font found)',
|
|
388
428
|
);
|
|
389
429
|
}
|
|
390
430
|
}
|
|
@@ -410,97 +450,99 @@ function jsonToNowlineText(contents: string, displayPath: string): string {
|
|
|
410
450
|
return printNowlineFile(ast);
|
|
411
451
|
}
|
|
412
452
|
|
|
413
|
-
|
|
453
|
+
/** What `parseAndValidate` stages with, once the source validates. */
|
|
454
|
+
interface StageRequest {
|
|
455
|
+
/** The layout inputs the export that follows will ask for. */
|
|
456
|
+
inputs: RenderInputs;
|
|
457
|
+
host: HostEnv;
|
|
458
|
+
/** Collects the text of each file the stage reads, for code frames. */
|
|
459
|
+
included: Map<string, string>;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Parse and validate; on errors, report them and throw. With `stage`, the
|
|
464
|
+
* validated document is also staged (include resolution, logo, layout) and a
|
|
465
|
+
* file that has includes reports the routed (wave-rule) diagnostics of that
|
|
466
|
+
* stage with the validator's, in the same report (specs/waves.md §6.1). The
|
|
467
|
+
* stage is returned for the export to reuse. Every other include error, and a
|
|
468
|
+
* stage that throws, is left for the caller to report where the kernel always
|
|
469
|
+
* did (exit 3).
|
|
470
|
+
*/
|
|
471
|
+
async function parseAndValidate(
|
|
472
|
+
contents: string,
|
|
473
|
+
args: ProduceArgs,
|
|
474
|
+
options: { stage?: StageRequest } = {},
|
|
475
|
+
) {
|
|
414
476
|
const result = await parseSource(contents, args.displayPath, { validate: true });
|
|
477
|
+
const diagnostics = [...result.diagnostics];
|
|
478
|
+
const sources = new Map<string, DiagnosticSource>([[args.displayPath, result.source]]);
|
|
479
|
+
let failed = result.hasErrors;
|
|
480
|
+
let stage: StageResult | undefined;
|
|
481
|
+
let stageFailure: unknown;
|
|
482
|
+
if (!failed && options.stage) {
|
|
483
|
+
const { inputs, host, included } = options.stage;
|
|
484
|
+
try {
|
|
485
|
+
stage = await stageForExport(contents, inputs, host, {
|
|
486
|
+
document: result.document,
|
|
487
|
+
services: getServices(),
|
|
488
|
+
});
|
|
489
|
+
} catch (err) {
|
|
490
|
+
stageFailure = err;
|
|
491
|
+
}
|
|
492
|
+
if (stage && result.ast.includes.length > 0) {
|
|
493
|
+
for (const d of routedDiagnostics(stage)) {
|
|
494
|
+
const isRoot = d.sourcePath === args.absInputPath;
|
|
495
|
+
const file = isRoot ? args.displayPath : includeDisplayPath(d.sourcePath, args);
|
|
496
|
+
const text = isRoot ? result.source.contents : included.get(d.sourcePath);
|
|
497
|
+
if (text !== undefined) sources.set(file, { file, contents: text });
|
|
498
|
+
diagnostics.push(adaptResolveDiagnostic(d, file, text));
|
|
499
|
+
if (d.severity === 'error') failed = true;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
415
503
|
// Text mode prints only when the run fails. JSON mode prints one document
|
|
416
504
|
// whenever any diagnostic exists, warnings included, so a warnings-only
|
|
417
505
|
// run still exits 0 with the document on stderr.
|
|
418
|
-
if (
|
|
419
|
-
emitDiagnostics(
|
|
420
|
-
result.diagnostics,
|
|
421
|
-
result.source,
|
|
422
|
-
args.displayPath,
|
|
423
|
-
args.operatorLocale,
|
|
424
|
-
args.diagnosticFormat,
|
|
425
|
-
);
|
|
506
|
+
if (failed || (args.diagnosticFormat === 'json' && diagnostics.length > 0)) {
|
|
507
|
+
emitDiagnostics(diagnostics, sources, args.operatorLocale, args.diagnosticFormat, args.log);
|
|
426
508
|
}
|
|
427
|
-
if (
|
|
509
|
+
if (failed) {
|
|
428
510
|
throw new CliError(ExitCode.ValidationError, '');
|
|
429
511
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
}
|
|
435
|
-
return result;
|
|
512
|
+
const directive = readDirectiveLocale(result.ast);
|
|
513
|
+
const { tag, source } = describeContentLocaleSource(directive, args.resolvedLocale);
|
|
514
|
+
args.log.verbose(`nowline: locale=${tag} (${source})`);
|
|
515
|
+
return { ...result, stage, stageFailure };
|
|
436
516
|
}
|
|
437
517
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
}
|
|
447
|
-
const abs = path.resolve(cwd, inputArg);
|
|
448
|
-
const dir = path.dirname(abs);
|
|
449
|
-
const { config } = await loadConfig(dir);
|
|
450
|
-
return config;
|
|
451
|
-
} catch {
|
|
452
|
-
return null;
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
function parseTheme(raw: string | undefined): ThemeName {
|
|
457
|
-
if (!raw) return 'light';
|
|
458
|
-
// `greyscale` (UK) is accepted and canonicalized to `grayscale` (US).
|
|
459
|
-
const theme = normalizeThemeName(raw);
|
|
460
|
-
if (!theme) {
|
|
461
|
-
throw new CliError(
|
|
462
|
-
ExitCode.InputError,
|
|
463
|
-
`nowline: invalid --theme "${raw}". Expected light, dark, or grayscale.`,
|
|
464
|
-
);
|
|
518
|
+
/**
|
|
519
|
+
* The routed (wave-rule) diagnostics of a stage's include resolution. Empty
|
|
520
|
+
* when the resolver reports any other include error: that one is more
|
|
521
|
+
* fundamental, and the export reports it as before (exit 3).
|
|
522
|
+
*/
|
|
523
|
+
function routedDiagnostics(stage: StageResult): ResolveDiagnostic[] {
|
|
524
|
+
if (stage.kind === 'staged' || (stage.kind === 'include-error' && !stage.legacy)) {
|
|
525
|
+
return stage.routed;
|
|
465
526
|
}
|
|
466
|
-
return
|
|
527
|
+
return [];
|
|
467
528
|
}
|
|
468
529
|
|
|
469
|
-
//
|
|
470
|
-
//
|
|
471
|
-
//
|
|
472
|
-
//
|
|
473
|
-
//
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
// (bars, milestones, axis) are floating and are never affected.
|
|
481
|
-
function resolveNowCli(args: { now?: string; timezone?: string }): Date | undefined {
|
|
482
|
-
let zone: NormalizedZone | undefined;
|
|
483
|
-
if (args.timezone) {
|
|
484
|
-
try {
|
|
485
|
-
zone = normalizeZone(args.timezone);
|
|
486
|
-
} catch (err) {
|
|
487
|
-
if (err instanceof TimezoneError) {
|
|
488
|
-
throw new CliError(ExitCode.InputError, err.message);
|
|
489
|
-
}
|
|
490
|
-
throw err;
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
const result = resolveToday({ now: args.now, zone });
|
|
494
|
-
// resolveToday returns undefined for unrecognised strings; surface that as
|
|
495
|
-
// an input error so the user gets a clear message instead of a silent no-line.
|
|
496
|
-
if (result === undefined && args.now !== undefined && args.now !== '-') {
|
|
530
|
+
// Walk up from the input (or `cwd` for stdin) to the nearest `.nowlinerc`.
|
|
531
|
+
// No rc file is not an error. One that exists but cannot be read or parsed is
|
|
532
|
+
// a usage error (specs/cli.md, `.nowlinerc`): silently ignoring it would
|
|
533
|
+
// render with settings the user believes they changed. The loader's reason
|
|
534
|
+
// already names the file, so it is passed through. `--serve` shares this.
|
|
535
|
+
export async function loadConfigFor(inputArg: string, cwd: string): Promise<LoadConfigResult> {
|
|
536
|
+
try {
|
|
537
|
+
if (inputArg === '-') return await loadConfig(cwd);
|
|
538
|
+
const abs = path.resolve(cwd, inputArg);
|
|
539
|
+
return await loadConfig(path.dirname(abs));
|
|
540
|
+
} catch (err) {
|
|
497
541
|
throw new CliError(
|
|
498
542
|
ExitCode.InputError,
|
|
499
|
-
`nowline: invalid
|
|
500
|
-
`Expected YYYY-MM-DD, YYYY-MM-DDTHH:MM:SS[Z|±HH:MM], or "-".`,
|
|
543
|
+
`nowline: invalid .nowlinerc: ${err instanceof Error ? err.message : String(err)}`,
|
|
501
544
|
);
|
|
502
545
|
}
|
|
503
|
-
return result;
|
|
504
546
|
}
|
|
505
547
|
|
|
506
548
|
function parseScale(raw: string | undefined): number | undefined {
|
|
@@ -515,54 +557,6 @@ function parseScale(raw: string | undefined): number | undefined {
|
|
|
515
557
|
return value;
|
|
516
558
|
}
|
|
517
559
|
|
|
518
|
-
function parseOrientation(raw: string | undefined): 'portrait' | 'landscape' | 'auto' | undefined {
|
|
519
|
-
if (!raw) return undefined;
|
|
520
|
-
const lower = raw.toLowerCase();
|
|
521
|
-
if (lower === 'portrait' || lower === 'landscape' || lower === 'auto') return lower;
|
|
522
|
-
throw new CliError(
|
|
523
|
-
ExitCode.InputError,
|
|
524
|
-
`nowline: invalid --orientation "${raw}". Expected portrait, landscape, or auto.`,
|
|
525
|
-
);
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
function parseMargin(raw: string | undefined): number | undefined {
|
|
529
|
-
if (!raw) return undefined;
|
|
530
|
-
if (/^\d+(?:\.\d+)?$/.test(raw)) return Number(raw); // bare number → points
|
|
531
|
-
try {
|
|
532
|
-
return lengthToPoints(parseLength(raw));
|
|
533
|
-
} catch (err) {
|
|
534
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
535
|
-
throw new CliError(ExitCode.InputError, `nowline: invalid --margin "${raw}": ${message}`);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
function stringFromConfig(
|
|
540
|
-
config: { [key: string]: unknown } | null,
|
|
541
|
-
key: string,
|
|
542
|
-
): string | undefined {
|
|
543
|
-
if (!config) return undefined;
|
|
544
|
-
const value = config[key];
|
|
545
|
-
return typeof value === 'string' ? value : undefined;
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
function boolFromConfig(config: { [key: string]: unknown } | null, key: string): boolean {
|
|
549
|
-
if (!config) return false;
|
|
550
|
-
const value = config[key];
|
|
551
|
-
return value === true;
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
function parseWidthArg(raw: string | undefined): number | undefined {
|
|
555
|
-
if (!raw) return undefined;
|
|
556
|
-
const value = parseInt(raw, 10);
|
|
557
|
-
if (!Number.isFinite(value) || value < 320) {
|
|
558
|
-
throw new CliError(
|
|
559
|
-
ExitCode.InputError,
|
|
560
|
-
`nowline: invalid --width "${raw}". Must be an integer ≥ 320.`,
|
|
561
|
-
);
|
|
562
|
-
}
|
|
563
|
-
return value;
|
|
564
|
-
}
|
|
565
|
-
|
|
566
560
|
/**
|
|
567
561
|
* `--diagnostic-format` is the explicit switch. Left unset, a `--dry-run` that
|
|
568
562
|
* asks for `--format=json` gets JSON diagnostics (the machine-consumer shape
|
|
@@ -575,20 +569,29 @@ function resolveDiagnosticFormat(args: ParsedArgs): DiagnosticFormat {
|
|
|
575
569
|
}
|
|
576
570
|
|
|
577
571
|
function emitDiagnostics(
|
|
578
|
-
diagnostics:
|
|
579
|
-
|
|
580
|
-
displayPath: string,
|
|
572
|
+
diagnostics: CliDiagnostic[],
|
|
573
|
+
sources: Map<string, DiagnosticSource>,
|
|
581
574
|
operatorLocale: string,
|
|
582
575
|
format: DiagnosticFormat,
|
|
576
|
+
log: Log,
|
|
583
577
|
): void {
|
|
584
|
-
|
|
585
|
-
const
|
|
578
|
+
// JSON is machine output and always prints whole; text drops warnings under -q.
|
|
579
|
+
const shown = format === 'text' ? errorsOnlyWhenQuiet(diagnostics, log) : diagnostics;
|
|
580
|
+
const rendered = formatDiagnostics(shown, format, sources, {
|
|
586
581
|
color: process.stderr.isTTY === true,
|
|
587
582
|
operatorLocale,
|
|
588
583
|
});
|
|
589
584
|
if (rendered) process.stderr.write(`${rendered}\n`);
|
|
590
585
|
}
|
|
591
586
|
|
|
587
|
+
/** Under `-q`, text-mode diagnostics keep only errors. Shared with serve. */
|
|
588
|
+
export function errorsOnlyWhenQuiet<T extends { severity: string }>(
|
|
589
|
+
diagnostics: T[],
|
|
590
|
+
log: Log,
|
|
591
|
+
): T[] {
|
|
592
|
+
return log.quiet ? diagnostics.filter((d) => d.severity === 'error') : diagnostics;
|
|
593
|
+
}
|
|
594
|
+
|
|
592
595
|
// ---- Asset resolver (still used by serve.ts) --------------------------------
|
|
593
596
|
|
|
594
597
|
export function createAssetResolver(assetRoot: string): AssetResolver {
|