@payload-auth-link/migrate 0.4.3 → 0.6.0
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/LICENSE +21 -0
- package/README.md +7 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +109 -19
- package/dist/cli.js.map +1 -1
- package/dist/doctor/database.d.ts.map +1 -1
- package/dist/doctor/database.js +31 -4
- package/dist/doctor/database.js.map +1 -1
- package/dist/doctor/run-doctor.d.ts.map +1 -1
- package/dist/doctor/run-doctor.js +67 -0
- package/dist/doctor/run-doctor.js.map +1 -1
- package/dist/doctor/types.d.ts +12 -0
- package/dist/doctor/types.d.ts.map +1 -1
- package/dist/doctor/users.d.ts.map +1 -1
- package/dist/doctor/users.js +20 -4
- package/dist/doctor/users.js.map +1 -1
- package/dist/importer/apply.d.ts +25 -5
- package/dist/importer/apply.d.ts.map +1 -1
- package/dist/importer/apply.js +164 -180
- package/dist/importer/apply.js.map +1 -1
- package/dist/importer/host-writes.d.ts +47 -0
- package/dist/importer/host-writes.d.ts.map +1 -0
- package/dist/importer/host-writes.js +198 -0
- package/dist/importer/host-writes.js.map +1 -0
- package/dist/importer/lifecycle.d.ts +26 -0
- package/dist/importer/lifecycle.d.ts.map +1 -1
- package/dist/importer/lifecycle.js +107 -22
- package/dist/importer/lifecycle.js.map +1 -1
- package/dist/importer/manifest-mac.d.ts +15 -0
- package/dist/importer/manifest-mac.d.ts.map +1 -0
- package/dist/importer/manifest-mac.js +73 -0
- package/dist/importer/manifest-mac.js.map +1 -0
- package/dist/importer/prelink.d.ts +57 -0
- package/dist/importer/prelink.d.ts.map +1 -0
- package/dist/importer/prelink.js +159 -0
- package/dist/importer/prelink.js.map +1 -0
- package/dist/importer/run-import.d.ts +15 -0
- package/dist/importer/run-import.d.ts.map +1 -1
- package/dist/importer/run-import.js +155 -14
- package/dist/importer/run-import.js.map +1 -1
- package/dist/importer/source.d.ts +35 -7
- package/dist/importer/source.d.ts.map +1 -1
- package/dist/importer/source.js +37 -6
- package/dist/importer/source.js.map +1 -1
- package/dist/importer/types.d.ts +34 -4
- package/dist/importer/types.d.ts.map +1 -1
- package/dist/importer/types.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/private-file.d.ts +8 -0
- package/dist/private-file.d.ts.map +1 -0
- package/dist/private-file.js +19 -0
- package/dist/private-file.js.map +1 -0
- package/package.json +10 -10
- package/src/cli.ts +117 -9
- package/src/doctor/database.ts +36 -4
- package/src/doctor/run-doctor.ts +69 -0
- package/src/doctor/types.ts +12 -0
- package/src/doctor/users.ts +23 -4
- package/src/importer/apply.ts +198 -182
- package/src/importer/host-writes.ts +231 -0
- package/src/importer/lifecycle.ts +131 -24
- package/src/importer/manifest-mac.ts +90 -0
- package/src/importer/prelink.ts +237 -0
- package/src/importer/run-import.ts +207 -29
- package/src/importer/source.ts +72 -14
- package/src/importer/types.ts +35 -4
- package/src/index.ts +13 -1
- package/src/private-file.ts +18 -0
package/dist/index.d.ts
CHANGED
|
@@ -10,11 +10,12 @@ export { classifyPlugins, pluginsFromSourceTree } from './doctor/plugins.js';
|
|
|
10
10
|
export { scanSource, projectedUserFields, listSourceFiles } from './doctor/source-scan.js';
|
|
11
11
|
export { analyzeUsersCollection, findUsersCollection, localStrategyOn } from './doctor/users.js';
|
|
12
12
|
export { introspectDatabase } from './doctor/database.js';
|
|
13
|
-
export { runFromPayloadAuth, runVerify, runRollback, runFinalize } from './importer/run-import.js';
|
|
13
|
+
export { runFromPayloadAuth, runVerify, runRollback, runFinalize, runAdoptPrelink, type ImportCommandOptions, type PrelinkCommandOptions, } from './importer/run-import.js';
|
|
14
|
+
export { prelinkUsers, type PrelinkOptions, type PrelinkResult } from './importer/prelink.js';
|
|
14
15
|
export { applyImport, summarizePlanRows } from './importer/apply.js';
|
|
15
|
-
export { countTables, findOrphans, openSource, probeSourceLayout, readFlatRows, readSourceUsers, unverifiedEmails, type OrphanScan, type SourceLayoutIssue, type SourceAccount, type SourceRow, type SourceUser, } from './importer/source.js';
|
|
16
|
-
export { readManifest, writeManifest, verifyImport, rollbackImport, finalizeManifest, defaultManifestPath, type VerifyIssue, type VerifyResult, } from './importer/lifecycle.js';
|
|
17
|
-
export { defaultSourceTables, defaultTargetSlugs, parseSourceMapJson, presentTables, resolveSourceTables, sourceModelNames, type CreatedRow, type ResolvedSourceTables, type SourceModel, type SourceTableMap, type TargetModel, type TargetSlugMap, type FieldWrite, type ImportManifest, type ImportPlan, type ImportResult, type PlanRow, type Unmappable, } from './importer/types.js';
|
|
16
|
+
export { countTables, findOrphans, openSource, probeSourceLayout, readFlatRows, readSourceUsers, unverifiedEmails, type OrphanScan, type SourceClient, type SourceLayoutIssue, type SourceAccount, type SourceRow, type SourceUser, } from './importer/source.js';
|
|
17
|
+
export { readManifest, writeManifest, verifyImport, rollbackImport, finalizeManifest, defaultManifestPath, defaultPrelinkManifestPath, type VerifyIssue, type VerifyResult, } from './importer/lifecycle.js';
|
|
18
|
+
export { defaultSourceTables, defaultTargetSlugs, parseSourceMapJson, presentTables, resolveSourceTables, sourceModelNames, type CreatedRow, type ResolvedSourceTables, type SourceModel, type SourceTableMap, type TargetModel, type TargetSlugMap, type FieldWrite, type ImportManifest, type ManifestKind, type ImportPlan, type ImportResult, type PlanRow, type Unmappable, } from './importer/types.js';
|
|
18
19
|
export { exportToJsonl, parseExportTables, exportTables } from './exporters.js';
|
|
19
20
|
export { cliMain } from './cli.js';
|
|
20
21
|
export type { BehaviourChange, DatabaseFinding, DoctorIssue, DoctorReport, PluginFinding, SourceFinding, UsersFinding, VersionFinding, } from './doctor/types.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,SAAS,EACT,gBAAgB,EAChB,qBAAqB,EACrB,mBAAmB,EACnB,WAAW,GACZ,MAAM,wBAAwB,CAAA;AAC/B,YAAY,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACxF,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAC5E,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAC1F,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAChG,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AACzD,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,SAAS,EACT,gBAAgB,EAChB,qBAAqB,EACrB,mBAAmB,EACnB,WAAW,GACZ,MAAM,wBAAwB,CAAA;AAC/B,YAAY,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACxF,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAC5E,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAC1F,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAChG,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AACzD,OAAO,EACL,kBAAkB,EAClB,SAAS,EACT,WAAW,EACX,WAAW,EACX,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAE,YAAY,EAAE,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAC7F,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AACpE,OAAO,EACL,WAAW,EACX,WAAW,EACX,UAAU,EACV,iBAAiB,EACjB,YAAY,EACZ,eAAe,EACf,gBAAgB,EAChB,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,KAAK,UAAU,GAChB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EACL,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,mBAAmB,EACnB,0BAA0B,EAC1B,KAAK,WAAW,EAChB,KAAK,YAAY,GAClB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,kBAAkB,EAClB,aAAa,EACb,mBAAmB,EACnB,gBAAgB,EAChB,KAAK,UAAU,EACf,KAAK,oBAAoB,EACzB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,OAAO,EACZ,KAAK,UAAU,GAChB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC/E,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAA;AAClC,YAAY,EACV,eAAe,EACf,eAAe,EACf,WAAW,EACX,YAAY,EACZ,aAAa,EACb,aAAa,EACb,YAAY,EACZ,cAAc,GACf,MAAM,mBAAmB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -9,10 +9,11 @@ export { classifyPlugins, pluginsFromSourceTree } from './doctor/plugins.js';
|
|
|
9
9
|
export { scanSource, projectedUserFields, listSourceFiles } from './doctor/source-scan.js';
|
|
10
10
|
export { analyzeUsersCollection, findUsersCollection, localStrategyOn } from './doctor/users.js';
|
|
11
11
|
export { introspectDatabase } from './doctor/database.js';
|
|
12
|
-
export { runFromPayloadAuth, runVerify, runRollback, runFinalize } from './importer/run-import.js';
|
|
12
|
+
export { runFromPayloadAuth, runVerify, runRollback, runFinalize, runAdoptPrelink, } from './importer/run-import.js';
|
|
13
|
+
export { prelinkUsers } from './importer/prelink.js';
|
|
13
14
|
export { applyImport, summarizePlanRows } from './importer/apply.js';
|
|
14
15
|
export { countTables, findOrphans, openSource, probeSourceLayout, readFlatRows, readSourceUsers, unverifiedEmails, } from './importer/source.js';
|
|
15
|
-
export { readManifest, writeManifest, verifyImport, rollbackImport, finalizeManifest, defaultManifestPath, } from './importer/lifecycle.js';
|
|
16
|
+
export { readManifest, writeManifest, verifyImport, rollbackImport, finalizeManifest, defaultManifestPath, defaultPrelinkManifestPath, } from './importer/lifecycle.js';
|
|
16
17
|
export { defaultSourceTables, defaultTargetSlugs, parseSourceMapJson, presentTables, resolveSourceTables, sourceModelNames, } from './importer/types.js';
|
|
17
18
|
export { exportToJsonl, parseExportTables, exportTables } from './exporters.js';
|
|
18
19
|
export { cliMain } from './cli.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,SAAS,EACT,gBAAgB,EAChB,qBAAqB,EACrB,mBAAmB,EACnB,WAAW,GACZ,MAAM,wBAAwB,CAAA;AAE/B,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACxF,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAC5E,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAC1F,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAChG,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AACzD,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,SAAS,EACT,gBAAgB,EAChB,qBAAqB,EACrB,mBAAmB,EACnB,WAAW,GACZ,MAAM,wBAAwB,CAAA;AAE/B,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACxF,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAC5E,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAC1F,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAChG,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AACzD,OAAO,EACL,kBAAkB,EAClB,SAAS,EACT,WAAW,EACX,WAAW,EACX,eAAe,GAGhB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAE,YAAY,EAA2C,MAAM,uBAAuB,CAAA;AAC7F,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AACpE,OAAO,EACL,WAAW,EACX,WAAW,EACX,UAAU,EACV,iBAAiB,EACjB,YAAY,EACZ,eAAe,EACf,gBAAgB,GAOjB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EACL,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,mBAAmB,EACnB,0BAA0B,GAG3B,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,kBAAkB,EAClB,aAAa,EACb,mBAAmB,EACnB,gBAAgB,GAcjB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC/E,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAA"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Write `content` to `path` readable by the owner only (0600). The
|
|
3
|
+
* mode is tightened on the open descriptor BEFORE any content lands, so
|
|
4
|
+
* a pre-existing world-readable file (an older kit's manifest, a
|
|
5
|
+
* reused `--out` path) never holds the new content with its old mode.
|
|
6
|
+
*/
|
|
7
|
+
export declare function writePrivateFile(path: string, content: string): void;
|
|
8
|
+
//# sourceMappingURL=private-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"private-file.d.ts","sourceRoot":"","sources":["../src/private-file.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CASpE"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { closeSync, fchmodSync, ftruncateSync, openSync, writeSync } from 'node:fs';
|
|
2
|
+
/**
|
|
3
|
+
* Write `content` to `path` readable by the owner only (0600). The
|
|
4
|
+
* mode is tightened on the open descriptor BEFORE any content lands, so
|
|
5
|
+
* a pre-existing world-readable file (an older kit's manifest, a
|
|
6
|
+
* reused `--out` path) never holds the new content with its old mode.
|
|
7
|
+
*/
|
|
8
|
+
export function writePrivateFile(path, content) {
|
|
9
|
+
const fd = openSync(path, 'a', 0o600);
|
|
10
|
+
try {
|
|
11
|
+
fchmodSync(fd, 0o600);
|
|
12
|
+
ftruncateSync(fd, 0);
|
|
13
|
+
writeSync(fd, content, 0);
|
|
14
|
+
}
|
|
15
|
+
finally {
|
|
16
|
+
closeSync(fd);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=private-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"private-file.js","sourceRoot":"","sources":["../src/private-file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAEnF;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY,EAAE,OAAe;IAC5D,MAAM,EAAE,GAAG,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAA;IACrC,IAAI,CAAC;QACH,UAAU,CAAC,EAAE,EAAE,KAAK,CAAC,CAAA;QACrB,aAAa,CAAC,EAAE,EAAE,CAAC,CAAC,CAAA;QACpB,SAAS,CAAC,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,CAAA;IAC3B,CAAC;YAAS,CAAC;QACT,SAAS,CAAC,EAAE,CAAC,CAAA;IACf,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@payload-auth-link/migrate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "payload-auth-link migration kit: read-only doctor/preflight, the payload-auth importer, and exporters.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"clean": "rm -rf dist *.tsbuildinfo"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@payload-auth-link/core": "0.
|
|
44
|
-
"pg": "^8.
|
|
43
|
+
"@payload-auth-link/core": "0.8.0",
|
|
44
|
+
"pg": "^8.23.1",
|
|
45
45
|
"typescript": "^6.0.3"
|
|
46
46
|
},
|
|
47
47
|
"publishConfig": {
|
|
@@ -53,17 +53,17 @@
|
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@better-auth/api-key": "1.7.7",
|
|
56
|
-
"@payload-auth-link/core": "0.
|
|
57
|
-
"@payload-auth-link/plugin": "0.
|
|
58
|
-
"@payload-auth-link/provider-better-auth": "0.
|
|
59
|
-
"@payloadcms/db-postgres": "3.
|
|
56
|
+
"@payload-auth-link/core": "0.8.0",
|
|
57
|
+
"@payload-auth-link/plugin": "0.12.0",
|
|
58
|
+
"@payload-auth-link/provider-better-auth": "0.9.0",
|
|
59
|
+
"@payloadcms/db-postgres": "3.91.0",
|
|
60
60
|
"@types/node": "^22",
|
|
61
|
-
"@types/pg": "^8.
|
|
61
|
+
"@types/pg": "^8.23.1",
|
|
62
62
|
"better-auth": "1.7.7",
|
|
63
|
-
"payload": "3.
|
|
63
|
+
"payload": "3.91.0"
|
|
64
64
|
},
|
|
65
65
|
"peerDependencies": {
|
|
66
|
-
"@payload-auth-link/next": "^0.
|
|
66
|
+
"@payload-auth-link/next": "^0.5.0",
|
|
67
67
|
"payload": "^3.83.0 || 4.0.0-beta.0"
|
|
68
68
|
},
|
|
69
69
|
"peerDependenciesMeta": {
|
package/src/cli.ts
CHANGED
|
@@ -9,16 +9,19 @@
|
|
|
9
9
|
* the `--json` echo of what the caller passed, and secrets inside them
|
|
10
10
|
* are the caller's own input.
|
|
11
11
|
*/
|
|
12
|
+
import type * as Codemods from './codemods.js'
|
|
12
13
|
import { runDoctor } from './doctor/run-doctor.js'
|
|
13
14
|
import type { DoctorReport } from './doctor/types.js'
|
|
14
15
|
import {
|
|
16
|
+
runAdoptPrelink,
|
|
15
17
|
runFinalize,
|
|
16
18
|
runFromPayloadAuth,
|
|
17
19
|
runRollback,
|
|
18
20
|
runVerify,
|
|
19
21
|
type ImportCommandOptions,
|
|
20
22
|
} from './importer/run-import.js'
|
|
21
|
-
import {
|
|
23
|
+
import { summarizePlanRows } from './importer/apply.js'
|
|
24
|
+
import { writePrivateFile } from './private-file.js'
|
|
22
25
|
import { readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'
|
|
23
26
|
import { extname, join, relative } from 'node:path'
|
|
24
27
|
|
|
@@ -72,7 +75,15 @@ Commands:
|
|
|
72
75
|
verify --config ./src/payload.config.ts [--database-url URL] [--json]
|
|
73
76
|
[--source-map '{"accounts":"user_accounts","sessions":"user_sessions"}']
|
|
74
77
|
rollback --config ./src/payload.config.ts [--manifest ...]
|
|
78
|
+
[--allow-unsigned-manifest] (a pre-0.6.0 manifest; also
|
|
79
|
+
accepted by from-payload-auth / adopt-prelink when resuming)
|
|
75
80
|
finalize [--manifest ...]
|
|
81
|
+
adopt-prelink --config ./src/payload.config.ts [--apply] [--json]
|
|
82
|
+
[--manifest ./payload-auth-link-prelink.json]
|
|
83
|
+
[--users-slug users] [--verified-field emailVerified]
|
|
84
|
+
(adopt mode: one better-auth identity per user, plus the
|
|
85
|
+
linking proof for verified emails; undo with
|
|
86
|
+
rollback --manifest ./payload-auth-link-prelink.json)
|
|
76
87
|
|
|
77
88
|
--source-map renames source models to your layout's tables (custom
|
|
78
89
|
slugs like user_accounts/user_sessions); a null value records a
|
|
@@ -93,6 +104,14 @@ Commands:
|
|
|
93
104
|
rollback undoes exactly the manifest; finalize marks it read-only
|
|
94
105
|
so the legacy tables may be dropped later, in their own migration.
|
|
95
106
|
|
|
107
|
+
The manifest lands in the CURRENT DIRECTORY unless --manifest says
|
|
108
|
+
otherwise. It holds ids and sealed values only (no emails in the
|
|
109
|
+
clear; sealed with the app's PAYLOAD_SECRET, so rollback must run
|
|
110
|
+
against the same app) and is written with mode 0600. It is signed
|
|
111
|
+
with the same secret: rollback and a resumed --apply refuse an
|
|
112
|
+
edited manifest. Delete it once
|
|
113
|
+
the migration is finalized and your soak window has passed.
|
|
114
|
+
|
|
96
115
|
codemod rewrites the three named payload-auth call-site patterns
|
|
97
116
|
(getPayloadAuth, payload.betterAuth.api.getSession, toNextJsHandler)
|
|
98
117
|
and reports what needs a human. Dry run by default; --write rewrites.
|
|
@@ -169,6 +188,17 @@ function formatReport(report: DoctorReport): string {
|
|
|
169
188
|
if (report.database.unverifiedUsers !== null) {
|
|
170
189
|
lines.push(` unverified users: ${report.database.unverifiedUsers}`)
|
|
171
190
|
}
|
|
191
|
+
if (report.database.bannedUsers !== undefined && report.database.bannedUsers !== null) {
|
|
192
|
+
lines.push(` banned users: ${report.database.bannedUsers}`)
|
|
193
|
+
}
|
|
194
|
+
if (report.database.anonymousUsers !== undefined && report.database.anonymousUsers !== null) {
|
|
195
|
+
lines.push(` anonymous users: ${report.database.anonymousUsers}`)
|
|
196
|
+
}
|
|
197
|
+
if ((report.database.oauthTables ?? []).length > 0) {
|
|
198
|
+
lines.push(
|
|
199
|
+
` oauth_* tables (NOT carried by the importer): ${(report.database.oauthTables ?? []).join(', ')}`
|
|
200
|
+
)
|
|
201
|
+
}
|
|
172
202
|
for (const orphan of report.database.orphans) {
|
|
173
203
|
lines.push(` ORPHANS ${orphan.kind}: ${orphan.count}`)
|
|
174
204
|
}
|
|
@@ -216,9 +246,18 @@ async function main(
|
|
|
216
246
|
'verify',
|
|
217
247
|
'rollback',
|
|
218
248
|
'finalize',
|
|
249
|
+
'adopt-prelink',
|
|
219
250
|
'codemod',
|
|
220
251
|
'export',
|
|
221
252
|
]
|
|
253
|
+
const json = flags.get('json') === true
|
|
254
|
+
// Every command prints (issue #88): progress and the summary go to
|
|
255
|
+
// stdout; --json replaces the human lines with one JSON document.
|
|
256
|
+
const log = (message: string): void => {
|
|
257
|
+
if (!json) {
|
|
258
|
+
out.write(`${message}\n`)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
222
261
|
if (command !== 'doctor' && !importCommands.includes(command)) {
|
|
223
262
|
err.write(`unknown command '${command}'\n\n${usage()}`)
|
|
224
263
|
return 3
|
|
@@ -263,7 +302,9 @@ async function main(
|
|
|
263
302
|
return 1
|
|
264
303
|
}
|
|
265
304
|
if (typeof flags.get('out') === 'string') {
|
|
266
|
-
|
|
305
|
+
// The export carries credential material (password hashes, OAuth
|
|
306
|
+
// tokens, sessions, API keys, JWKS private keys): owner-only.
|
|
307
|
+
writePrivateFile(flags.get('out') as string, result.jsonl)
|
|
267
308
|
out.write(`wrote ${result.totalRows} row(s) to ${String(flags.get('out'))}\n`)
|
|
268
309
|
} else {
|
|
269
310
|
out.write(result.jsonl)
|
|
@@ -280,6 +321,10 @@ async function main(
|
|
|
280
321
|
}
|
|
281
322
|
const srcDir = new URL(srcFlag, `file://${process.cwd()}/`).pathname
|
|
282
323
|
const write = flags.get('write') === true
|
|
324
|
+
// Lazy: the codemods parse with the TypeScript compiler API
|
|
325
|
+
// (~24 MB). Only this command loads it; importing the package
|
|
326
|
+
// (or running doctor/import/export) never does.
|
|
327
|
+
const codemods: typeof Codemods = await import('./codemods.js')
|
|
283
328
|
const changed: string[] = []
|
|
284
329
|
const manual: string[] = []
|
|
285
330
|
const walk = (dir: string): void => {
|
|
@@ -304,7 +349,7 @@ async function main(
|
|
|
304
349
|
walk(full)
|
|
305
350
|
} else if (['.ts', '.tsx', '.js', '.jsx', '.mts', '.mjs'].includes(extname(entry))) {
|
|
306
351
|
const before = readFileSync(full, 'utf8')
|
|
307
|
-
const result = applyCodemods(relative(srcDir, full), before)
|
|
352
|
+
const result = codemods.applyCodemods(relative(srcDir, full), before)
|
|
308
353
|
if (result.changed) {
|
|
309
354
|
if (write) {
|
|
310
355
|
writeFileSync(full, result.after)
|
|
@@ -331,11 +376,15 @@ async function main(
|
|
|
331
376
|
// finalize [--manifest ...]
|
|
332
377
|
// (no --config: manifest-only; no Payload instance is loaded)
|
|
333
378
|
if (command === 'finalize') {
|
|
334
|
-
await runFinalize(
|
|
335
|
-
|
|
379
|
+
const finalized = await runFinalize({
|
|
380
|
+
log,
|
|
381
|
+
...(typeof flags.get('manifest') === 'string'
|
|
336
382
|
? { manifestPath: flags.get('manifest') as string }
|
|
337
|
-
: {}
|
|
338
|
-
)
|
|
383
|
+
: {}),
|
|
384
|
+
})
|
|
385
|
+
if (json) {
|
|
386
|
+
out.write(`${JSON.stringify({ finalized: finalized.finalized })}\n`)
|
|
387
|
+
}
|
|
339
388
|
return 0
|
|
340
389
|
}
|
|
341
390
|
// The remaining commands (from-payload-auth, verify, rollback) all
|
|
@@ -345,6 +394,30 @@ async function main(
|
|
|
345
394
|
err.write(`${command} requires --config <path to payload.config.ts>\n`)
|
|
346
395
|
return 3
|
|
347
396
|
}
|
|
397
|
+
if (command === 'adopt-prelink') {
|
|
398
|
+
const usersSlug = flags.get('users-slug')
|
|
399
|
+
const verifiedField = flags.get('verified-field')
|
|
400
|
+
const result = await runAdoptPrelink({
|
|
401
|
+
configPath: new URL(configFlag, `file://${process.cwd()}/`).pathname,
|
|
402
|
+
apply: flags.get('apply') === true,
|
|
403
|
+
json,
|
|
404
|
+
log,
|
|
405
|
+
...(typeof flags.get('manifest') === 'string'
|
|
406
|
+
? { manifestPath: flags.get('manifest') as string }
|
|
407
|
+
: {}),
|
|
408
|
+
...(typeof usersSlug === 'string' ? { usersSlug } : {}),
|
|
409
|
+
...(typeof verifiedField === 'string' ? { verifiedField } : {}),
|
|
410
|
+
allowUnsignedManifest: flags.get('allow-unsigned-manifest') === true,
|
|
411
|
+
})
|
|
412
|
+
if (json) {
|
|
413
|
+
out.write(`${JSON.stringify(result, null, 2)}\n`)
|
|
414
|
+
}
|
|
415
|
+
if (result.unmappable.length > 0) {
|
|
416
|
+
err.write(`ABORT: ${result.unmappable.length} unmappable user(s); nothing was written\n`)
|
|
417
|
+
return 1
|
|
418
|
+
}
|
|
419
|
+
return 0
|
|
420
|
+
}
|
|
348
421
|
if ((command === 'from-payload-auth' || command === 'verify') && databaseUrl === '') {
|
|
349
422
|
err.write(`${command} requires --database-url or DATABASE_URL\n`)
|
|
350
423
|
return 3
|
|
@@ -365,10 +438,30 @@ async function main(
|
|
|
365
438
|
...(typeof flags.get('source-map') === 'string'
|
|
366
439
|
? { sourceMapJson: flags.get('source-map') as string }
|
|
367
440
|
: {}),
|
|
368
|
-
json
|
|
441
|
+
json,
|
|
442
|
+
log,
|
|
443
|
+
allowUnsignedManifest: flags.get('allow-unsigned-manifest') === true,
|
|
369
444
|
}
|
|
370
445
|
if (command === 'from-payload-auth') {
|
|
371
446
|
const outcome = await runFromPayloadAuth(importOptions)
|
|
447
|
+
if (json) {
|
|
448
|
+
out.write(
|
|
449
|
+
`${JSON.stringify(
|
|
450
|
+
{
|
|
451
|
+
exitCode: outcome.exitCode,
|
|
452
|
+
created: outcome.result?.created ?? 0,
|
|
453
|
+
skipped: outcome.result?.skipped ?? 0,
|
|
454
|
+
fieldWrites: outcome.result?.fieldWrites ?? 0,
|
|
455
|
+
rows: summarizePlanRows(outcome.plan.rows),
|
|
456
|
+
unmappable: outcome.plan.unmappable,
|
|
457
|
+
warnings: outcome.plan.warnings,
|
|
458
|
+
unverified: outcome.unverified.length,
|
|
459
|
+
},
|
|
460
|
+
null,
|
|
461
|
+
2
|
|
462
|
+
)}\n`
|
|
463
|
+
)
|
|
464
|
+
}
|
|
372
465
|
if (outcome.plan.unmappable.length > 0) {
|
|
373
466
|
err.write(
|
|
374
467
|
`ABORT: ${outcome.plan.unmappable.length} unmappable row(s); nothing was silently skipped\n`
|
|
@@ -379,10 +472,16 @@ async function main(
|
|
|
379
472
|
}
|
|
380
473
|
if (command === 'verify') {
|
|
381
474
|
const result = await runVerify(importOptions)
|
|
475
|
+
if (json) {
|
|
476
|
+
out.write(`${JSON.stringify(result, null, 2)}\n`)
|
|
477
|
+
}
|
|
382
478
|
return result.ok ? 0 : 1
|
|
383
479
|
}
|
|
384
480
|
if (command === 'rollback') {
|
|
385
|
-
await runRollback(importOptions)
|
|
481
|
+
const undone = await runRollback(importOptions)
|
|
482
|
+
if (json) {
|
|
483
|
+
out.write(`${JSON.stringify(undone)}\n`)
|
|
484
|
+
}
|
|
386
485
|
return 0
|
|
387
486
|
}
|
|
388
487
|
}
|
|
@@ -423,7 +522,16 @@ const invokedDirectly =
|
|
|
423
522
|
if (invokedDirectly) {
|
|
424
523
|
main(process.argv.slice(2))
|
|
425
524
|
.then((code) => {
|
|
525
|
+
// Every resource the command opened is closed by now (issue #88:
|
|
526
|
+
// Payload destroyed, pg clients ended); exit explicitly anyway,
|
|
527
|
+
// after stdout/stderr drain, so a third-party handle in the app's
|
|
528
|
+
// config (a cron, a websocket) can never park a finished CLI.
|
|
426
529
|
process.exitCode = code
|
|
530
|
+
process.stdout.write('', () => {
|
|
531
|
+
process.stderr.write('', () => {
|
|
532
|
+
process.exit(code)
|
|
533
|
+
})
|
|
534
|
+
})
|
|
427
535
|
})
|
|
428
536
|
.catch((error: unknown) => {
|
|
429
537
|
// Name the command that failed (issue #71) — 'doctor failed:' was
|
package/src/doctor/database.ts
CHANGED
|
@@ -100,18 +100,41 @@ export async function introspectDatabase(
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
let unverifiedUsers: number | null = null
|
|
103
|
+
let bannedUsers: number | null = null
|
|
104
|
+
let anonymousUsers: number | null = null
|
|
103
105
|
const usersTable = tables['users']
|
|
104
106
|
if (usersTable !== null && (await tableExists(client, usersTable))) {
|
|
105
107
|
const columns = await columnsOf(client, usersTable)
|
|
106
|
-
|
|
108
|
+
const countWhere = async (predicate: string): Promise<number | null> => {
|
|
107
109
|
const result = await client.query(
|
|
108
|
-
`SELECT count(*)::int AS n FROM ${usersTable} WHERE
|
|
110
|
+
`SELECT count(*)::int AS n FROM ${usersTable} WHERE ${predicate}`
|
|
109
111
|
)
|
|
110
112
|
const first = result.rows[0] as { n?: number } | undefined
|
|
111
|
-
|
|
113
|
+
return first?.n ?? null
|
|
114
|
+
}
|
|
115
|
+
if (columns.has('email_verified')) {
|
|
116
|
+
unverifiedUsers = await countWhere('email_verified = false')
|
|
117
|
+
}
|
|
118
|
+
// Better Auth plugin state the import must carry (issue #88).
|
|
119
|
+
if (columns.has('banned')) {
|
|
120
|
+
bannedUsers = await countWhere('banned = true')
|
|
121
|
+
}
|
|
122
|
+
if (columns.has('is_anonymous')) {
|
|
123
|
+
anonymousUsers = await countWhere('is_anonymous = true')
|
|
112
124
|
}
|
|
113
125
|
}
|
|
114
126
|
|
|
127
|
+
// OAuth-provider plugin tables (oauthProvider / oidcProvider /
|
|
128
|
+
// mcp): the importer does not carry them (issue #88).
|
|
129
|
+
const oauthResult = await client.query(
|
|
130
|
+
`SELECT table_name FROM information_schema.tables
|
|
131
|
+
WHERE table_schema = 'public' AND table_name LIKE 'oauth\\_%'
|
|
132
|
+
ORDER BY table_name`
|
|
133
|
+
)
|
|
134
|
+
const oauthTables = oauthResult.rows.map((row) =>
|
|
135
|
+
String((row as { table_name: unknown }).table_name)
|
|
136
|
+
)
|
|
137
|
+
|
|
115
138
|
const orphans: { kind: string; count: number }[] = []
|
|
116
139
|
const skippedChecks: string[] = []
|
|
117
140
|
const orphanChecks: readonly {
|
|
@@ -206,7 +229,16 @@ export async function introspectDatabase(
|
|
|
206
229
|
}
|
|
207
230
|
}
|
|
208
231
|
|
|
209
|
-
return {
|
|
232
|
+
return {
|
|
233
|
+
tables: counted,
|
|
234
|
+
unverifiedUsers,
|
|
235
|
+
orphans,
|
|
236
|
+
missingTables: missing,
|
|
237
|
+
skippedChecks,
|
|
238
|
+
bannedUsers,
|
|
239
|
+
anonymousUsers,
|
|
240
|
+
oauthTables,
|
|
241
|
+
}
|
|
210
242
|
} finally {
|
|
211
243
|
await client.end()
|
|
212
244
|
}
|
package/src/doctor/run-doctor.ts
CHANGED
|
@@ -143,6 +143,29 @@ export function collectionSlugs(config: unknown): string[] {
|
|
|
143
143
|
.filter((slug): slug is string => typeof slug === 'string')
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Top-level field names of one collection in the loaded config, or
|
|
148
|
+
* `null` when the config declares no such collection.
|
|
149
|
+
*/
|
|
150
|
+
function collectionFieldNames(config: unknown, slug: string): Set<string> | null {
|
|
151
|
+
if (!isRecord(config) || !Array.isArray(config.collections)) {
|
|
152
|
+
return null
|
|
153
|
+
}
|
|
154
|
+
const collection = config.collections.find(
|
|
155
|
+
(entry): entry is Record<string, unknown> => isRecord(entry) && entry.slug === slug
|
|
156
|
+
)
|
|
157
|
+
if (collection === undefined || !Array.isArray(collection.fields)) {
|
|
158
|
+
return null
|
|
159
|
+
}
|
|
160
|
+
const names = new Set<string>()
|
|
161
|
+
for (const field of collection.fields) {
|
|
162
|
+
if (isRecord(field) && typeof field.name === 'string') {
|
|
163
|
+
names.add(field.name)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return names
|
|
167
|
+
}
|
|
168
|
+
|
|
146
169
|
/** Default config loader: a dynamic import of the TS/JS config module. */
|
|
147
170
|
async function defaultLoadModule(path: string): Promise<unknown> {
|
|
148
171
|
const mod: unknown = await import(pathToFileURL(path).href)
|
|
@@ -241,6 +264,13 @@ export async function runDoctor(options: DoctorOptions): Promise<DoctorReport> {
|
|
|
241
264
|
const files = filesById.get(finding.id)
|
|
242
265
|
return files === undefined ? finding : { ...finding, files }
|
|
243
266
|
})
|
|
267
|
+
if (pluginIds.includes('generic-oauth')) {
|
|
268
|
+
warnings.push({
|
|
269
|
+
code: 'generic-oauth-providers',
|
|
270
|
+
detail:
|
|
271
|
+
"genericOAuth() is declared: its provider list lives in the engine options, not the database — carry every `config` entry (providerId, discovery URL, client id/secret, scopes) into the new engine's genericOAuth() call. The importer copies the accounts rows (provider_id stays), so a provider missing from the new config strands those users' sign-in",
|
|
272
|
+
})
|
|
273
|
+
}
|
|
244
274
|
for (const finding of pluginFindings) {
|
|
245
275
|
if (finding.status === 'removed-upstream') {
|
|
246
276
|
blockers.push({
|
|
@@ -316,6 +346,45 @@ export async function runDoctor(options: DoctorOptions): Promise<DoctorReport> {
|
|
|
316
346
|
for (const skip of found.skippedChecks ?? []) {
|
|
317
347
|
warnings.push({ code: 'orphan-check-skipped', detail: skip })
|
|
318
348
|
}
|
|
349
|
+
// Better Auth plugin state and tables the migration must account
|
|
350
|
+
// for (issue #88). When the loaded config is the TARGET (it
|
|
351
|
+
// declares the engine's ba-users collection), a target that
|
|
352
|
+
// cannot hold the state is a blocker — the importer refuses it.
|
|
353
|
+
const baUsersFields = configLoaded ? collectionFieldNames(config, 'ba-users') : null
|
|
354
|
+
const banned = found.bannedUsers ?? 0
|
|
355
|
+
if (banned > 0) {
|
|
356
|
+
if (baUsersFields !== null && !baUsersFields.has('banned')) {
|
|
357
|
+
blockers.push({
|
|
358
|
+
code: 'target-cannot-hold-bans',
|
|
359
|
+
detail: `${banned} banned user(s) on the source, but this config's ba-users collection declares no 'banned' field — add Better Auth's admin() plugin to the engine (autoDeclare derives banned/banReason/banExpires); the importer refuses to import a banned user unbanned`,
|
|
360
|
+
})
|
|
361
|
+
} else {
|
|
362
|
+
warnings.push({
|
|
363
|
+
code: 'banned-users',
|
|
364
|
+
detail: `${banned} banned user(s) (Better Auth admin plugin): the importer carries banned/banReason/banExpires onto ba-users only when the target engine declares admin(), and refuses the run otherwise — a banned user never imports unbanned`,
|
|
365
|
+
})
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
const anonymous = found.anonymousUsers ?? 0
|
|
369
|
+
if (anonymous > 0) {
|
|
370
|
+
if (baUsersFields !== null && !baUsersFields.has('isAnonymous')) {
|
|
371
|
+
blockers.push({
|
|
372
|
+
code: 'target-cannot-hold-anonymous',
|
|
373
|
+
detail: `${anonymous} anonymous user(s) on the source, but this config's ba-users collection declares no 'isAnonymous' field — add Better Auth's anonymous() plugin to the engine, or delete the anonymous users before importing`,
|
|
374
|
+
})
|
|
375
|
+
} else {
|
|
376
|
+
warnings.push({
|
|
377
|
+
code: 'anonymous-users',
|
|
378
|
+
detail: `${anonymous} anonymous user(s) (Better Auth anonymous plugin): they import with isAnonymous only when the target engine declares anonymous(), and the plugin refuses anonymous sessions by design — they never JIT-link to a Payload document`,
|
|
379
|
+
})
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
if ((found.oauthTables ?? []).length > 0) {
|
|
383
|
+
warnings.push({
|
|
384
|
+
code: 'oauth-tables-not-carried',
|
|
385
|
+
detail: `OAuth-provider tables present (${(found.oauthTables ?? []).join(', ')}): the importer does NOT copy them — registered OAuth clients, consents, and issued tokens stay in the legacy tables. Re-register the clients on @better-auth/oauth-provider (a separate schema migration) before dropping the legacy tables`,
|
|
386
|
+
})
|
|
387
|
+
}
|
|
319
388
|
} catch (error) {
|
|
320
389
|
warnings.push({
|
|
321
390
|
code: 'database-unreachable',
|
package/src/doctor/types.ts
CHANGED
|
@@ -80,6 +80,18 @@ export type DatabaseFinding = {
|
|
|
80
80
|
readonly missingTables?: readonly { readonly model: string; readonly table: string }[]
|
|
81
81
|
/** Orphan checks that could not run, with the reason (issue #68). */
|
|
82
82
|
readonly skippedChecks?: readonly string[]
|
|
83
|
+
/**
|
|
84
|
+
* Users banned by Better Auth's admin plugin (`banned = true`), or
|
|
85
|
+
* `null` when the source has no `banned` column (issue #88).
|
|
86
|
+
*/
|
|
87
|
+
readonly bannedUsers?: number | null
|
|
88
|
+
/** Anonymous-plugin users (`is_anonymous = true`), or `null` (issue #88). */
|
|
89
|
+
readonly anonymousUsers?: number | null
|
|
90
|
+
/**
|
|
91
|
+
* OAuth-provider plugin tables present (`oauth_*`, issue #88). The
|
|
92
|
+
* importer does NOT carry them; the doctor says so explicitly.
|
|
93
|
+
*/
|
|
94
|
+
readonly oauthTables?: readonly string[]
|
|
83
95
|
}
|
|
84
96
|
|
|
85
97
|
/** Default-behaviour changes in payload-auth-link 0.7.0 that bite. */
|
package/src/doctor/users.ts
CHANGED
|
@@ -98,6 +98,15 @@ export function localStrategyOn(collection: Record<string, unknown>): boolean {
|
|
|
98
98
|
return isRecord(auth) ? auth.disableLocalStrategy !== true : false
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
/**
|
|
102
|
+
* The payload-auth-link plugin's own auth strategy name
|
|
103
|
+
* (`packages/plugin/src/strategy.ts`). A users collection carrying it
|
|
104
|
+
* has ALREADY been through `authLink` — its identities/linkable/…
|
|
105
|
+
* fields are the plugin's own injections, not host collisions
|
|
106
|
+
* (issue #88: the doctor run against the TARGET config).
|
|
107
|
+
*/
|
|
108
|
+
const pluginStrategyName = 'auth-link'
|
|
109
|
+
|
|
101
110
|
export function analyzeUsersCollection(
|
|
102
111
|
collection: Record<string, unknown> | null,
|
|
103
112
|
rolesFieldName: string
|
|
@@ -114,10 +123,20 @@ export function analyzeUsersCollection(
|
|
|
114
123
|
const issues: DoctorIssue[] = []
|
|
115
124
|
const notes: string[] = []
|
|
116
125
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
126
|
+
// On the target config the plugin already injected these fields:
|
|
127
|
+
// they are its own, never a collision (issue #88).
|
|
128
|
+
const pluginInstalled = strategiesOf(collection).includes(pluginStrategyName)
|
|
129
|
+
const collisions = pluginInstalled
|
|
130
|
+
? []
|
|
131
|
+
: fields
|
|
132
|
+
.map(fieldName)
|
|
133
|
+
.filter((name): name is string => name !== null)
|
|
134
|
+
.filter((name) => injectedFieldNames.has(name))
|
|
135
|
+
if (pluginInstalled) {
|
|
136
|
+
notes.push(
|
|
137
|
+
'payload-auth-link is already installed on this collection (its auth-link strategy is present): the injected fields are the plugin’s own'
|
|
138
|
+
)
|
|
139
|
+
}
|
|
121
140
|
|
|
122
141
|
let roleField: UsersFinding['roleField'] = null
|
|
123
142
|
const roleCandidates = fields.filter((field) => {
|