@kaeawc/auto-mobile 0.0.40 → 0.0.42
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 +3 -3
- package/README.md.backup +3 -3
- package/dist/schemas/test-plan.schema.json +6 -2
- package/dist/schemas/tool-definitions.json +2549 -546
- package/dist/src/db/eventTables.ts +8 -0
- package/dist/src/db/migrations/2025_12_28_000_initial_schema.ts +3 -3
- package/dist/src/db/migrations/2025_12_30_000_performance_thresholds.ts +3 -3
- package/dist/src/db/migrations/2025_12_30_001_navigation_graph.ts +9 -9
- package/dist/src/db/migrations/2025_12_31_000_accessibility_baselines.ts +3 -3
- package/dist/src/db/migrations/2025_12_31_001_memory_audit.ts +4 -4
- package/dist/src/db/migrations/2026_01_01_000_recomposition_metrics.ts +2 -2
- package/dist/src/db/migrations/2026_01_02_000_prediction_history.ts +4 -4
- package/dist/src/db/migrations/2026_01_03_000_feature_flags.ts +3 -3
- package/dist/src/db/migrations/2026_01_03_000_test_executions.ts +2 -2
- package/dist/src/db/migrations/2026_01_05_000_tool_calls.ts +2 -2
- package/dist/src/db/migrations/2026_01_08_000_test_coverage.ts +4 -4
- package/dist/src/db/migrations/2026_01_09_000_video_recordings.ts +3 -3
- package/dist/src/db/migrations/2026_01_10_000_device_snapshots.ts +3 -3
- package/dist/src/db/migrations/2026_01_14_000_appearance_config.ts +3 -3
- package/dist/src/db/migrations/2026_01_25_001_test_run_details.ts +2 -2
- package/dist/src/db/migrations/2026_01_27_000_crash_anr_monitoring.ts +2 -2
- package/dist/src/db/migrations/2026_01_27_000_failures.ts +7 -7
- package/dist/src/db/migrations/2026_01_29_000_named_nodes.ts +3 -3
- package/dist/src/db/migrations/2026_01_30_000_performance_live_metrics.ts +1 -1
- package/dist/src/db/migrations/2026_03_15_000_telemetry_events.ts +5 -5
- package/dist/src/db/migrations/2026_03_18_000_navigation_events.ts +2 -2
- package/dist/src/db/migrations/2026_03_19_000_storage_events.ts +2 -2
- package/dist/src/db/migrations/2026_03_19_001_layout_events.ts +2 -2
- package/dist/src/db/migrations/2026_04_01_000_drop_custom_events.ts +2 -2
- package/dist/src/db/migrations/2026_04_02_000_device_sessions.ts +3 -3
- package/dist/src/db/migrations/2026_07_01_000_failure_groups_signature_unique.ts +162 -0
- package/dist/src/db/migrations/2026_07_02_000_event_composite_indexes.ts +62 -0
- package/dist/src/db/migrations/2026_07_03_000_drop_redundant_device_indexes.ts +66 -0
- package/dist/src/db/migrations/2026_07_03_000_repair_datetime_now_defaults.ts +268 -0
- package/dist/src/db/migrations/2026_07_04_000_storage_events_key_lookup.ts +60 -0
- package/dist/src/db/migrations/2026_07_05_000_repair_updated_at_defaults.ts +242 -0
- package/dist/src/index.js +533 -390
- package/dist/src/index.js.map +1 -1
- package/dist/vendor/libwebp/COPYING +30 -0
- package/dist/vendor/libwebp/PATENTS +23 -0
- package/dist/vendor/libwebp/README.md +13 -0
- package/dist/vendor/libwebp/README.upstream.md +54 -0
- package/dist/vendor/libwebp/win32-x64/cwebp.exe +0 -0
- package/dist/vendor/libwebp/win32-x64/dwebp.exe +0 -0
- package/package.json +9 -4
- package/schemas/test-plan.schema.json +6 -2
- package/schemas/tool-definitions.json +2549 -546
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { type Kysely, sql } from "kysely";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* #2937 — give eight tables' `updated_at` columns their `(datetime('now'))`
|
|
5
|
+
* default on already-upgraded databases.
|
|
6
|
+
*
|
|
7
|
+
* PR #2922 (issue #2913) reconciled the schema by adding
|
|
8
|
+
* `.defaultTo(sql`(datetime('now'))`)` to these eight tables' `updated_at`
|
|
9
|
+
* migration declarations, matching their `created_at` sibling. But editing a
|
|
10
|
+
* historical migration body does NOT re-run it — Kysely tracks executed
|
|
11
|
+
* migrations by name, with no content checksum — so the added default lands only
|
|
12
|
+
* on FRESH/replayed databases. A database that already ran the pre-#2922
|
|
13
|
+
* migrations keeps the old `updated_at TEXT NOT NULL` column with no default,
|
|
14
|
+
* and a defaulted insert (one that omits `updated_at`) fails its NOT NULL
|
|
15
|
+
* constraint there.
|
|
16
|
+
*
|
|
17
|
+
* This mirrors the `created_at` situation before #2915, minus the live bug:
|
|
18
|
+
* `created_at` had a broken string-literal default actively poisoning new rows,
|
|
19
|
+
* which the {@link file://./2026_07_03_000_repair_datetime_now_defaults.ts}
|
|
20
|
+
* rebuild repaired. `updated_at` has no such poison — every current writer
|
|
21
|
+
* supplies it explicitly — so this migration only needs to REBUILD each column's
|
|
22
|
+
* default; the existing rows already hold real, explicitly-written timestamps and
|
|
23
|
+
* are copied through untouched (no row repair).
|
|
24
|
+
*
|
|
25
|
+
* REBUILD MECHANICS — identical to the #2915 repair: SQLite cannot alter a column
|
|
26
|
+
* default in place, and bun:sqlite forbids editing `sqlite_master` directly, so
|
|
27
|
+
* each table is rebuilt via a twin: create a twin whose `updated_at` carries the
|
|
28
|
+
* corrected default, copy the rows with `INSERT ... SELECT *`, drop the original,
|
|
29
|
+
* rename the twin back, replay the table's explicit indexes/triggers, and restore
|
|
30
|
+
* its `sqlite_sequence` AUTOINCREMENT high-water mark (so a rebuilt table cannot
|
|
31
|
+
* reuse a deleted top id). FK enforcement is toggled OFF around the transaction so
|
|
32
|
+
* dropping a referenced parent (e.g. `navigation_apps`) cannot cascade-delete its
|
|
33
|
+
* children.
|
|
34
|
+
*
|
|
35
|
+
* TARGETING — the eight tables are enumerated in {@link TABLES_WITH_DEFAULTED_UPDATED_AT}
|
|
36
|
+
* (exactly the set #2922 edited), and each is rebuilt only when its live
|
|
37
|
+
* `updated_at` default is absent. Detection reads
|
|
38
|
+
* `pragma_table_info(...).dflt_value`: `NULL` means the no-default upgraded column
|
|
39
|
+
* (rebuild it); the corrected default reports as `datetime('now')` and is skipped.
|
|
40
|
+
* `device_sessions` / `failure_groups` are deliberately NOT listed — their
|
|
41
|
+
* `updated_at` default has existed on every database since #2915 repaired the
|
|
42
|
+
* broken literal they originally shipped, so they need no rebuild.
|
|
43
|
+
*
|
|
44
|
+
* Safe on a fresh DB and idempotent: a fresh/replayed or already-rebuilt schema
|
|
45
|
+
* carries the default, so nothing matches and the whole pass is a no-op that never
|
|
46
|
+
* touches FK state. A second run finds every column already defaulted.
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The eight tables whose `updated_at` default was added by PR #2922 (a migration
|
|
51
|
+
* edit that does not replay), so an upgraded DB's copy still lacks it. Ordered
|
|
52
|
+
* arbitrarily; each is independently rebuilt.
|
|
53
|
+
*/
|
|
54
|
+
const TABLES_WITH_DEFAULTED_UPDATED_AT = [
|
|
55
|
+
"device_configs",
|
|
56
|
+
"navigation_apps",
|
|
57
|
+
"prediction_transition_stats",
|
|
58
|
+
"accessibility_baselines",
|
|
59
|
+
"feature_flags",
|
|
60
|
+
"video_recording_configs",
|
|
61
|
+
"device_snapshot_configs",
|
|
62
|
+
"appearance_configs",
|
|
63
|
+
] as const;
|
|
64
|
+
|
|
65
|
+
/** The corrected default expression `updated_at` should carry, as raw SQL. */
|
|
66
|
+
const CORRECTED_DEFAULT = "(datetime('now'))";
|
|
67
|
+
|
|
68
|
+
interface ColumnInfoRow {
|
|
69
|
+
name: string;
|
|
70
|
+
dflt_value: string | null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface SqlRow {
|
|
74
|
+
sql: string | null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
78
|
+
// Fast path: only the tables whose `updated_at` column exists AND has no
|
|
79
|
+
// default need a rebuild. On a fresh/already-fixed schema this is empty, so we
|
|
80
|
+
// return WITHOUT touching PRAGMA foreign_keys — leaving the connection's FK
|
|
81
|
+
// state exactly as the caller set it, as the common case (every existing DB and
|
|
82
|
+
// test) requires. Only the rare upgraded-DB path below toggles it.
|
|
83
|
+
const tablesToRebuild: string[] = [];
|
|
84
|
+
for (const table of TABLES_WITH_DEFAULTED_UPDATED_AT) {
|
|
85
|
+
if (await updatedAtLacksDefault(db, table)) {
|
|
86
|
+
tablesToRebuild.push(table);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (tablesToRebuild.length === 0) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Rebuilding a table that OTHER tables reference by foreign key requires FK
|
|
94
|
+
// enforcement OFF: `DROP TABLE` performs an implicit DELETE that would otherwise
|
|
95
|
+
// fire `ON DELETE CASCADE` and wipe the child rows (deferral only delays the
|
|
96
|
+
// constraint *check*, not the cascade *action*). `PRAGMA foreign_keys` is a
|
|
97
|
+
// no-op inside a transaction, so it is toggled on the connection around the
|
|
98
|
+
// transaction — safe because the migrator does not wrap SQLite migrations in a
|
|
99
|
+
// transaction and the run is serialized by the migration lock. Restored to ON
|
|
100
|
+
// afterwards, the state `configureSqliteDatabase` and the rest of the chain
|
|
101
|
+
// expect on the production connection.
|
|
102
|
+
await sql`PRAGMA foreign_keys = OFF`.execute(db);
|
|
103
|
+
try {
|
|
104
|
+
// One transaction so a mid-rebuild failure cannot leave a half-swapped schema.
|
|
105
|
+
await db.transaction().execute(async trx => {
|
|
106
|
+
for (const table of tablesToRebuild) {
|
|
107
|
+
await rebuildTableWithUpdatedAtDefault(trx, table);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
} finally {
|
|
111
|
+
await sql`PRAGMA foreign_keys = ON`.execute(db);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* True when `table` exists and its `updated_at` column is present with NO stored
|
|
117
|
+
* default (the pre-#2922 upgraded-DB shape). A corrected `(datetime('now'))`
|
|
118
|
+
* default reports its `dflt_value` as `datetime('now')` (non-null) and is skipped;
|
|
119
|
+
* a missing table or column yields no matching row and is also skipped.
|
|
120
|
+
*
|
|
121
|
+
* The table name is inlined as a literal (`sql.lit`) rather than a bound
|
|
122
|
+
* parameter: a parameterized `pragma_table_info(?)` read was observed to
|
|
123
|
+
* intermittently return a null `dflt_value` under parallel-file load (a bun:sqlite
|
|
124
|
+
* quirk, see #2922's test), which here — where null is the very signal that
|
|
125
|
+
* triggers a rebuild — would spuriously rebuild a fresh table.
|
|
126
|
+
*/
|
|
127
|
+
async function updatedAtLacksDefault(db: Kysely<unknown>, table: string): Promise<boolean> {
|
|
128
|
+
const columns = await sql<ColumnInfoRow>`
|
|
129
|
+
SELECT name, dflt_value FROM pragma_table_info(${sql.lit(table)})
|
|
130
|
+
`.execute(db);
|
|
131
|
+
const updatedAt = columns.rows.find(column => column.name === "updated_at");
|
|
132
|
+
return updatedAt !== undefined && updatedAt.dflt_value === null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Rebuild `table` so its `updated_at` column gains the `(datetime('now'))`
|
|
137
|
+
* default, preserving data, indexes, and triggers. Only the `updated_at` column's
|
|
138
|
+
* `DEFAULT` clause changes; the column set and order are untouched, so
|
|
139
|
+
* `INSERT ... SELECT *` lines the rows up 1:1.
|
|
140
|
+
*/
|
|
141
|
+
async function rebuildTableWithUpdatedAtDefault(
|
|
142
|
+
trx: Kysely<unknown>,
|
|
143
|
+
table: string
|
|
144
|
+
): Promise<void> {
|
|
145
|
+
const createRow = await sql<SqlRow>`
|
|
146
|
+
SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ${table}
|
|
147
|
+
`.execute(trx);
|
|
148
|
+
const createSql = createRow.rows[0]?.sql;
|
|
149
|
+
if (!createSql) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const tempTable = `__rebuild_${table}`;
|
|
154
|
+
const correctedSql = addUpdatedAtDefault(replaceFirstTableName(createSql, table, tempTable));
|
|
155
|
+
|
|
156
|
+
// Capture the table's own indexes and triggers before the drop — auto-indexes
|
|
157
|
+
// backing PRIMARY KEY / UNIQUE constraints have a NULL `sql` and are recreated
|
|
158
|
+
// by the CREATE TABLE itself, so only the explicit ones need replaying.
|
|
159
|
+
const auxiliary = await sql<SqlRow>`
|
|
160
|
+
SELECT sql FROM sqlite_master
|
|
161
|
+
WHERE tbl_name = ${table} AND type IN ('index', 'trigger') AND sql IS NOT NULL
|
|
162
|
+
`.execute(trx);
|
|
163
|
+
|
|
164
|
+
// Capture the AUTOINCREMENT high-water mark. `sqlite_sequence` records the
|
|
165
|
+
// highest rowid EVER used so AUTOINCREMENT never reuses a deleted id; the
|
|
166
|
+
// copy/drop/rename below would otherwise reset it to `max(current id)` and let a
|
|
167
|
+
// deleted top id be handed out again. Restored after the rename.
|
|
168
|
+
const originalSequence = await captureSequence(trx, table);
|
|
169
|
+
|
|
170
|
+
await sql.raw(correctedSql).execute(trx);
|
|
171
|
+
await sql`INSERT INTO ${sql.ref(tempTable)} SELECT * FROM ${sql.ref(table)}`.execute(trx);
|
|
172
|
+
await sql`DROP TABLE ${sql.ref(table)}`.execute(trx);
|
|
173
|
+
await sql`ALTER TABLE ${sql.ref(tempTable)} RENAME TO ${sql.ref(table)}`.execute(trx);
|
|
174
|
+
for (const { sql: auxSql } of auxiliary.rows) {
|
|
175
|
+
if (auxSql) {
|
|
176
|
+
await sql.raw(auxSql).execute(trx);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (originalSequence !== undefined) {
|
|
181
|
+
await sql`DELETE FROM sqlite_sequence WHERE name = ${table}`.execute(trx);
|
|
182
|
+
await sql`INSERT INTO sqlite_sequence (name, seq) VALUES (${table}, ${originalSequence})`.execute(
|
|
183
|
+
trx
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Read a table's `sqlite_sequence` high-water mark, or `undefined` when the table
|
|
190
|
+
* is not AUTOINCREMENT / has never been inserted into (no sequence row) or the
|
|
191
|
+
* `sqlite_sequence` table does not exist (no AUTOINCREMENT table in the DB).
|
|
192
|
+
*/
|
|
193
|
+
async function captureSequence(
|
|
194
|
+
trx: Kysely<unknown>,
|
|
195
|
+
table: string
|
|
196
|
+
): Promise<number | undefined> {
|
|
197
|
+
const hasSequenceTable = await sql<{ name: string }>`
|
|
198
|
+
SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'sqlite_sequence'
|
|
199
|
+
`.execute(trx);
|
|
200
|
+
if (hasSequenceTable.rows.length === 0) {
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
const seqRow = await sql<{ seq: number }>`
|
|
204
|
+
SELECT seq FROM sqlite_sequence WHERE name = ${table}
|
|
205
|
+
`.execute(trx);
|
|
206
|
+
return seqRow.rows[0]?.seq;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Inject `default (datetime('now'))` into the `updated_at` column definition of a
|
|
211
|
+
* `CREATE TABLE` statement, right after its type keyword. Matches only when the
|
|
212
|
+
* column has no existing default (the type is immediately followed by `not null`),
|
|
213
|
+
* so it is a no-op on a schema that already carries the default and never
|
|
214
|
+
* double-adds. Only the first (and only) `"updated_at"` column definition is
|
|
215
|
+
* rewritten.
|
|
216
|
+
*/
|
|
217
|
+
function addUpdatedAtDefault(createSql: string): string {
|
|
218
|
+
return createSql.replace(
|
|
219
|
+
/("updated_at"\s+\w+)(\s+not\s+null)/i,
|
|
220
|
+
`$1 default ${CORRECTED_DEFAULT}$2`
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Replace the table name in a `CREATE TABLE` statement, tolerating the double-
|
|
226
|
+
* quoted (Kysely) and bare forms and arbitrary whitespace after the keyword.
|
|
227
|
+
* Only the first occurrence — the table being declared — is rewritten; a later
|
|
228
|
+
* self-reference (e.g. a table-level FK) keeps pointing at the real name so the
|
|
229
|
+
* copy still works.
|
|
230
|
+
*/
|
|
231
|
+
function replaceFirstTableName(createSql: string, from: string, to: string): string {
|
|
232
|
+
return createSql.replace(
|
|
233
|
+
/^(\s*CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?)(?:"([^"]+)"|(\w+))/i,
|
|
234
|
+
(match, prefix: string, quotedName: string | undefined, bareName: string | undefined) =>
|
|
235
|
+
(quotedName ?? bareName) === from ? `${prefix}"${to}"` : match
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export async function down(): Promise<void> {
|
|
240
|
+
// Irreversible repair: the corrected default carries no information worth
|
|
241
|
+
// reverting, so the down migration is intentionally a no-op.
|
|
242
|
+
}
|