@wirechunk/cli 0.0.1-rc.3 → 0.0.2
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/build/main.js +4157 -454
- package/package.json +6 -1
- package/src/commands/bootstrap.ts +1 -1
- package/src/commands/create-extension-version.ts +48 -102
- package/src/commands/create-user.ts +9 -6
- package/src/commands/edit-admin.ts +77 -15
- package/src/commands/ext-dev/{db-connect-info.ts → get-db-url.ts} +3 -3
- package/src/commands/ext-dev/init-db.ts +3 -2
- package/src/core-api/api.ts +4216 -0
- package/src/core-api/mutations/create-extension-version.generated.ts +96 -0
- package/src/core-api/mutations/create-extension-version.graphql +14 -0
- package/src/core-api/operations.ts +23 -0
- package/src/main.ts +13 -6
- package/src/users/permissions.ts +15 -12
- package/tsconfig.json +4 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirechunk/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -19,17 +19,22 @@
|
|
|
19
19
|
"bin": {
|
|
20
20
|
"wirechunk": "build/main.js"
|
|
21
21
|
},
|
|
22
|
+
"imports": {
|
|
23
|
+
"#api": "./src/core-api/api.ts"
|
|
24
|
+
},
|
|
22
25
|
"dependencies": {
|
|
23
26
|
"argon2": "^0.41.1"
|
|
24
27
|
},
|
|
25
28
|
"devDependencies": {
|
|
26
29
|
"@commander-js/extra-typings": "^13.0.0",
|
|
30
|
+
"@graphql-typed-document-node/core": "^3.2.0",
|
|
27
31
|
"@types/archiver": "^6.0.3",
|
|
28
32
|
"@wirechunk/backend-lib": "0.0.0",
|
|
29
33
|
"@wirechunk/lib": "0.0.0",
|
|
30
34
|
"archiver": "^7.0.1",
|
|
31
35
|
"chalk": "^5.3.0",
|
|
32
36
|
"commander": "^13.0.0",
|
|
37
|
+
"graphql-request": "^7.1.2",
|
|
33
38
|
"slonik": "^46.3.0",
|
|
34
39
|
"slonik-interceptor-query-logging": "^46.3.0",
|
|
35
40
|
"zod": "^3.24.1"
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { cleanTinyId } from '@wirechunk/lib/clean-small-id.ts';
|
|
3
|
+
import { normalizeDomain } from '@wirechunk/lib/domains.ts';
|
|
3
4
|
import { normalizeEmailAddress } from '@wirechunk/lib/emails.ts';
|
|
4
5
|
import {
|
|
5
6
|
defaultFormattedDataTemplate,
|
|
6
7
|
defaultNotificationEmailBodyTemplate,
|
|
7
8
|
} from '@wirechunk/lib/mixer/form-formatting-templates.ts';
|
|
8
|
-
import { normalizeDomain } from '@wirechunk/server/site-domains/util.ts';
|
|
9
9
|
import type { DatabasePool } from 'slonik';
|
|
10
10
|
import { createPool, sql } from 'slonik';
|
|
11
11
|
import type { Env } from '../env.ts';
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { parseEnv } from 'node:util';
|
|
1
4
|
import { requireValidExtensionDir } from '@wirechunk/backend-lib/extensions/require-extension-dir.ts';
|
|
2
5
|
import { parseErrorMessage } from '@wirechunk/lib/errors.ts';
|
|
3
6
|
import archiver from 'archiver';
|
|
4
|
-
import {
|
|
7
|
+
import { GraphQLClient } from 'graphql-request';
|
|
8
|
+
import { createExtensionVersion as createExtensionVersionRequest } from '../core-api/operations.ts';
|
|
5
9
|
import type { Env } from '../env.ts';
|
|
6
10
|
import { requireApiToken } from '../env.ts';
|
|
7
11
|
import type { WithGlobalOptions } from '../global-options.js';
|
|
@@ -16,44 +20,21 @@ const bytesFormat = Intl.NumberFormat('en', {
|
|
|
16
20
|
maximumFractionDigits: 1,
|
|
17
21
|
});
|
|
18
22
|
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
message: z.string(),
|
|
30
|
-
}),
|
|
31
|
-
z.object({
|
|
32
|
-
__typename: z.literal('GenericInternalError'),
|
|
33
|
-
message: z.string(),
|
|
34
|
-
}),
|
|
35
|
-
z.object({
|
|
36
|
-
__typename: z.literal('GenericUserError'),
|
|
37
|
-
message: z.string(),
|
|
38
|
-
}),
|
|
39
|
-
]);
|
|
40
|
-
|
|
41
|
-
const graphQlErrorSchema = z.object({
|
|
42
|
-
message: z.string(),
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
const createExtensionVersionResultSchema = z.object({
|
|
46
|
-
errors: z.array(graphQlErrorSchema).optional().nullable(),
|
|
47
|
-
data: z
|
|
48
|
-
.object({
|
|
49
|
-
createExtensionVersion: createExtensionVersionResultDataSchema,
|
|
50
|
-
})
|
|
51
|
-
.nullable(),
|
|
52
|
-
});
|
|
23
|
+
const ignoreGlobs = [
|
|
24
|
+
'**/.git/**/*',
|
|
25
|
+
'.git/**/*',
|
|
26
|
+
'eslint.config.js',
|
|
27
|
+
'**/eslint.config.js',
|
|
28
|
+
'node_modules/**/*',
|
|
29
|
+
'**/node_modules/**/*',
|
|
30
|
+
'yalc.lock',
|
|
31
|
+
'**/yalc.lock',
|
|
32
|
+
];
|
|
53
33
|
|
|
54
34
|
type CreateExtensionVersionOptions = {
|
|
55
35
|
extensionId?: string;
|
|
56
36
|
versionName: string;
|
|
37
|
+
configFile?: string;
|
|
57
38
|
};
|
|
58
39
|
|
|
59
40
|
export const createExtensionVersion = async (
|
|
@@ -84,6 +65,20 @@ export const createExtensionVersion = async (
|
|
|
84
65
|
enableServer = false;
|
|
85
66
|
enableDb = false;
|
|
86
67
|
}
|
|
68
|
+
let config: Record<string, string> | null = null;
|
|
69
|
+
if (opts.configFile) {
|
|
70
|
+
const configFilePath = resolve(cwd, opts.configFile);
|
|
71
|
+
if (opts.verbose) {
|
|
72
|
+
console.log(`Loading config file ${configFilePath}`);
|
|
73
|
+
}
|
|
74
|
+
const configFile = await readFile(configFilePath, 'utf8');
|
|
75
|
+
try {
|
|
76
|
+
config = parseEnv(configFile) as Record<string, string>;
|
|
77
|
+
} catch (e) {
|
|
78
|
+
console.error(`Failed to parse config file at ${configFilePath}:`, parseErrorMessage(e));
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
87
82
|
|
|
88
83
|
const { versionName } = opts;
|
|
89
84
|
console.log(`Creating extension version ${versionName} (Extension ID ${extensionId})
|
|
@@ -98,69 +93,27 @@ export const createExtensionVersion = async (
|
|
|
98
93
|
if (opts.verbose) {
|
|
99
94
|
console.log(`POST ${url}`);
|
|
100
95
|
}
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
__typename
|
|
112
|
-
... on CreateExtensionVersionSuccessResult {
|
|
113
|
-
extensionVersion {
|
|
114
|
-
id
|
|
115
|
-
}
|
|
116
|
-
signedUrl
|
|
117
|
-
}
|
|
118
|
-
... on Error {
|
|
119
|
-
message
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
`,
|
|
124
|
-
variables: {
|
|
125
|
-
input: {
|
|
126
|
-
extensionId,
|
|
127
|
-
extensionName: extConfig.name,
|
|
128
|
-
versionName,
|
|
129
|
-
enableServer,
|
|
130
|
-
enableDb,
|
|
131
|
-
},
|
|
96
|
+
const result = await createExtensionVersionRequest({
|
|
97
|
+
client: new GraphQLClient(url),
|
|
98
|
+
variables: {
|
|
99
|
+
input: {
|
|
100
|
+
extensionId,
|
|
101
|
+
extensionName: extConfig.name,
|
|
102
|
+
versionName,
|
|
103
|
+
enableServer,
|
|
104
|
+
enableDb,
|
|
105
|
+
config: config ? JSON.stringify(config) : undefined,
|
|
132
106
|
},
|
|
133
|
-
}
|
|
107
|
+
},
|
|
108
|
+
sessionAuthToken: apiToken,
|
|
134
109
|
});
|
|
135
|
-
if (
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const data = await createResult.json();
|
|
139
|
-
console.error(`${message}:`, parseErrorMessage(data));
|
|
140
|
-
process.exit(1);
|
|
141
|
-
} catch {
|
|
142
|
-
console.error(message);
|
|
143
|
-
process.exit(1);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
const data = await createResult.json();
|
|
147
|
-
const parseResult = createExtensionVersionResultSchema.safeParse(data);
|
|
148
|
-
if (!parseResult.success) {
|
|
149
|
-
console.error('Failed to create an extension version:', parseResult.error.message);
|
|
150
|
-
process.exit(1);
|
|
151
|
-
}
|
|
152
|
-
const result = parseResult.data;
|
|
153
|
-
if (result.errors) {
|
|
154
|
-
console.error('Failed to create an extension version:', parseErrorMessage(result));
|
|
155
|
-
process.exit(1);
|
|
156
|
-
}
|
|
157
|
-
if (result.data?.createExtensionVersion.__typename === 'CreateExtensionVersionSuccessResult') {
|
|
158
|
-
extensionVersionId = result.data.createExtensionVersion.extensionVersion.id;
|
|
159
|
-
signedUrl = result.data.createExtensionVersion.signedUrl;
|
|
110
|
+
if (result.createExtensionVersion.__typename === 'CreateExtensionVersionSuccessResult') {
|
|
111
|
+
extensionVersionId = result.createExtensionVersion.extensionVersion.id;
|
|
112
|
+
signedUrl = result.createExtensionVersion.signedUrl;
|
|
160
113
|
} else {
|
|
161
114
|
console.error(
|
|
162
|
-
`Failed to create an extension version (${result.
|
|
163
|
-
|
|
115
|
+
`Failed to create an extension version (${result.createExtensionVersion.__typename}):`,
|
|
116
|
+
result.createExtensionVersion.message,
|
|
164
117
|
);
|
|
165
118
|
process.exit(1);
|
|
166
119
|
}
|
|
@@ -197,14 +150,7 @@ export const createExtensionVersion = async (
|
|
|
197
150
|
|
|
198
151
|
archive.glob('**/*', {
|
|
199
152
|
cwd,
|
|
200
|
-
ignore:
|
|
201
|
-
'**/.git/**/*',
|
|
202
|
-
'.git/**/*',
|
|
203
|
-
'eslint.config.js',
|
|
204
|
-
'**/eslint.config.js',
|
|
205
|
-
'node_modules/**/*',
|
|
206
|
-
'**/node_modules/**/*',
|
|
207
|
-
],
|
|
153
|
+
ignore: ignoreGlobs,
|
|
208
154
|
});
|
|
209
155
|
|
|
210
156
|
if (opts.verbose) {
|
|
@@ -67,8 +67,7 @@ export const createUser = async (
|
|
|
67
67
|
|
|
68
68
|
let platformId: string | null | undefined = opts.platformId;
|
|
69
69
|
let orgId: string | null | undefined = opts.orgId;
|
|
70
|
-
|
|
71
|
-
// let orgPrimary = false;
|
|
70
|
+
let orgPrimary = false;
|
|
72
71
|
|
|
73
72
|
try {
|
|
74
73
|
const user = await db.transaction(async (db) => {
|
|
@@ -115,7 +114,7 @@ export const createUser = async (
|
|
|
115
114
|
if (opts.verbose) {
|
|
116
115
|
console.log(`Created org ID ${orgId}`);
|
|
117
116
|
}
|
|
118
|
-
|
|
117
|
+
orgPrimary = true;
|
|
119
118
|
}
|
|
120
119
|
|
|
121
120
|
const user = await db.one(
|
|
@@ -124,9 +123,13 @@ export const createUser = async (
|
|
|
124
123
|
)`insert into "Users" ("platformId", "email", "emailVerified", "password", "passwordStatus", "orgId", "role", "status", "firstName", "lastName") values (${platformId}, ${email}, ${opts.emailVerified}, ${password}, 'Ok', ${orgId}, ${role}, ${status}, ${firstName}, ${lastName}) returning "id"`,
|
|
125
124
|
);
|
|
126
125
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
126
|
+
if (orgPrimary) {
|
|
127
|
+
await db.maybeOne(
|
|
128
|
+
sql.type(
|
|
129
|
+
voidSelectSchema,
|
|
130
|
+
)`update "Orgs" set "primaryUserId" = ${user.id} where "id" = ${orgId}`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
130
133
|
|
|
131
134
|
return user;
|
|
132
135
|
});
|
|
@@ -1,19 +1,31 @@
|
|
|
1
|
+
import { cleanSmallId } from '@wirechunk/lib/clean-small-id.ts';
|
|
1
2
|
import { createPool, sql, UniqueIntegrityConstraintViolationError } from 'slonik';
|
|
2
3
|
import { z } from 'zod';
|
|
3
4
|
import type { Env } from '../env.ts';
|
|
4
5
|
import { requireCoreDbUrl } from '../env.ts';
|
|
5
6
|
import { detailedUniqueIntegrityConstraintViolationError } from '../errors.ts';
|
|
6
7
|
import type { WithGlobalOptions } from '../global-options.ts';
|
|
8
|
+
import {
|
|
9
|
+
grantAllUserPlatformPermissions,
|
|
10
|
+
revokeAllUserPlatformPermissions,
|
|
11
|
+
} from '../users/permissions.ts';
|
|
12
|
+
import { voidSelectSchema } from '../util.ts';
|
|
7
13
|
|
|
8
|
-
const
|
|
14
|
+
const findPlatformAdminSchema = z.object({
|
|
9
15
|
id: z.string(),
|
|
10
16
|
platformId: z.string(),
|
|
17
|
+
active: z.boolean(),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const findUserSchema = z.object({
|
|
21
|
+
id: z.string(),
|
|
11
22
|
});
|
|
12
23
|
|
|
13
24
|
type EditAdminOptions = {
|
|
14
25
|
platformId: string;
|
|
15
26
|
userId: string;
|
|
16
27
|
owner?: boolean;
|
|
28
|
+
active?: boolean;
|
|
17
29
|
revokeAllPermissions?: boolean;
|
|
18
30
|
};
|
|
19
31
|
|
|
@@ -22,7 +34,7 @@ export const editAdmin = async (
|
|
|
22
34
|
env: Env,
|
|
23
35
|
): Promise<void> => {
|
|
24
36
|
const db = await createPool(requireCoreDbUrl(env));
|
|
25
|
-
const { platformId, userId, owner, revokeAllPermissions } = opts;
|
|
37
|
+
const { platformId, userId, owner, active, revokeAllPermissions } = opts;
|
|
26
38
|
|
|
27
39
|
if (owner && revokeAllPermissions) {
|
|
28
40
|
console.error(
|
|
@@ -33,9 +45,9 @@ export const editAdmin = async (
|
|
|
33
45
|
|
|
34
46
|
try {
|
|
35
47
|
await db.transaction(async (db) => {
|
|
36
|
-
|
|
48
|
+
let platformAdmin = await db.maybeOne(
|
|
37
49
|
sql.type(
|
|
38
|
-
|
|
50
|
+
findPlatformAdminSchema,
|
|
39
51
|
)`select "id" from "PlatformAdmins" where "platformId" = ${platformId} and "userId" = ${userId}`,
|
|
40
52
|
);
|
|
41
53
|
if (!platformAdmin) {
|
|
@@ -46,17 +58,67 @@ export const editAdmin = async (
|
|
|
46
58
|
throw new Error(`User with ID ${userId} not found`);
|
|
47
59
|
}
|
|
48
60
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
if (owner) {
|
|
62
|
+
if (!platformAdmin) {
|
|
63
|
+
platformAdmin = await db.one(
|
|
64
|
+
sql.type(findPlatformAdminSchema)`
|
|
65
|
+
insert into "PlatformAdmins" ("id", "platformId", "userId", "owner", "active")
|
|
66
|
+
values (${cleanSmallId()}, ${platformId}, ${userId}, ${active ?? true}, true)
|
|
67
|
+
returning "id", "platformId", "active"
|
|
68
|
+
`,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
await grantAllUserPlatformPermissions({ platformAdminId: platformAdmin.id }, db);
|
|
72
|
+
if (opts.verbose) {
|
|
73
|
+
console.log('Set the user as an owner on the platform');
|
|
74
|
+
}
|
|
75
|
+
} else if (owner === false) {
|
|
76
|
+
if (platformAdmin) {
|
|
77
|
+
await db.query(
|
|
78
|
+
sql.type(voidSelectSchema)`
|
|
79
|
+
update "PlatformAdmins"
|
|
80
|
+
set "owner" = false
|
|
81
|
+
where "id" = ${platformAdmin.id}
|
|
82
|
+
`,
|
|
83
|
+
);
|
|
84
|
+
if (opts.verbose) {
|
|
85
|
+
console.log('Removed the user’s owner privileges on the platform');
|
|
86
|
+
}
|
|
87
|
+
} else {
|
|
88
|
+
console.log('This user is not an admin on this platform');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (typeof active === 'boolean') {
|
|
92
|
+
if (platformAdmin) {
|
|
93
|
+
await db.query(
|
|
94
|
+
sql.type(voidSelectSchema)`
|
|
95
|
+
update "PlatformAdmins"
|
|
96
|
+
set "active" = ${active}
|
|
97
|
+
where "id" = ${platformAdmin.id}
|
|
98
|
+
`,
|
|
99
|
+
);
|
|
100
|
+
} else {
|
|
101
|
+
if (active) {
|
|
102
|
+
// Automatically create a platform admin.
|
|
103
|
+
await db.one(
|
|
104
|
+
sql.type(voidSelectSchema)`
|
|
105
|
+
insert into "PlatformAdmins" ("id", "platformId", "userId", "owner", "active")
|
|
106
|
+
values (${cleanSmallId()}, ${platformId}, ${userId}, false, ${active})
|
|
107
|
+
`,
|
|
108
|
+
);
|
|
109
|
+
} else {
|
|
110
|
+
console.log('This user is not an admin on this platform');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (revokeAllPermissions) {
|
|
115
|
+
if (platformAdmin) {
|
|
116
|
+
await revokeAllUserPlatformPermissions({ platformAdminId: platformAdmin.id }, db);
|
|
117
|
+
console.log('Revoked all platform permissions of user');
|
|
118
|
+
} else {
|
|
119
|
+
console.log('This user is not an admin on this platform');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
60
122
|
});
|
|
61
123
|
} catch (e) {
|
|
62
124
|
if (e instanceof UniqueIntegrityConstraintViolationError) {
|
|
@@ -5,13 +5,13 @@ import type { Env } from '../../env.ts';
|
|
|
5
5
|
import type { WithGlobalOptions } from '../../global-options.js';
|
|
6
6
|
import { getExtensionDbConnectInfo, replaceEnvVar } from '../../util.ts';
|
|
7
7
|
|
|
8
|
-
type
|
|
8
|
+
type GetDbUrlOptions = {
|
|
9
9
|
extensionId?: string;
|
|
10
10
|
dbName?: string;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
export const
|
|
14
|
-
opts: WithGlobalOptions<
|
|
13
|
+
export const getDbUrl = async (
|
|
14
|
+
opts: WithGlobalOptions<GetDbUrlOptions>,
|
|
15
15
|
env: Env,
|
|
16
16
|
): Promise<void> => {
|
|
17
17
|
const connInfo = getExtensionDbConnectInfo(opts, env);
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
getExtensionDbConnectInfo,
|
|
11
11
|
requireExtensionIdOptionOrEnvVar,
|
|
12
12
|
} from '../../util.ts';
|
|
13
|
-
import {
|
|
13
|
+
import { getDbUrl } from './get-db-url.ts';
|
|
14
14
|
|
|
15
15
|
const initExtDb = async ({
|
|
16
16
|
// A connection to the extension database by a superuser role.
|
|
@@ -149,7 +149,8 @@ export const initDb = async (
|
|
|
149
149
|
const coreDbUrl = requireCoreDbUrl(env);
|
|
150
150
|
const extensionId = requireExtensionIdOptionOrEnvVar(opts, env);
|
|
151
151
|
|
|
152
|
-
|
|
152
|
+
// This writes the DATABASE_URL environment variable to a .env.local (or .env.<env-mode>.local) file.
|
|
153
|
+
await getDbUrl(opts, env);
|
|
153
154
|
|
|
154
155
|
const db = await createPool(coreDbUrl, dbPoolOptions(opts));
|
|
155
156
|
|