flint-orm 0.4.0 → 0.4.1
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/API.md +7 -1
- package/README.md +3 -1
- package/package.json +3 -3
- package/src/cli.ts +0 -361
package/API.md
CHANGED
|
@@ -110,7 +110,9 @@ text()
|
|
|
110
110
|
.unique() // UNIQUE
|
|
111
111
|
.default('hello') // DEFAULT 'hello'
|
|
112
112
|
.defaultFn(() => new Date()) // DEFAULT (computed at insert)
|
|
113
|
-
.references(otherColumn)
|
|
113
|
+
.references(otherColumn) // REFERENCES otherColumn
|
|
114
|
+
.onDelete('cascade') // ON DELETE CASCADE
|
|
115
|
+
.onUpdate('set null'); // ON UPDATE SET NULL
|
|
114
116
|
```
|
|
115
117
|
|
|
116
118
|
**Integer-only:**
|
|
@@ -591,6 +593,9 @@ import { addTable, dropTable, renameTable, addColumn, dropColumn, renameColumn,
|
|
|
591
593
|
| `renameColumn` | `ALTER TABLE ... RENAME COLUMN ... TO` |
|
|
592
594
|
| `createIndex` | `CREATE [UNIQUE] INDEX ...` |
|
|
593
595
|
| `dropIndex` | `DROP INDEX ...` |
|
|
596
|
+
| `modifyColumn` | `ALTER TABLE ... ALTER COLUMN ...` |
|
|
597
|
+
| `modifyIndex` | `DROP INDEX IF EXISTS ...; CREATE ...` |
|
|
598
|
+
| `rebuildTable` | Temp table → copy → drop → rename |
|
|
594
599
|
|
|
595
600
|
---
|
|
596
601
|
|
|
@@ -606,6 +611,7 @@ import { addTable, dropTable, renameTable, addColumn, dropColumn, renameColumn,
|
|
|
606
611
|
| `SQLExpression` | `{ sql: string; params: unknown[] }` |
|
|
607
612
|
| `Executable` | Anything with a `.toSQL()` method (for `batch()`) |
|
|
608
613
|
| `Driver` | `'bun-sqlite' \| 'better-sqlite3' \| 'libsql' \| 'libsql-web' \| 'turso' \| 'turso-sync'` |
|
|
614
|
+
| `RebuildTableOp` | Migration op that recreates a table with a new schema |
|
|
609
615
|
|
|
610
616
|
---
|
|
611
617
|
|
package/README.md
CHANGED
|
@@ -78,6 +78,8 @@ const posts = table('posts', {
|
|
|
78
78
|
- `.default(value)` — static default value
|
|
79
79
|
- `.defaultFn(fn)` — dynamic default (called on insert when value is omitted)
|
|
80
80
|
- `.references(target)` — foreign key reference
|
|
81
|
+
- `.onDelete(action)` — foreign key ON DELETE action (`cascade`, `set null`, `set default`, `restrict`, `no action`)
|
|
82
|
+
- `.onUpdate(action)` — foreign key ON UPDATE action (`cascade`, `set null`, `set default`, `restrict`, `no action`)
|
|
81
83
|
- `.autoIncrement()` — integer auto-increment (integer columns only)
|
|
82
84
|
- `.defaultNow()` — use `Date.now()` as default (date columns only)
|
|
83
85
|
- `.onUpdateTimestamp()` — always set to `Date.now()` on update (date columns only)
|
|
@@ -323,7 +325,7 @@ flint migrate --dry-run # show what would run
|
|
|
323
325
|
5. Writes a migration folder with `migration.ts` (operations) + `state.json` (snapshot)
|
|
324
326
|
6. `flint migrate` reads pending migrations and executes them in order
|
|
325
327
|
|
|
326
|
-
Unsafe changes (type changes, primary key changes)
|
|
328
|
+
Unsafe changes (type changes, primary key changes, FK modifications, UNIQUE changes, DEFAULT removal) automatically emit a `rebuildTable` operation that recreates the table with the new schema and copies data safely within a transaction.
|
|
327
329
|
|
|
328
330
|
## Subpath Imports
|
|
329
331
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flint-orm",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"bin": {
|
|
5
|
-
"flint": "./src/cli.
|
|
5
|
+
"flint": "./dist/src/cli.js"
|
|
6
6
|
},
|
|
7
7
|
"files": [
|
|
8
8
|
"dist",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
}
|
|
60
60
|
},
|
|
61
61
|
"scripts": {
|
|
62
|
-
"build": "bun build ./src/index.ts ./src/entries/bun-sqlite.ts ./src/entries/better-sqlite3.ts ./src/entries/libsql.ts ./src/entries/libsql-web.ts ./src/entries/turso-sync.ts ./src/entries/turso.ts ./src/entries/table.ts ./src/entries/expressions.ts ./src/entries/config.ts ./src/migration/index.ts --outdir ./dist --target bun && tsc --project tsconfig.build.json",
|
|
62
|
+
"build": "bun build ./src/index.ts ./src/entries/bun-sqlite.ts ./src/entries/better-sqlite3.ts ./src/entries/libsql.ts ./src/entries/libsql-web.ts ./src/entries/turso-sync.ts ./src/entries/turso.ts ./src/entries/table.ts ./src/entries/expressions.ts ./src/entries/config.ts ./src/migration/index.ts ./src/cli.ts --outdir ./dist --target bun && tsc --project tsconfig.build.json",
|
|
63
63
|
"typecheck": "tsc --noEmit",
|
|
64
64
|
"lint": "oxlint --fix",
|
|
65
65
|
"format": "oxfmt --write",
|
package/src/cli.ts
DELETED
|
@@ -1,361 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
// ---------------------------------------------------------------------------
|
|
3
|
-
// flint CLI — migration generation for flint-orm
|
|
4
|
-
// ---------------------------------------------------------------------------
|
|
5
|
-
|
|
6
|
-
import { parseArgs, styleText } from 'node:util';
|
|
7
|
-
import { statSync, readdirSync, existsSync, readFileSync } from 'node:fs';
|
|
8
|
-
import { join, resolve, isAbsolute } from 'node:path';
|
|
9
|
-
import { pathToFileURL } from 'node:url';
|
|
10
|
-
import type { AnyTable, TableDef } from './schema/table.js';
|
|
11
|
-
import type { SchemaState } from './migration/types.js';
|
|
12
|
-
import { outro, log, cancel, isCancel, select, pc, note } from './cli/ui.js';
|
|
13
|
-
import { CancellationError } from './migration/diff.js';
|
|
14
|
-
import type { RenamePrompt } from './migration/diff.js';
|
|
15
|
-
import type { Executor } from './executor.js';
|
|
16
|
-
|
|
17
|
-
// ---------------------------------------------------------------------------
|
|
18
|
-
// Config loading
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
|
|
21
|
-
interface FlintConfig {
|
|
22
|
-
/** Which SQLite driver to use. */
|
|
23
|
-
driver: string;
|
|
24
|
-
/** Database connection details. */
|
|
25
|
-
database: { url: string; authToken?: string };
|
|
26
|
-
/** Path to schema file or directory. */
|
|
27
|
-
schema: string;
|
|
28
|
-
/** Path to migrations directory (default: ./flint). */
|
|
29
|
-
migrations?: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async function loadConfig(): Promise<FlintConfig> {
|
|
33
|
-
const configPath = resolve(process.cwd(), 'flint.config.ts');
|
|
34
|
-
const configUrl = pathToFileURL(configPath).href;
|
|
35
|
-
const mod = await import(configUrl);
|
|
36
|
-
return mod.default as FlintConfig;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// ---------------------------------------------------------------------------
|
|
40
|
-
// Schema discovery — import table() exports from schema path
|
|
41
|
-
// ---------------------------------------------------------------------------
|
|
42
|
-
|
|
43
|
-
function isTableDef(value: unknown): boolean {
|
|
44
|
-
return (
|
|
45
|
-
value !== null &&
|
|
46
|
-
typeof value === 'object' &&
|
|
47
|
-
'_' in value &&
|
|
48
|
-
typeof (value as Record<string, unknown>)._ === 'object' &&
|
|
49
|
-
typeof ((value as Record<string, Record<string, unknown>>)._ as Record<string, unknown>).name === 'string'
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
async function discoverTables(schemaPath: string): Promise<unknown[]> {
|
|
54
|
-
const abs = isAbsolute(schemaPath) ? schemaPath : resolve(process.cwd(), schemaPath);
|
|
55
|
-
const stat = statSync(abs);
|
|
56
|
-
|
|
57
|
-
if (stat.isFile()) {
|
|
58
|
-
return importTableFile(abs);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (stat.isDirectory()) {
|
|
62
|
-
return importTableFolder(abs);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
throw new Error(`Schema path does not exist: ${abs}`);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async function importTableFile(filePath: string): Promise<unknown[]> {
|
|
69
|
-
const url = pathToFileURL(filePath).href;
|
|
70
|
-
const mod = await import(url);
|
|
71
|
-
const tables: unknown[] = [];
|
|
72
|
-
|
|
73
|
-
for (const exportValue of Object.values(mod)) {
|
|
74
|
-
if (isTableDef(exportValue)) {
|
|
75
|
-
tables.push(exportValue);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return tables;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async function importTableFolder(folderPath: string): Promise<unknown[]> {
|
|
83
|
-
const entries = readdirSync(folderPath).filter((e) => e.endsWith('.ts'));
|
|
84
|
-
const tables: unknown[] = [];
|
|
85
|
-
|
|
86
|
-
for (const entry of entries) {
|
|
87
|
-
const filePath = join(folderPath, entry);
|
|
88
|
-
const discovered = await importTableFile(filePath);
|
|
89
|
-
tables.push(...discovered);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return tables;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// ---------------------------------------------------------------------------
|
|
96
|
-
// Commands
|
|
97
|
-
// ---------------------------------------------------------------------------
|
|
98
|
-
|
|
99
|
-
async function cmdGenerate(args: ReturnType<typeof parseArgs>['values'], config: FlintConfig): Promise<void> {
|
|
100
|
-
const name = typeof args.name === 'string' ? args.name : undefined;
|
|
101
|
-
const preview = args.preview === true;
|
|
102
|
-
const promptRename: RenamePrompt = (message, options) => select({ message: pc.bold(message), options }) as Promise<string | symbol>;
|
|
103
|
-
|
|
104
|
-
log.info(`Discovering schema from: ${config.schema}`);
|
|
105
|
-
const tables = await discoverTables(config.schema);
|
|
106
|
-
|
|
107
|
-
if (tables.length === 0) {
|
|
108
|
-
cancel('No table() definitions found in schema path.');
|
|
109
|
-
process.exit(1);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
log.info(`Found ${tables.length} table(s): ${tables.map((t) => (t as Record<string, Record<string, unknown>>)._?.name ?? 'unknown').join(', ')}`);
|
|
113
|
-
|
|
114
|
-
if (preview) {
|
|
115
|
-
// Dynamic import of migration functions
|
|
116
|
-
const { serializeSchema } = await import('./migration/serialize.js');
|
|
117
|
-
const { diffSchemas, emptyState, resolveRenames } = await import('./migration/diff.js');
|
|
118
|
-
const { generateSQL } = await import('./migration/sql.js');
|
|
119
|
-
|
|
120
|
-
// Find latest state
|
|
121
|
-
const migrationsDir = resolve(process.cwd(), config.migrations ?? './flint');
|
|
122
|
-
let previousState: SchemaState | null = null;
|
|
123
|
-
if (existsSync(migrationsDir)) {
|
|
124
|
-
const folders = readdirSync(migrationsDir)
|
|
125
|
-
.filter((e) => /^\d{10}_/.test(e))
|
|
126
|
-
.sort()
|
|
127
|
-
.reverse();
|
|
128
|
-
for (const folder of folders) {
|
|
129
|
-
const statePath = join(migrationsDir, folder, 'state.json');
|
|
130
|
-
if (existsSync(statePath)) {
|
|
131
|
-
previousState = JSON.parse(readFileSync(statePath, 'utf-8'));
|
|
132
|
-
break;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
if (!previousState) previousState = emptyState();
|
|
137
|
-
|
|
138
|
-
const currentState = serializeSchema(tables as AnyTable[]);
|
|
139
|
-
const rawOps = diffSchemas(previousState, currentState);
|
|
140
|
-
|
|
141
|
-
if (rawOps.length === 0) {
|
|
142
|
-
outro('Schema is already up to date.');
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Resolve renames interactively
|
|
147
|
-
const operations = await resolveRenames(rawOps, { interactive: true, prompt: promptRename });
|
|
148
|
-
|
|
149
|
-
const sql = generateSQL(operations);
|
|
150
|
-
log.info(`Operations: ${operations.length}`);
|
|
151
|
-
note(sql, 'SQL', { format: (text) => styleText('dim', text) });
|
|
152
|
-
log.info('(dry run, no files written)');
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Dynamic import of the generate function
|
|
157
|
-
const { generate } = await import('./migration/generate.js');
|
|
158
|
-
|
|
159
|
-
try {
|
|
160
|
-
const migrationsDir = resolve(process.cwd(), config.migrations ?? './flint');
|
|
161
|
-
const result = await generate(tables as TableDef<any>[], migrationsDir, {
|
|
162
|
-
name,
|
|
163
|
-
interactive: true,
|
|
164
|
-
prompt: promptRename,
|
|
165
|
-
});
|
|
166
|
-
log.info(`Operations: ${result.operations.length}`);
|
|
167
|
-
note(result.sql, 'SQL', { format: (text) => styleText('dim', text) });
|
|
168
|
-
log.success(`Migration generated: ${migrationsDir}/${result.folderName}`);
|
|
169
|
-
} catch (err: unknown) {
|
|
170
|
-
if (err instanceof Error && err.message.includes('No changes detected')) {
|
|
171
|
-
outro('Schema is already up to date.');
|
|
172
|
-
} else {
|
|
173
|
-
throw err;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
async function cmdMigrate(args: ReturnType<typeof parseArgs>['values'], config: FlintConfig): Promise<void> {
|
|
179
|
-
const { migrate, getMigrationStatus } = await import('./migration/migrate.js');
|
|
180
|
-
|
|
181
|
-
const migrationsDir = resolve(process.cwd(), config.migrations ?? './flint');
|
|
182
|
-
const dryRun = args['dry-run'] === true;
|
|
183
|
-
const statusOnly = args.status === true;
|
|
184
|
-
|
|
185
|
-
// Dynamically create executor based on configured driver
|
|
186
|
-
const isLocalDriver = config.driver === 'bun-sqlite' || config.driver === 'better-sqlite3';
|
|
187
|
-
const dbUrl = isLocalDriver ? resolve(process.cwd(), config.database.url) : config.database.url;
|
|
188
|
-
|
|
189
|
-
let executor: Executor;
|
|
190
|
-
|
|
191
|
-
switch (config.driver) {
|
|
192
|
-
case 'bun-sqlite': {
|
|
193
|
-
const { BunSqliteExecutor } = await import('./drivers/bun-sqlite');
|
|
194
|
-
const { Database } = await import('bun:sqlite');
|
|
195
|
-
executor = new BunSqliteExecutor(new Database(dbUrl));
|
|
196
|
-
break;
|
|
197
|
-
}
|
|
198
|
-
case 'better-sqlite3': {
|
|
199
|
-
const { BetterSqlite3Executor } = await import('./drivers/better-sqlite3');
|
|
200
|
-
const Database = (await import('better-sqlite3')).default;
|
|
201
|
-
executor = new BetterSqlite3Executor(new Database(dbUrl));
|
|
202
|
-
break;
|
|
203
|
-
}
|
|
204
|
-
case 'libsql': {
|
|
205
|
-
const { LibsqlExecutor } = await import('./drivers/libsql');
|
|
206
|
-
const { createClient: createLibsqlClient } = await import('@libsql/client');
|
|
207
|
-
executor = new LibsqlExecutor(createLibsqlClient({ url: dbUrl, authToken: config.database.authToken }));
|
|
208
|
-
break;
|
|
209
|
-
}
|
|
210
|
-
case 'libsql-web': {
|
|
211
|
-
const { LibsqlWebExecutor } = await import('./drivers/libsql-web');
|
|
212
|
-
const { createClient: createLibsqlWebClient } = await import('@libsql/client/web');
|
|
213
|
-
executor = new LibsqlWebExecutor(createLibsqlWebClient({ url: dbUrl, authToken: config.database.authToken }));
|
|
214
|
-
break;
|
|
215
|
-
}
|
|
216
|
-
case 'turso-sync': {
|
|
217
|
-
const { TursoSyncExecutor } = await import('./drivers/turso-sync');
|
|
218
|
-
const { connect } = await import('@tursodatabase/sync');
|
|
219
|
-
const db = await connect({ path: dbUrl, authToken: config.database.authToken });
|
|
220
|
-
executor = new TursoSyncExecutor(db);
|
|
221
|
-
break;
|
|
222
|
-
}
|
|
223
|
-
case 'turso': {
|
|
224
|
-
const { TursoExecutor } = await import('./drivers/turso');
|
|
225
|
-
const { connect } = await import('@tursodatabase/database');
|
|
226
|
-
const db = await connect(dbUrl);
|
|
227
|
-
executor = new TursoExecutor(db);
|
|
228
|
-
break;
|
|
229
|
-
}
|
|
230
|
-
default:
|
|
231
|
-
cancel(`Unsupported driver: ${config.driver}`);
|
|
232
|
-
process.exit(1);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
try {
|
|
236
|
-
if (statusOnly) {
|
|
237
|
-
const status = await getMigrationStatus(executor, migrationsDir);
|
|
238
|
-
|
|
239
|
-
if (status.applied.length === 0 && status.pending.length === 0) {
|
|
240
|
-
outro('No migrations found.');
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (status.applied.length > 0) {
|
|
245
|
-
log.info('Applied migrations:');
|
|
246
|
-
for (const m of status.applied) {
|
|
247
|
-
log.success(`${m.name} (${m.folderName})`);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
if (status.pending.length > 0) {
|
|
252
|
-
log.warn('Pending migrations:');
|
|
253
|
-
for (const m of status.pending) {
|
|
254
|
-
log.message(` ○ ${m.name} (${m.folderName})`);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
log.info(`${status.applied.length} applied, ${status.pending.length} pending`);
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const result = await migrate(executor, {
|
|
263
|
-
migrationsDir,
|
|
264
|
-
dryRun,
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
if (result.applied.length === 0) {
|
|
268
|
-
outro('No pending migrations — database is up to date.');
|
|
269
|
-
} else {
|
|
270
|
-
log.info(`${dryRun ? 'Would apply' : 'Applied'} ${result.applied.length} migration(s):`);
|
|
271
|
-
for (const appliedName of result.applied) {
|
|
272
|
-
log.success(appliedName);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (result.skipped.length > 0 && !dryRun) {
|
|
277
|
-
log.info(`Skipped ${result.skipped.length} already applied migration(s)`);
|
|
278
|
-
}
|
|
279
|
-
} finally {
|
|
280
|
-
executor.close();
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// ---------------------------------------------------------------------------
|
|
285
|
-
// Help
|
|
286
|
-
// ---------------------------------------------------------------------------
|
|
287
|
-
|
|
288
|
-
function printHelp(): void {
|
|
289
|
-
log.message(`
|
|
290
|
-
Usage:
|
|
291
|
-
flint <command> [options]
|
|
292
|
-
|
|
293
|
-
Commands:
|
|
294
|
-
generate Generate a new migration from schema changes
|
|
295
|
-
migrate Apply pending migrations to the database
|
|
296
|
-
|
|
297
|
-
Options for generate:
|
|
298
|
-
--name <name> Migration name (optional)
|
|
299
|
-
--preview Show what would be generated without writing files
|
|
300
|
-
|
|
301
|
-
Options for migrate:
|
|
302
|
-
--status Show applied and pending migrations
|
|
303
|
-
--dry-run Show what would be applied without executing
|
|
304
|
-
--name <name> Apply only the named migration
|
|
305
|
-
|
|
306
|
-
Examples:
|
|
307
|
-
flint generate --name init_schema
|
|
308
|
-
flint generate --preview
|
|
309
|
-
flint migrate
|
|
310
|
-
flint migrate --status
|
|
311
|
-
flint migrate --dry-run
|
|
312
|
-
`);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// ---------------------------------------------------------------------------
|
|
316
|
-
// Main
|
|
317
|
-
// ---------------------------------------------------------------------------
|
|
318
|
-
|
|
319
|
-
async function main(): Promise<void> {
|
|
320
|
-
const { values, positionals } = parseArgs({
|
|
321
|
-
args: process.argv.slice(2),
|
|
322
|
-
options: {
|
|
323
|
-
name: { type: 'string', short: 'n' },
|
|
324
|
-
preview: { type: 'boolean', short: 'p' },
|
|
325
|
-
help: { type: 'boolean', short: 'h' },
|
|
326
|
-
status: { type: 'boolean', short: 's' },
|
|
327
|
-
'dry-run': { type: 'boolean', short: 'd' },
|
|
328
|
-
},
|
|
329
|
-
allowPositionals: true,
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
if (values.help || positionals.length === 0) {
|
|
333
|
-
printHelp();
|
|
334
|
-
process.exit(0);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
const command = positionals[0];
|
|
338
|
-
const config = await loadConfig();
|
|
339
|
-
|
|
340
|
-
switch (command) {
|
|
341
|
-
case 'generate':
|
|
342
|
-
await cmdGenerate(values, config);
|
|
343
|
-
break;
|
|
344
|
-
case 'migrate':
|
|
345
|
-
await cmdMigrate(values, config);
|
|
346
|
-
break;
|
|
347
|
-
default:
|
|
348
|
-
cancel(`Unknown command: ${command}`);
|
|
349
|
-
printHelp();
|
|
350
|
-
process.exit(1);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
main().catch((err) => {
|
|
355
|
-
if (isCancel(err) || err instanceof CancellationError) {
|
|
356
|
-
cancel('Operation cancelled.');
|
|
357
|
-
} else {
|
|
358
|
-
cancel(err.message ?? 'An error occurred');
|
|
359
|
-
}
|
|
360
|
-
process.exit(1);
|
|
361
|
-
});
|