@trypattern/sdk 0.1.23 → 0.1.24
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/dist/cli/app.js +54 -5
- package/dist/cli/startingValues.d.ts +9 -0
- package/dist/cli/startingValues.js +58 -0
- package/package.json +1 -1
package/dist/cli/app.js
CHANGED
|
@@ -22,6 +22,7 @@ import { readStarter, starterContentOf, withStarter } from "./starter.js";
|
|
|
22
22
|
import { addLiveMedia, login, publish, pull, restore, versions as listVersions } from "./publishing.js";
|
|
23
23
|
import { CHANGE_SET_ROWS } from "../publishWire.js";
|
|
24
24
|
import { DATA_USAGE, data } from "./data.js";
|
|
25
|
+
import { withStartingValues } from "./startingValues.js";
|
|
25
26
|
import { toolBin } from "./tools.js";
|
|
26
27
|
import { isNewer, latestPattern, newerPattern, updateNote } from "./updates.js";
|
|
27
28
|
function dependsOnPattern(directory) {
|
|
@@ -215,7 +216,10 @@ changing seed.ts or starter/. Works while the dev server runs; reload the page t
|
|
|
215
216
|
details: `app migration add_due_dates
|
|
216
217
|
Compares the tables in schema.ts (plus Pattern's users, sessions and settings tables) with server/migrations and writes the
|
|
217
218
|
SQL that moves the database from one to the other, named <number>_<name>.sql. app start applies new
|
|
218
|
-
migrations to the local database. Prints "No schema changes" when the migrations already match
|
|
219
|
+
migrations to the local database. Prints "No schema changes" when the migrations already match.
|
|
220
|
+
A new required column starts as empty, 0 or false on rows that already exist (the live site has rows),
|
|
221
|
+
and a column made required fills its empty rows the same way; each is noted. To start them on a
|
|
222
|
+
real value, add an UPDATE after it in the migration file.`,
|
|
219
223
|
run: migration,
|
|
220
224
|
takesArgument: true
|
|
221
225
|
},
|
|
@@ -1295,18 +1299,63 @@ async function migration(name) {
|
|
|
1295
1299
|
'--name',
|
|
1296
1300
|
name
|
|
1297
1301
|
]);
|
|
1302
|
+
const notes = code === 0 ? startNewColumns(name) : [];
|
|
1298
1303
|
const warning = code === 0 ? ownerBackfillWarning(name) : undefined;
|
|
1299
1304
|
return {
|
|
1300
1305
|
code,
|
|
1301
|
-
data: {
|
|
1302
|
-
|
|
1306
|
+
data: {
|
|
1307
|
+
output: output.trimEnd(),
|
|
1308
|
+
startingValues: notes,
|
|
1309
|
+
...(warning && { warning })
|
|
1310
|
+
},
|
|
1311
|
+
text: `${code === 0 ? '✓' : '✗'} app migration ${name}\n\n${indent(output.trimEnd())}${notes.map((note) => `\n note ${note}`).join('')}${warning === undefined ? '' : `\n\n! ${warning}`}`
|
|
1303
1312
|
};
|
|
1304
1313
|
}
|
|
1305
|
-
function
|
|
1306
|
-
|
|
1314
|
+
function newestMigration(name) {
|
|
1315
|
+
return readdirSync(MIGRATIONS_DIR)
|
|
1307
1316
|
.filter((entry) => entry.endsWith(`_${name}.sql`))
|
|
1308
1317
|
.sort()
|
|
1309
1318
|
.at(-1);
|
|
1319
|
+
}
|
|
1320
|
+
function previousTables(file) {
|
|
1321
|
+
const journal = join(MIGRATIONS_DIR, 'meta', '_journal.json');
|
|
1322
|
+
if (!existsSync(journal))
|
|
1323
|
+
return {};
|
|
1324
|
+
const entries = z
|
|
1325
|
+
.object({
|
|
1326
|
+
entries: z.array(z.object({ idx: z.number(), tag: z.string() }))
|
|
1327
|
+
})
|
|
1328
|
+
.parse(JSON.parse(readText(journal))).entries;
|
|
1329
|
+
const at = entries.findIndex((entry) => `${entry.tag}.sql` === file);
|
|
1330
|
+
const before = at > 0 ? entries[at - 1] : undefined;
|
|
1331
|
+
if (before === undefined)
|
|
1332
|
+
return {};
|
|
1333
|
+
const snapshot = join(MIGRATIONS_DIR, 'meta', `${String(before.idx).padStart(4, '0')}_snapshot.json`);
|
|
1334
|
+
if (!existsSync(snapshot))
|
|
1335
|
+
return {};
|
|
1336
|
+
return z
|
|
1337
|
+
.object({
|
|
1338
|
+
tables: z.record(z.string(), z.object({
|
|
1339
|
+
columns: z.record(z.string(), z.object({ notNull: z.boolean() }))
|
|
1340
|
+
}))
|
|
1341
|
+
})
|
|
1342
|
+
.parse(JSON.parse(readText(snapshot))).tables;
|
|
1343
|
+
}
|
|
1344
|
+
function startNewColumns(name) {
|
|
1345
|
+
const file = newestMigration(name);
|
|
1346
|
+
if (file === undefined)
|
|
1347
|
+
return [];
|
|
1348
|
+
const path = join(MIGRATIONS_DIR, file);
|
|
1349
|
+
const tables = Object.fromEntries(Object.entries(previousTables(file)).map(([table, { columns }]) => [
|
|
1350
|
+
table,
|
|
1351
|
+
columns
|
|
1352
|
+
]));
|
|
1353
|
+
const { sql, notes } = withStartingValues(readText(path), tables);
|
|
1354
|
+
writeFileSync(path, sql);
|
|
1355
|
+
return notes.map((note) => `${file}: ${note}`);
|
|
1356
|
+
}
|
|
1357
|
+
function ownerBackfillWarning(name) {
|
|
1358
|
+
const file = newestMigration(name);
|
|
1310
1359
|
if (file === undefined)
|
|
1311
1360
|
return undefined;
|
|
1312
1361
|
const sql = readFileSync(join(MIGRATIONS_DIR, file), 'utf8');
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
type Columns = Readonly<Record<string, {
|
|
2
|
+
notNull: boolean;
|
|
3
|
+
}>>;
|
|
4
|
+
export type PreviousTables = Readonly<Record<string, Columns>>;
|
|
5
|
+
export declare function withStartingValues(sql: string, previous: PreviousTables): {
|
|
6
|
+
sql: string;
|
|
7
|
+
notes: string[];
|
|
8
|
+
};
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const ADDED = /^ALTER TABLE `(\w+)` ADD `(\w+)` (text|integer|real) NOT NULL;/gm;
|
|
2
|
+
const REBUILT = /CREATE TABLE `__new_(\w+)` \(([^;]*?)\n\);(\n(?:--> statement-breakpoint\n)?\n?)INSERT INTO `__new_\1`\(([^)]*)\) SELECT ([^;]*) FROM `\1`;/g;
|
|
3
|
+
const REQUIRED = /^(\s*`(\w+)` (text|integer|real)) NOT NULL(,?)$/;
|
|
4
|
+
const STARTING_VALUES = {
|
|
5
|
+
text: "''",
|
|
6
|
+
integer: '0',
|
|
7
|
+
real: '0'
|
|
8
|
+
};
|
|
9
|
+
const SPOKEN = {
|
|
10
|
+
text: 'empty',
|
|
11
|
+
integer: '0 (or "no")',
|
|
12
|
+
real: '0'
|
|
13
|
+
};
|
|
14
|
+
function quoted(names) {
|
|
15
|
+
return names.split(',').map((name) => name.trim().replaceAll('"', ''));
|
|
16
|
+
}
|
|
17
|
+
export function withStartingValues(sql, previous) {
|
|
18
|
+
const started = [];
|
|
19
|
+
const added = sql.replaceAll(ADDED, (_line, table, column, type) => {
|
|
20
|
+
started.push({ table, column, type });
|
|
21
|
+
return `ALTER TABLE \`${table}\` ADD \`${column}\` ${type} NOT NULL DEFAULT ${STARTING_VALUES[type] ?? "''"};`;
|
|
22
|
+
});
|
|
23
|
+
const rebuilt = added.replaceAll(REBUILT, (whole, table, body, between, into, from) => {
|
|
24
|
+
const old = previous[table];
|
|
25
|
+
if (old === undefined)
|
|
26
|
+
return whole;
|
|
27
|
+
const types = new Map();
|
|
28
|
+
const lines = body.split('\n').map((line) => {
|
|
29
|
+
const match = REQUIRED.exec(line);
|
|
30
|
+
if (match === null || line.includes('PRIMARY KEY'))
|
|
31
|
+
return line;
|
|
32
|
+
const [, start = '', column = '', type = '', comma = ''] = match;
|
|
33
|
+
types.set(column, type);
|
|
34
|
+
return `${start} NOT NULL DEFAULT ${STARTING_VALUES[type] ?? "''"}${comma}`;
|
|
35
|
+
});
|
|
36
|
+
const selected = quoted(from).map((column) => {
|
|
37
|
+
const type = types.get(column);
|
|
38
|
+
const before = old[column];
|
|
39
|
+
if (type === undefined)
|
|
40
|
+
return `"${column}"`;
|
|
41
|
+
const value = STARTING_VALUES[type] ?? "''";
|
|
42
|
+
if (before === undefined) {
|
|
43
|
+
started.push({ table, column, type });
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
if (!before.notNull) {
|
|
47
|
+
started.push({ table, column, type });
|
|
48
|
+
return `coalesce("${column}", ${value})`;
|
|
49
|
+
}
|
|
50
|
+
return `"${column}"`;
|
|
51
|
+
});
|
|
52
|
+
return `CREATE TABLE \`__new_${table}\` (${lines.join('\n')}\n);${between}INSERT INTO \`__new_${table}\`(${into}) SELECT ${selected.join(', ')} FROM \`${table}\`;`;
|
|
53
|
+
});
|
|
54
|
+
return {
|
|
55
|
+
sql: rebuilt,
|
|
56
|
+
notes: started.map(({ table, column, type }) => `${table}.${column} starts as ${SPOKEN[type] ?? 'empty'} on rows that already exist. To start them on a real value, add UPDATE \`${table}\` SET \`${column}\` = … after it in the migration.`)
|
|
57
|
+
};
|
|
58
|
+
}
|