11#!/usr/bin/env node
2- import { parseArgs } from "node:util" ;
2+ import { parseArgs , type ParseArgsConfig } from "node:util" ;
33import { mkdir , readFile , writeFile } from "node:fs/promises" ;
4- import { dirname } from "node:path" ;
4+ import { dirname , join , resolve } from "node:path" ;
55import { build } from "./build.ts" ;
6+ import { bundle } from "./bundle.ts" ;
7+ import { generateKeyPair } from "./keys.ts" ;
68import { generateKt , generateTs } from "./schema/generate.ts" ;
79import { loadSchema } from "./schema/load.ts" ;
810
9- const USAGE = `usage: tinyui build [--root <dir>] [--out <dir>] [--qjsc <path>] [--js-only]
11+ const USAGE = `usage: tinyui build [--root <dir>] [--out <dir>] [--qjsc <path>] [--js-only] [--version <v>]
12+ tinyui bundle --runtime-version <rv> --signing-key <pem> [--dist <dir>] [--rollout <0-100>] [--out <dir>]
13+ tinyui keys generate [--out <pem>]
1014 tinyui schema --entry <schema.ts> [--ts <file>] [--kt <file> --package <pkg> [--object <Name>]] [--check]
1115
12- build
13- --root project root, pages under <root>/src/pages (default: cwd)
16+ build compile the pages of the package described by <root>/tinyui.config.json
17+ --root project root (default: cwd)
1418 --out output directory (default: <root>/dist)
1519 --qjsc path to qjsc-kmp (default: $TINYUI_QJSC, then PATH)
1620 --js-only emit ESM sources and source maps only, skip bytecode
21+ --version package version to record (default: <createdAt>-<git sha>)
22+
23+ bundle turn a build into the signed upload directory <out>/<pkg>/<rv>/ (docs/updates.md)
24+ --runtime-version the host's declared runtimeVersion
25+ --signing-key PKCS#8 PEM whose public key is the one in tinyui.config.json
26+ --dist tinyui build output (default: ./dist)
27+ --rollout percentage written to current.json (default: 100)
28+ --out output root (default: <dist>/ota)
29+
30+ keys generate
31+ --out where to write the private key (default: ./tinyui-signing-key.pem); the public key is printed
1732
1833schema
1934 --entry TS module whose default export is the component list
@@ -23,40 +38,77 @@ schema
2338 --check exit 1 if a target is not already up to date, write nothing
2439` ;
2540
26- async function main ( argv : string [ ] ) : Promise < number > {
27- const { values, positionals } = parseArgs ( {
28- args : argv ,
29- allowPositionals : true ,
41+ type Options = NonNullable < ParseArgsConfig [ "options" ] > ;
42+
43+ const COMMANDS : Record < string , { options : Options ; run : ( v : Record < string , unknown > , positionals : string [ ] ) => Promise < number > } > = {
44+ build : {
45+ options : { root : { type : "string" } , out : { type : "string" } , qjsc : { type : "string" } , "js-only" : { type : "boolean" , default : false } , version : { type : "string" } } ,
46+ run : async ( v ) => {
47+ const result = await build ( {
48+ root : ( v [ "root" ] as string | undefined ) ?? process . cwd ( ) ,
49+ ...( v [ "out" ] !== undefined && { out : v [ "out" ] as string } ) ,
50+ ...( v [ "qjsc" ] !== undefined && { qjsc : v [ "qjsc" ] as string } ) ,
51+ ...( v [ "version" ] !== undefined && { version : v [ "version" ] as string } ) ,
52+ jsOnly : v [ "js-only" ] as boolean ,
53+ } ) ;
54+ for ( const m of [ ...result . runtime , ...result . pages ] ) process . stdout . write ( `${ m . name } -> ${ m . bin ?? m . js } \n` ) ;
55+ return 0 ;
56+ } ,
57+ } ,
58+ bundle : {
59+ options : { "runtime-version" : { type : "string" } , "signing-key" : { type : "string" } , dist : { type : "string" } , rollout : { type : "string" } , out : { type : "string" } } ,
60+ run : async ( v ) => {
61+ const runtimeVersion = v [ "runtime-version" ] as string | undefined ;
62+ const signingKey = v [ "signing-key" ] as string | undefined ;
63+ if ( ! runtimeVersion ) throw new Error ( "bundle: --runtime-version is required" ) ;
64+ if ( ! signingKey ) throw new Error ( "bundle: --signing-key is required" ) ;
65+ const result = await bundle ( {
66+ dist : ( v [ "dist" ] as string | undefined ) ?? resolve ( "dist" ) ,
67+ runtimeVersion,
68+ signingKey,
69+ ...( v [ "rollout" ] !== undefined && { rollout : Number ( v [ "rollout" ] ) } ) ,
70+ ...( v [ "out" ] !== undefined && { out : v [ "out" ] as string } ) ,
71+ } ) ;
72+ process . stdout . write ( `${ result . version } -> ${ result . dir } \n` ) ;
73+ return 0 ;
74+ } ,
75+ } ,
76+ keys : {
77+ options : { out : { type : "string" } } ,
78+ run : async ( v , positionals ) => {
79+ if ( positionals [ 1 ] !== "generate" ) throw new Error ( "keys: expected `keys generate`" ) ;
80+ const out = ( v [ "out" ] as string | undefined ) ?? resolve ( "tinyui-signing-key.pem" ) ;
81+ const pair = generateKeyPair ( ) ;
82+ await mkdir ( dirname ( out ) , { recursive : true } ) ;
83+ // the private key must never silently replace one already in use
84+ await writeFile ( out , pair . privateKeyPem , { flag : "wx" , mode : 0o600 } ) ;
85+ process . stderr . write ( `private key written to ${ out } ; keep it with the publisher only\n` ) ;
86+ process . stdout . write ( `${ pair . publicKey } \n` ) ;
87+ return 0 ;
88+ } ,
89+ } ,
90+ schema : {
3091 options : {
31- root : { type : "string" } ,
32- out : { type : "string" } ,
33- qjsc : { type : "string" } ,
34- "js-only" : { type : "boolean" , default : false } ,
3592 entry : { type : "string" } ,
3693 ts : { type : "string" } ,
3794 kt : { type : "string" } ,
3895 package : { type : "string" } ,
3996 object : { type : "string" , default : "BuiltinSchemas" } ,
4097 "node-import" : { type : "string" , default : "tinyui-core" } ,
4198 check : { type : "boolean" , default : false } ,
42- help : { type : "boolean" , short : "h" , default : false } ,
4399 } ,
44- } ) ;
45- if ( values . help || ( positionals [ 0 ] !== "build" && positionals [ 0 ] !== "schema" ) ) {
100+ run : ( v ) => schema ( v as Parameters < typeof schema > [ 0 ] ) ,
101+ } ,
102+ } ;
103+
104+ async function main ( argv : string [ ] ) : Promise < number > {
105+ const command = COMMANDS [ argv [ 0 ] ?? "" ] ;
106+ if ( ! command || argv . includes ( "--help" ) || argv . includes ( "-h" ) ) {
46107 process . stdout . write ( USAGE ) ;
47- return values . help ? 0 : 2 ;
48- }
49- if ( positionals [ 0 ] === "schema" ) return schema ( values ) ;
50- const result = await build ( {
51- root : values . root ?? process . cwd ( ) ,
52- ...( values . out !== undefined && { out : values . out } ) ,
53- ...( values . qjsc !== undefined && { qjsc : values . qjsc } ) ,
54- jsOnly : values [ "js-only" ] ,
55- } ) ;
56- for ( const m of [ ...result . runtime , ...result . pages ] ) {
57- process . stdout . write ( `${ m . name } -> ${ m . bin ?? m . js } \n` ) ;
108+ return argv . includes ( "--help" ) || argv . includes ( "-h" ) ? 0 : 2 ;
58109 }
59- return 0 ;
110+ const { values, positionals } = parseArgs ( { args : argv , allowPositionals : true , options : command . options } ) ;
111+ return command . run ( values , positionals ) ;
60112}
61113
62114async function schema ( v : { entry ?: string ; ts ?: string ; kt ?: string ; package ?: string ; object : string ; check : boolean ; "node-import" : string } ) : Promise < number > {
0 commit comments