@nowline/cli 0.2.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/LICENSE +190 -0
- package/README.md +372 -0
- package/dist/cli/args.d.ts +54 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +165 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/formats.d.ts +61 -0
- package/dist/cli/formats.d.ts.map +1 -0
- package/dist/cli/formats.js +153 -0
- package/dist/cli/formats.js.map +1 -0
- package/dist/cli/help.d.ts +3 -0
- package/dist/cli/help.d.ts.map +1 -0
- package/dist/cli/help.js +90 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/output-path.d.ts +57 -0
- package/dist/cli/output-path.d.ts.map +1 -0
- package/dist/cli/output-path.js +70 -0
- package/dist/cli/output-path.js.map +1 -0
- package/dist/commands/init.d.ts +20 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +80 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/render.d.ts +15 -0
- package/dist/commands/render.d.ts.map +1 -0
- package/dist/commands/render.js +435 -0
- package/dist/commands/render.js.map +1 -0
- package/dist/commands/serve.d.ts +16 -0
- package/dist/commands/serve.d.ts.map +1 -0
- package/dist/commands/serve.js +287 -0
- package/dist/commands/serve.js.map +1 -0
- package/dist/convert/parse-json.d.ts +7 -0
- package/dist/convert/parse-json.d.ts.map +1 -0
- package/dist/convert/parse-json.js +34 -0
- package/dist/convert/parse-json.js.map +1 -0
- package/dist/convert/printer.d.ts +6 -0
- package/dist/convert/printer.d.ts.map +1 -0
- package/dist/convert/printer.js +334 -0
- package/dist/convert/printer.js.map +1 -0
- package/dist/convert/schema.d.ts +33 -0
- package/dist/convert/schema.d.ts.map +1 -0
- package/dist/convert/schema.js +77 -0
- package/dist/convert/schema.js.map +1 -0
- package/dist/core/parse.d.ts +24 -0
- package/dist/core/parse.d.ts.map +1 -0
- package/dist/core/parse.js +58 -0
- package/dist/core/parse.js.map +1 -0
- package/dist/diagnostics/adapt.d.ts +46 -0
- package/dist/diagnostics/adapt.d.ts.map +1 -0
- package/dist/diagnostics/adapt.js +109 -0
- package/dist/diagnostics/adapt.js.map +1 -0
- package/dist/diagnostics/format.d.ts +18 -0
- package/dist/diagnostics/format.d.ts.map +1 -0
- package/dist/diagnostics/format.js +41 -0
- package/dist/diagnostics/format.js.map +1 -0
- package/dist/diagnostics/index.d.ts +5 -0
- package/dist/diagnostics/index.d.ts.map +1 -0
- package/dist/diagnostics/index.js +5 -0
- package/dist/diagnostics/index.js.map +1 -0
- package/dist/diagnostics/json.d.ts +8 -0
- package/dist/diagnostics/json.d.ts.map +1 -0
- package/dist/diagnostics/json.js +24 -0
- package/dist/diagnostics/json.js.map +1 -0
- package/dist/diagnostics/model.d.ts +44 -0
- package/dist/diagnostics/model.d.ts.map +1 -0
- package/dist/diagnostics/model.js +2 -0
- package/dist/diagnostics/model.js.map +1 -0
- package/dist/diagnostics/text.d.ts +6 -0
- package/dist/diagnostics/text.d.ts.map +1 -0
- package/dist/diagnostics/text.js +43 -0
- package/dist/diagnostics/text.js.map +1 -0
- package/dist/generated/templates.d.ts +4 -0
- package/dist/generated/templates.d.ts.map +1 -0
- package/dist/generated/templates.js +9 -0
- package/dist/generated/templates.js.map +1 -0
- package/dist/generated/version.d.ts +11 -0
- package/dist/generated/version.d.ts.map +1 -0
- package/dist/generated/version.js +8 -0
- package/dist/generated/version.js.map +1 -0
- package/dist/i18n/locale.d.ts +56 -0
- package/dist/i18n/locale.d.ts.map +1 -0
- package/dist/i18n/locale.js +107 -0
- package/dist/i18n/locale.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -0
- package/dist/io/config.d.ts +2 -0
- package/dist/io/config.d.ts.map +1 -0
- package/dist/io/config.js +5 -0
- package/dist/io/config.js.map +1 -0
- package/dist/io/exit-codes.d.ts +12 -0
- package/dist/io/exit-codes.d.ts.map +1 -0
- package/dist/io/exit-codes.js +15 -0
- package/dist/io/exit-codes.js.map +1 -0
- package/dist/io/read.d.ts +13 -0
- package/dist/io/read.d.ts.map +1 -0
- package/dist/io/read.js +53 -0
- package/dist/io/read.js.map +1 -0
- package/dist/io/write.d.ts +32 -0
- package/dist/io/write.d.ts.map +1 -0
- package/dist/io/write.js +61 -0
- package/dist/io/write.js.map +1 -0
- package/dist/version.d.ts +13 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +20 -0
- package/dist/version.js.map +1 -0
- package/man/fr/nowline.1 +424 -0
- package/man/fr/nowline.5 +1864 -0
- package/man/nowline.1 +517 -0
- package/man/nowline.5 +1784 -0
- package/package.json +66 -0
- package/scripts/bundle-templates.mjs +105 -0
- package/scripts/compile.mjs +131 -0
- package/src/cli/args.ts +252 -0
- package/src/cli/formats.ts +207 -0
- package/src/cli/help.ts +92 -0
- package/src/cli/output-path.ts +98 -0
- package/src/commands/init.ts +99 -0
- package/src/commands/render.ts +566 -0
- package/src/commands/serve.ts +322 -0
- package/src/convert/parse-json.ts +57 -0
- package/src/convert/printer.ts +376 -0
- package/src/convert/schema.ts +105 -0
- package/src/core/parse.ts +93 -0
- package/src/diagnostics/adapt.ts +148 -0
- package/src/diagnostics/format.ts +70 -0
- package/src/diagnostics/index.ts +4 -0
- package/src/diagnostics/json.ts +30 -0
- package/src/diagnostics/model.ts +48 -0
- package/src/diagnostics/text.ts +62 -0
- package/src/generated/templates.ts +12 -0
- package/src/generated/version.ts +18 -0
- package/src/i18n/locale.ts +133 -0
- package/src/index.ts +60 -0
- package/src/io/config.ts +11 -0
- package/src/io/exit-codes.ts +18 -0
- package/src/io/read.ts +70 -0
- package/src/io/write.ts +94 -0
- package/src/version.ts +21 -0
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nowline/cli",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Nowline command-line interface — validate, convert, init",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"nowline": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"man": [
|
|
13
|
+
"./man/nowline.1",
|
|
14
|
+
"./man/nowline.5"
|
|
15
|
+
],
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist/",
|
|
24
|
+
"man/",
|
|
25
|
+
"src/",
|
|
26
|
+
"scripts/"
|
|
27
|
+
],
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@babel/code-frame": "^7.29.0",
|
|
30
|
+
"@clack/prompts": "^0.11.0",
|
|
31
|
+
"chalk": "^5.6.0",
|
|
32
|
+
"citty": "^0.2.2",
|
|
33
|
+
"consola": "^3.4.0",
|
|
34
|
+
"js-yaml": "^4.1.0",
|
|
35
|
+
"langium": "~4.2.2",
|
|
36
|
+
"@nowline/config": "0.2.0",
|
|
37
|
+
"@nowline/core": "0.2.0",
|
|
38
|
+
"@nowline/export-core": "0.2.0",
|
|
39
|
+
"@nowline/export-html": "0.2.0",
|
|
40
|
+
"@nowline/export-mermaid": "0.2.0",
|
|
41
|
+
"@nowline/export-msproj": "0.2.0",
|
|
42
|
+
"@nowline/export-png": "0.2.0",
|
|
43
|
+
"@nowline/layout": "0.2.0",
|
|
44
|
+
"@nowline/export-pdf": "0.2.0",
|
|
45
|
+
"@nowline/renderer": "0.2.0",
|
|
46
|
+
"@nowline/export-xlsx": "0.2.0"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/babel__code-frame": "^7.0.6",
|
|
50
|
+
"@types/js-yaml": "^4.0.9",
|
|
51
|
+
"@types/node": "^22.0.0",
|
|
52
|
+
"typescript": "~5.7.0",
|
|
53
|
+
"vitest": "^3.1.0"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"bundle-templates": "node scripts/bundle-templates.mjs",
|
|
57
|
+
"prebuild": "node scripts/bundle-templates.mjs",
|
|
58
|
+
"build": "tsc -b tsconfig.json",
|
|
59
|
+
"watch": "tsc -b tsconfig.json --watch",
|
|
60
|
+
"pretest": "node scripts/bundle-templates.mjs",
|
|
61
|
+
"test": "vitest run",
|
|
62
|
+
"test:watch": "vitest",
|
|
63
|
+
"compile": "node scripts/compile.mjs",
|
|
64
|
+
"compile:local": "node scripts/compile.mjs --target=local"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const packageRoot = path.resolve(__dirname, '..');
|
|
9
|
+
const repoRoot = path.resolve(packageRoot, '..', '..');
|
|
10
|
+
const examplesDir = path.join(repoRoot, 'examples');
|
|
11
|
+
const outDir = path.join(packageRoot, 'src', 'generated');
|
|
12
|
+
|
|
13
|
+
const TEMPLATES = [
|
|
14
|
+
{ name: 'minimal', file: 'minimal.nowline' },
|
|
15
|
+
{ name: 'teams', file: 'teams.nowline' },
|
|
16
|
+
{ name: 'product', file: 'product.nowline' },
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
mkdirSync(outDir, { recursive: true });
|
|
20
|
+
|
|
21
|
+
// 1. Templates
|
|
22
|
+
const templatesOutFile = path.join(outDir, 'templates.ts');
|
|
23
|
+
const entries = TEMPLATES.map(({ name, file }) => {
|
|
24
|
+
const abs = path.join(examplesDir, file);
|
|
25
|
+
const contents = readFileSync(abs, 'utf-8');
|
|
26
|
+
return ` ${JSON.stringify(name)}: ${JSON.stringify(contents)}`;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const templatesBanner = [
|
|
30
|
+
'// Auto-generated by scripts/bundle-templates.mjs. Do not edit by hand.',
|
|
31
|
+
'// Run `pnpm run bundle-templates` to regenerate.',
|
|
32
|
+
'',
|
|
33
|
+
"export type TemplateName = 'minimal' | 'teams' | 'product';",
|
|
34
|
+
'',
|
|
35
|
+
"export const TEMPLATE_NAMES: readonly TemplateName[] = ['minimal', 'teams', 'product'];",
|
|
36
|
+
'',
|
|
37
|
+
'export const TEMPLATES: Record<TemplateName, string> = {',
|
|
38
|
+
].join('\n');
|
|
39
|
+
|
|
40
|
+
const templatesFooter = '\n};\n';
|
|
41
|
+
|
|
42
|
+
writeFileSync(templatesOutFile, `${templatesBanner}\n${entries.join(',\n')}${templatesFooter}`);
|
|
43
|
+
console.log(`wrote ${path.relative(packageRoot, templatesOutFile)}`);
|
|
44
|
+
|
|
45
|
+
// 2. Version + build metadata.
|
|
46
|
+
//
|
|
47
|
+
// CLI_VERSION is the static SemVer from package.json (the same string
|
|
48
|
+
// `npm publish` uses). CLI_BUILD captures the git state at compile time
|
|
49
|
+
// so `nowline --version` can distinguish a tagged release from a dev
|
|
50
|
+
// build. The two are emitted side-by-side; the renderer composes the
|
|
51
|
+
// final string at runtime per SemVer's build-metadata rules:
|
|
52
|
+
//
|
|
53
|
+
// release -> "0.1.0"
|
|
54
|
+
// dev (clean) -> "0.1.0+abc1234"
|
|
55
|
+
// dev (dirty) -> "0.1.0+abc1234.dirty"
|
|
56
|
+
//
|
|
57
|
+
// `+...` is SemVer build metadata (RFC 2.0 §10) — informational only;
|
|
58
|
+
// publishers (npm, Marketplace) reject it on their own version fields,
|
|
59
|
+
// so it never reaches package.json.
|
|
60
|
+
|
|
61
|
+
const versionOutFile = path.join(outDir, 'version.ts');
|
|
62
|
+
const pkg = JSON.parse(readFileSync(path.join(packageRoot, 'package.json'), 'utf-8'));
|
|
63
|
+
|
|
64
|
+
function tryGit(args) {
|
|
65
|
+
try {
|
|
66
|
+
return execFileSync('git', args, {
|
|
67
|
+
cwd: repoRoot,
|
|
68
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
69
|
+
encoding: 'utf-8',
|
|
70
|
+
}).trim();
|
|
71
|
+
} catch {
|
|
72
|
+
return '';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const sha = tryGit(['rev-parse', '--short=7', 'HEAD']);
|
|
77
|
+
// `--exact-match` exits non-zero (and tryGit returns '') when HEAD is
|
|
78
|
+
// not a tagged commit, which we treat as "this is a dev build".
|
|
79
|
+
const exactTag = tryGit(['describe', '--exact-match', '--tags', 'HEAD']);
|
|
80
|
+
const expectedTag = `v${pkg.version}`;
|
|
81
|
+
const isRelease = exactTag === expectedTag;
|
|
82
|
+
const isDirty = tryGit(['status', '--porcelain']) !== '';
|
|
83
|
+
|
|
84
|
+
writeFileSync(
|
|
85
|
+
versionOutFile,
|
|
86
|
+
[
|
|
87
|
+
'// Auto-generated by scripts/bundle-templates.mjs. Do not edit by hand.',
|
|
88
|
+
'',
|
|
89
|
+
`export const CLI_VERSION = ${JSON.stringify(pkg.version)};`,
|
|
90
|
+
'',
|
|
91
|
+
'export interface CliBuild {',
|
|
92
|
+
' /** Short git SHA at build time, or empty when not in a git checkout. */',
|
|
93
|
+
' readonly sha: string;',
|
|
94
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: emits a TypeScript JSDoc comment that intentionally contains a template-literal placeholder string for readers, not for evaluation.
|
|
95
|
+
' /** True when HEAD is the tag matching `v${CLI_VERSION}`. */',
|
|
96
|
+
' readonly isRelease: boolean;',
|
|
97
|
+
' /** True when the working tree had uncommitted changes at build time. */',
|
|
98
|
+
' readonly isDirty: boolean;',
|
|
99
|
+
'}',
|
|
100
|
+
'',
|
|
101
|
+
`export const CLI_BUILD: CliBuild = ${JSON.stringify({ sha, isRelease, isDirty }, null, 4)};`,
|
|
102
|
+
'',
|
|
103
|
+
].join('\n'),
|
|
104
|
+
);
|
|
105
|
+
console.log(`wrote ${path.relative(packageRoot, versionOutFile)}`);
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Compile the CLI into standalone binaries for the six supported targets.
|
|
3
|
+
// Uses `bun build --compile`. Requires bun to be installed; delegates errors
|
|
4
|
+
// from bun rather than attempting a polyfill.
|
|
5
|
+
//
|
|
6
|
+
// One binary per platform: `nowline-<suffix>`. Bundles every `@nowline/export-*`
|
|
7
|
+
// package — see `specs/cli-distribution.md` for the rationale (the bun runtime
|
|
8
|
+
// dominates compiled binary size; a tiny/full split paid only ~5% size dividend
|
|
9
|
+
// for the cost of doubled CI/release/distribution channels).
|
|
10
|
+
|
|
11
|
+
import { spawnSync } from 'node:child_process';
|
|
12
|
+
import { mkdirSync, readdirSync, rmSync, statSync } from 'node:fs';
|
|
13
|
+
import * as path from 'node:path';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
|
|
16
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const packageRoot = path.resolve(__dirname, '..');
|
|
18
|
+
|
|
19
|
+
// Per-target size ceilings (MB). Bun's standalone runtime varies by ~50 MB
|
|
20
|
+
// across targets — darwin-arm64 ships ~60 MB, linux-x64 with glibc compat
|
|
21
|
+
// shims is ~95 MB, and windows-x64 is ~110 MB. A single global ceiling would
|
|
22
|
+
// either let darwin regressions slide or fail every Linux/Windows build, so
|
|
23
|
+
// each target carries its own budget = (currently measured size) + ~6–10 MB
|
|
24
|
+
// headroom for future bun-runtime growth and modest exporter additions.
|
|
25
|
+
// Tight by design: a breach should trigger the conversation called out in
|
|
26
|
+
// `specs/cli-distribution.md` "Size budget", not be silently absorbed.
|
|
27
|
+
//
|
|
28
|
+
// Last measured (bun 1.3.13) using --target on macOS-arm64:
|
|
29
|
+
// darwin-arm64=70 darwin-x64=75 linux-arm64=107 linux-x64=107
|
|
30
|
+
// windows-arm64=119 windows-x64=122
|
|
31
|
+
const ALL_TARGETS = [
|
|
32
|
+
{ id: 'bun-darwin-arm64', suffix: 'macos-arm64', maxMb: 80 },
|
|
33
|
+
{ id: 'bun-darwin-x64', suffix: 'macos-x64', maxMb: 85 },
|
|
34
|
+
{ id: 'bun-linux-x64', suffix: 'linux-x64', maxMb: 115 },
|
|
35
|
+
{ id: 'bun-linux-arm64', suffix: 'linux-arm64', maxMb: 115 },
|
|
36
|
+
{ id: 'bun-windows-x64', suffix: 'windows-x64.exe', maxMb: 130 },
|
|
37
|
+
{ id: 'bun-windows-arm64', suffix: 'windows-arm64.exe', maxMb: 125 },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
function parseArgs(argv) {
|
|
41
|
+
const out = { target: 'all' };
|
|
42
|
+
for (const arg of argv.slice(2)) {
|
|
43
|
+
if (arg.startsWith('--target=')) out.target = arg.slice('--target='.length);
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function pickTargets(selector) {
|
|
49
|
+
if (selector === 'all') return ALL_TARGETS;
|
|
50
|
+
if (selector === 'local') {
|
|
51
|
+
const platformMap = {
|
|
52
|
+
'darwin/arm64': 'bun-darwin-arm64',
|
|
53
|
+
'darwin/x64': 'bun-darwin-x64',
|
|
54
|
+
'linux/x64': 'bun-linux-x64',
|
|
55
|
+
'linux/arm64': 'bun-linux-arm64',
|
|
56
|
+
'win32/x64': 'bun-windows-x64',
|
|
57
|
+
'win32/arm64': 'bun-windows-arm64',
|
|
58
|
+
};
|
|
59
|
+
const key = `${process.platform}/${process.arch}`;
|
|
60
|
+
const id = platformMap[key];
|
|
61
|
+
if (!id) throw new Error(`Unsupported local platform: ${key}`);
|
|
62
|
+
return ALL_TARGETS.filter((t) => t.id === id);
|
|
63
|
+
}
|
|
64
|
+
const match = ALL_TARGETS.filter((t) => t.id === selector || t.suffix === selector);
|
|
65
|
+
if (match.length === 0) throw new Error(`Unknown --target: ${selector}`);
|
|
66
|
+
return match;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function main() {
|
|
70
|
+
const { target } = parseArgs(process.argv);
|
|
71
|
+
const targets = pickTargets(target);
|
|
72
|
+
const outDir = path.join(packageRoot, 'dist-bin');
|
|
73
|
+
if (!safeStat(outDir)) {
|
|
74
|
+
mkdirSync(outDir, { recursive: true });
|
|
75
|
+
} else {
|
|
76
|
+
for (const name of readdirSync(outDir)) {
|
|
77
|
+
if (name.startsWith('nowline-')) {
|
|
78
|
+
rmSync(path.join(outDir, name), { force: true });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const entry = path.join(packageRoot, 'dist', 'index.js');
|
|
84
|
+
if (!safeStat(entry)) {
|
|
85
|
+
console.error(
|
|
86
|
+
`error: expected ${path.relative(packageRoot, entry)} to exist; run \`pnpm build\` first.`,
|
|
87
|
+
);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let failed = 0;
|
|
92
|
+
for (const tgt of targets) {
|
|
93
|
+
const outName = `nowline-${tgt.suffix}`;
|
|
94
|
+
const outPath = path.join(outDir, outName);
|
|
95
|
+
console.log(`compiling ${tgt.id} -> ${path.relative(packageRoot, outPath)}`);
|
|
96
|
+
const args = ['build', entry, '--compile', '--target', tgt.id, '--outfile', outPath];
|
|
97
|
+
const result = spawnSync('bun', args, { stdio: 'inherit', cwd: packageRoot });
|
|
98
|
+
if (result.status !== 0) {
|
|
99
|
+
console.error(` FAILED ${tgt.id}`);
|
|
100
|
+
failed += 1;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const targetBySuffix = new Map(ALL_TARGETS.map((t) => [`nowline-${t.suffix}`, t]));
|
|
105
|
+
for (const entryName of readdirSync(outDir)) {
|
|
106
|
+
if (!entryName.startsWith('nowline-')) continue;
|
|
107
|
+
const tgt = targetBySuffix.get(entryName);
|
|
108
|
+
if (!tgt) continue; // unknown artifact; size budget is per known target
|
|
109
|
+
const p = path.join(outDir, entryName);
|
|
110
|
+
const size = statSync(p).size;
|
|
111
|
+
const mb = (size / 1024 / 1024).toFixed(1);
|
|
112
|
+
const maxBytes = tgt.maxMb * 1024 * 1024;
|
|
113
|
+
console.log(` ${entryName}: ${mb} MB (max ${tgt.maxMb} MB)`);
|
|
114
|
+
if (size > maxBytes) {
|
|
115
|
+
console.error(` ERROR: ${entryName} is larger than ${tgt.maxMb} MB (${mb} MB).`);
|
|
116
|
+
failed += 1;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
process.exit(failed === 0 ? 0 : 1);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function safeStat(p) {
|
|
124
|
+
try {
|
|
125
|
+
return statSync(p);
|
|
126
|
+
} catch {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
main();
|
package/src/cli/args.ts
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { type ParseArgsConfig, parseArgs } from 'node:util';
|
|
2
|
+
import { CliError, ExitCode } from '../io/exit-codes.js';
|
|
3
|
+
|
|
4
|
+
export type ModeKind = 'render' | 'serve' | 'init' | 'help' | 'version';
|
|
5
|
+
|
|
6
|
+
export interface ParsedArgs {
|
|
7
|
+
/** Resolved mode after dispatch (mutual-exclusivity already checked). */
|
|
8
|
+
mode: ModeKind;
|
|
9
|
+
/** Positional argument, or undefined. Render = input path. Init = project name. Serve = input path. */
|
|
10
|
+
positional?: string;
|
|
11
|
+
/** True if `--dry-run` / `-n` was passed (only valid for render mode). */
|
|
12
|
+
dryRun: boolean;
|
|
13
|
+
/** Logging level. Verbose and quiet are mutually exclusive. */
|
|
14
|
+
logLevel: 'verbose' | 'quiet' | 'normal';
|
|
15
|
+
|
|
16
|
+
// I/O
|
|
17
|
+
output?: string;
|
|
18
|
+
format?: string;
|
|
19
|
+
inputFormat?: string;
|
|
20
|
+
|
|
21
|
+
// Render options
|
|
22
|
+
theme?: string;
|
|
23
|
+
/** Now-line date string from `--now`. Undefined means "use the actual
|
|
24
|
+
* current date" (the default). The literal value `"-"` means
|
|
25
|
+
* "suppress the now-line entirely" (mirrors the Unix-`-` convention
|
|
26
|
+
* used elsewhere in the CLI). Otherwise expected as YYYY-MM-DD;
|
|
27
|
+
* parsed downstream by resolveNowArg. */
|
|
28
|
+
now?: string;
|
|
29
|
+
noLinks: boolean;
|
|
30
|
+
scale?: string;
|
|
31
|
+
strict: boolean;
|
|
32
|
+
width?: string;
|
|
33
|
+
assetRoot?: string;
|
|
34
|
+
|
|
35
|
+
// Format-specific options (m2c)
|
|
36
|
+
pageSize?: string;
|
|
37
|
+
orientation?: string;
|
|
38
|
+
margin?: string;
|
|
39
|
+
fontSans?: string;
|
|
40
|
+
fontMono?: string;
|
|
41
|
+
headless: boolean;
|
|
42
|
+
start?: string;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* BCP-47 locale override (`fr-CA`, `fr`, …). When omitted the CLI
|
|
46
|
+
* falls back to `LC_ALL` / `LC_MESSAGES` / `LANG`, then to the
|
|
47
|
+
* file's `nowline v1 locale:` directive, then to `en-US`. See
|
|
48
|
+
* `specs/localization.md`.
|
|
49
|
+
*/
|
|
50
|
+
locale?: string;
|
|
51
|
+
|
|
52
|
+
// Serve options
|
|
53
|
+
port?: string;
|
|
54
|
+
host?: string;
|
|
55
|
+
open: boolean;
|
|
56
|
+
|
|
57
|
+
// Validate / dry-run formatting
|
|
58
|
+
diagnosticFormat?: string;
|
|
59
|
+
|
|
60
|
+
// Init
|
|
61
|
+
template?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Pure argument parser. Walks `argv` once, identifies mode flags, applies
|
|
66
|
+
* mutual-exclusivity rules, and returns a fully-resolved `ParsedArgs`. Throws
|
|
67
|
+
* `CliError(ExitCode.InputError)` for usage errors.
|
|
68
|
+
*
|
|
69
|
+
* Help / version short-circuit any other flag combinations.
|
|
70
|
+
*/
|
|
71
|
+
export function parseArgv(argv: readonly string[]): ParsedArgs {
|
|
72
|
+
if (argv.length === 0) {
|
|
73
|
+
return {
|
|
74
|
+
mode: 'help',
|
|
75
|
+
dryRun: false,
|
|
76
|
+
logLevel: 'normal',
|
|
77
|
+
noLinks: false,
|
|
78
|
+
strict: false,
|
|
79
|
+
open: false,
|
|
80
|
+
headless: false,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const config: ParseArgsConfig = {
|
|
85
|
+
args: argv as string[],
|
|
86
|
+
allowPositionals: true,
|
|
87
|
+
strict: true,
|
|
88
|
+
options: {
|
|
89
|
+
help: { type: 'boolean', short: 'h' },
|
|
90
|
+
version: { type: 'boolean', short: 'V' },
|
|
91
|
+
verbose: { type: 'boolean', short: 'v' },
|
|
92
|
+
quiet: { type: 'boolean', short: 'q' },
|
|
93
|
+
|
|
94
|
+
output: { type: 'string', short: 'o' },
|
|
95
|
+
format: { type: 'string', short: 'f' },
|
|
96
|
+
'input-format': { type: 'string' },
|
|
97
|
+
|
|
98
|
+
serve: { type: 'boolean' },
|
|
99
|
+
init: { type: 'boolean' },
|
|
100
|
+
'dry-run': { type: 'boolean', short: 'n' },
|
|
101
|
+
|
|
102
|
+
theme: { type: 'string', short: 't' },
|
|
103
|
+
// `--now <date>` overrides the now-line position; without it, the
|
|
104
|
+
// CLI defaults to today's calendar date. Use `--now -` to
|
|
105
|
+
// suppress the now-line entirely (Unix-`-` sentinel; same idea
|
|
106
|
+
// as `-o -` for stdout).
|
|
107
|
+
now: { type: 'string' },
|
|
108
|
+
'no-links': { type: 'boolean' },
|
|
109
|
+
scale: { type: 'string', short: 's' },
|
|
110
|
+
strict: { type: 'boolean' },
|
|
111
|
+
width: { type: 'string', short: 'w' },
|
|
112
|
+
'asset-root': { type: 'string' },
|
|
113
|
+
|
|
114
|
+
port: { type: 'string', short: 'p' },
|
|
115
|
+
host: { type: 'string' },
|
|
116
|
+
open: { type: 'boolean' },
|
|
117
|
+
|
|
118
|
+
'diagnostic-format': { type: 'string' },
|
|
119
|
+
|
|
120
|
+
template: { type: 'string' },
|
|
121
|
+
|
|
122
|
+
// Format-specific (m2c)
|
|
123
|
+
'page-size': { type: 'string' },
|
|
124
|
+
orientation: { type: 'string' },
|
|
125
|
+
margin: { type: 'string' },
|
|
126
|
+
'font-sans': { type: 'string' },
|
|
127
|
+
'font-mono': { type: 'string' },
|
|
128
|
+
headless: { type: 'boolean' },
|
|
129
|
+
start: { type: 'string' },
|
|
130
|
+
|
|
131
|
+
// Localization (m-loc-b)
|
|
132
|
+
locale: { type: 'string' },
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
let parsed: ReturnType<typeof parseArgs>;
|
|
137
|
+
try {
|
|
138
|
+
parsed = parseArgs(config);
|
|
139
|
+
} catch (err) {
|
|
140
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
141
|
+
throw new CliError(ExitCode.InputError, formatUsageError(message));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const values = parsed.values as Record<string, unknown>;
|
|
145
|
+
const positionals = parsed.positionals;
|
|
146
|
+
|
|
147
|
+
if (values.help === true) {
|
|
148
|
+
return {
|
|
149
|
+
mode: 'help',
|
|
150
|
+
dryRun: false,
|
|
151
|
+
logLevel: 'normal',
|
|
152
|
+
noLinks: false,
|
|
153
|
+
strict: false,
|
|
154
|
+
open: false,
|
|
155
|
+
headless: false,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
if (values.version === true) {
|
|
159
|
+
return {
|
|
160
|
+
mode: 'version',
|
|
161
|
+
dryRun: false,
|
|
162
|
+
logLevel: 'normal',
|
|
163
|
+
noLinks: false,
|
|
164
|
+
strict: false,
|
|
165
|
+
open: false,
|
|
166
|
+
headless: false,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (values.verbose === true && values.quiet === true) {
|
|
171
|
+
throw new CliError(
|
|
172
|
+
ExitCode.InputError,
|
|
173
|
+
'nowline: --verbose and --quiet are mutually exclusive.',
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const modes: ModeKind[] = [];
|
|
178
|
+
if (values.serve === true) modes.push('serve');
|
|
179
|
+
if (values.init === true) modes.push('init');
|
|
180
|
+
if (modes.length > 1) {
|
|
181
|
+
throw new CliError(
|
|
182
|
+
ExitCode.InputError,
|
|
183
|
+
`nowline: --${modes[0]} and --${modes[1]} are mutually exclusive.`,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const dryRun = values['dry-run'] === true;
|
|
188
|
+
const mode: ModeKind = modes[0] ?? 'render';
|
|
189
|
+
|
|
190
|
+
if (dryRun && (mode === 'serve' || mode === 'init')) {
|
|
191
|
+
throw new CliError(
|
|
192
|
+
ExitCode.InputError,
|
|
193
|
+
`nowline: --dry-run cannot be combined with --${mode}.`,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (positionals.length > 1) {
|
|
198
|
+
const extras = positionals
|
|
199
|
+
.slice(1)
|
|
200
|
+
.map((p) => JSON.stringify(p))
|
|
201
|
+
.join(' ');
|
|
202
|
+
throw new CliError(ExitCode.InputError, `nowline: unexpected extra arguments: ${extras}.`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const logLevel: ParsedArgs['logLevel'] =
|
|
206
|
+
values.verbose === true ? 'verbose' : values.quiet === true ? 'quiet' : 'normal';
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
mode,
|
|
210
|
+
positional: positionals[0],
|
|
211
|
+
dryRun,
|
|
212
|
+
logLevel,
|
|
213
|
+
output: stringOrUndefined(values.output),
|
|
214
|
+
format: stringOrUndefined(values.format),
|
|
215
|
+
inputFormat: stringOrUndefined(values['input-format']),
|
|
216
|
+
theme: stringOrUndefined(values.theme),
|
|
217
|
+
now: stringOrUndefined(values.now),
|
|
218
|
+
noLinks: values['no-links'] === true,
|
|
219
|
+
scale: stringOrUndefined(values.scale),
|
|
220
|
+
strict: values.strict === true,
|
|
221
|
+
width: stringOrUndefined(values.width),
|
|
222
|
+
assetRoot: stringOrUndefined(values['asset-root']),
|
|
223
|
+
port: stringOrUndefined(values.port),
|
|
224
|
+
host: stringOrUndefined(values.host),
|
|
225
|
+
open: values.open === true,
|
|
226
|
+
diagnosticFormat: stringOrUndefined(values['diagnostic-format']),
|
|
227
|
+
template: stringOrUndefined(values.template),
|
|
228
|
+
pageSize: stringOrUndefined(values['page-size']),
|
|
229
|
+
orientation: stringOrUndefined(values.orientation),
|
|
230
|
+
margin: stringOrUndefined(values.margin),
|
|
231
|
+
fontSans: stringOrUndefined(values['font-sans']),
|
|
232
|
+
fontMono: stringOrUndefined(values['font-mono']),
|
|
233
|
+
headless: values.headless === true,
|
|
234
|
+
start: stringOrUndefined(values.start),
|
|
235
|
+
locale: stringOrUndefined(values.locale),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function stringOrUndefined(value: unknown): string | undefined {
|
|
240
|
+
if (typeof value === 'string') return value;
|
|
241
|
+
return undefined;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function formatUsageError(message: string): string {
|
|
245
|
+
if (message.startsWith('Unknown option')) {
|
|
246
|
+
return `nowline: ${message}. Try --help for usage.`;
|
|
247
|
+
}
|
|
248
|
+
if (message.includes('expected')) {
|
|
249
|
+
return `nowline: ${message}. Try --help for usage.`;
|
|
250
|
+
}
|
|
251
|
+
return `nowline: ${message}`;
|
|
252
|
+
}
|