@anthonyalayo/drizzle-kit 1.0.1
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/.gitignore +25 -0
- package/README.md +79 -0
- package/build.dev.ts +51 -0
- package/build.ts +112 -0
- package/imports-checker/analyze.ts +78 -0
- package/imports-checker/checker.ts +296 -0
- package/imports-checker/grammar/grammar.ohm +121 -0
- package/imports-checker/grammar/grammar.ohm-bundle.d.ts +164 -0
- package/imports-checker/grammar/grammar.ohm-bundle.js +753 -0
- package/imports-checker/index.ts +48 -0
- package/package.json +142 -0
- package/patches/difflib@0.2.4.patch +31 -0
- package/src/@types/utils.ts +58 -0
- package/src/api.ts +504 -0
- package/src/cli/commands/_es5.ts +2 -0
- package/src/cli/commands/check.ts +52 -0
- package/src/cli/commands/drop.ts +60 -0
- package/src/cli/commands/introspect.ts +772 -0
- package/src/cli/commands/libSqlPushUtils.ts +355 -0
- package/src/cli/commands/migrate.ts +1526 -0
- package/src/cli/commands/mysqlIntrospect.ts +53 -0
- package/src/cli/commands/mysqlPushUtils.ts +352 -0
- package/src/cli/commands/mysqlUp.ts +102 -0
- package/src/cli/commands/pgIntrospect.ts +55 -0
- package/src/cli/commands/pgPushUtils.ts +269 -0
- package/src/cli/commands/pgUp.ts +217 -0
- package/src/cli/commands/push.ts +637 -0
- package/src/cli/commands/singlestoreIntrospect.ts +53 -0
- package/src/cli/commands/singlestorePushUtils.ts +456 -0
- package/src/cli/commands/singlestoreUp.ts +1 -0
- package/src/cli/commands/sqliteIntrospect.ts +96 -0
- package/src/cli/commands/sqlitePushUtils.ts +322 -0
- package/src/cli/commands/sqliteUp.ts +52 -0
- package/src/cli/commands/utils.ts +858 -0
- package/src/cli/connections.ts +914 -0
- package/src/cli/index.ts +48 -0
- package/src/cli/schema.ts +794 -0
- package/src/cli/selector-ui.ts +38 -0
- package/src/cli/utils.ts +112 -0
- package/src/cli/validations/cli.ts +79 -0
- package/src/cli/validations/common.ts +193 -0
- package/src/cli/validations/libsql.ts +27 -0
- package/src/cli/validations/mysql.ts +61 -0
- package/src/cli/validations/outputs.ts +91 -0
- package/src/cli/validations/postgres.ts +83 -0
- package/src/cli/validations/singlestore.ts +61 -0
- package/src/cli/validations/sqlite.ts +104 -0
- package/src/cli/validations/studio.ts +24 -0
- package/src/cli/views.ts +658 -0
- package/src/extensions/getTablesFilterByExtensions.ts +16 -0
- package/src/extensions/vector.ts +10 -0
- package/src/global.ts +61 -0
- package/src/index.ts +326 -0
- package/src/introspect-mysql.ts +1030 -0
- package/src/introspect-pg.ts +1435 -0
- package/src/introspect-singlestore.ts +913 -0
- package/src/introspect-sqlite.ts +533 -0
- package/src/jsonDiffer.js +882 -0
- package/src/jsonStatements.ts +3590 -0
- package/src/loader.mjs +57 -0
- package/src/migrationPreparator.ts +217 -0
- package/src/schemaValidator.ts +23 -0
- package/src/serializer/index.ts +142 -0
- package/src/serializer/mysqlImports.ts +38 -0
- package/src/serializer/mysqlSchema.ts +423 -0
- package/src/serializer/mysqlSerializer.ts +999 -0
- package/src/serializer/pgImports.ts +106 -0
- package/src/serializer/pgSchema.ts +937 -0
- package/src/serializer/pgSerializer.ts +2223 -0
- package/src/serializer/singlestoreImports.ts +38 -0
- package/src/serializer/singlestoreSchema.ts +257 -0
- package/src/serializer/singlestoreSerializer.ts +767 -0
- package/src/serializer/sqliteImports.ts +41 -0
- package/src/serializer/sqliteSchema.ts +352 -0
- package/src/serializer/sqliteSerializer.ts +931 -0
- package/src/serializer/studio.ts +694 -0
- package/src/serializer/utils.ts +45 -0
- package/src/simulator.ts +157 -0
- package/src/snapshotsDiffer.ts +4398 -0
- package/src/sqlgenerator.ts +4212 -0
- package/src/statementCombiner.ts +597 -0
- package/src/utils/certs.ts +44 -0
- package/src/utils/words.ts +1333 -0
- package/src/utils.ts +370 -0
- package/tests/bin.test.ts +58 -0
- package/tests/cli/d1http.config.ts +12 -0
- package/tests/cli/drizzle.config.ts +9 -0
- package/tests/cli/durable-sqlite.config.ts +7 -0
- package/tests/cli/expo.config.ts +7 -0
- package/tests/cli/postgres.config.ts +13 -0
- package/tests/cli/postgres2.config.ts +17 -0
- package/tests/cli/schema.ts +1 -0
- package/tests/cli/turso.config.ts +10 -0
- package/tests/cli-export.test.ts +79 -0
- package/tests/cli-generate.test.ts +259 -0
- package/tests/cli-migrate.test.ts +103 -0
- package/tests/cli-push.test.ts +126 -0
- package/tests/common.ts +15 -0
- package/tests/indexes/common.ts +21 -0
- package/tests/indexes/pg.test.ts +245 -0
- package/tests/introspect/libsql.test.ts +35 -0
- package/tests/introspect/mysql.test.ts +319 -0
- package/tests/introspect/pg.test.ts +894 -0
- package/tests/introspect/singlestore.test.ts +275 -0
- package/tests/introspect/sqlite.test.ts +122 -0
- package/tests/libsql-checks.test.ts +308 -0
- package/tests/libsql-statements.test.ts +989 -0
- package/tests/libsql-views.test.ts +218 -0
- package/tests/migrate/libsq-schema.ts +6 -0
- package/tests/migrate/libsql-migrate.test.ts +58 -0
- package/tests/migrate/migrations/0000_little_blizzard.sql +4 -0
- package/tests/migrate/migrations/0001_nebulous_storm.sql +10 -0
- package/tests/migrate/migrations/meta/0000_snapshot.json +40 -0
- package/tests/migrate/migrations/meta/0001_snapshot.json +40 -0
- package/tests/migrate/migrations/meta/_journal.json +20 -0
- package/tests/mysql-checks.test.ts +291 -0
- package/tests/mysql-generated.test.ts +1290 -0
- package/tests/mysql-schemas.test.ts +156 -0
- package/tests/mysql-views.test.ts +553 -0
- package/tests/mysql.test.ts +863 -0
- package/tests/pg-array.test.ts +368 -0
- package/tests/pg-checks.test.ts +329 -0
- package/tests/pg-columns.test.ts +484 -0
- package/tests/pg-domains.test.ts +306 -0
- package/tests/pg-enums.test.ts +751 -0
- package/tests/pg-generated.test.ts +529 -0
- package/tests/pg-identity.test.ts +447 -0
- package/tests/pg-schemas.test.ts +105 -0
- package/tests/pg-sequences.test.ts +298 -0
- package/tests/pg-tables.test.ts +1021 -0
- package/tests/pg-views.test.ts +1929 -0
- package/tests/push/common.ts +58 -0
- package/tests/push/libsql.test.ts +1400 -0
- package/tests/push/mysql-push.test.ts +345 -0
- package/tests/push/mysql.test.ts +788 -0
- package/tests/push/pg.test.ts +4341 -0
- package/tests/push/singlestore-push.test.ts +894 -0
- package/tests/push/singlestore.test.ts +444 -0
- package/tests/push/sqlite.test.ts +1613 -0
- package/tests/rls/pg-policy.test.ts +1656 -0
- package/tests/rls/pg-role.test.ts +234 -0
- package/tests/schemaDiffer.ts +2852 -0
- package/tests/singlestore-generated.test.ts +1290 -0
- package/tests/singlestore-schemas.test.ts +155 -0
- package/tests/singlestore.test.ts +978 -0
- package/tests/sqlite-checks.test.ts +308 -0
- package/tests/sqlite-columns.test.ts +1049 -0
- package/tests/sqlite-generated.test.ts +1726 -0
- package/tests/sqlite-tables.test.ts +637 -0
- package/tests/sqlite-views.test.ts +218 -0
- package/tests/statements-combiner/libsql-statements-combiner.test.ts +1812 -0
- package/tests/statements-combiner/singlestore-statements-combiner.test.ts +882 -0
- package/tests/statements-combiner/sqlite-statements-combiner.test.ts +1211 -0
- package/tests/test/sqlite.test.ts +39 -0
- package/tests/testsinglestore.ts +29 -0
- package/tests/validations.test.ts +869 -0
- package/tests/wrap-param.test.ts +16 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.cli-types.json +9 -0
- package/tsconfig.json +28 -0
- package/vitest.config.ts +34 -0
package/.gitignore
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/*
|
|
2
|
+
**/.DS_Store
|
|
3
|
+
|
|
4
|
+
!src
|
|
5
|
+
!imports-checker
|
|
6
|
+
!tests
|
|
7
|
+
!vitest.config.ts
|
|
8
|
+
!README.md
|
|
9
|
+
!CONTRIBUTING.md
|
|
10
|
+
!schema.ts
|
|
11
|
+
|
|
12
|
+
!.eslint
|
|
13
|
+
!.gitignore
|
|
14
|
+
!package.json
|
|
15
|
+
!tsconfig.json
|
|
16
|
+
!tsconfig.cli-types.json
|
|
17
|
+
!tsconfig.build.json
|
|
18
|
+
!pnpm-lock.yaml
|
|
19
|
+
!.github
|
|
20
|
+
!build.ts
|
|
21
|
+
!build.dev.ts
|
|
22
|
+
|
|
23
|
+
tests/test.ts
|
|
24
|
+
|
|
25
|
+
!patches
|
package/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
## Drizzle Kit
|
|
2
|
+
|
|
3
|
+
Drizzle Kit is a CLI migrator tool for Drizzle ORM. It is probably the one and only tool that lets you completely automatically generate SQL migrations and covers ~95% of the common cases like deletions and renames by prompting user input.
|
|
4
|
+
<https://github.com/drizzle-team/drizzle-kit-mirror> - is a mirror repository for issues.
|
|
5
|
+
|
|
6
|
+
## Documentation
|
|
7
|
+
|
|
8
|
+
Check the full documentation on [the website](https://orm.drizzle.team/kit-docs/overview).
|
|
9
|
+
|
|
10
|
+
### How it works
|
|
11
|
+
|
|
12
|
+
Drizzle Kit traverses a schema module and generates a snapshot to compare with the previous version, if there is one.
|
|
13
|
+
Based on the difference, it will generate all needed SQL migrations. If there are any cases that can't be resolved automatically, such as renames, it will prompt the user for input.
|
|
14
|
+
|
|
15
|
+
For example, for this schema module:
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
// src/db/schema.ts
|
|
19
|
+
|
|
20
|
+
import { integer, pgTable, serial, text, varchar } from "drizzle-orm/pg-core";
|
|
21
|
+
|
|
22
|
+
const users = pgTable("users", {
|
|
23
|
+
id: serial("id").primaryKey(),
|
|
24
|
+
fullName: varchar("full_name", { length: 256 }),
|
|
25
|
+
}, (table) => ({
|
|
26
|
+
nameIdx: index("name_idx", table.fullName),
|
|
27
|
+
})
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
export const authOtp = pgTable("auth_otp", {
|
|
31
|
+
id: serial("id").primaryKey(),
|
|
32
|
+
phone: varchar("phone", { length: 256 }),
|
|
33
|
+
userId: integer("user_id").references(() => users.id),
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
It will generate:
|
|
38
|
+
|
|
39
|
+
```SQL
|
|
40
|
+
CREATE TABLE IF NOT EXISTS auth_otp (
|
|
41
|
+
"id" SERIAL PRIMARY KEY,
|
|
42
|
+
"phone" character varying(256),
|
|
43
|
+
"user_id" INT
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
47
|
+
"id" SERIAL PRIMARY KEY,
|
|
48
|
+
"full_name" character varying(256)
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
DO $$ BEGIN
|
|
52
|
+
ALTER TABLE auth_otp ADD CONSTRAINT auth_otp_user_id_fkey FOREIGN KEY ("user_id") REFERENCES users(id);
|
|
53
|
+
EXCEPTION
|
|
54
|
+
WHEN duplicate_object THEN null;
|
|
55
|
+
END $$;
|
|
56
|
+
|
|
57
|
+
CREATE INDEX IF NOT EXISTS users_full_name_index ON users (full_name);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Installation & configuration
|
|
61
|
+
|
|
62
|
+
```shell
|
|
63
|
+
npm install -D drizzle-kit
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Running with CLI options:
|
|
67
|
+
|
|
68
|
+
```jsonc
|
|
69
|
+
// package.json
|
|
70
|
+
{
|
|
71
|
+
"scripts": {
|
|
72
|
+
"generate": "drizzle-kit generate --out migrations-folder --schema src/db/schema.ts"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```shell
|
|
78
|
+
npm run generate
|
|
79
|
+
```
|
package/build.dev.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as esbuild from 'esbuild';
|
|
2
|
+
import { cpSync } from 'node:fs';
|
|
3
|
+
|
|
4
|
+
const driversPackages = [
|
|
5
|
+
// postgres drivers
|
|
6
|
+
'pg',
|
|
7
|
+
'postgres',
|
|
8
|
+
'@vercel/postgres',
|
|
9
|
+
'@neondatabase/serverless',
|
|
10
|
+
// mysql drivers
|
|
11
|
+
'mysql2',
|
|
12
|
+
'@planetscale/database',
|
|
13
|
+
// sqlite drivers
|
|
14
|
+
'@libsql/client',
|
|
15
|
+
'better-sqlite3',
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
esbuild.buildSync({
|
|
19
|
+
entryPoints: ['./src/utils.ts'],
|
|
20
|
+
bundle: true,
|
|
21
|
+
outfile: 'dist/utils.js',
|
|
22
|
+
format: 'cjs',
|
|
23
|
+
target: 'node16',
|
|
24
|
+
platform: 'node',
|
|
25
|
+
external: ['drizzle-orm', 'esbuild', ...driversPackages],
|
|
26
|
+
banner: {
|
|
27
|
+
js: `#!/usr/bin/env -S node --loader @esbuild-kit/esm-loader --no-warnings`,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
esbuild.buildSync({
|
|
32
|
+
entryPoints: ['./src/cli/index.ts'],
|
|
33
|
+
bundle: true,
|
|
34
|
+
outfile: 'dist/index.cjs',
|
|
35
|
+
format: 'cjs',
|
|
36
|
+
target: 'node16',
|
|
37
|
+
platform: 'node',
|
|
38
|
+
external: [
|
|
39
|
+
'commander',
|
|
40
|
+
'json-diff',
|
|
41
|
+
'glob',
|
|
42
|
+
'esbuild',
|
|
43
|
+
'drizzle-orm',
|
|
44
|
+
...driversPackages,
|
|
45
|
+
],
|
|
46
|
+
banner: {
|
|
47
|
+
js: `#!/usr/bin/env -S node --loader ./dist/loader.mjs --no-warnings`,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
cpSync('./src/loader.mjs', 'dist/loader.mjs');
|
package/build.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/// <reference types="bun-types" />
|
|
2
|
+
import * as esbuild from 'esbuild';
|
|
3
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import * as tsup from 'tsup';
|
|
5
|
+
import pkg from './package.json';
|
|
6
|
+
|
|
7
|
+
const driversPackages = [
|
|
8
|
+
// postgres drivers
|
|
9
|
+
'pg',
|
|
10
|
+
'postgres',
|
|
11
|
+
'@vercel/postgres',
|
|
12
|
+
'@neondatabase/serverless',
|
|
13
|
+
'@electric-sql/pglite',
|
|
14
|
+
// mysql drivers
|
|
15
|
+
'mysql2',
|
|
16
|
+
'@planetscale/database',
|
|
17
|
+
// sqlite drivers
|
|
18
|
+
'@libsql/client',
|
|
19
|
+
'better-sqlite3',
|
|
20
|
+
'bun:sqlite',
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
esbuild.buildSync({
|
|
24
|
+
entryPoints: ['./src/utils.ts'],
|
|
25
|
+
bundle: true,
|
|
26
|
+
outfile: 'dist/utils.js',
|
|
27
|
+
format: 'cjs',
|
|
28
|
+
target: 'node16',
|
|
29
|
+
platform: 'node',
|
|
30
|
+
external: [
|
|
31
|
+
'commander',
|
|
32
|
+
'json-diff',
|
|
33
|
+
'glob',
|
|
34
|
+
'esbuild',
|
|
35
|
+
'drizzle-orm',
|
|
36
|
+
...driversPackages,
|
|
37
|
+
],
|
|
38
|
+
banner: {
|
|
39
|
+
js: `#!/usr/bin/env node`,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
esbuild.buildSync({
|
|
44
|
+
entryPoints: ['./src/utils.ts'],
|
|
45
|
+
bundle: true,
|
|
46
|
+
outfile: 'dist/utils.mjs',
|
|
47
|
+
format: 'esm',
|
|
48
|
+
target: 'node16',
|
|
49
|
+
platform: 'node',
|
|
50
|
+
external: [
|
|
51
|
+
'commander',
|
|
52
|
+
'json-diff',
|
|
53
|
+
'glob',
|
|
54
|
+
'esbuild',
|
|
55
|
+
'drizzle-orm',
|
|
56
|
+
...driversPackages,
|
|
57
|
+
],
|
|
58
|
+
banner: {
|
|
59
|
+
js: `#!/usr/bin/env node`,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
esbuild.buildSync({
|
|
64
|
+
entryPoints: ['./src/cli/index.ts'],
|
|
65
|
+
bundle: true,
|
|
66
|
+
outfile: 'dist/bin.cjs',
|
|
67
|
+
format: 'cjs',
|
|
68
|
+
target: 'node16',
|
|
69
|
+
platform: 'node',
|
|
70
|
+
define: {
|
|
71
|
+
'process.env.DRIZZLE_KIT_VERSION': `"${pkg.version}"`,
|
|
72
|
+
},
|
|
73
|
+
external: [
|
|
74
|
+
'esbuild',
|
|
75
|
+
'drizzle-orm',
|
|
76
|
+
...driversPackages,
|
|
77
|
+
],
|
|
78
|
+
banner: {
|
|
79
|
+
js: `#!/usr/bin/env node`,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const main = async () => {
|
|
84
|
+
await tsup.build({
|
|
85
|
+
entryPoints: ['./src/index.ts', './src/api.ts'],
|
|
86
|
+
outDir: './dist',
|
|
87
|
+
external: ['bun:sqlite'],
|
|
88
|
+
splitting: false,
|
|
89
|
+
dts: true,
|
|
90
|
+
format: ['cjs', 'esm'],
|
|
91
|
+
outExtension: (ctx) => {
|
|
92
|
+
if (ctx.format === 'cjs') {
|
|
93
|
+
return {
|
|
94
|
+
dts: '.d.ts',
|
|
95
|
+
js: '.js',
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
dts: '.d.mts',
|
|
100
|
+
js: '.mjs',
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const apiCjs = readFileSync('./dist/api.js', 'utf8').replace(/await import\(/g, 'require(');
|
|
106
|
+
writeFileSync('./dist/api.js', apiCjs);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
main().catch((e) => {
|
|
110
|
+
console.error(e);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import type { Node } from 'ohm-js';
|
|
3
|
+
import JSImports from './grammar/grammar.ohm-bundle';
|
|
4
|
+
|
|
5
|
+
export type CollectionItem = {
|
|
6
|
+
type: 'data' | 'types';
|
|
7
|
+
source: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
function recursiveRun(...args: Node[]): boolean {
|
|
11
|
+
for (const arg of args) {
|
|
12
|
+
if (
|
|
13
|
+
arg.ctorName === 'Rest'
|
|
14
|
+
|| arg.ctorName === 'comment'
|
|
15
|
+
|| arg.ctorName === 'stringLiteral'
|
|
16
|
+
) {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (
|
|
21
|
+
arg.ctorName === 'ImportExpr_From'
|
|
22
|
+
|| arg.ctorName === 'ImportExpr_NoFrom'
|
|
23
|
+
) {
|
|
24
|
+
arg['analyze']();
|
|
25
|
+
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (arg.isTerminal()) continue;
|
|
30
|
+
|
|
31
|
+
for (const c of arg.children) {
|
|
32
|
+
if (!recursiveRun(c)) return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
function init(collection: CollectionItem[]) {
|
|
39
|
+
const semantics = JSImports.createSemantics();
|
|
40
|
+
|
|
41
|
+
semantics.addOperation('analyze', {
|
|
42
|
+
JSImports(arg0, arg1) {
|
|
43
|
+
recursiveRun(arg0, arg1);
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
ImportExpr_From(kImport, importInner, kFrom, importSource) {
|
|
47
|
+
const ruleName = importInner.children[0]!.ctorName;
|
|
48
|
+
const importType = ruleName === 'ImportInner_Type' || ruleName === 'ImportInner_Types'
|
|
49
|
+
? 'types'
|
|
50
|
+
: 'data';
|
|
51
|
+
|
|
52
|
+
collection.push({
|
|
53
|
+
source: importSource.children[1]!.sourceString!,
|
|
54
|
+
type: importType,
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
ImportExpr_NoFrom(kImport, importSource) {
|
|
59
|
+
collection.push({
|
|
60
|
+
source: importSource.children[1]!.sourceString!,
|
|
61
|
+
type: 'data',
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return semantics;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function analyze(path: string) {
|
|
70
|
+
const file = readFileSync(path).toString();
|
|
71
|
+
const match = JSImports.match(file, 'JSImports');
|
|
72
|
+
|
|
73
|
+
if (match.failed()) throw new Error(`Failed to parse file: ${path}`);
|
|
74
|
+
const collection: CollectionItem[] = [];
|
|
75
|
+
|
|
76
|
+
init(collection)(match)['analyze']();
|
|
77
|
+
return collection;
|
|
78
|
+
}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import m from 'micromatch';
|
|
3
|
+
import { dirname, join as joinPath, relative, resolve as resolvePath } from 'path';
|
|
4
|
+
import { analyze } from './analyze';
|
|
5
|
+
|
|
6
|
+
type External = {
|
|
7
|
+
file: string;
|
|
8
|
+
import: string;
|
|
9
|
+
type: 'data' | 'types';
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type Issue = {
|
|
13
|
+
file: string;
|
|
14
|
+
imports: IssueImport[];
|
|
15
|
+
accessChains: ChainLink[][];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type IssueImport = {
|
|
19
|
+
name: string;
|
|
20
|
+
type: 'data' | 'types';
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type ChainLink = {
|
|
24
|
+
file: string;
|
|
25
|
+
import: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type ListMode = 'whitelist' | 'blacklist';
|
|
29
|
+
|
|
30
|
+
class ImportAnalyzer {
|
|
31
|
+
private localImportRegex = /^(\.?\.?\/|\.\.?$)/;
|
|
32
|
+
private importedFileFormatRegex = /^.*\.(ts|tsx|mts|cts|js|jsx|mjs|cjs|json)$/i;
|
|
33
|
+
|
|
34
|
+
private visited: Set<string> = new Set<string>();
|
|
35
|
+
|
|
36
|
+
private externals: External[] = [];
|
|
37
|
+
private accessChains: Record<string, ChainLink[][]> = {};
|
|
38
|
+
|
|
39
|
+
constructor(
|
|
40
|
+
private basePath: string,
|
|
41
|
+
private entry: string,
|
|
42
|
+
private listMode: ListMode,
|
|
43
|
+
private readonly wantedList: string[],
|
|
44
|
+
private localPaths: string[],
|
|
45
|
+
private logger?: boolean,
|
|
46
|
+
private ignoreTypes?: boolean,
|
|
47
|
+
) {}
|
|
48
|
+
|
|
49
|
+
private isDirectory = (path: string) => {
|
|
50
|
+
try {
|
|
51
|
+
return fs.lstatSync(path).isDirectory();
|
|
52
|
+
} catch (e) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
private isFile = (path: string) => {
|
|
58
|
+
try {
|
|
59
|
+
return fs.lstatSync(path).isFile();
|
|
60
|
+
} catch (e) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
private localizePath = (path: string) => relative(resolvePath(this.basePath), resolvePath(path));
|
|
66
|
+
|
|
67
|
+
private isCustomLocal = (importTarget: string) =>
|
|
68
|
+
!!this.localPaths.find(
|
|
69
|
+
(l) =>
|
|
70
|
+
importTarget === l
|
|
71
|
+
|| importTarget.startsWith(l.endsWith('/') ? l : `${l}/`),
|
|
72
|
+
);
|
|
73
|
+
private isLocal = (importTarget: string) =>
|
|
74
|
+
this.localImportRegex.test(importTarget)
|
|
75
|
+
|| this.isCustomLocal(importTarget);
|
|
76
|
+
private isTsFormat = (path: string) => this.importedFileFormatRegex.test(path);
|
|
77
|
+
|
|
78
|
+
private resolveCustomLocalPath = (
|
|
79
|
+
absoluteBase: string,
|
|
80
|
+
base: string,
|
|
81
|
+
target: string,
|
|
82
|
+
): string => {
|
|
83
|
+
return joinPath(absoluteBase, target);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
private resolveTargetFile = (path: string): string => {
|
|
87
|
+
if (this.isFile(path)) return path;
|
|
88
|
+
|
|
89
|
+
const formats = [
|
|
90
|
+
'.ts',
|
|
91
|
+
'.mts',
|
|
92
|
+
'.cts',
|
|
93
|
+
'.tsx',
|
|
94
|
+
'.js',
|
|
95
|
+
'.mjs',
|
|
96
|
+
'.cjs',
|
|
97
|
+
'.jsx',
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
for (const format of formats) {
|
|
101
|
+
const indexPath = joinPath(path, `/index${format}`);
|
|
102
|
+
if (this.isFile(indexPath)) return indexPath;
|
|
103
|
+
|
|
104
|
+
const formatFilePath = `${path}${format}`;
|
|
105
|
+
if (this.isFile(formatFilePath)) return formatFilePath;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return path;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
private resolveTargetPath = (
|
|
112
|
+
absoluteBase: string,
|
|
113
|
+
base: string,
|
|
114
|
+
target: string,
|
|
115
|
+
): string => {
|
|
116
|
+
if (this.isCustomLocal(target)) {
|
|
117
|
+
return this.resolveTargetFile(
|
|
118
|
+
this.resolveCustomLocalPath(absoluteBase, base, target),
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const dir = this.isDirectory(base) ? base : dirname(base);
|
|
123
|
+
const joined = joinPath(dir, target);
|
|
124
|
+
|
|
125
|
+
return this.resolveTargetFile(joined);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
private _analyzeImports = (
|
|
129
|
+
target: string = this.entry,
|
|
130
|
+
basePath: string = this.basePath,
|
|
131
|
+
accessChain: ChainLink[] = [],
|
|
132
|
+
) => {
|
|
133
|
+
if (this.visited.has(target)) return;
|
|
134
|
+
|
|
135
|
+
const locals: string[] = [];
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
if (this.logger) console.log(`${this.localizePath(target)}`);
|
|
139
|
+
|
|
140
|
+
const imports = analyze(target);
|
|
141
|
+
|
|
142
|
+
for (const { source: i, type } of imports) {
|
|
143
|
+
if (this.ignoreTypes && type === 'types') continue;
|
|
144
|
+
|
|
145
|
+
if (this.isLocal(i)) {
|
|
146
|
+
locals.push(i);
|
|
147
|
+
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
this.externals.push({
|
|
152
|
+
file: this.localizePath(target),
|
|
153
|
+
import: i,
|
|
154
|
+
type: type,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
} catch (e) {
|
|
158
|
+
throw e;
|
|
159
|
+
} finally {
|
|
160
|
+
this.visited.add(target);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
for (const local of locals) {
|
|
164
|
+
const transformedTarget = this.resolveTargetPath(basePath, target, local);
|
|
165
|
+
|
|
166
|
+
const localChain = [
|
|
167
|
+
...accessChain,
|
|
168
|
+
{
|
|
169
|
+
file: this.localizePath(target),
|
|
170
|
+
import: local,
|
|
171
|
+
},
|
|
172
|
+
];
|
|
173
|
+
|
|
174
|
+
const localized = this.localizePath(transformedTarget);
|
|
175
|
+
|
|
176
|
+
if (this.accessChains[localized]) {
|
|
177
|
+
this.accessChains[localized].push(localChain);
|
|
178
|
+
} else this.accessChains[localized] = [localChain];
|
|
179
|
+
|
|
180
|
+
if (this.isTsFormat(transformedTarget)) {
|
|
181
|
+
this._analyzeImports(transformedTarget, basePath, localChain);
|
|
182
|
+
} else {
|
|
183
|
+
throw new Error(`unrecognized: ${localized}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
public analyzeImports = () => {
|
|
189
|
+
const entryLocalized = this.localizePath(this.entry);
|
|
190
|
+
if (!this.accessChains[entryLocalized]) {
|
|
191
|
+
this.accessChains[entryLocalized] = [[]];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
this._analyzeImports();
|
|
195
|
+
|
|
196
|
+
const rawIssues = this.listMode === 'whitelist'
|
|
197
|
+
? this.externals.filter((e) => !m([e.import], this.wantedList).length)
|
|
198
|
+
: this.externals.filter((e) => m([e.import], this.wantedList).length);
|
|
199
|
+
|
|
200
|
+
const issueMap: Record<string, Issue> = {};
|
|
201
|
+
for (const { file, import: i, type } of rawIssues) {
|
|
202
|
+
if (issueMap[file]) {
|
|
203
|
+
issueMap[file].imports.push({
|
|
204
|
+
name: i,
|
|
205
|
+
type,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
issueMap[file] = {
|
|
212
|
+
file,
|
|
213
|
+
imports: [
|
|
214
|
+
{
|
|
215
|
+
name: i,
|
|
216
|
+
type,
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
accessChains: this.accessChains[file]!,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
issues: Object.entries(issueMap).map(([file, data]) => {
|
|
225
|
+
for (const chain of data.accessChains) {
|
|
226
|
+
chain.push({
|
|
227
|
+
file,
|
|
228
|
+
import: '',
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return data;
|
|
233
|
+
}),
|
|
234
|
+
accessChains: this.accessChains,
|
|
235
|
+
};
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export type CustomLocalPathResolver = (
|
|
240
|
+
basePath: string,
|
|
241
|
+
path: string,
|
|
242
|
+
target: string,
|
|
243
|
+
) => string;
|
|
244
|
+
|
|
245
|
+
export type AnalyzeImportsConfig =
|
|
246
|
+
& {
|
|
247
|
+
basePath: string;
|
|
248
|
+
entry: string;
|
|
249
|
+
logger?: boolean;
|
|
250
|
+
ignoreTypes?: boolean;
|
|
251
|
+
localPaths?: string[];
|
|
252
|
+
}
|
|
253
|
+
& (
|
|
254
|
+
| {
|
|
255
|
+
blackList: string[];
|
|
256
|
+
}
|
|
257
|
+
| {
|
|
258
|
+
whiteList: string[];
|
|
259
|
+
}
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
type AnyAnalyzeImportsConfig = {
|
|
263
|
+
basePath: string;
|
|
264
|
+
entry: string;
|
|
265
|
+
blackList?: string[];
|
|
266
|
+
whiteList?: string[];
|
|
267
|
+
logger?: boolean;
|
|
268
|
+
ignoreTypes?: boolean;
|
|
269
|
+
localPaths?: string[];
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
export function analyzeImports(cfg: AnalyzeImportsConfig) {
|
|
273
|
+
const {
|
|
274
|
+
basePath,
|
|
275
|
+
blackList,
|
|
276
|
+
whiteList,
|
|
277
|
+
entry,
|
|
278
|
+
localPaths: localImports,
|
|
279
|
+
ignoreTypes,
|
|
280
|
+
logger,
|
|
281
|
+
} = cfg as AnyAnalyzeImportsConfig;
|
|
282
|
+
const mode = whiteList ? 'whitelist' : 'blacklist';
|
|
283
|
+
const wantedList = blackList ?? whiteList!;
|
|
284
|
+
|
|
285
|
+
const analyzer = new ImportAnalyzer(
|
|
286
|
+
joinPath(basePath),
|
|
287
|
+
joinPath(entry),
|
|
288
|
+
mode,
|
|
289
|
+
wantedList,
|
|
290
|
+
localImports ?? [],
|
|
291
|
+
logger,
|
|
292
|
+
ignoreTypes,
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
return analyzer.analyzeImports();
|
|
296
|
+
}
|