@databricks-solutions/lakebase-scm-utils 0.3.7 → 0.3.9
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/scripts/index.cjs +2 -2
- package/dist/scripts/index.js +2 -2
- package/dist/scripts/lakebase/ci-resolve-branch.cli.cjs +2 -2
- package/dist/scripts/lakebase/ci-resolve-branch.cli.js +2 -2
- package/dist/scripts/lakebase/collapse-heads.cli.cjs +2 -2
- package/dist/scripts/lakebase/collapse-heads.cli.js +2 -2
- package/dist/scripts/lakebase/detect-language.cli.cjs +2 -2
- package/dist/scripts/lakebase/detect-language.cli.js +2 -2
- package/dist/scripts/lakebase/doctor.cli.cjs +2 -2
- package/dist/scripts/lakebase/doctor.cli.js +2 -2
- package/dist/scripts/lakebase/get-connection.cli.cjs +2 -2
- package/dist/scripts/lakebase/get-connection.cli.js +2 -2
- package/dist/scripts/lakebase/index.cjs +2 -2
- package/dist/scripts/lakebase/index.js +2 -2
- package/dist/scripts/lakebase/infra-runner.cli.cjs +2 -2
- package/dist/scripts/lakebase/infra-runner.cli.js +2 -2
- package/dist/scripts/lakebase/new-migration.cli.cjs +2 -2
- package/dist/scripts/lakebase/new-migration.cli.js +2 -2
- package/dist/scripts/lakebase/schema-diff.cli.cjs +2 -2
- package/dist/scripts/lakebase/schema-diff.cli.js +2 -2
- package/dist/scripts/lakebase/schema-migrate.cli.cjs +2 -2
- package/dist/scripts/lakebase/schema-migrate.cli.js +2 -2
- package/dist/scripts/lakebase/scm-claim-feature.cli.cjs +2 -2
- package/dist/scripts/lakebase/scm-claim-feature.cli.js +2 -2
- package/dist/scripts/lakebase/scm-doctor.cli.cjs +2 -2
- package/dist/scripts/lakebase/scm-doctor.cli.js +2 -2
- package/dist/scripts/lakebase/scm-merge.cli.cjs +2 -2
- package/dist/scripts/lakebase/scm-merge.cli.js +2 -2
- package/dist/scripts/lakebase/scm-reconcile-tier.cli.cjs +2 -2
- package/dist/scripts/lakebase/scm-reconcile-tier.cli.js +2 -2
- package/dist/scripts/util/index.cjs +2 -2
- package/dist/scripts/util/index.js +2 -2
- package/package.json +1 -1
- package/templates/project/common/consort-capture.ts +121 -3
- package/templates/project/common/tests/e2e/__pycache__/conftest.cpython-314.pyc +0 -0
- package/templates/project/common/tests/e2e/conftest.py +5 -1
- package/templates/project/python/tests/__pycache__/conftest.cpython-314.pyc +0 -0
- package/templates/project/python/tests/conftest.py +128 -5
package/dist/scripts/index.cjs
CHANGED
|
@@ -88959,8 +88959,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
88959
88959
|
var cached;
|
|
88960
88960
|
function substrateSelfVersion() {
|
|
88961
88961
|
if (cached !== void 0) return cached;
|
|
88962
|
-
if ("0.3.
|
|
88963
|
-
cached = "0.3.
|
|
88962
|
+
if ("0.3.9".length > 0) {
|
|
88963
|
+
cached = "0.3.9";
|
|
88964
88964
|
return cached;
|
|
88965
88965
|
}
|
|
88966
88966
|
cached = "unknown";
|
package/dist/scripts/index.js
CHANGED
|
@@ -9737,8 +9737,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
9737
9737
|
var cached;
|
|
9738
9738
|
function substrateSelfVersion() {
|
|
9739
9739
|
if (cached !== void 0) return cached;
|
|
9740
|
-
if ("0.3.
|
|
9741
|
-
cached = "0.3.
|
|
9740
|
+
if ("0.3.9".length > 0) {
|
|
9741
|
+
cached = "0.3.9";
|
|
9742
9742
|
return cached;
|
|
9743
9743
|
}
|
|
9744
9744
|
cached = "unknown";
|
|
@@ -79629,8 +79629,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
79629
79629
|
var cached;
|
|
79630
79630
|
function substrateSelfVersion() {
|
|
79631
79631
|
if (cached !== void 0) return cached;
|
|
79632
|
-
if ("0.3.
|
|
79633
|
-
cached = "0.3.
|
|
79632
|
+
if ("0.3.9".length > 0) {
|
|
79633
|
+
cached = "0.3.9";
|
|
79634
79634
|
return cached;
|
|
79635
79635
|
}
|
|
79636
79636
|
cached = "unknown";
|
|
@@ -721,8 +721,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
721
721
|
var cached;
|
|
722
722
|
function substrateSelfVersion() {
|
|
723
723
|
if (cached !== void 0) return cached;
|
|
724
|
-
if ("0.3.
|
|
725
|
-
cached = "0.3.
|
|
724
|
+
if ("0.3.9".length > 0) {
|
|
725
|
+
cached = "0.3.9";
|
|
726
726
|
return cached;
|
|
727
727
|
}
|
|
728
728
|
cached = "unknown";
|
|
@@ -79290,8 +79290,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
79290
79290
|
var cached;
|
|
79291
79291
|
function substrateSelfVersion() {
|
|
79292
79292
|
if (cached !== void 0) return cached;
|
|
79293
|
-
if ("0.3.
|
|
79294
|
-
cached = "0.3.
|
|
79293
|
+
if ("0.3.9".length > 0) {
|
|
79294
|
+
cached = "0.3.9";
|
|
79295
79295
|
return cached;
|
|
79296
79296
|
}
|
|
79297
79297
|
cached = "unknown";
|
|
@@ -393,8 +393,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
393
393
|
var cached;
|
|
394
394
|
function substrateSelfVersion() {
|
|
395
395
|
if (cached !== void 0) return cached;
|
|
396
|
-
if ("0.3.
|
|
397
|
-
cached = "0.3.
|
|
396
|
+
if ("0.3.9".length > 0) {
|
|
397
|
+
cached = "0.3.9";
|
|
398
398
|
return cached;
|
|
399
399
|
}
|
|
400
400
|
cached = "unknown";
|
|
@@ -79290,8 +79290,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
79290
79290
|
var cached;
|
|
79291
79291
|
function substrateSelfVersion() {
|
|
79292
79292
|
if (cached !== void 0) return cached;
|
|
79293
|
-
if ("0.3.
|
|
79294
|
-
cached = "0.3.
|
|
79293
|
+
if ("0.3.9".length > 0) {
|
|
79294
|
+
cached = "0.3.9";
|
|
79295
79295
|
return cached;
|
|
79296
79296
|
}
|
|
79297
79297
|
cached = "unknown";
|
|
@@ -393,8 +393,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
393
393
|
var cached;
|
|
394
394
|
function substrateSelfVersion() {
|
|
395
395
|
if (cached !== void 0) return cached;
|
|
396
|
-
if ("0.3.
|
|
397
|
-
cached = "0.3.
|
|
396
|
+
if ("0.3.9".length > 0) {
|
|
397
|
+
cached = "0.3.9";
|
|
398
398
|
return cached;
|
|
399
399
|
}
|
|
400
400
|
cached = "unknown";
|
|
@@ -80800,8 +80800,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
80800
80800
|
var cached;
|
|
80801
80801
|
function substrateSelfVersion() {
|
|
80802
80802
|
if (cached !== void 0) return cached;
|
|
80803
|
-
if ("0.3.
|
|
80804
|
-
cached = "0.3.
|
|
80803
|
+
if ("0.3.9".length > 0) {
|
|
80804
|
+
cached = "0.3.9";
|
|
80805
80805
|
return cached;
|
|
80806
80806
|
}
|
|
80807
80807
|
cached = "unknown";
|
|
@@ -1959,8 +1959,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
1959
1959
|
var cached;
|
|
1960
1960
|
function substrateSelfVersion() {
|
|
1961
1961
|
if (cached !== void 0) return cached;
|
|
1962
|
-
if ("0.3.
|
|
1963
|
-
cached = "0.3.
|
|
1962
|
+
if ("0.3.9".length > 0) {
|
|
1963
|
+
cached = "0.3.9";
|
|
1964
1964
|
return cached;
|
|
1965
1965
|
}
|
|
1966
1966
|
cached = "unknown";
|
|
@@ -79285,8 +79285,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
79285
79285
|
var cached;
|
|
79286
79286
|
function substrateSelfVersion() {
|
|
79287
79287
|
if (cached !== void 0) return cached;
|
|
79288
|
-
if ("0.3.
|
|
79289
|
-
cached = "0.3.
|
|
79288
|
+
if ("0.3.9".length > 0) {
|
|
79289
|
+
cached = "0.3.9";
|
|
79290
79290
|
return cached;
|
|
79291
79291
|
}
|
|
79292
79292
|
cached = "unknown";
|
|
@@ -389,8 +389,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
389
389
|
var cached;
|
|
390
390
|
function substrateSelfVersion() {
|
|
391
391
|
if (cached !== void 0) return cached;
|
|
392
|
-
if ("0.3.
|
|
393
|
-
cached = "0.3.
|
|
392
|
+
if ("0.3.9".length > 0) {
|
|
393
|
+
cached = "0.3.9";
|
|
394
394
|
return cached;
|
|
395
395
|
}
|
|
396
396
|
cached = "unknown";
|
|
@@ -81607,8 +81607,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
81607
81607
|
var cached;
|
|
81608
81608
|
function substrateSelfVersion() {
|
|
81609
81609
|
if (cached !== void 0) return cached;
|
|
81610
|
-
if ("0.3.
|
|
81611
|
-
cached = "0.3.
|
|
81610
|
+
if ("0.3.9".length > 0) {
|
|
81611
|
+
cached = "0.3.9";
|
|
81612
81612
|
return cached;
|
|
81613
81613
|
}
|
|
81614
81614
|
cached = "unknown";
|
|
@@ -2521,8 +2521,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
2521
2521
|
var cached;
|
|
2522
2522
|
function substrateSelfVersion() {
|
|
2523
2523
|
if (cached !== void 0) return cached;
|
|
2524
|
-
if ("0.3.
|
|
2525
|
-
cached = "0.3.
|
|
2524
|
+
if ("0.3.9".length > 0) {
|
|
2525
|
+
cached = "0.3.9";
|
|
2526
2526
|
return cached;
|
|
2527
2527
|
}
|
|
2528
2528
|
cached = "unknown";
|
|
@@ -79290,8 +79290,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
79290
79290
|
var cached;
|
|
79291
79291
|
function substrateSelfVersion() {
|
|
79292
79292
|
if (cached !== void 0) return cached;
|
|
79293
|
-
if ("0.3.
|
|
79294
|
-
cached = "0.3.
|
|
79293
|
+
if ("0.3.9".length > 0) {
|
|
79294
|
+
cached = "0.3.9";
|
|
79295
79295
|
return cached;
|
|
79296
79296
|
}
|
|
79297
79297
|
cached = "unknown";
|
|
@@ -393,8 +393,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
393
393
|
var cached;
|
|
394
394
|
function substrateSelfVersion() {
|
|
395
395
|
if (cached !== void 0) return cached;
|
|
396
|
-
if ("0.3.
|
|
397
|
-
cached = "0.3.
|
|
396
|
+
if ("0.3.9".length > 0) {
|
|
397
|
+
cached = "0.3.9";
|
|
398
398
|
return cached;
|
|
399
399
|
}
|
|
400
400
|
cached = "unknown";
|
|
@@ -79290,8 +79290,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
79290
79290
|
var cached;
|
|
79291
79291
|
function substrateSelfVersion() {
|
|
79292
79292
|
if (cached !== void 0) return cached;
|
|
79293
|
-
if ("0.3.
|
|
79294
|
-
cached = "0.3.
|
|
79293
|
+
if ("0.3.9".length > 0) {
|
|
79294
|
+
cached = "0.3.9";
|
|
79295
79295
|
return cached;
|
|
79296
79296
|
}
|
|
79297
79297
|
cached = "unknown";
|
|
@@ -393,8 +393,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
393
393
|
var cached;
|
|
394
394
|
function substrateSelfVersion() {
|
|
395
395
|
if (cached !== void 0) return cached;
|
|
396
|
-
if ("0.3.
|
|
397
|
-
cached = "0.3.
|
|
396
|
+
if ("0.3.9".length > 0) {
|
|
397
|
+
cached = "0.3.9";
|
|
398
398
|
return cached;
|
|
399
399
|
}
|
|
400
400
|
cached = "unknown";
|
|
@@ -79288,8 +79288,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
79288
79288
|
var cached;
|
|
79289
79289
|
function substrateSelfVersion() {
|
|
79290
79290
|
if (cached !== void 0) return cached;
|
|
79291
|
-
if ("0.3.
|
|
79292
|
-
cached = "0.3.
|
|
79291
|
+
if ("0.3.9".length > 0) {
|
|
79292
|
+
cached = "0.3.9";
|
|
79293
79293
|
return cached;
|
|
79294
79294
|
}
|
|
79295
79295
|
cached = "unknown";
|
|
@@ -389,8 +389,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
389
389
|
var cached;
|
|
390
390
|
function substrateSelfVersion() {
|
|
391
391
|
if (cached !== void 0) return cached;
|
|
392
|
-
if ("0.3.
|
|
393
|
-
cached = "0.3.
|
|
392
|
+
if ("0.3.9".length > 0) {
|
|
393
|
+
cached = "0.3.9";
|
|
394
394
|
return cached;
|
|
395
395
|
}
|
|
396
396
|
cached = "unknown";
|
|
@@ -79324,8 +79324,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
79324
79324
|
var cached;
|
|
79325
79325
|
function substrateSelfVersion() {
|
|
79326
79326
|
if (cached !== void 0) return cached;
|
|
79327
|
-
if ("0.3.
|
|
79328
|
-
cached = "0.3.
|
|
79327
|
+
if ("0.3.9".length > 0) {
|
|
79328
|
+
cached = "0.3.9";
|
|
79329
79329
|
return cached;
|
|
79330
79330
|
}
|
|
79331
79331
|
cached = "unknown";
|
|
@@ -427,8 +427,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
427
427
|
var cached;
|
|
428
428
|
function substrateSelfVersion() {
|
|
429
429
|
if (cached !== void 0) return cached;
|
|
430
|
-
if ("0.3.
|
|
431
|
-
cached = "0.3.
|
|
430
|
+
if ("0.3.9".length > 0) {
|
|
431
|
+
cached = "0.3.9";
|
|
432
432
|
return cached;
|
|
433
433
|
}
|
|
434
434
|
cached = "unknown";
|
|
@@ -79783,8 +79783,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
79783
79783
|
var cached;
|
|
79784
79784
|
function substrateSelfVersion() {
|
|
79785
79785
|
if (cached !== void 0) return cached;
|
|
79786
|
-
if ("0.3.
|
|
79787
|
-
cached = "0.3.
|
|
79786
|
+
if ("0.3.9".length > 0) {
|
|
79787
|
+
cached = "0.3.9";
|
|
79788
79788
|
return cached;
|
|
79789
79789
|
}
|
|
79790
79790
|
cached = "unknown";
|
|
@@ -864,8 +864,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
864
864
|
var cached;
|
|
865
865
|
function substrateSelfVersion() {
|
|
866
866
|
if (cached !== void 0) return cached;
|
|
867
|
-
if ("0.3.
|
|
868
|
-
cached = "0.3.
|
|
867
|
+
if ("0.3.9".length > 0) {
|
|
868
|
+
cached = "0.3.9";
|
|
869
869
|
return cached;
|
|
870
870
|
}
|
|
871
871
|
cached = "unknown";
|
|
@@ -88240,8 +88240,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
88240
88240
|
var cached;
|
|
88241
88241
|
function substrateSelfVersion() {
|
|
88242
88242
|
if (cached !== void 0) return cached;
|
|
88243
|
-
if ("0.3.
|
|
88244
|
-
cached = "0.3.
|
|
88243
|
+
if ("0.3.9".length > 0) {
|
|
88244
|
+
cached = "0.3.9";
|
|
88245
88245
|
return cached;
|
|
88246
88246
|
}
|
|
88247
88247
|
cached = "unknown";
|
|
@@ -9393,8 +9393,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
9393
9393
|
var cached;
|
|
9394
9394
|
function substrateSelfVersion() {
|
|
9395
9395
|
if (cached !== void 0) return cached;
|
|
9396
|
-
if ("0.3.
|
|
9397
|
-
cached = "0.3.
|
|
9396
|
+
if ("0.3.9".length > 0) {
|
|
9397
|
+
cached = "0.3.9";
|
|
9398
9398
|
return cached;
|
|
9399
9399
|
}
|
|
9400
9400
|
cached = "unknown";
|
|
@@ -88825,8 +88825,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
88825
88825
|
var cached;
|
|
88826
88826
|
function substrateSelfVersion() {
|
|
88827
88827
|
if (cached !== void 0) return cached;
|
|
88828
|
-
if ("0.3.
|
|
88829
|
-
cached = "0.3.
|
|
88828
|
+
if ("0.3.9".length > 0) {
|
|
88829
|
+
cached = "0.3.9";
|
|
88830
88830
|
return cached;
|
|
88831
88831
|
}
|
|
88832
88832
|
cached = "unknown";
|
|
@@ -9978,8 +9978,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
9978
9978
|
var cached;
|
|
9979
9979
|
function substrateSelfVersion() {
|
|
9980
9980
|
if (cached !== void 0) return cached;
|
|
9981
|
-
if ("0.3.
|
|
9982
|
-
cached = "0.3.
|
|
9981
|
+
if ("0.3.9".length > 0) {
|
|
9982
|
+
cached = "0.3.9";
|
|
9983
9983
|
return cached;
|
|
9984
9984
|
}
|
|
9985
9985
|
cached = "unknown";
|
|
@@ -79288,8 +79288,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
79288
79288
|
var cached;
|
|
79289
79289
|
function substrateSelfVersion() {
|
|
79290
79290
|
if (cached !== void 0) return cached;
|
|
79291
|
-
if ("0.3.
|
|
79292
|
-
cached = "0.3.
|
|
79291
|
+
if ("0.3.9".length > 0) {
|
|
79292
|
+
cached = "0.3.9";
|
|
79293
79293
|
return cached;
|
|
79294
79294
|
}
|
|
79295
79295
|
cached = "unknown";
|
|
@@ -389,8 +389,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
389
389
|
var cached;
|
|
390
390
|
function substrateSelfVersion() {
|
|
391
391
|
if (cached !== void 0) return cached;
|
|
392
|
-
if ("0.3.
|
|
393
|
-
cached = "0.3.
|
|
392
|
+
if ("0.3.9".length > 0) {
|
|
393
|
+
cached = "0.3.9";
|
|
394
394
|
return cached;
|
|
395
395
|
}
|
|
396
396
|
cached = "unknown";
|
|
@@ -1667,8 +1667,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
1667
1667
|
var cached;
|
|
1668
1668
|
function substrateSelfVersion() {
|
|
1669
1669
|
if (cached !== void 0) return cached;
|
|
1670
|
-
if ("0.3.
|
|
1671
|
-
cached = "0.3.
|
|
1670
|
+
if ("0.3.9".length > 0) {
|
|
1671
|
+
cached = "0.3.9";
|
|
1672
1672
|
return cached;
|
|
1673
1673
|
}
|
|
1674
1674
|
cached = "unknown";
|
|
@@ -1630,8 +1630,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
1630
1630
|
var cached;
|
|
1631
1631
|
function substrateSelfVersion() {
|
|
1632
1632
|
if (cached !== void 0) return cached;
|
|
1633
|
-
if ("0.3.
|
|
1634
|
-
cached = "0.3.
|
|
1633
|
+
if ("0.3.9".length > 0) {
|
|
1634
|
+
cached = "0.3.9";
|
|
1635
1635
|
return cached;
|
|
1636
1636
|
}
|
|
1637
1637
|
cached = "unknown";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@databricks-solutions/lakebase-scm-utils",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.9",
|
|
4
4
|
"description": "Lakebase SCM + substrate engine: database branching, paired-branch SCM workflows, connection/credential minting, schema migration, project scaffold + deploy primitives, and the git/github utility layer. Extracted from consort for portable, standalone consumption by lakebase-scm-extension and the SFTDD orchestration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
// the client's bundler-resolution tsconfig. Importing these from here keeps the client Playwright-free.
|
|
10
10
|
//
|
|
11
11
|
// Pure observability: everything is wrapped so a write can NEVER fail a test/suite.
|
|
12
|
-
import { appendFileSync, existsSync, mkdirSync } from "node:fs";
|
|
13
|
-
import { dirname, join } from "node:path";
|
|
12
|
+
import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync } from "node:fs";
|
|
13
|
+
import { basename, dirname, join } from "node:path";
|
|
14
14
|
|
|
15
15
|
/** Walk up from `start` to the project's `.consort` dir (tests run from root OR client/, so cwd
|
|
16
16
|
* varies); fall back to `<cwd>/.consort` (created on write) if none is found. */
|
|
@@ -33,15 +33,133 @@ export function emitTestRun(id: string, status: TestRunStatus, absFile: string,
|
|
|
33
33
|
mkdirSync(dir, { recursive: true });
|
|
34
34
|
const root = dirname(dir);
|
|
35
35
|
const file = (absFile || "").replace(`${root}/`, "");
|
|
36
|
+
const ts = new Date().toISOString();
|
|
36
37
|
appendFileSync(
|
|
37
38
|
join(dir, "test-run.jsonl"),
|
|
38
|
-
JSON.stringify({ ts
|
|
39
|
+
JSON.stringify({ ts, test_id: id, status, file, kind }) + "\n",
|
|
39
40
|
);
|
|
41
|
+
// RECORD a durable per-test `test.result` into the agent-log AT RUN TIME (not a cycle-end batch),
|
|
42
|
+
// so the dashboard lights each bubble the moment its test runs (live), scrubbing lights each test
|
|
43
|
+
// AT the point it actually ran (the event sits at its real position in the stream), and a REPLAY of
|
|
44
|
+
// the recording does the same — the per-test trail is part of the durable record, not a live-only
|
|
45
|
+
// overlay. Attributed to its story via the on-disk test-lists (the agent-log fold keys by
|
|
46
|
+
// feature|story|test). Best-effort: a reporter write must never fail the suite.
|
|
47
|
+
recordTestResultEvent(dir, id, status, file, ts);
|
|
40
48
|
} catch {
|
|
41
49
|
/* best-effort: a per-test reporter write must never fail the suite */
|
|
42
50
|
}
|
|
43
51
|
}
|
|
44
52
|
|
|
53
|
+
/** Owner of a test id, resolved from the project's per-story test-lists (the attribution the reporter
|
|
54
|
+
* lacks — it only sees id + file). Cached per process (a reporter process is one suite run). */
|
|
55
|
+
interface TestOwner { feature_id: string; story_id: string; ac_id: string; scenario_file?: string }
|
|
56
|
+
const ownerIndexCache = new Map<string, Map<string, TestOwner[]>>();
|
|
57
|
+
|
|
58
|
+
function buildOwnerIndex(consortDir: string): Map<string, TestOwner[]> {
|
|
59
|
+
const cached = ownerIndexCache.get(consortDir);
|
|
60
|
+
if (cached) return cached;
|
|
61
|
+
const index = new Map<string, TestOwner[]>();
|
|
62
|
+
try {
|
|
63
|
+
const featuresDir = join(consortDir, "features");
|
|
64
|
+
for (const f of readdirSync(featuresDir)) {
|
|
65
|
+
const storiesDir = join(featuresDir, f, "stories");
|
|
66
|
+
let stories: string[];
|
|
67
|
+
try { stories = readdirSync(storiesDir); } catch { continue; }
|
|
68
|
+
for (const s of stories) {
|
|
69
|
+
const listFile = join(storiesDir, s, "test-list-per-story.json");
|
|
70
|
+
if (!existsSync(listFile)) continue;
|
|
71
|
+
let list: { feature_id?: string; story_id?: string; items?: unknown[] };
|
|
72
|
+
try { list = JSON.parse(readFileSync(listFile, "utf8")); } catch { continue; }
|
|
73
|
+
const featureId = typeof list.feature_id === "string" ? list.feature_id : f;
|
|
74
|
+
const storyId = typeof list.story_id === "string" ? list.story_id : s;
|
|
75
|
+
for (const raw of list.items ?? []) {
|
|
76
|
+
const it = raw as { id?: unknown; ac_id?: unknown; scenario_file?: unknown };
|
|
77
|
+
if (typeof it.id !== "string") continue;
|
|
78
|
+
const owners = index.get(it.id) ?? [];
|
|
79
|
+
owners.push({
|
|
80
|
+
feature_id: featureId,
|
|
81
|
+
story_id: storyId,
|
|
82
|
+
ac_id: typeof it.ac_id === "string" ? it.ac_id : "unknown",
|
|
83
|
+
...(typeof it.scenario_file === "string" ? { scenario_file: it.scenario_file } : {}),
|
|
84
|
+
});
|
|
85
|
+
index.set(it.id, owners);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
/* no test-lists (pre-design project) — leave the index empty; the kit's cycle-scoped emit covers it */
|
|
91
|
+
}
|
|
92
|
+
ownerIndexCache.set(consortDir, index);
|
|
93
|
+
return index;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** The feature currently on the build lane, from `.consort/next.json` — the disambiguator of last
|
|
97
|
+
* resort when a reused T-id's file does not pin a single owner (e.g. a fitness/behavior test whose
|
|
98
|
+
* test-list item carries no scenario_file). Null when unreadable. */
|
|
99
|
+
function activeFeature(consortDir: string): string | null {
|
|
100
|
+
try {
|
|
101
|
+
const next = JSON.parse(readFileSync(join(consortDir, "next.json"), "utf8")) as { feature?: unknown };
|
|
102
|
+
return typeof next.feature === "string" ? next.feature : null;
|
|
103
|
+
} catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Resolve a (test id, file) to its single owning story. One owner -> it. Several (a T-id reused across
|
|
109
|
+
* features — ids are unique only WITHIN a story) -> (1) the owner whose scenario_file basename matches
|
|
110
|
+
* the running file (a client/e2e spec pins ITSELF, so a sibling feature's same-id spec resolves to its
|
|
111
|
+
* own story); then (2) the owner in the feature currently on the build lane (next.json) — a
|
|
112
|
+
* fitness/behavior test carries no scenario_file, so during THIS feature's build its reused id belongs
|
|
113
|
+
* to the active feature's story, while another feature's same-id test already resolved via (1). Else
|
|
114
|
+
* null (skip, never mis-attribute). */
|
|
115
|
+
function resolveOwner(consortDir: string, id: string, file: string): TestOwner | null {
|
|
116
|
+
const base = /^(T\d+)/.exec(id)?.[1] ?? id;
|
|
117
|
+
const index = buildOwnerIndex(consortDir);
|
|
118
|
+
const owners = index.get(id) ?? index.get(base) ?? [];
|
|
119
|
+
if (owners.length === 0) return null;
|
|
120
|
+
if (owners.length === 1) return owners[0];
|
|
121
|
+
// (1) The owner whose scenario_file matches the running file (unique match).
|
|
122
|
+
const fileBase = basename(file || "");
|
|
123
|
+
if (fileBase) {
|
|
124
|
+
const byFile = owners.filter((o) => o.scenario_file && basename(o.scenario_file) === fileBase);
|
|
125
|
+
if (byFile.length === 1) return byFile[0];
|
|
126
|
+
}
|
|
127
|
+
// (2) Fall back to the owner in the currently-building feature (one match only).
|
|
128
|
+
const active = activeFeature(consortDir);
|
|
129
|
+
if (active) {
|
|
130
|
+
const byFeature = owners.filter((o) => o.feature_id === active);
|
|
131
|
+
if (byFeature.length === 1) return byFeature[0];
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Append a durable, schema-valid `test.result` to the agent-log (+ the corpus mirror when a recording
|
|
137
|
+
* is in flight), shaped exactly like the kit's emitAgentLogEvent output so the dashboard fold reads it
|
|
138
|
+
* identically. Only emitted for an attributable test; best-effort. */
|
|
139
|
+
export function recordTestResultEvent(consortDir: string, id: string, status: TestRunStatus, file: string, ts: string): void {
|
|
140
|
+
try {
|
|
141
|
+
const owner = resolveOwner(consortDir, id, file);
|
|
142
|
+
if (!owner) return; // cannot attribute (ambiguous reused id) — skip rather than mis-key a bubble
|
|
143
|
+
const line =
|
|
144
|
+
JSON.stringify({
|
|
145
|
+
timestamp: ts,
|
|
146
|
+
level: "info",
|
|
147
|
+
role: "driver",
|
|
148
|
+
event: "test.result",
|
|
149
|
+
message: `TEST ${id} ${status}`,
|
|
150
|
+
metadata: { feature_id: owner.feature_id, test_id: id, status, story_id: owner.story_id, ac_id: owner.ac_id },
|
|
151
|
+
}) + "\n";
|
|
152
|
+
appendFileSync(join(consortDir, "agent-log.jsonl"), line);
|
|
153
|
+
// Mirror into the corpus so a RECORDED run replays the per-test trail (same env the kit uses).
|
|
154
|
+
const recordDir = process.env.LAKEBASE_CONSORT_RECORD_DIR?.trim();
|
|
155
|
+
if (recordDir) {
|
|
156
|
+
try { mkdirSync(recordDir, { recursive: true }); appendFileSync(join(recordDir, "agent-log.jsonl"), line); } catch { /* mirror is best-effort */ }
|
|
157
|
+
}
|
|
158
|
+
} catch {
|
|
159
|
+
/* best-effort: recording a per-test result must never fail the suite */
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
45
163
|
/** Append a per-test TABLE snapshot (one line per table) to `.consort/table-snapshots.jsonl`, keyed
|
|
46
164
|
* by test id — parallel to `emitTestRun`. Best-effort. */
|
|
47
165
|
export function emitTableSnapshot(id: string, absFile: string, table: string, rows: unknown[], rowCount: number | null): void {
|
|
@@ -25,6 +25,7 @@ import os
|
|
|
25
25
|
import subprocess
|
|
26
26
|
import tempfile
|
|
27
27
|
import time
|
|
28
|
+
import urllib.error
|
|
28
29
|
import urllib.request
|
|
29
30
|
|
|
30
31
|
import pytest
|
|
@@ -41,7 +42,10 @@ def _is_up(url: str) -> bool:
|
|
|
41
42
|
return resp.status < 500
|
|
42
43
|
except urllib.error.HTTPError as exc: # noqa: PERF203 - a 4xx still means it's up
|
|
43
44
|
return exc.code < 500
|
|
44
|
-
except
|
|
45
|
+
except (urllib.error.URLError, OSError):
|
|
46
|
+
# NOT a broad-integrity swallow: a readiness poll returns False on the server
|
|
47
|
+
# not-yet-listening (connection refused / DNS / socket timeout — all URLError/OSError),
|
|
48
|
+
# never masks a DB-integrity violation. Narrow so the smell gate does not false-positive.
|
|
45
49
|
return False
|
|
46
50
|
|
|
47
51
|
|
|
Binary file
|
|
@@ -34,7 +34,7 @@ def db_session():
|
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
@pytest.fixture(autouse=True)
|
|
37
|
-
def _restore_migration_head_after_each_test():
|
|
37
|
+
def _restore_migration_head_after_each_test(request):
|
|
38
38
|
"""Within-run test isolation for migration up/down fixtures.
|
|
39
39
|
|
|
40
40
|
Some tests drive Alembic up/down against the shared verify database (a
|
|
@@ -42,10 +42,18 @@ def _restore_migration_head_after_each_test():
|
|
|
42
42
|
`upgrade head`). Run sequentially against ONE database, those fixtures can
|
|
43
43
|
leave it half-migrated , or, if a downgrade overshoots, below a table a later
|
|
44
44
|
test needs , so the next test fails with `relation ... does not exist`. This
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
teardown brings the DB back to `head` after a schema-mutating test.
|
|
46
|
+
|
|
47
|
+
SCOPED to ``@pytest.mark.migration`` tests only (perf): ONLY a migration test
|
|
48
|
+
downgrades/alters the schema , and the ``migration-marker-presence`` smell
|
|
49
|
+
GUARANTEES every schema-mutating test carries that marker , so a NON-migration
|
|
50
|
+
test cannot have changed the schema and needs no restore. Restoring after every
|
|
51
|
+
test ran a full alembic env-load + DB round-trip (~1-2s) after each of the many
|
|
52
|
+
non-migration tests to 'restore' a schema they never touched , the dominant cost
|
|
53
|
+
of the non-migration pass. Skipping it there is safe by construction."""
|
|
48
54
|
yield
|
|
55
|
+
if request.node.get_closest_marker("migration") is None:
|
|
56
|
+
return # a non-migration test cannot have altered the schema (marker smell enforces it)
|
|
49
57
|
try:
|
|
50
58
|
from pathlib import Path
|
|
51
59
|
|
|
@@ -95,11 +103,100 @@ def _consort_emit_test_run(test_id, status, item):
|
|
|
95
103
|
rel = str(Path(str(item.fspath))).replace(f"{root}/", "")
|
|
96
104
|
out = root / ".consort"
|
|
97
105
|
out.mkdir(parents=True, exist_ok=True)
|
|
106
|
+
ts = datetime.now(timezone.utc).isoformat()
|
|
98
107
|
line = json.dumps(
|
|
99
|
-
{"ts":
|
|
108
|
+
{"ts": ts, "test_id": test_id, "status": status, "file": rel, "kind": "behavior"}
|
|
100
109
|
)
|
|
101
110
|
with open(out / "test-run.jsonl", "a", encoding="utf-8") as fh:
|
|
102
111
|
fh.write(line + "\n")
|
|
112
|
+
# RECORD a durable per-test test.result into the agent-log AT RUN TIME (mirrors the TS reporter
|
|
113
|
+
# in consort-capture.ts) so the dashboard lights each behavior test the moment it runs, scrubbing
|
|
114
|
+
# lights it at its real run position, and a replay of the recording does the same.
|
|
115
|
+
_consort_record_test_result(out, test_id, status, rel, ts)
|
|
116
|
+
except (OSError, ValueError, TypeError):
|
|
117
|
+
pass
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _consort_test_owner(consort_dir, test_id, file_rel):
|
|
121
|
+
"""Resolve (feature_id, story_id, ac_id) for a test id from the per-story test-lists — the
|
|
122
|
+
attribution the reporter lacks (it sees only id + file). Ids are unique only WITHIN a story, so a
|
|
123
|
+
reused id has several owners: (1) the owner whose scenario_file basename matches ``file_rel`` (a
|
|
124
|
+
client/e2e spec pins ITSELF); then (2) the owner in the feature currently on the build lane
|
|
125
|
+
(``next.json``) — a fitness/behavior test carries no scenario_file, so during THIS feature's build
|
|
126
|
+
its reused id belongs to the active feature (a sibling feature's same-id test already resolved via
|
|
127
|
+
(1)). Else None (skip, never mis-key). Mirrors consort-capture.ts resolveOwner. Scoped, best-effort."""
|
|
128
|
+
try:
|
|
129
|
+
import glob
|
|
130
|
+
import json
|
|
131
|
+
import os
|
|
132
|
+
import re
|
|
133
|
+
|
|
134
|
+
m = re.match(r"^(T\d+)", test_id or "")
|
|
135
|
+
base = m.group(1) if m else test_id
|
|
136
|
+
owners = [] # (feature, story, ac, scenario_file)
|
|
137
|
+
for p in glob.glob(os.path.join(str(consort_dir), "features", "*", "stories", "*", "test-list-per-story.json")):
|
|
138
|
+
try:
|
|
139
|
+
with open(p, encoding="utf-8") as fh:
|
|
140
|
+
o = json.load(fh)
|
|
141
|
+
except (OSError, ValueError):
|
|
142
|
+
continue
|
|
143
|
+
feat, story = o.get("feature_id"), o.get("story_id")
|
|
144
|
+
for it in o.get("items", []):
|
|
145
|
+
if it.get("id") in (test_id, base):
|
|
146
|
+
owners.append((feat, story, it.get("ac_id") or "unknown", it.get("scenario_file")))
|
|
147
|
+
if not owners:
|
|
148
|
+
return None
|
|
149
|
+
if len(owners) == 1:
|
|
150
|
+
return owners[0][:3]
|
|
151
|
+
# (1) the owner whose scenario_file matches the running file (unique match)
|
|
152
|
+
fb = os.path.basename(file_rel or "")
|
|
153
|
+
by_file = [o for o in owners if o[3] and os.path.basename(o[3]) == fb]
|
|
154
|
+
if len(by_file) == 1:
|
|
155
|
+
return by_file[0][:3]
|
|
156
|
+
# (2) fall back to the owner in the currently-building feature (one match only)
|
|
157
|
+
try:
|
|
158
|
+
with open(os.path.join(str(consort_dir), "next.json"), encoding="utf-8") as fh:
|
|
159
|
+
active = json.load(fh).get("feature")
|
|
160
|
+
except (OSError, ValueError):
|
|
161
|
+
active = None
|
|
162
|
+
if active:
|
|
163
|
+
by_feat = [o for o in owners if o[0] == active]
|
|
164
|
+
if len(by_feat) == 1:
|
|
165
|
+
return by_feat[0][:3]
|
|
166
|
+
return None
|
|
167
|
+
except (OSError, ValueError, TypeError):
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _consort_record_test_result(out, test_id, status, file_rel, ts):
|
|
172
|
+
"""Append a schema-shaped ``test.result`` to ``.consort/agent-log.jsonl`` (+ the corpus mirror when
|
|
173
|
+
a recording is in flight), identical in shape to the kit's emitAgentLogEvent output so the dashboard
|
|
174
|
+
fold reads it the same. Only for an attributable test; scoped except, never breaks the suite."""
|
|
175
|
+
try:
|
|
176
|
+
import json
|
|
177
|
+
import os
|
|
178
|
+
|
|
179
|
+
owner = _consort_test_owner(out, test_id, file_rel)
|
|
180
|
+
if not owner:
|
|
181
|
+
return
|
|
182
|
+
feat, story, ac = owner
|
|
183
|
+
line = json.dumps(
|
|
184
|
+
{
|
|
185
|
+
"timestamp": ts,
|
|
186
|
+
"level": "info",
|
|
187
|
+
"role": "driver",
|
|
188
|
+
"event": "test.result",
|
|
189
|
+
"message": f"TEST {test_id} {status}",
|
|
190
|
+
"metadata": {"feature_id": feat, "test_id": test_id, "status": status, "story_id": story, "ac_id": ac},
|
|
191
|
+
}
|
|
192
|
+
)
|
|
193
|
+
with open(os.path.join(str(out), "agent-log.jsonl"), "a", encoding="utf-8") as fh:
|
|
194
|
+
fh.write(line + "\n")
|
|
195
|
+
rec = (os.environ.get("LAKEBASE_CONSORT_RECORD_DIR") or "").strip()
|
|
196
|
+
if rec:
|
|
197
|
+
os.makedirs(rec, exist_ok=True)
|
|
198
|
+
with open(os.path.join(rec, "agent-log.jsonl"), "a", encoding="utf-8") as fh:
|
|
199
|
+
fh.write(line + "\n")
|
|
103
200
|
except (OSError, ValueError, TypeError):
|
|
104
201
|
pass
|
|
105
202
|
|
|
@@ -162,6 +259,32 @@ def _consort_capture_tables(test_id, item):
|
|
|
162
259
|
pass
|
|
163
260
|
|
|
164
261
|
|
|
262
|
+
def pytest_collection_modifyitems(config, items):
|
|
263
|
+
"""Build-lane SCOPE (per-cycle waste elimination): when ``SFTDD_TEST_IDS`` is set (a space/comma
|
|
264
|
+
list of test ids), run ONLY the tests whose ``@pytest.mark.testid`` is in that set and DESELECT the
|
|
265
|
+
rest. The build's per-cycle GREEN verify sets it to the CURRENT STORY's ids, so a cycle re-runs
|
|
266
|
+
only its own story instead of every feature's suite (intra-story regressions still caught); the
|
|
267
|
+
story-close REFACTOR + feature-deploy gates leave it UNSET and run the FULL cross-story suite (that
|
|
268
|
+
is where a cross-story regression is caught, before merge). UNTAGGED items (no testid — shared
|
|
269
|
+
fixtures / infra) are always KEPT, never deselected. Unset/empty => no-op (full collection)."""
|
|
270
|
+
import os
|
|
271
|
+
import re
|
|
272
|
+
|
|
273
|
+
raw = (os.environ.get("SFTDD_TEST_IDS") or "").strip()
|
|
274
|
+
if not raw:
|
|
275
|
+
return
|
|
276
|
+
wanted = {t for t in re.split(r"[,\s]+", raw) if t}
|
|
277
|
+
if not wanted:
|
|
278
|
+
return
|
|
279
|
+
selected, deselected = [], []
|
|
280
|
+
for item in items:
|
|
281
|
+
tid = _consort_testid(item)
|
|
282
|
+
(selected if (tid is None or tid in wanted) else deselected).append(item)
|
|
283
|
+
if deselected:
|
|
284
|
+
config.hook.pytest_deselected(items=deselected)
|
|
285
|
+
items[:] = selected
|
|
286
|
+
|
|
287
|
+
|
|
165
288
|
def pytest_runtest_setup(item):
|
|
166
289
|
"""Emit ``running`` the moment a tagged test starts, so the dashboard shows it in-flight."""
|
|
167
290
|
test_id = _consort_testid(item)
|