@tobi2409/fullsty 0.5.5 → 0.5.7
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/README.md +7 -7
- package/package.json +1 -1
- package/scripts/create-project.js +21 -21
- package/scripts/extension-wrappers/@tobi2409/authentication-core-db/current-user.ts +37 -17
- package/scripts/extension-wrappers/@tobi2409/authentication-core-db/login.ts +45 -22
- package/scripts/extension-wrappers/@tobi2409/authentication-core-db/register.ts +65 -44
- package/scripts/extension-wrappers/@tobi2409/authentication-core-db/shared.ts +11 -11
- package/scripts/extension-wrappers/@tobi2409/authentication-core-lib/authentication-core-lib-wrapper.ts +1 -1
- package/scripts/extension-wrappers/db-connection/db-connection-wrapper.ts +17 -14
- package/scripts/extension-wrappers/db-connection/extension.json +1 -1
- package/scripts/extension-wrappers/kysely/extension.json +1 -1
- package/scripts/extension-wrappers/kysely/kysely-wrapper.ts +22 -20
- package/scripts/extension-wrappers/pg/extension.json +1 -1
- package/scripts/extension-wrappers/pg/pg-wrapper.ts +37 -33
- package/scripts/extension-wrappers/sql-template-tag/extension.json +1 -1
- package/scripts/extension-wrappers/sql-template-tag/sql-template-tag-wrapper.ts +1 -1
- package/scripts/fullsty-server-pkg.js +25 -25
- package/scripts/project-frame/client-package.json +1 -1
- package/scripts/project-frame/package.json +1 -1
- package/scripts/project-frame/proc2rest.config.json +10 -1
- package/scripts/project-frame/server-package.json +5 -4
- package/scripts/project-frame/src/client/index.html +33 -29
- package/scripts/project-frame/src/client/index.js +8 -5
- package/scripts/project-frame/src/server/server.ts +1 -1
- package/scripts/shared/file-copy.js +11 -11
- package/scripts/shared/project-json-utils.js +8 -8
- package/scripts/shared/server-package-utils.js +41 -39
package/README.md
CHANGED
|
@@ -73,11 +73,11 @@ Alternatively, you can also use `npx fullsty-server-pkg`.
|
|
|
73
73
|
What it does:
|
|
74
74
|
|
|
75
75
|
- `add <package>`
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
- adds the package to the project's server package file, which is initially created from [project-frame/server-package.json](project-frame/server-package.json)
|
|
77
|
+
- copies a matching wrapper from [scripts/extension-wrappers](scripts/extension-wrappers) into [projects/demo1/src/server](projects/demo1/src/server)
|
|
78
78
|
- `remove <package>`
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
- removes the package from the project's server package file
|
|
80
|
+
- removes the matching wrapper from [projects/demo1/src/server](projects/demo1/src/server)
|
|
81
81
|
|
|
82
82
|
The script does not manage the project-level [package.json](project-frame/package.json).
|
|
83
83
|
|
|
@@ -98,10 +98,10 @@ npm run start
|
|
|
98
98
|
## package.json responsibilities
|
|
99
99
|
|
|
100
100
|
- Project-level package file: [project-frame/package.json](project-frame/package.json)
|
|
101
|
-
|
|
101
|
+
- Used for generation tasks (`generate-server`, `generate-client`, `generate`).
|
|
102
102
|
- Generated server package file: [project-frame/server-package.json](project-frame/server-package.json)
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
- Becomes `generated/server/package.json`.
|
|
104
|
+
- Used to run the generated server (`npm run start`).
|
|
105
105
|
|
|
106
106
|
## 6) Install client dependencies
|
|
107
107
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tobi2409/fullsty",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.7",
|
|
4
4
|
"description": "Fullsty is a modular fullstack framework starter built from three core parts: template-engine, layout and proc2rest. It provides a project frame for building fullstack applications with a clear separation of client and server code.",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"author": "Tobias Hollstein",
|
|
@@ -1,46 +1,46 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import fs from
|
|
4
|
-
import path from
|
|
5
|
-
import { copyDirectory } from
|
|
6
|
-
import { readJson, writeJson } from
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
import path from 'path'
|
|
5
|
+
import { copyDirectory } from './shared/file-copy.js'
|
|
6
|
+
import { readJson, writeJson } from './shared/project-json-utils.js'
|
|
7
7
|
|
|
8
8
|
// Update package.json name in the copied project frame.
|
|
9
9
|
function updatePackageName(targetProjectDir, projectName) {
|
|
10
|
-
const packageJson = readJson(targetProjectDir,
|
|
11
|
-
packageJson.name = projectName
|
|
12
|
-
writeJson(targetProjectDir,
|
|
10
|
+
const packageJson = readJson(targetProjectDir, 'package.json')
|
|
11
|
+
packageJson.name = projectName
|
|
12
|
+
writeJson(targetProjectDir, 'package.json', packageJson)
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
function main() {
|
|
16
|
-
const projectName = process.argv[2]
|
|
16
|
+
const projectName = process.argv[2]
|
|
17
17
|
|
|
18
18
|
if (!projectName) {
|
|
19
|
-
console.error(
|
|
20
|
-
process.exit(1)
|
|
19
|
+
console.error('Usage: node create-project.js <project-name>')
|
|
20
|
+
process.exit(1)
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
const scriptDir = path.dirname(new URL(import.meta.url).pathname)
|
|
24
|
-
const sourceFrameDir = path.join(scriptDir,
|
|
25
|
-
const targetProjectDir = path.resolve(process.cwd(), projectName)
|
|
23
|
+
const scriptDir = path.dirname(new URL(import.meta.url).pathname)
|
|
24
|
+
const sourceFrameDir = path.join(scriptDir, 'project-frame')
|
|
25
|
+
const targetProjectDir = path.resolve(process.cwd(), projectName)
|
|
26
26
|
|
|
27
27
|
if (!fs.existsSync(sourceFrameDir)) {
|
|
28
|
-
throw new Error(`project-frame not found: ${sourceFrameDir}`)
|
|
28
|
+
throw new Error(`project-frame not found: ${sourceFrameDir}`)
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
if (fs.existsSync(targetProjectDir)) {
|
|
32
|
-
throw new Error(`Target directory already exists: ${targetProjectDir}`)
|
|
32
|
+
throw new Error(`Target directory already exists: ${targetProjectDir}`)
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
copyDirectory(sourceFrameDir, targetProjectDir)
|
|
36
|
-
updatePackageName(targetProjectDir, projectName)
|
|
35
|
+
copyDirectory(sourceFrameDir, targetProjectDir)
|
|
36
|
+
updatePackageName(targetProjectDir, projectName)
|
|
37
37
|
|
|
38
|
-
console.log(`\n✅ Project created: ${targetProjectDir}`)
|
|
38
|
+
console.log(`\n✅ Project created: ${targetProjectDir}`)
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
try {
|
|
42
|
-
main()
|
|
42
|
+
main()
|
|
43
43
|
} catch (error) {
|
|
44
|
-
console.error(`\n❌ ${error.message}`)
|
|
45
|
-
process.exit(1)
|
|
44
|
+
console.error(`\n❌ ${error.message}`)
|
|
45
|
+
process.exit(1)
|
|
46
46
|
}
|
|
@@ -1,47 +1,67 @@
|
|
|
1
|
-
import { DbConnection } from
|
|
1
|
+
import { DbConnection } from '../../db-connection/db-connection-wrapper.ts'
|
|
2
2
|
// @ts-ignore kysely is supplied by the server package in generated projects.
|
|
3
|
-
import { KyselyWrapper } from
|
|
4
|
-
import { authenticationCoreLib } from
|
|
5
|
-
import {
|
|
3
|
+
import { KyselyWrapper } from '../../kysely/kysely-wrapper.ts'
|
|
4
|
+
import { authenticationCoreLib } from '../../@tobi2409/authentication-core-lib/authentication-core-lib-wrapper.ts'
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_TABLE_NAME,
|
|
7
|
+
DEFAULT_USER_COLUMN,
|
|
8
|
+
UserColumn
|
|
9
|
+
} from './shared.ts'
|
|
6
10
|
|
|
7
11
|
export namespace AuthenticationCoreKyselyCurrentUser {
|
|
8
12
|
export async function getCurrentUser(
|
|
9
13
|
token: string,
|
|
10
|
-
jwtKey: Parameters<
|
|
14
|
+
jwtKey: Parameters<
|
|
15
|
+
typeof authenticationCoreLib.AuthenticationCoreCurrentUser.getCurrentUser
|
|
16
|
+
>[1],
|
|
11
17
|
dbConnection: DbConnection,
|
|
12
18
|
tableName: string = DEFAULT_TABLE_NAME,
|
|
13
19
|
columns: UserColumn = DEFAULT_USER_COLUMN,
|
|
14
|
-
verifyOptions: Parameters<
|
|
20
|
+
verifyOptions: Parameters<
|
|
21
|
+
typeof authenticationCoreLib.AuthenticationCoreCurrentUser.getCurrentUser
|
|
22
|
+
>[3] = {}
|
|
15
23
|
): Promise<string> {
|
|
16
24
|
try {
|
|
17
|
-
const db = KyselyWrapper.buildQueryCompiler(
|
|
25
|
+
const db = KyselyWrapper.buildQueryCompiler(
|
|
26
|
+
dbConnection.getDriverName()
|
|
27
|
+
)
|
|
18
28
|
|
|
19
29
|
const isActiveCallback = async (uuid: string): Promise<boolean> => {
|
|
20
30
|
const compiledQuery = db
|
|
21
31
|
.selectFrom(tableName)
|
|
22
32
|
.select([
|
|
23
|
-
KyselyWrapper.sql.ref(columns.isActive).as(
|
|
33
|
+
KyselyWrapper.sql.ref(columns.isActive).as('isActive')
|
|
24
34
|
])
|
|
25
|
-
.where(KyselyWrapper.sql.ref(columns.uuid),
|
|
35
|
+
.where(KyselyWrapper.sql.ref(columns.uuid), '=', uuid)
|
|
26
36
|
.limit(1)
|
|
27
|
-
.compile()
|
|
37
|
+
.compile()
|
|
28
38
|
|
|
29
39
|
// 'any' is used instead of 'unknown' because calling .query() on an unknown type requires
|
|
30
40
|
// either a type assertion or a separate interface. DbConnection is driver-agnostic, so
|
|
31
41
|
// the concrete pool type (e.g. pg.Pool) is not available here without adding a dependency.
|
|
32
|
-
const connection = await dbConnection.getConnection() as any
|
|
33
|
-
const result = await connection.query(
|
|
42
|
+
const connection = (await dbConnection.getConnection()) as any
|
|
43
|
+
const result = await connection.query(
|
|
44
|
+
compiledQuery.sql,
|
|
45
|
+
compiledQuery.parameters
|
|
46
|
+
)
|
|
34
47
|
|
|
35
48
|
if (!result.rows?.[0]) {
|
|
36
|
-
return false
|
|
49
|
+
return false
|
|
37
50
|
}
|
|
38
51
|
|
|
39
|
-
return Boolean(
|
|
40
|
-
|
|
52
|
+
return Boolean(
|
|
53
|
+
(result.rows[0] as Record<string, unknown>).isActive
|
|
54
|
+
)
|
|
55
|
+
}
|
|
41
56
|
|
|
42
|
-
return authenticationCoreLib.AuthenticationCoreCurrentUser.getCurrentUser(
|
|
57
|
+
return authenticationCoreLib.AuthenticationCoreCurrentUser.getCurrentUser(
|
|
58
|
+
token,
|
|
59
|
+
jwtKey,
|
|
60
|
+
isActiveCallback,
|
|
61
|
+
verifyOptions
|
|
62
|
+
)
|
|
43
63
|
} catch (error) {
|
|
44
|
-
throw error
|
|
64
|
+
throw error
|
|
45
65
|
}
|
|
46
66
|
}
|
|
47
67
|
}
|
|
@@ -1,13 +1,19 @@
|
|
|
1
|
-
import { DbConnection } from
|
|
1
|
+
import { DbConnection } from '../../db-connection/db-connection-wrapper.ts'
|
|
2
2
|
// @ts-ignore kysely is supplied by the server package in generated projects.
|
|
3
|
-
import { KyselyWrapper } from
|
|
4
|
-
import { authenticationCoreLib } from
|
|
5
|
-
import {
|
|
3
|
+
import { KyselyWrapper } from '../../kysely/kysely-wrapper.ts'
|
|
4
|
+
import { authenticationCoreLib } from '../../@tobi2409/authentication-core-lib/authentication-core-lib-wrapper.ts'
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_TABLE_NAME,
|
|
7
|
+
DEFAULT_USER_COLUMN,
|
|
8
|
+
UserColumn
|
|
9
|
+
} from './shared.ts'
|
|
6
10
|
|
|
7
11
|
export namespace AuthenticationCoreKyselyLogin {
|
|
8
|
-
function mapFetchedUser(
|
|
12
|
+
function mapFetchedUser(
|
|
13
|
+
row: Record<string, unknown> | undefined
|
|
14
|
+
): authenticationCoreLib.FetchedUser | undefined {
|
|
9
15
|
if (!row) {
|
|
10
|
-
return undefined
|
|
16
|
+
return undefined
|
|
11
17
|
}
|
|
12
18
|
|
|
13
19
|
return {
|
|
@@ -15,43 +21,60 @@ export namespace AuthenticationCoreKyselyLogin {
|
|
|
15
21
|
mail: String(row.mail),
|
|
16
22
|
password: String(row.password),
|
|
17
23
|
isActive: Boolean(row.isActive)
|
|
18
|
-
}
|
|
24
|
+
}
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
export async function login(
|
|
22
28
|
typedMail: string,
|
|
23
29
|
typedPassword: string,
|
|
24
|
-
jwtKey: Parameters<
|
|
30
|
+
jwtKey: Parameters<
|
|
31
|
+
typeof authenticationCoreLib.AuthenticationCoreLogin.login
|
|
32
|
+
>[3],
|
|
25
33
|
dbConnection: DbConnection,
|
|
26
34
|
tableName: string = DEFAULT_TABLE_NAME,
|
|
27
35
|
columns: UserColumn = DEFAULT_USER_COLUMN,
|
|
28
|
-
jwtOptions: Parameters<
|
|
36
|
+
jwtOptions: Parameters<
|
|
37
|
+
typeof authenticationCoreLib.AuthenticationCoreLogin.login
|
|
38
|
+
>[4] = {}
|
|
29
39
|
): Promise<string> {
|
|
30
40
|
try {
|
|
31
|
-
const db = KyselyWrapper.buildQueryCompiler(
|
|
41
|
+
const db = KyselyWrapper.buildQueryCompiler(
|
|
42
|
+
dbConnection.getDriverName()
|
|
43
|
+
)
|
|
32
44
|
|
|
33
45
|
const compiledQuery = db
|
|
34
46
|
.selectFrom(tableName)
|
|
35
47
|
.select([
|
|
36
|
-
KyselyWrapper.sql.ref(columns.uuid).as(
|
|
37
|
-
KyselyWrapper.sql.ref(columns.mail).as(
|
|
38
|
-
KyselyWrapper.sql.ref(columns.password).as(
|
|
39
|
-
KyselyWrapper.sql.ref(columns.isActive).as(
|
|
48
|
+
KyselyWrapper.sql.ref(columns.uuid).as('uuid'),
|
|
49
|
+
KyselyWrapper.sql.ref(columns.mail).as('mail'),
|
|
50
|
+
KyselyWrapper.sql.ref(columns.password).as('password'),
|
|
51
|
+
KyselyWrapper.sql.ref(columns.isActive).as('isActive')
|
|
40
52
|
])
|
|
41
|
-
.where(KyselyWrapper.sql.ref(columns.mail),
|
|
53
|
+
.where(KyselyWrapper.sql.ref(columns.mail), '=', typedMail)
|
|
42
54
|
.limit(1)
|
|
43
|
-
.compile()
|
|
55
|
+
.compile()
|
|
44
56
|
|
|
45
57
|
// 'any' is used instead of 'unknown' because calling .query() on an unknown type requires
|
|
46
58
|
// either a type assertion or a separate interface. DbConnection is driver-agnostic, so
|
|
47
59
|
// the concrete pool type (e.g. pg.Pool) is not available here without adding a dependency.
|
|
48
|
-
const connection = await dbConnection.getConnection() as any
|
|
49
|
-
const result = await connection.query(
|
|
60
|
+
const connection = (await dbConnection.getConnection()) as any
|
|
61
|
+
const result = await connection.query(
|
|
62
|
+
compiledQuery.sql,
|
|
63
|
+
compiledQuery.parameters
|
|
64
|
+
)
|
|
50
65
|
|
|
51
|
-
const fetchedUser = mapFetchedUser(
|
|
52
|
-
|
|
66
|
+
const fetchedUser = mapFetchedUser(
|
|
67
|
+
result.rows?.[0] as Record<string, unknown> | undefined
|
|
68
|
+
)
|
|
69
|
+
return authenticationCoreLib.AuthenticationCoreLogin.login(
|
|
70
|
+
typedMail,
|
|
71
|
+
typedPassword,
|
|
72
|
+
fetchedUser,
|
|
73
|
+
jwtKey,
|
|
74
|
+
jwtOptions
|
|
75
|
+
)
|
|
53
76
|
} catch (error) {
|
|
54
|
-
throw error
|
|
77
|
+
throw error
|
|
55
78
|
}
|
|
56
79
|
}
|
|
57
|
-
}
|
|
80
|
+
}
|
|
@@ -1,13 +1,19 @@
|
|
|
1
|
-
import { DbConnection } from
|
|
1
|
+
import { DbConnection } from '../../db-connection/db-connection-wrapper.ts'
|
|
2
2
|
// @ts-ignore kysely is supplied by the server package in generated projects.
|
|
3
|
-
import { KyselyWrapper } from
|
|
4
|
-
import { authenticationCoreLib } from
|
|
5
|
-
import {
|
|
3
|
+
import { KyselyWrapper } from '../../kysely/kysely-wrapper.ts'
|
|
4
|
+
import { authenticationCoreLib } from '../../@tobi2409/authentication-core-lib/authentication-core-lib-wrapper.ts'
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_TABLE_NAME,
|
|
7
|
+
DEFAULT_USER_COLUMN,
|
|
8
|
+
UserColumn
|
|
9
|
+
} from './shared.ts'
|
|
6
10
|
|
|
7
11
|
export namespace AuthenticationCoreKyselyRegister {
|
|
8
|
-
function mapFetchedUser(
|
|
12
|
+
function mapFetchedUser(
|
|
13
|
+
row: Record<string, unknown> | undefined
|
|
14
|
+
): authenticationCoreLib.FetchedUser | undefined {
|
|
9
15
|
if (!row) {
|
|
10
|
-
return undefined
|
|
16
|
+
return undefined
|
|
11
17
|
}
|
|
12
18
|
|
|
13
19
|
return {
|
|
@@ -15,7 +21,7 @@ export namespace AuthenticationCoreKyselyRegister {
|
|
|
15
21
|
mail: String(row.mail),
|
|
16
22
|
password: String(row.password),
|
|
17
23
|
isActive: Boolean(row.isActive)
|
|
18
|
-
}
|
|
24
|
+
}
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
export async function register(
|
|
@@ -25,29 +31,38 @@ export namespace AuthenticationCoreKyselyRegister {
|
|
|
25
31
|
dbConnection: DbConnection,
|
|
26
32
|
tableName: string = DEFAULT_TABLE_NAME,
|
|
27
33
|
columns: UserColumn = DEFAULT_USER_COLUMN,
|
|
28
|
-
mailTransportConfig?: Parameters<
|
|
29
|
-
|
|
34
|
+
mailTransportConfig?: Parameters<
|
|
35
|
+
typeof authenticationCoreLib.AuthenticationCoreRegister.register
|
|
36
|
+
>[5],
|
|
37
|
+
hashOptions?: Parameters<
|
|
38
|
+
typeof authenticationCoreLib.AuthenticationCoreRegister.register
|
|
39
|
+
>[6]
|
|
30
40
|
): Promise<authenticationCoreLib.FetchedUser> {
|
|
31
|
-
const db = KyselyWrapper.buildQueryCompiler(
|
|
41
|
+
const db = KyselyWrapper.buildQueryCompiler(
|
|
42
|
+
dbConnection.getDriverName()
|
|
43
|
+
)
|
|
32
44
|
|
|
33
45
|
// 'any' is used instead of 'unknown' because calling pool/client APIs on an unknown type requires
|
|
34
46
|
// either a type assertion or a separate interface. DbConnection is driver-agnostic, so
|
|
35
47
|
// the concrete pool/client type (e.g. pg.Pool/PoolClient) is not available here without adding a dependency.
|
|
36
|
-
const pool = await dbConnection.getConnection() as any
|
|
37
|
-
const transactionConnection = await pool.connect()
|
|
48
|
+
const pool = (await dbConnection.getConnection()) as any
|
|
49
|
+
const transactionConnection = await pool.connect()
|
|
38
50
|
|
|
39
51
|
const mailExistsRoutine = async (mail: string): Promise<boolean> => {
|
|
40
52
|
const compiledQuery = db
|
|
41
53
|
.selectFrom(tableName)
|
|
42
|
-
.select((eb: any) => [eb.val(1).as(
|
|
43
|
-
.where(KyselyWrapper.sql.ref(columns.mail),
|
|
54
|
+
.select((eb: any) => [eb.val(1).as('exists')])
|
|
55
|
+
.where(KyselyWrapper.sql.ref(columns.mail), '=', mail)
|
|
44
56
|
.limit(1)
|
|
45
|
-
.compile()
|
|
57
|
+
.compile()
|
|
46
58
|
|
|
47
|
-
const result = await transactionConnection.query(
|
|
59
|
+
const result = await transactionConnection.query(
|
|
60
|
+
compiledQuery.sql,
|
|
61
|
+
compiledQuery.parameters
|
|
62
|
+
)
|
|
48
63
|
|
|
49
|
-
return (result.rows?.length ?? 0) > 0
|
|
50
|
-
}
|
|
64
|
+
return (result.rows?.length ?? 0) > 0
|
|
65
|
+
}
|
|
51
66
|
|
|
52
67
|
const dataProcessing = async (
|
|
53
68
|
identification: string,
|
|
@@ -59,50 +74,56 @@ export namespace AuthenticationCoreKyselyRegister {
|
|
|
59
74
|
[columns.password]: hashedPassword,
|
|
60
75
|
[columns.isActive]: false,
|
|
61
76
|
...inputData
|
|
62
|
-
}
|
|
77
|
+
}
|
|
63
78
|
|
|
64
79
|
const compiledQuery = db
|
|
65
80
|
.insertInto(tableName)
|
|
66
81
|
.values(userData as any)
|
|
67
82
|
.returning([
|
|
68
|
-
KyselyWrapper.sql.ref(columns.uuid).as(
|
|
69
|
-
KyselyWrapper.sql.ref(columns.mail).as(
|
|
70
|
-
KyselyWrapper.sql.ref(columns.password).as(
|
|
71
|
-
KyselyWrapper.sql.ref(columns.isActive).as(
|
|
83
|
+
KyselyWrapper.sql.ref(columns.uuid).as('uuid'),
|
|
84
|
+
KyselyWrapper.sql.ref(columns.mail).as('mail'),
|
|
85
|
+
KyselyWrapper.sql.ref(columns.password).as('password'),
|
|
86
|
+
KyselyWrapper.sql.ref(columns.isActive).as('isActive')
|
|
72
87
|
])
|
|
73
|
-
.compile()
|
|
88
|
+
.compile()
|
|
74
89
|
|
|
75
|
-
const result = await transactionConnection.query(
|
|
76
|
-
|
|
77
|
-
|
|
90
|
+
const result = await transactionConnection.query(
|
|
91
|
+
compiledQuery.sql,
|
|
92
|
+
compiledQuery.parameters
|
|
93
|
+
)
|
|
94
|
+
return mapFetchedUser(
|
|
95
|
+
result.rows?.[0] as Record<string, unknown> | undefined
|
|
96
|
+
)
|
|
97
|
+
}
|
|
78
98
|
|
|
79
99
|
try {
|
|
80
|
-
await transactionConnection.query(
|
|
100
|
+
await transactionConnection.query('BEGIN')
|
|
81
101
|
|
|
82
|
-
const registeredUser =
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
102
|
+
const registeredUser =
|
|
103
|
+
await authenticationCoreLib.AuthenticationCoreRegister.register(
|
|
104
|
+
registrationInputData,
|
|
105
|
+
mailExistsRoutine,
|
|
106
|
+
customInputData,
|
|
107
|
+
dataProcessing,
|
|
108
|
+
verificationMail,
|
|
109
|
+
mailTransportConfig,
|
|
110
|
+
hashOptions
|
|
111
|
+
)
|
|
91
112
|
|
|
92
|
-
await transactionConnection.query(
|
|
93
|
-
return registeredUser
|
|
113
|
+
await transactionConnection.query('COMMIT')
|
|
114
|
+
return registeredUser
|
|
94
115
|
} catch (error) {
|
|
95
116
|
try {
|
|
96
|
-
await transactionConnection.query(
|
|
117
|
+
await transactionConnection.query('ROLLBACK')
|
|
97
118
|
} catch {
|
|
98
119
|
// Ignore rollback errors, original error is more relevant.
|
|
99
120
|
}
|
|
100
121
|
|
|
101
|
-
throw error
|
|
122
|
+
throw error
|
|
102
123
|
} finally {
|
|
103
|
-
if (typeof transactionConnection.release ===
|
|
104
|
-
transactionConnection.release()
|
|
124
|
+
if (typeof transactionConnection.release === 'function') {
|
|
125
|
+
transactionConnection.release()
|
|
105
126
|
}
|
|
106
127
|
}
|
|
107
128
|
}
|
|
108
|
-
}
|
|
129
|
+
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
export type UserColumn = {
|
|
2
|
-
uuid: string
|
|
3
|
-
mail: string
|
|
4
|
-
password: string
|
|
5
|
-
isActive: string
|
|
6
|
-
}
|
|
2
|
+
uuid: string
|
|
3
|
+
mail: string
|
|
4
|
+
password: string
|
|
5
|
+
isActive: string
|
|
6
|
+
}
|
|
7
7
|
|
|
8
|
-
export const DEFAULT_TABLE_NAME =
|
|
8
|
+
export const DEFAULT_TABLE_NAME = 'users'
|
|
9
9
|
|
|
10
10
|
export const DEFAULT_USER_COLUMN: UserColumn = {
|
|
11
|
-
uuid:
|
|
12
|
-
mail:
|
|
13
|
-
password:
|
|
14
|
-
isActive:
|
|
15
|
-
}
|
|
11
|
+
uuid: 'uuid',
|
|
12
|
+
mail: 'mail',
|
|
13
|
+
password: 'password',
|
|
14
|
+
isActive: 'isActive'
|
|
15
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// @ts-ignore package is supplied by the server package in generated projects.
|
|
2
|
-
export * as authenticationCoreLib from
|
|
2
|
+
export * as authenticationCoreLib from '@tobi2409/authentication-core-lib'
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
type DriverName =
|
|
1
|
+
type DriverName = 'pg' | 'mysql' | 'sqlite'
|
|
2
2
|
|
|
3
3
|
interface ConnectionData {
|
|
4
|
-
connectionName?: string
|
|
5
|
-
}
|
|
4
|
+
connectionName?: string
|
|
5
|
+
}
|
|
6
6
|
|
|
7
7
|
export class DbConnection {
|
|
8
|
-
private connectionWrapper?: unknown
|
|
8
|
+
private connectionWrapper?: unknown
|
|
9
9
|
|
|
10
10
|
constructor(
|
|
11
11
|
private readonly driverName: DriverName,
|
|
@@ -13,32 +13,35 @@ export class DbConnection {
|
|
|
13
13
|
) {}
|
|
14
14
|
|
|
15
15
|
getDriverName(): DriverName {
|
|
16
|
-
return this.driverName
|
|
16
|
+
return this.driverName
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
async getConnection(): Promise<unknown> {
|
|
20
|
-
if (this.driverName ===
|
|
20
|
+
if (this.driverName === 'pg') {
|
|
21
21
|
if (!this.connectionWrapper) {
|
|
22
22
|
// @ts-ignore pg-wrapper is supplied by the pg extension in generated projects.
|
|
23
|
-
const { PgConnectionWrapper } =
|
|
24
|
-
|
|
23
|
+
const { PgConnectionWrapper } =
|
|
24
|
+
await import('../pg/pg-wrapper.ts')
|
|
25
|
+
this.connectionWrapper = new PgConnectionWrapper(
|
|
26
|
+
this.connectionData.connectionName ?? 'default'
|
|
27
|
+
)
|
|
25
28
|
}
|
|
26
29
|
|
|
27
|
-
return (this.connectionWrapper as any).getPgPool()
|
|
30
|
+
return (this.connectionWrapper as any).getPgPool()
|
|
28
31
|
}
|
|
29
32
|
|
|
30
|
-
throw new Error(`Driver is not integrated yet: ${this.driverName}`)
|
|
33
|
+
throw new Error(`Driver is not integrated yet: ${this.driverName}`)
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
async close(): Promise<void> {
|
|
34
|
-
if (this.driverName ===
|
|
37
|
+
if (this.driverName === 'pg') {
|
|
35
38
|
if (this.connectionWrapper) {
|
|
36
|
-
await (this.connectionWrapper as any).closePool()
|
|
39
|
+
await (this.connectionWrapper as any).closePool()
|
|
37
40
|
}
|
|
38
41
|
|
|
39
|
-
return
|
|
42
|
+
return
|
|
40
43
|
}
|
|
41
44
|
|
|
42
|
-
throw new Error(`Driver is not integrated yet: ${this.driverName}`)
|
|
45
|
+
throw new Error(`Driver is not integrated yet: ${this.driverName}`)
|
|
43
46
|
}
|
|
44
47
|
}
|
|
@@ -1,29 +1,31 @@
|
|
|
1
1
|
// @ts-ignore kysely is supplied by the server package in generated projects.
|
|
2
|
-
import * as k from
|
|
2
|
+
import * as k from 'kysely'
|
|
3
3
|
|
|
4
|
-
type DriverName =
|
|
4
|
+
type DriverName = 'pg' | 'mysql' | 'sqlite'
|
|
5
5
|
|
|
6
6
|
export namespace KyselyWrapper {
|
|
7
|
-
|
|
7
|
+
export const sql = k.sql
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
export function getKysely(): typeof k {
|
|
10
|
+
return k
|
|
11
|
+
}
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
export function buildQueryCompiler(driverName: DriverName): k.Kysely<any> {
|
|
14
|
+
const kysely = getKysely() as any
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
if (driverName === 'pg') {
|
|
17
|
+
return new kysely.Kysely({
|
|
18
|
+
dialect: {
|
|
19
|
+
createAdapter: () => new kysely.PostgresAdapter(),
|
|
20
|
+
createDriver: () => new kysely.DummyDriver(),
|
|
21
|
+
createIntrospector: (db: any) =>
|
|
22
|
+
new kysely.PostgresIntrospector(db),
|
|
23
|
+
createQueryCompiler: () =>
|
|
24
|
+
new kysely.PostgresQueryCompiler()
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
}
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
throw new Error(`Driver is not integrated yet: ${driverName}`)
|
|
30
|
+
}
|
|
29
31
|
}
|
|
@@ -1,73 +1,77 @@
|
|
|
1
1
|
// @ts-ignore pg dependencies are supplied by the server package in generated projects.
|
|
2
|
-
import dotenv from
|
|
2
|
+
import dotenv from 'dotenv'
|
|
3
3
|
// @ts-ignore node types are supplied by the server package in generated projects.
|
|
4
|
-
import fs from
|
|
4
|
+
import fs from 'fs'
|
|
5
5
|
// @ts-ignore node types are supplied by the server package in generated projects.
|
|
6
|
-
import path from
|
|
6
|
+
import path from 'path'
|
|
7
7
|
// @ts-ignore pg dependencies are supplied by the server package in generated projects.
|
|
8
|
-
import { Pool } from
|
|
8
|
+
import { Pool } from 'pg'
|
|
9
9
|
// @ts-ignore node types are supplied by the server package in generated projects.
|
|
10
|
-
import { fileURLToPath } from
|
|
10
|
+
import { fileURLToPath } from 'url'
|
|
11
11
|
|
|
12
12
|
export class PgConnectionWrapper {
|
|
13
|
-
private pool: Pool | null = null
|
|
14
|
-
private pgEnv: Record<string, string> = {}
|
|
15
|
-
private pgEnvLoaded: boolean = false
|
|
16
|
-
private readonly wrapperDir: string = path.dirname(
|
|
17
|
-
|
|
13
|
+
private pool: Pool | null = null
|
|
14
|
+
private pgEnv: Record<string, string> = {}
|
|
15
|
+
private pgEnvLoaded: boolean = false
|
|
16
|
+
private readonly wrapperDir: string = path.dirname(
|
|
17
|
+
fileURLToPath(import.meta.url)
|
|
18
|
+
)
|
|
19
|
+
private readonly envFilePath: string = path.join(this.wrapperDir, '.env.pg')
|
|
18
20
|
|
|
19
|
-
constructor(private readonly connectionName: string =
|
|
21
|
+
constructor(private readonly connectionName: string = 'default') {}
|
|
20
22
|
|
|
21
23
|
private envKey(key: string): string {
|
|
22
|
-
const prefix = this.connectionName
|
|
23
|
-
|
|
24
|
+
const prefix = this.connectionName
|
|
25
|
+
.toUpperCase()
|
|
26
|
+
.replace(/[^A-Z0-9_]/g, '_')
|
|
27
|
+
return `${prefix}_${key}`
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
private loadPgEnv(): void {
|
|
27
31
|
// only load the .env.pg file once
|
|
28
32
|
if (this.pgEnvLoaded) {
|
|
29
|
-
return
|
|
33
|
+
return
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
// Load .env.pg into a local cache instead of writing into process.env.
|
|
33
37
|
// This keeps the pg wrapper isolated from other wrappers such as auth,
|
|
34
38
|
// which may load their own .env files later.
|
|
35
39
|
const envFileContent = fs.existsSync(this.envFilePath)
|
|
36
|
-
? fs.readFileSync(this.envFilePath,
|
|
37
|
-
:
|
|
40
|
+
? fs.readFileSync(this.envFilePath, 'utf8')
|
|
41
|
+
: ''
|
|
38
42
|
|
|
39
|
-
this.pgEnv = dotenv.parse(envFileContent)
|
|
40
|
-
this.pgEnvLoaded = true
|
|
43
|
+
this.pgEnv = dotenv.parse(envFileContent)
|
|
44
|
+
this.pgEnvLoaded = true
|
|
41
45
|
}
|
|
42
46
|
|
|
43
47
|
private getEnv(key: string, fallback?: string): string {
|
|
44
|
-
this.loadPgEnv()
|
|
45
|
-
return this.pgEnv[this.envKey(key)] ?? fallback ??
|
|
48
|
+
this.loadPgEnv()
|
|
49
|
+
return this.pgEnv[this.envKey(key)] ?? fallback ?? ''
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
getPgPool(): Pool {
|
|
49
53
|
if (this.pool) {
|
|
50
|
-
return this.pool
|
|
54
|
+
return this.pool
|
|
51
55
|
}
|
|
52
56
|
|
|
53
57
|
this.pool = new Pool({
|
|
54
|
-
host: this.getEnv(
|
|
55
|
-
port: Number(this.getEnv(
|
|
56
|
-
user: this.getEnv(
|
|
57
|
-
password: this.getEnv(
|
|
58
|
-
database: this.getEnv(
|
|
59
|
-
max: Number(this.getEnv(
|
|
60
|
-
})
|
|
58
|
+
host: this.getEnv('PGHOST', '127.0.0.1'),
|
|
59
|
+
port: Number(this.getEnv('PGPORT', '5432')),
|
|
60
|
+
user: this.getEnv('PGUSER', 'postgres'),
|
|
61
|
+
password: this.getEnv('PGPASSWORD', 'postgres'),
|
|
62
|
+
database: this.getEnv('PGDATABASE', 'fullsty_demo'),
|
|
63
|
+
max: Number(this.getEnv('POOL_MAX', '10'))
|
|
64
|
+
})
|
|
61
65
|
|
|
62
|
-
return this.pool
|
|
66
|
+
return this.pool
|
|
63
67
|
}
|
|
64
68
|
|
|
65
69
|
async closePool(): Promise<void> {
|
|
66
70
|
if (!this.pool) {
|
|
67
|
-
return
|
|
71
|
+
return
|
|
68
72
|
}
|
|
69
73
|
|
|
70
|
-
await this.pool.end()
|
|
71
|
-
this.pool = null
|
|
74
|
+
await this.pool.end()
|
|
75
|
+
this.pool = null
|
|
72
76
|
}
|
|
73
|
-
}
|
|
77
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { default as sql } from
|
|
1
|
+
export { default as sql } from 'sql-template-tag'
|
|
@@ -1,46 +1,46 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import path from
|
|
4
|
-
import { handleAdd, handleRemove } from
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import { handleAdd, handleRemove } from './shared/server-package-utils.js'
|
|
5
5
|
|
|
6
6
|
function main() {
|
|
7
|
-
const args = process.argv.slice(2)
|
|
8
|
-
const command = args[0]
|
|
7
|
+
const args = process.argv.slice(2)
|
|
8
|
+
const command = args[0]
|
|
9
9
|
|
|
10
10
|
if (!command) {
|
|
11
11
|
console.log(
|
|
12
|
-
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
process.exit(1)
|
|
12
|
+
'Usage: node fullsty-server-pkg.js <add|remove> <package-name> [more-package-names...]'
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
process.exit(1)
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const scriptDir = path.dirname(new URL(import.meta.url).pathname)
|
|
19
|
-
const projectDir = process.cwd()
|
|
20
|
-
const serverDir = path.join(projectDir,
|
|
21
|
-
const packageNames = args.slice(1).filter((arg) => !arg.startsWith(
|
|
18
|
+
const scriptDir = path.dirname(new URL(import.meta.url).pathname)
|
|
19
|
+
const projectDir = process.cwd()
|
|
20
|
+
const serverDir = path.join(projectDir, 'src', 'server')
|
|
21
|
+
const packageNames = args.slice(1).filter((arg) => !arg.startsWith('-'))
|
|
22
22
|
|
|
23
|
-
if (packageNames.length === 0 || ![
|
|
23
|
+
if (packageNames.length === 0 || !['add', 'remove'].includes(command)) {
|
|
24
24
|
console.log(
|
|
25
|
-
|
|
26
|
-
)
|
|
25
|
+
'Usage: node fullsty-server-pkg.js <add|remove> <package-name> [more-package-names...]'
|
|
26
|
+
)
|
|
27
27
|
|
|
28
|
-
process.exit(1)
|
|
28
|
+
process.exit(1)
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
if (command ===
|
|
32
|
-
handleAdd(projectDir, scriptDir, serverDir, packageNames)
|
|
33
|
-
return
|
|
31
|
+
if (command === 'add') {
|
|
32
|
+
handleAdd(projectDir, scriptDir, serverDir, packageNames)
|
|
33
|
+
return
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
handleRemove(projectDir, scriptDir, serverDir, packageNames)
|
|
36
|
+
handleRemove(projectDir, scriptDir, serverDir, packageNames)
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
try {
|
|
40
|
-
main()
|
|
40
|
+
main()
|
|
41
41
|
} catch (error) {
|
|
42
42
|
console.error(
|
|
43
|
-
`\n❌ ${error instanceof Error ? error.message : String(error)}
|
|
44
|
-
)
|
|
45
|
-
process.exit(1)
|
|
46
|
-
}
|
|
43
|
+
`\n❌ ${error instanceof Error ? error.message : String(error)}`
|
|
44
|
+
)
|
|
45
|
+
process.exit(1)
|
|
46
|
+
}
|
|
@@ -6,16 +6,25 @@
|
|
|
6
6
|
"namespace": "Server"
|
|
7
7
|
}
|
|
8
8
|
],
|
|
9
|
+
"rawServerFiles": [],
|
|
9
10
|
"apiUrl": "http://localhost:3000",
|
|
10
11
|
"cors": {
|
|
11
|
-
"
|
|
12
|
+
"allowOrigin": "*"
|
|
12
13
|
},
|
|
14
|
+
"rateLimit": {
|
|
15
|
+
"windowDurationMs": 900000,
|
|
16
|
+
"maxRequests": 100,
|
|
17
|
+
"useStandardHeaders": true,
|
|
18
|
+
"useLegacyHeaders": false
|
|
19
|
+
},
|
|
20
|
+
"errorStatusCodes": {},
|
|
13
21
|
"method-rules": {
|
|
14
22
|
"GET": "^(get|list|find)",
|
|
15
23
|
"POST": "^(create|add|insert)",
|
|
16
24
|
"PATCH": "^(update|set|patch)",
|
|
17
25
|
"DELETE": "^(delete|remove)"
|
|
18
26
|
},
|
|
27
|
+
"method-rules-custom-functions": {},
|
|
19
28
|
"binaryTypes": [
|
|
20
29
|
"ArrayBuffer",
|
|
21
30
|
"Uint8Array",
|
|
@@ -6,11 +6,12 @@
|
|
|
6
6
|
"start": "tsx routes.generated.ts"
|
|
7
7
|
},
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@msgpack/msgpack": "^3.
|
|
10
|
-
"cors": "^2.8.
|
|
11
|
-
"dotenv": "^
|
|
9
|
+
"@msgpack/msgpack": "^3.1.3",
|
|
10
|
+
"cors": "^2.8.6",
|
|
11
|
+
"dotenv": "^17.4.2",
|
|
12
12
|
"express": "^5.2.1",
|
|
13
|
-
"express-rate-limit": "^
|
|
13
|
+
"express-rate-limit": "^8.3.1",
|
|
14
|
+
"helmet": "^8.3.0"
|
|
14
15
|
},
|
|
15
16
|
"devDependencies": {
|
|
16
17
|
"@types/cors": "^2.8.17",
|
|
@@ -1,36 +1,40 @@
|
|
|
1
|
-
<!
|
|
1
|
+
<!doctype html>
|
|
2
2
|
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Document</title>
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
<script type="importmap">
|
|
9
|
+
{
|
|
10
|
+
"imports": {
|
|
11
|
+
"template-engine/": "./node_modules/@tobi2409/template-engine/src/"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
</script>
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
<link
|
|
17
|
+
rel="stylesheet"
|
|
18
|
+
href="node_modules/@tobi2409/layout/src/layout.css"
|
|
19
|
+
/>
|
|
20
|
+
<link rel="stylesheet" href="index.css" />
|
|
21
|
+
</head>
|
|
22
|
+
<body>
|
|
23
|
+
<div id="app"></div>
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
<template id="app-template">
|
|
26
|
+
<h1>👋 Hello, <get>name</get></h1>
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
<p><get>serverMessage</get></p>
|
|
29
|
+
</template>
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
<template-use
|
|
32
|
+
id="app-template-use"
|
|
33
|
+
template-id="app-template"
|
|
34
|
+
mount-id="app"
|
|
35
|
+
>
|
|
36
|
+
</template-use>
|
|
32
37
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
</
|
|
36
|
-
</html>
|
|
38
|
+
<script type="module" src="index.js"></script>
|
|
39
|
+
</body>
|
|
40
|
+
</html>
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import TemplateEngine from 'template-engine/template-engine.js'
|
|
4
4
|
|
|
5
|
-
const data = TemplateEngine.reactive(
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
const data = TemplateEngine.reactive(
|
|
6
|
+
{
|
|
7
|
+
name: 'my friend',
|
|
8
|
+
serverMessage: 'Server not responded yet'
|
|
9
|
+
},
|
|
10
|
+
document.getElementById('app-template-use')
|
|
11
|
+
)
|
|
9
12
|
|
|
10
|
-
data.serverMessage = await Server.helloFromServer('my friend')
|
|
13
|
+
data.serverMessage = await Server.helloFromServer('my friend')
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import fs from
|
|
2
|
-
import path from
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
3
|
|
|
4
4
|
// Recursively copy a directory and all nested files/folders.
|
|
5
5
|
export function copyDirectory(sourceDir, targetDir, options = {}) {
|
|
6
|
-
const excludedNames = new Set(options.excludedNames ?? [])
|
|
6
|
+
const excludedNames = new Set(options.excludedNames ?? [])
|
|
7
7
|
|
|
8
|
-
fs.mkdirSync(targetDir, { recursive: true })
|
|
9
|
-
const entries = fs.readdirSync(sourceDir, { withFileTypes: true })
|
|
8
|
+
fs.mkdirSync(targetDir, { recursive: true })
|
|
9
|
+
const entries = fs.readdirSync(sourceDir, { withFileTypes: true })
|
|
10
10
|
|
|
11
11
|
for (const entry of entries) {
|
|
12
12
|
if (excludedNames.has(entry.name)) {
|
|
13
|
-
continue
|
|
13
|
+
continue
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
const sourcePath = path.join(sourceDir, entry.name)
|
|
17
|
-
const targetPath = path.join(targetDir, entry.name)
|
|
16
|
+
const sourcePath = path.join(sourceDir, entry.name)
|
|
17
|
+
const targetPath = path.join(targetDir, entry.name)
|
|
18
18
|
|
|
19
19
|
if (entry.isDirectory()) {
|
|
20
|
-
copyDirectory(sourcePath, targetPath, options)
|
|
20
|
+
copyDirectory(sourcePath, targetPath, options)
|
|
21
21
|
} else {
|
|
22
|
-
fs.copyFileSync(sourcePath, targetPath)
|
|
22
|
+
fs.copyFileSync(sourcePath, targetPath)
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
-
}
|
|
25
|
+
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import fs from
|
|
2
|
-
import path from
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
3
|
|
|
4
4
|
export function readJson(projectDir, fileName) {
|
|
5
|
-
const filePath = path.join(projectDir, fileName)
|
|
5
|
+
const filePath = path.join(projectDir, fileName)
|
|
6
6
|
|
|
7
7
|
if (!fs.existsSync(filePath)) {
|
|
8
|
-
throw new Error(`${fileName} not found: ${filePath}`)
|
|
8
|
+
throw new Error(`${fileName} not found: ${filePath}`)
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
return JSON.parse(fs.readFileSync(filePath,
|
|
11
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'))
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export function writeJson(projectDir, fileName, json) {
|
|
15
|
-
const filePath = path.join(projectDir, fileName)
|
|
16
|
-
fs.writeFileSync(filePath, `${JSON.stringify(json, null, 2)}\n`,
|
|
17
|
-
}
|
|
15
|
+
const filePath = path.join(projectDir, fileName)
|
|
16
|
+
fs.writeFileSync(filePath, `${JSON.stringify(json, null, 2)}\n`, 'utf8')
|
|
17
|
+
}
|
|
@@ -1,84 +1,86 @@
|
|
|
1
|
-
import fs from
|
|
2
|
-
import path from
|
|
3
|
-
import { copyDirectory } from
|
|
4
|
-
import { readJson, writeJson } from
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { copyDirectory } from './file-copy.js'
|
|
4
|
+
import { readJson, writeJson } from './project-json-utils.js'
|
|
5
5
|
|
|
6
|
-
const EXTENSION_CONFIG_FILE =
|
|
6
|
+
const EXTENSION_CONFIG_FILE = 'extension.json'
|
|
7
7
|
|
|
8
8
|
function wrapperDirFromPackage(scriptDir, packageName) {
|
|
9
|
-
return path.join(scriptDir,
|
|
9
|
+
return path.join(scriptDir, 'extension-wrappers', packageName)
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
function shouldAddToServerPackage(wrapperDir) {
|
|
13
|
-
const extensionConfig = readJson(wrapperDir, EXTENSION_CONFIG_FILE)
|
|
14
|
-
return extensionConfig.addToServerPackage
|
|
13
|
+
const extensionConfig = readJson(wrapperDir, EXTENSION_CONFIG_FILE)
|
|
14
|
+
return extensionConfig.addToServerPackage
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export function handleAdd(projectDir, scriptDir, serverDir, packageNames) {
|
|
18
|
-
const json = readJson(projectDir,
|
|
18
|
+
const json = readJson(projectDir, 'server-package.json')
|
|
19
19
|
|
|
20
|
-
if (!json.dependencies || typeof json.dependencies !==
|
|
21
|
-
json.dependencies = {}
|
|
20
|
+
if (!json.dependencies || typeof json.dependencies !== 'object') {
|
|
21
|
+
json.dependencies = {}
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
for (const packageName of packageNames) {
|
|
25
|
-
const wrapperDir = wrapperDirFromPackage(scriptDir, packageName)
|
|
26
|
-
const targetWrapperDir = path.join(serverDir, packageName)
|
|
27
|
-
const wrapperExists = fs.existsSync(wrapperDir)
|
|
25
|
+
const wrapperDir = wrapperDirFromPackage(scriptDir, packageName)
|
|
26
|
+
const targetWrapperDir = path.join(serverDir, packageName)
|
|
27
|
+
const wrapperExists = fs.existsSync(wrapperDir)
|
|
28
28
|
|
|
29
29
|
if (!wrapperExists) {
|
|
30
|
-
json.dependencies[packageName] =
|
|
31
|
-
continue
|
|
30
|
+
json.dependencies[packageName] = 'latest'
|
|
31
|
+
continue
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
if (shouldAddToServerPackage(wrapperDir)) {
|
|
35
|
-
json.dependencies[packageName] =
|
|
35
|
+
json.dependencies[packageName] = 'latest'
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
copyDirectory(wrapperDir, targetWrapperDir, {
|
|
39
|
-
excludedNames: [EXTENSION_CONFIG_FILE]
|
|
40
|
-
})
|
|
41
|
-
console.log(
|
|
39
|
+
excludedNames: [EXTENSION_CONFIG_FILE]
|
|
40
|
+
})
|
|
41
|
+
console.log(
|
|
42
|
+
`\n✅ Copied wrapper for ${packageName} to src/server/${packageName}`
|
|
43
|
+
)
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
writeJson(projectDir,
|
|
46
|
+
writeJson(projectDir, 'server-package.json', json)
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
export function handleRemove(projectDir, scriptDir, serverDir, packageNames) {
|
|
48
|
-
const json = readJson(projectDir,
|
|
50
|
+
const json = readJson(projectDir, 'server-package.json')
|
|
49
51
|
|
|
50
|
-
if (!json.dependencies || typeof json.dependencies !==
|
|
51
|
-
json.dependencies = {}
|
|
52
|
+
if (!json.dependencies || typeof json.dependencies !== 'object') {
|
|
53
|
+
json.dependencies = {}
|
|
52
54
|
}
|
|
53
55
|
|
|
54
|
-
if (!json.devDependencies || typeof json.devDependencies !==
|
|
55
|
-
json.devDependencies = {}
|
|
56
|
+
if (!json.devDependencies || typeof json.devDependencies !== 'object') {
|
|
57
|
+
json.devDependencies = {}
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
for (const packageName of packageNames) {
|
|
59
|
-
const wrapperDir = wrapperDirFromPackage(scriptDir, packageName)
|
|
60
|
-
const targetWrapperDir = path.join(serverDir, packageName)
|
|
61
|
-
const wrapperExists = fs.existsSync(wrapperDir)
|
|
61
|
+
const wrapperDir = wrapperDirFromPackage(scriptDir, packageName)
|
|
62
|
+
const targetWrapperDir = path.join(serverDir, packageName)
|
|
63
|
+
const wrapperExists = fs.existsSync(wrapperDir)
|
|
62
64
|
|
|
63
65
|
if (!wrapperExists) {
|
|
64
|
-
delete json.dependencies[packageName]
|
|
65
|
-
delete json.devDependencies[packageName]
|
|
66
|
-
continue
|
|
66
|
+
delete json.dependencies[packageName]
|
|
67
|
+
delete json.devDependencies[packageName]
|
|
68
|
+
continue
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
if (shouldAddToServerPackage(wrapperDir)) {
|
|
70
|
-
delete json.dependencies[packageName]
|
|
71
|
-
delete json.devDependencies[packageName]
|
|
72
|
+
delete json.dependencies[packageName]
|
|
73
|
+
delete json.devDependencies[packageName]
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
if (fs.existsSync(targetWrapperDir)) {
|
|
75
|
-
fs.rmSync(targetWrapperDir, { recursive: true, force: true })
|
|
77
|
+
fs.rmSync(targetWrapperDir, { recursive: true, force: true })
|
|
76
78
|
|
|
77
79
|
console.log(
|
|
78
|
-
`\n✅ Removed wrapper for ${packageName} from src/server/${packageName}
|
|
79
|
-
)
|
|
80
|
+
`\n✅ Removed wrapper for ${packageName} from src/server/${packageName}`
|
|
81
|
+
)
|
|
80
82
|
}
|
|
81
83
|
}
|
|
82
84
|
|
|
83
|
-
writeJson(projectDir,
|
|
84
|
-
}
|
|
85
|
+
writeJson(projectDir, 'server-package.json', json)
|
|
86
|
+
}
|