tovuk 0.1.47

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.
@@ -0,0 +1,197 @@
1
+ import type { JsonObject } from './types.ts'
2
+
3
+ function frontendPackageJson(name: string): string {
4
+ return jsonSource({
5
+ name,
6
+ private: true,
7
+ type: 'module',
8
+ scripts: {
9
+ typecheck: 'oxlint src vite.config.ts --deny-warnings --type-aware --type-check --tsconfig tsconfig.json',
10
+ lint: 'oxlint src vite.config.ts --deny-warnings && fallow dead-code --production --include-dupes --include-entry-exports --fail-on-issues && fallow dupes --production --mode semantic --threshold 1 --ignore-imports --fail-on-issues && fallow health --production --max-cyclomatic 10 --max-cognitive 15 --max-crap 20 --complexity',
11
+ build: 'vite build',
12
+ preview: 'vite preview --host 0.0.0.0'
13
+ },
14
+ dependencies: {
15
+ '@tanstack/react-router': '^1.170.8',
16
+ react: '^19.2.6',
17
+ 'react-dom': '^19.2.6'
18
+ },
19
+ devDependencies: {
20
+ '@types/node': '^25.9.1',
21
+ '@types/react': '^19.2.15',
22
+ '@types/react-dom': '^19.2.3',
23
+ '@vitejs/plugin-react': '^6.0.2',
24
+ fallow: '^2.84.0',
25
+ oxlint: '^1.67.0',
26
+ 'oxlint-tsgolint': '^0.23.0',
27
+ vite: '^8.0.14'
28
+ }
29
+ })
30
+ }
31
+
32
+ function frontendTsConfig(): string {
33
+ return jsonSource({
34
+ compilerOptions: {
35
+ allowUnreachableCode: false,
36
+ allowUnusedLabels: false,
37
+ alwaysStrict: true,
38
+ erasableSyntaxOnly: true,
39
+ exactOptionalPropertyTypes: true,
40
+ forceConsistentCasingInFileNames: true,
41
+ isolatedModules: true,
42
+ jsx: 'react-jsx',
43
+ lib: ['ESNext', 'DOM'],
44
+ module: 'ESNext',
45
+ moduleDetection: 'force',
46
+ moduleResolution: 'Bundler',
47
+ noEmit: true,
48
+ noFallthroughCasesInSwitch: true,
49
+ noImplicitAny: true,
50
+ noImplicitOverride: true,
51
+ noImplicitReturns: true,
52
+ noImplicitThis: true,
53
+ noPropertyAccessFromIndexSignature: true,
54
+ noUncheckedIndexedAccess: true,
55
+ noUncheckedSideEffectImports: true,
56
+ noUnusedLocals: true,
57
+ noUnusedParameters: true,
58
+ skipLibCheck: false,
59
+ strict: true,
60
+ strictBindCallApply: true,
61
+ strictFunctionTypes: true,
62
+ strictNullChecks: true,
63
+ strictPropertyInitialization: true,
64
+ target: 'ES2022',
65
+ types: ['vite/client', 'node'],
66
+ useUnknownInCatchVariables: true,
67
+ verbatimModuleSyntax: true
68
+ },
69
+ include: ['src', 'vite.config.ts']
70
+ })
71
+ }
72
+
73
+ function rustApiSource(): string {
74
+ return `use std::{
75
+ io::{Read, Write},
76
+ net::{TcpListener, TcpStream},
77
+ };
78
+
79
+ fn main() -> std::io::Result<()> {
80
+ let port = std::env::var("PORT").unwrap_or_else(|_error| "3000".to_owned());
81
+ let listener = TcpListener::bind(format!("0.0.0.0:{port}"))?;
82
+
83
+ for stream in listener.incoming() {
84
+ handle(stream?)?;
85
+ }
86
+
87
+ Ok(())
88
+ }
89
+
90
+ fn handle(mut stream: TcpStream) -> std::io::Result<()> {
91
+ let mut buffer = [0_u8; 2048];
92
+ let size = stream.read(&mut buffer)?;
93
+ let request = String::from_utf8_lossy(&buffer[..size]);
94
+ let mut parts = request
95
+ .lines()
96
+ .next()
97
+ .unwrap_or_default()
98
+ .split_whitespace();
99
+ let method = parts.next().unwrap_or_default();
100
+ let path = parts.next().unwrap_or("/");
101
+ let origin = request
102
+ .lines()
103
+ .find_map(|line| line.strip_prefix("Origin: "))
104
+ .unwrap_or("*");
105
+ let cors_origin = allowed_origin(origin);
106
+
107
+ if method == "OPTIONS" {
108
+ return write_response(&mut stream, "204 No Content", "", &cors_origin);
109
+ }
110
+
111
+ let body = if path == "/healthz" {
112
+ r#"{"ok":true}"#
113
+ } else {
114
+ r#"{"message":"hello from tovuk","backend":"rust"}"#
115
+ };
116
+ write_response(&mut stream, "200 OK", body, &cors_origin)
117
+ }
118
+
119
+ fn allowed_origin(request_origin: &str) -> String {
120
+ let configured = std::env::var("FRONTEND_ORIGIN").unwrap_or_else(|_error| request_origin.to_owned());
121
+ if configured == "*" || configured == request_origin {
122
+ configured
123
+ } else {
124
+ "null".to_owned()
125
+ }
126
+ }
127
+
128
+ fn write_response(
129
+ stream: &mut TcpStream,
130
+ status: &str,
131
+ body: &str,
132
+ origin: &str,
133
+ ) -> std::io::Result<()> {
134
+ write!(
135
+ stream,
136
+ "HTTP/1.1 {status}\\r\\ncontent-type: application/json\\r\\ncontent-length: {}\\r\\naccess-control-allow-origin: {origin}\\r\\naccess-control-allow-methods: GET, OPTIONS\\r\\naccess-control-allow-headers: content-type, authorization\\r\\nconnection: close\\r\\n\\r\\n{body}",
137
+ body.len()
138
+ )
139
+ }
140
+ `
141
+ }
142
+
143
+ function frontendSource(apiBaseUrl: string): string {
144
+ return `import { createRootRoute, createRouter, RouterProvider } from '@tanstack/react-router'
145
+ import { createRoot } from 'react-dom/client'
146
+ import './styles.css'
147
+
148
+ const apiBaseUrl = import.meta.env.VITE_API_URL ?? '${apiBaseUrl}'
149
+
150
+ function App() {
151
+ return (
152
+ <main>
153
+ <section>
154
+ <h1>Tovuk TanStack Frontend</h1>
155
+ <p>Static runtime, dynamic Rust backend calls.</p>
156
+ <code>{apiBaseUrl}</code>
157
+ </section>
158
+ </main>
159
+ )
160
+ }
161
+
162
+ const rootRoute = createRootRoute({ component: App })
163
+ const router = createRouter({ routeTree: rootRoute })
164
+
165
+ declare module '@tanstack/react-router' {
166
+ interface Register {
167
+ router: typeof router
168
+ }
169
+ }
170
+
171
+ const rootElement = document.getElementById('root')
172
+ if (rootElement === null) {
173
+ throw new Error('missing root element')
174
+ }
175
+
176
+ createRoot(rootElement).render(<RouterProvider router={router} />)
177
+ `
178
+ }
179
+
180
+ function frontendViteEnvSource(): string {
181
+ return `/// <reference types="vite/client" />
182
+
183
+ interface ViteTypeOptions {
184
+ strictImportMetaEnv: unknown
185
+ }
186
+
187
+ interface ImportMetaEnv {
188
+ readonly VITE_API_URL?: string
189
+ }
190
+ `
191
+ }
192
+
193
+ function jsonSource(value: JsonObject): string {
194
+ return `${JSON.stringify(value, null, 2)}\n`
195
+ }
196
+
197
+ export { frontendPackageJson, frontendSource, frontendTsConfig, frontendViteEnvSource, rustApiSource }
@@ -0,0 +1,151 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
2
+ import path from 'node:path'
3
+ import { DEFAULT_RUST_CHECK_COMMAND, PROJECT_TEMPLATES } from './constants.ts'
4
+ import { agentError } from './errors.ts'
5
+ import { doctorProject } from './doctor.ts'
6
+ import { frontendBuildCommand, frontendCheckCommand } from './frontend-policy.ts'
7
+ import { ensureDirectory, inferProjectKind, serviceNameFromCargo, serviceNameFromDir, serviceNameFromPackage } from './project.ts'
8
+ import { frontendPackageJson, frontendSource, frontendTsConfig, frontendViteEnvSource, rustApiSource } from './template-sources.ts'
9
+ import type { TemplateName } from './types.ts'
10
+
11
+ type TemplateWriter = (projectDir: string) => void
12
+ type TemplateFile = readonly [relative: string, source: string]
13
+
14
+ const TEMPLATE_WRITERS: Readonly<Record<TemplateName, TemplateWriter>> = {
15
+ 'rust-api': (projectDir): void => writeRustApiTemplate(projectDir, serviceNameFromDir(projectDir)),
16
+ 'tanstack-static-frontend': (projectDir): void => writeFrontendTemplate(projectDir, serviceNameFromDir(projectDir), '/api'),
17
+ 'fullstack-rust-tanstack': (projectDir): void => {
18
+ writeRustApiTemplate(path.join(projectDir, 'api'), 'api')
19
+ writeFrontendTemplate(path.join(projectDir, 'web'), 'web', 'http://localhost:3000')
20
+ }
21
+ }
22
+
23
+ function initProject(projectDir: string, template = ''): void {
24
+ if (template) {
25
+ mkdirSync(projectDir, { recursive: true, mode: 0o755 })
26
+ createTemplate(projectDir, template)
27
+ return
28
+ }
29
+ ensureDirectory(projectDir)
30
+
31
+ const configPath = path.join(projectDir, 'tovuk.toml')
32
+ if (existsSync(configPath)) {
33
+ console.log('tovuk.toml already exists')
34
+ return
35
+ }
36
+
37
+ const kind = inferProjectKind(projectDir)
38
+ const source = kind === 'static_frontend'
39
+ ? frontendConfig(projectDir)
40
+ : rustBackendConfig(projectDir)
41
+
42
+ writeFileSync(configPath, source, { mode: 0o644 })
43
+ console.log(`created ${path.relative(process.cwd(), configPath)}`)
44
+ console.log(`detected ${kind}`)
45
+ }
46
+
47
+ function createTemplate(projectDir: string, template: string): void {
48
+ if (!isTemplateName(template)) {
49
+ throw agentError('invalid_template', 'Tovuk template is unknown.', `Use one of: ${[...PROJECT_TEMPLATES].join(', ')}.`, false)
50
+ }
51
+ TEMPLATE_WRITERS[template](projectDir)
52
+ console.log(`created ${template} template`)
53
+ }
54
+
55
+ function writeRustApiTemplate(projectDir: string, name: string): void {
56
+ mkdirSync(path.join(projectDir, 'src'), { recursive: true, mode: 0o755 })
57
+ const files: readonly TemplateFile[] = [
58
+ ['Cargo.toml', `[package]
59
+ name = "${name}"
60
+ version = "0.1.0"
61
+ edition = "2024"
62
+ publish = false
63
+
64
+ [lints.rust]
65
+ unsafe_code = "forbid"
66
+ warnings = "deny"
67
+ `],
68
+ ['Cargo.lock', `# This file is automatically @generated by Cargo.
69
+ version = 4
70
+
71
+ [[package]]
72
+ name = "${name}"
73
+ version = "0.1.0"
74
+ `],
75
+ ['src/main.rs', rustApiSource()],
76
+ ['tovuk.toml', rustBackendConfig(projectDir)]
77
+ ]
78
+ writeTemplateFiles(projectDir, files)
79
+ }
80
+
81
+ function writeFrontendTemplate(projectDir: string, name: string, apiBaseUrl: string): void {
82
+ mkdirSync(path.join(projectDir, 'src'), { recursive: true, mode: 0o755 })
83
+ const files: readonly TemplateFile[] = [
84
+ ['package.json', frontendPackageJson(name)],
85
+ ['index.html', '<div id="root"></div><script type="module" src="/src/main.tsx"></script>\n'],
86
+ ['src/styles.css', 'body{margin:0;font-family:system-ui,sans-serif}main{min-height:100svh;display:grid;place-items:center;padding:2rem}code{font-family:ui-monospace,monospace}\n'],
87
+ ['src/vite-env.d.ts', frontendViteEnvSource()],
88
+ ['src/main.tsx', frontendSource(apiBaseUrl)],
89
+ ['tsconfig.json', frontendTsConfig()],
90
+ ['vite.config.ts', 'import react from "@vitejs/plugin-react";\nimport { defineConfig } from "vite";\n\nexport default defineConfig({ plugins: [react()] });\n'],
91
+ ['tovuk.toml', frontendConfig(projectDir)]
92
+ ]
93
+ writeTemplateFiles(projectDir, files)
94
+ console.log('run package install in the frontend directory before doctor: bun install or npm install')
95
+ }
96
+
97
+ function writeTemplateFiles(projectDir: string, files: readonly TemplateFile[]): void {
98
+ for (const [relative, source] of files) {
99
+ writeNewFile(path.join(projectDir, relative), source)
100
+ }
101
+ }
102
+
103
+ function writeNewFile(file: string, source: string): void {
104
+ if (existsSync(file)) {
105
+ throw agentError('file_exists', `Refusing to overwrite ${path.relative(process.cwd(), file)}.`, 'Move the existing file or choose an empty directory, then retry.', false)
106
+ }
107
+ writeFileSync(file, source, { mode: 0o644 })
108
+ }
109
+
110
+ function rustBackendConfig(projectDir: string): string {
111
+ const name = serviceNameFromCargo(projectDir) || serviceNameFromDir(projectDir)
112
+ return `name = "${name}"
113
+
114
+ [build]
115
+ check = "${DEFAULT_RUST_CHECK_COMMAND}"
116
+ command = "cargo build --release"
117
+
118
+ [run]
119
+ command = "./target/release/${name}"
120
+ port = 3000
121
+ health = "/healthz"
122
+
123
+ [resources]
124
+ memory = "512mb"
125
+ cpu = "0.25"
126
+ idle_timeout_minutes = 15
127
+ `
128
+ }
129
+
130
+ function frontendConfig(projectDir: string): string {
131
+ const name = serviceNameFromPackage(projectDir) || serviceNameFromDir(projectDir)
132
+ return `name = "${name}"
133
+ kind = "static_frontend"
134
+
135
+ [build]
136
+ check = "${frontendCheckCommand(projectDir)}"
137
+ command = "${frontendBuildCommand(projectDir)}"
138
+ output = "dist"
139
+ `
140
+ }
141
+
142
+ function installProject(projectDir: string, template = ''): void {
143
+ initProject(projectDir, template)
144
+ doctorProject(projectDir, false)
145
+ }
146
+
147
+ function isTemplateName(value: string): value is TemplateName {
148
+ return PROJECT_TEMPLATES.has(value)
149
+ }
150
+
151
+ export { initProject, installProject }
@@ -0,0 +1,74 @@
1
+ type JsonPrimitive = string | number | boolean | null
2
+ export type JsonValue = JsonPrimitive | JsonObject | JsonValue[]
3
+ export interface JsonObject {
4
+ [key: string]: JsonValue | undefined
5
+ }
6
+
7
+ export type ApiMethod = 'DELETE' | 'GET' | 'POST' | 'PUT'
8
+ export type ProjectKind = 'rust_backend' | 'static_frontend'
9
+ export type DiscoveredProjectKind = ProjectKind | 'unknown'
10
+ export type TemplateName = 'fullstack-rust-tanstack' | 'rust-api' | 'tanstack-static-frontend'
11
+
12
+ export interface AgentErrorPayload extends JsonObject {
13
+ code: string
14
+ message: string
15
+ agent_instruction: string | null
16
+ docs_url: string | null
17
+ checkout_url: string | null
18
+ }
19
+
20
+ export interface CliOptions {
21
+ command: string
22
+ args: string[]
23
+ apiUrl: string
24
+ app: string
25
+ build: string
26
+ deploy: string
27
+ limit: string
28
+ cursor: string
29
+ failingCommand: string
30
+ firstLogLine: string
31
+ token: string
32
+ template: string
33
+ severity: string
34
+ port: number
35
+ waitTimeoutSeconds: number
36
+ json: boolean
37
+ database: boolean
38
+ wait: boolean
39
+ help: boolean
40
+ version: boolean
41
+ }
42
+
43
+ export interface PackageManifest {
44
+ name?: string
45
+ scripts?: Record<string, string | undefined>
46
+ }
47
+
48
+ export type BuildConfig = JsonObject & { command: string; check: string; output?: string }
49
+ export type RunConfig = JsonObject & { command?: string; port: number; health: string }
50
+ export type ResourceConfig = JsonObject & { memory: string; cpu: string; idle_timeout_minutes: number }
51
+ export type TovukConfig = JsonObject & { name?: string; kind: ProjectKind; build: BuildConfig; run: RunConfig; resources: ResourceConfig }
52
+ export type DoctorCheck = JsonObject & { name: string; ok: boolean; message: string; agent_instruction: string | null }
53
+ export type DoctorReport = JsonObject & { ok: boolean; project: string; config: TovukConfig | null; checks: DoctorCheck[] }
54
+ export type ProjectDoctorReport = DoctorReport & { relative: string }
55
+ export type WorkspaceDoctorReport = JsonObject & { ok: boolean; workspace: string; projects: ProjectDoctorReport[] }
56
+ export type DeployProjectInfo = { dir: string; relative: string; name: string; kind: DiscoveredProjectKind }
57
+ export type DeployPlanProject = { project: DeployProjectInfo; wantsDatabase: boolean }
58
+ export type FrontendSourceReport = { typescript: string[]; javascript: string[] }
59
+ export type LoginStartResponse = JsonObject & { loginUrl?: string; userCode?: string; deviceCode?: string; expiresInSeconds?: number; intervalSeconds?: number }
60
+ export type LoginPollResponse = JsonObject & { status?: string; token?: string; email?: string; intervalSeconds?: number }
61
+ export type AppSummary = JsonObject & { id?: string; name?: string; url?: string; databaseStorageMib?: number }
62
+ export type AppsResponse = JsonObject & { apps: AppSummary[] }
63
+ export type BuildJob = JsonObject & { id: string }
64
+ export type AppDeployTarget = JsonObject & { id: string; url: string }
65
+ export type BuildRecord = JsonObject & { id: string; status: string }
66
+ export type BuildStatusResponse = JsonObject & { build?: BuildRecord }
67
+ export type DeployResponse = JsonObject & { app: AppDeployTarget; build_job: BuildJob; final_build?: BuildRecord | null }
68
+ export type WorkspaceDeployResult = { project: DeployProjectInfo; wantsDatabase: boolean; response: DeployResponse; finalBuild?: BuildRecord }
69
+ export type LogLine = JsonObject & { timestamp: string; stream: string; message: string }
70
+ export type LogsResponse = JsonObject & { lines: LogLine[]; has_more: boolean; next_cursor: string }
71
+ export type CheckoutResponse = JsonObject & { checkout: { reason?: string; url: string } }
72
+
73
+ export type FileVisitor = (file: string, relative: string) => void
74
+ export type PathVisitor = (file: string) => void
@@ -0,0 +1,61 @@
1
+ import { existsSync, readFileSync, readdirSync } from 'node:fs'
2
+ import path from 'node:path'
3
+ import { WORKSPACE_EXCLUDED_DIRS } from './constants.ts'
4
+ import { parseTovukToml } from './config.ts'
5
+ import { ensureDirectory } from './project.ts'
6
+ import type { DeployProjectInfo, DiscoveredProjectKind } from './types.ts'
7
+
8
+ function discoverDeployProjects(rootDir: string): DeployProjectInfo[] {
9
+ ensureDirectory(rootDir)
10
+ if (existsSync(path.join(rootDir, 'tovuk.toml'))) {
11
+ return [deployProjectInfo(rootDir, rootDir)]
12
+ }
13
+
14
+ const projectDirs: string[] = []
15
+ discoverProjectDirs(rootDir, projectDirs)
16
+ return projectDirs
17
+ .map((dir) => deployProjectInfo(dir, rootDir))
18
+ .toSorted(compareDeployProjects)
19
+ }
20
+
21
+ function discoverProjectDirs(dir: string, projectDirs: string[]): void {
22
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
23
+ if (!entry.isDirectory() || WORKSPACE_EXCLUDED_DIRS.has(entry.name)) {
24
+ continue
25
+ }
26
+
27
+ const child = path.join(dir, entry.name)
28
+ if (existsSync(path.join(child, 'tovuk.toml'))) {
29
+ projectDirs.push(child)
30
+ continue
31
+ }
32
+ discoverProjectDirs(child, projectDirs)
33
+ }
34
+ }
35
+
36
+ function deployProjectInfo(dir: string, rootDir: string): DeployProjectInfo {
37
+ const relative = path.relative(rootDir, dir).replace(/\\/gu, '/') || '.'
38
+ try {
39
+ const config = parseTovukToml(readFileSync(path.join(dir, 'tovuk.toml'), 'utf8'), dir)
40
+ return { dir, relative, name: config.name || '', kind: config.kind }
41
+ } catch {
42
+ return { dir, relative, name: '', kind: 'unknown' }
43
+ }
44
+ }
45
+
46
+ function compareDeployProjects(left: DeployProjectInfo, right: DeployProjectInfo): number {
47
+ return kindOrder(left.kind) - kindOrder(right.kind)
48
+ || left.relative.localeCompare(right.relative)
49
+ }
50
+
51
+ function kindOrder(kind: DiscoveredProjectKind): number {
52
+ if (kind === 'rust_backend') {
53
+ return 0
54
+ }
55
+ if (kind === 'static_frontend') {
56
+ return 1
57
+ }
58
+ return 2
59
+ }
60
+
61
+ export { discoverDeployProjects }
package/src/tovuk.ts ADDED
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env tsx
2
+ import { VERSION, HELP } from './internal/constants.ts'
3
+ import { parseArgs, projectPath } from './internal/args.ts'
4
+ import { agentError, printAgentError, TovukError } from './internal/errors.ts'
5
+ import { initProject, installProject } from './internal/templates.ts'
6
+ import { doctorProject } from './internal/doctor.ts'
7
+ import { previewProject } from './internal/preview.ts'
8
+ import { login } from './internal/auth.ts'
9
+ import { deploy } from './internal/deploy.ts'
10
+ import { apps, builds, activity, billing, capabilities, database, deploys, domainsCommand, envCommand, inspect, logs, me, overview, status, support, usage } from './internal/commands.ts'
11
+ import type { CliOptions } from './internal/types.ts'
12
+
13
+ type CommandHandler = (cli: CliOptions) => Promise<void> | void
14
+
15
+ const COMMANDS = new Map<string, CommandHandler>([
16
+ ['init', (cli): void => initProject(projectPath(cli.args[0]), cli.template)],
17
+ ['install', (cli): void => installProject(projectPath(cli.args[0]), cli.template)],
18
+ ['doctor', (cli): void => doctorProject(projectPath(cli.args[0]), cli.json)],
19
+ ['preview', (cli): void => previewProject(projectPath(cli.args[0]), cli.port)],
20
+ ['login', login],
21
+ ['deploy', (cli): Promise<void> => deploy(projectPath(cli.args[0]), cli)],
22
+ ['capabilities', capabilities],
23
+ ['me', me],
24
+ ['usage', usage],
25
+ ['activity', activity],
26
+ ['apps', apps],
27
+ ['overview', overview],
28
+ ['deploys', deploys],
29
+ ['builds', builds],
30
+ ['logs', logs],
31
+ ['status', status],
32
+ ['inspect', inspect],
33
+ ['db', database],
34
+ ['database', database],
35
+ ['env', envCommand],
36
+ ['domains', domainsCommand],
37
+ ['billing', billing],
38
+ ['support', support]
39
+ ])
40
+
41
+ async function main(): Promise<void> {
42
+ const cli = parseArgs(process.argv.slice(2))
43
+
44
+ if (cli.help) {
45
+ console.log(HELP)
46
+ return
47
+ }
48
+
49
+ if (cli.version) {
50
+ console.log(VERSION)
51
+ return
52
+ }
53
+
54
+ const command = COMMANDS.get(cli.command)
55
+ if (!command) {
56
+ throw agentError('unknown_command', 'Unknown Tovuk command.', 'Run `npx tovuk --help` and retry with a supported command.', cli.json)
57
+ }
58
+ await command(cli)
59
+ }
60
+
61
+ main().catch((error: unknown): void => {
62
+ if (error instanceof TovukError) {
63
+ printAgentError(error.payload, error.json)
64
+ process.exitCode = error.exitCode
65
+ return
66
+ }
67
+
68
+ const message = error instanceof Error ? error.message : String(error)
69
+ console.error(`tovuk failed: ${message}`)
70
+ process.exitCode = 1
71
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2023",
4
+ "lib": ["ES2023", "DOM"],
5
+ "module": "NodeNext",
6
+ "moduleResolution": "NodeNext",
7
+ "rootDir": "src",
8
+ "allowImportingTsExtensions": true,
9
+ "allowJs": false,
10
+ "checkJs": false,
11
+ "declaration": true,
12
+ "noEmit": true,
13
+ "noEmitOnError": true,
14
+ "strict": true,
15
+ "strictBuiltinIteratorReturn": true,
16
+ "noImplicitAny": true,
17
+ "strictNullChecks": true,
18
+ "strictFunctionTypes": true,
19
+ "strictBindCallApply": true,
20
+ "strictPropertyInitialization": true,
21
+ "noImplicitThis": true,
22
+ "useUnknownInCatchVariables": true,
23
+ "alwaysStrict": true,
24
+ "exactOptionalPropertyTypes": true,
25
+ "noUncheckedIndexedAccess": true,
26
+ "noUncheckedSideEffectImports": true,
27
+ "noPropertyAccessFromIndexSignature": true,
28
+ "noImplicitOverride": true,
29
+ "noImplicitReturns": true,
30
+ "noFallthroughCasesInSwitch": true,
31
+ "noUnusedLocals": true,
32
+ "noUnusedParameters": true,
33
+ "allowUnreachableCode": false,
34
+ "allowUnusedLabels": false,
35
+ "verbatimModuleSyntax": true,
36
+ "isolatedModules": true,
37
+ "isolatedDeclarations": true,
38
+ "erasableSyntaxOnly": true,
39
+ "moduleDetection": "force",
40
+ "resolvePackageJsonExports": true,
41
+ "resolvePackageJsonImports": true,
42
+ "maxNodeModuleJsDepth": 0,
43
+ "forceConsistentCasingInFileNames": true,
44
+ "skipLibCheck": false,
45
+ "types": ["node"]
46
+ },
47
+ "include": ["src/**/*.ts"]
48
+ }