@everystack/cli 0.4.100 → 0.4.102
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/CHANGELOG.md +91 -0
- package/package.json +3 -3
- package/src/cli/authz-baseline.ts +8 -4
- package/src/cli/authz-compile.ts +61 -7
- package/src/cli/authz-contract.ts +23 -14
- package/src/cli/authz-derive.ts +282 -30
- package/src/cli/authz-reconcile.ts +6 -4
- package/src/cli/commands/db-check.ts +40 -0
- package/src/cli/commands/db-generate.ts +10 -9
- package/src/cli/commands/db-plan.ts +30 -13
- package/src/cli/commands/db-pull.ts +153 -63
- package/src/cli/commands/db-reconcile.ts +22 -3
- package/src/cli/commands/db-sync.ts +6 -5
- package/src/cli/commands/db.ts +91 -8
- package/src/cli/commands/update.ts +3 -2
- package/src/cli/db-build.ts +1 -0
- package/src/cli/declared-derived.ts +3 -1
- package/src/cli/declared-diff.ts +4 -1
- package/src/cli/derived-apply.ts +116 -10
- package/src/cli/derived-grants.ts +4 -2
- package/src/cli/derived-introspect.ts +23 -3
- package/src/cli/derived-plan.ts +37 -1
- package/src/cli/derived-render.ts +33 -34
- package/src/cli/edge-plan.ts +57 -9
- package/src/cli/generated-artifacts.ts +22 -0
- package/src/cli/generated-names.ts +108 -0
- package/src/cli/host-url.ts +48 -27
- package/src/cli/index.ts +5 -5
- package/src/cli/lifecycle-canon.ts +37 -0
- package/src/cli/lifecycle-derived.ts +721 -0
- package/src/cli/lifecycle-pull.ts +177 -0
- package/src/cli/lifecycle-verify.ts +232 -0
- package/src/cli/migration-compile.ts +8 -3
- package/src/cli/migration-generate.ts +23 -3
- package/src/cli/model-map.ts +255 -0
- package/src/cli/model-render.ts +226 -76
- package/src/cli/plain-output.ts +198 -10
- package/src/cli/runbook/model-section.ts +8 -5
- package/src/cli/schema-compile.ts +35 -17
- package/src/cli/schema-diff.ts +166 -31
- package/src/cli/schema-fingerprint.ts +39 -6
- package/src/cli/schema-introspect.ts +64 -2
- package/src/cli/schema-source.ts +40 -9
- package/src/cli/schema-swap.ts +3 -2
- package/src/cli/state-apply.ts +10 -4
- package/src/cli/ts-template.ts +52 -0
package/src/cli/schema-swap.ts
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* so the swap is a pointer flip, never a recompute.
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
|
-
import type
|
|
33
|
+
import { relationDeleteActions, type ModelDescriptor } from '@everystack/model';
|
|
34
34
|
import { modelForeignKeys, qualifiedTable } from './schema-compile';
|
|
35
35
|
import { compileTableContract } from './authz-compile';
|
|
36
36
|
import { emitSwapAuthzSql } from './authz-reconcile';
|
|
@@ -73,10 +73,11 @@ export interface CrossSchemaFk {
|
|
|
73
73
|
*/
|
|
74
74
|
export function crossSchemaForeignKeys(models: ModelDescriptor[], targetSchema: string): CrossSchemaFk[] {
|
|
75
75
|
const out: CrossSchemaFk[] = [];
|
|
76
|
+
const dependents = relationDeleteActions(models);
|
|
76
77
|
for (const model of models) {
|
|
77
78
|
if ((model.schema || 'public') === targetSchema) continue; // internal edge, not external
|
|
78
79
|
const from = qualifiedTable(model);
|
|
79
|
-
for (const fk of modelForeignKeys(model)) {
|
|
80
|
+
for (const fk of modelForeignKeys(model, dependents)) {
|
|
80
81
|
if (schemaOf(fk.refTable) !== targetSchema) continue; // not pointing into the swapped schema
|
|
81
82
|
const cols = fk.columns.map((c) => `"${c}"`).join(', ');
|
|
82
83
|
const refCols = fk.refColumns.map((c) => `"${c}"`).join(', ');
|
package/src/cli/state-apply.ts
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
import { execSync, execFileSync } from 'node:child_process';
|
|
21
21
|
import type { ModelDescriptor } from '@everystack/model';
|
|
22
22
|
import { introspectContract, type AuthzContract, type QueryRunner } from './authz-contract';
|
|
23
|
-
import { borrowedSessionRunner, type SessionRunner } from './session';
|
|
23
|
+
import { borrowedSessionRunner, CANONICAL_SEARCH_PATH, type SessionRunner } from './session';
|
|
24
24
|
import { introspectSchema, type SchemaSnapshot } from './schema-introspect';
|
|
25
25
|
import { FUNCTIONS_SQL, contractFunctionRow } from './security-catalog';
|
|
26
26
|
import { generateMigrationSql, HELD_DROP_PREFIX } from './migration-generate';
|
|
@@ -572,9 +572,14 @@ export async function applyGeneratedStatements(
|
|
|
572
572
|
// point; they join the row on the amendment, where their outcome is known.
|
|
573
573
|
const prerequisiteSql = prerequisites
|
|
574
574
|
.flatMap((prerequisite) => [renderStatePrerequisiteGuard(prerequisite), prerequisite.sql]);
|
|
575
|
+
// The plan is rendered against the canonical path — public bare, non-public qualified — so it
|
|
576
|
+
// must RUN under it. Under an operator path like `analytics, public`, a bare
|
|
577
|
+
// `CREATE TYPE "post_status"` would land in analytics. Transaction-local and inside the
|
|
578
|
+
// batch's own string, so it can neither be skipped nor outlive the batch.
|
|
579
|
+
const stateBatch = batch.length > 0 ? [renderLocalSearchPath(CANONICAL_SEARCH_PATH), ...batch] : [];
|
|
575
580
|
// A SQL-language prerequisite may read a table this same state edge creates. PostgreSQL
|
|
576
581
|
// validates such bodies at CREATE time unless this transaction-local setting defers it.
|
|
577
|
-
let batchSql = [...(prerequisites.length > 0 ? ['SET LOCAL check_function_bodies = off'] : []), ...prerequisiteSql, ...
|
|
582
|
+
let batchSql = [...(prerequisites.length > 0 ? ['SET LOCAL check_function_bodies = off'] : []), ...prerequisiteSql, ...stateBatch].join('\n;\n');
|
|
578
583
|
const validationSql = validations.join('\n;\n');
|
|
579
584
|
const started = now();
|
|
580
585
|
try {
|
|
@@ -587,13 +592,14 @@ export async function applyGeneratedStatements(
|
|
|
587
592
|
const originalPath = settings?.search_path;
|
|
588
593
|
if (typeof originalPath !== 'string') throw new Error('could not capture prerequisite search_path; no state was applied.');
|
|
589
594
|
// Defaults and SQL-standard bodies bind names at CREATE time. Use the full declared
|
|
590
|
-
// path recorded at mint, then restore the exact connection setting
|
|
595
|
+
// path recorded at mint, then restore the exact connection setting; the state DDL
|
|
596
|
+
// that follows pins the canonical path itself.
|
|
591
597
|
batchSql = ['SET LOCAL check_function_bodies = off', ...prerequisites.flatMap((prerequisite) => [
|
|
592
598
|
renderStatePrerequisiteGuard(prerequisite),
|
|
593
599
|
...(prerequisite.searchPath === null ? [] : [renderLocalSearchPath(prerequisite.searchPath)]),
|
|
594
600
|
prerequisite.sql,
|
|
595
601
|
...(prerequisite.searchPath === null ? [] : [renderLocalSearchPath(originalPath)]),
|
|
596
|
-
]), ...
|
|
602
|
+
]), ...stateBatch].join('\n;\n');
|
|
597
603
|
await transactionRunner(batchSql);
|
|
598
604
|
// `defHash` is PostgreSQL's canonical deparse, so source text cannot predict it. Read it
|
|
599
605
|
// after the CREATE through the OPEN transaction, then write provenance before COMMIT.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escape live text for the body of a template literal in generated source, so the template
|
|
3
|
+
* evaluates back to EXACTLY that text: backslash first, then backtick, then `${`. Live database
|
|
4
|
+
* text is data. Unescaped, `${…}` in it ran when the generated file was imported, and a
|
|
5
|
+
* backslash before a backtick closed the template. Every renderer that puts live text inside
|
|
6
|
+
* sql`…` uses this one rule.
|
|
7
|
+
*/
|
|
8
|
+
export function templateBody(text: string): string {
|
|
9
|
+
return text.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Live text as a single-quoted string literal in generated source, evaluating back to EXACTLY
|
|
14
|
+
* that text. Backslash first, then the quote, then every line terminator: a raw newline or
|
|
15
|
+
* carriage return ends a string literal, so a live name holding one broke the generated file.
|
|
16
|
+
* U+2028 and U+2029 are legal inside a string since ES2019 but not in every older parser, so
|
|
17
|
+
* they are escaped too. Ordinary text comes out unchanged.
|
|
18
|
+
*/
|
|
19
|
+
export function tsString(text: string): string {
|
|
20
|
+
const body = text
|
|
21
|
+
.replace(/\\/g, '\\\\')
|
|
22
|
+
.replace(/'/g, "\\'")
|
|
23
|
+
.replace(/\n/g, '\\n')
|
|
24
|
+
.replace(/\r/g, '\\r')
|
|
25
|
+
.replace(/\u2028/g, '\\u2028')
|
|
26
|
+
.replace(/\u2029/g, '\\u2029');
|
|
27
|
+
return `'${body}'`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Live text bound for a `//` line comment in generated source. JavaScript ends a line comment
|
|
32
|
+
* at ANY line terminator (LF, CR, U+2028, U+2029), so a live name holding one ended the comment
|
|
33
|
+
* and the rest of the name ran as code. Each terminator becomes its visible escape sequence
|
|
34
|
+
* (`\n`, `\r`, `\u2028`, `\u2029`): the comment stays one line, and a reader still sees that
|
|
35
|
+
* the live name held a line break. Ordinary text comes out unchanged.
|
|
36
|
+
*/
|
|
37
|
+
export function commentText(text: string): string {
|
|
38
|
+
return text
|
|
39
|
+
.replace(/\n/g, '\\n')
|
|
40
|
+
.replace(/\r/g, '\\r')
|
|
41
|
+
.replace(/\u2028/g, '\\u2028')
|
|
42
|
+
.replace(/\u2029/g, '\\u2029');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Live text that is MEANT to span several comment lines (a deparsed predicate in a note): split
|
|
47
|
+
* on every line terminator, so each piece gets its own `//`. Splitting on `\n` alone left a CR,
|
|
48
|
+
* U+2028 or U+2029 inside one line, which still ended the comment.
|
|
49
|
+
*/
|
|
50
|
+
export function commentLines(text: string): string[] {
|
|
51
|
+
return text.split(/\r\n|[\n\r\u2028\u2029]/);
|
|
52
|
+
}
|