@lightdash/cli 0.3406.0 → 0.3406.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/dist/.tsbuildinfo +1 -1
- package/dist/handlers/contentAsCode/fileNames.d.ts +24 -0
- package/dist/handlers/contentAsCode/fileNames.d.ts.map +1 -0
- package/dist/handlers/contentAsCode/fileNames.js +67 -0
- package/dist/handlers/contentAsCode/fileNames.test.d.ts +2 -0
- package/dist/handlers/contentAsCode/fileNames.test.d.ts.map +1 -0
- package/dist/handlers/contentAsCode/fileNames.test.js +79 -0
- package/dist/handlers/contentAsCode/projectResources.d.ts +8 -0
- package/dist/handlers/contentAsCode/projectResources.d.ts.map +1 -0
- package/dist/handlers/contentAsCode/projectResources.js +95 -0
- package/dist/handlers/contentAsCode/registry.d.ts +9 -0
- package/dist/handlers/contentAsCode/registry.d.ts.map +1 -0
- package/dist/handlers/contentAsCode/registry.js +44 -0
- package/dist/handlers/contentAsCode/registry.test.d.ts +2 -0
- package/dist/handlers/contentAsCode/registry.test.d.ts.map +1 -0
- package/dist/handlers/contentAsCode/registry.test.js +18 -0
- package/dist/handlers/contentAsCode/resource.d.ts +67 -0
- package/dist/handlers/contentAsCode/resource.d.ts.map +1 -0
- package/dist/handlers/contentAsCode/resource.js +171 -0
- package/dist/handlers/contentAsCode/resource.test.d.ts +2 -0
- package/dist/handlers/contentAsCode/resource.test.d.ts.map +1 -0
- package/dist/handlers/contentAsCode/resource.test.js +39 -0
- package/dist/handlers/download.d.ts.map +1 -1
- package/dist/handlers/download.js +59 -109
- package/dist/handlers/organizationContent/customRoles.d.ts +4 -3
- package/dist/handlers/organizationContent/customRoles.d.ts.map +1 -1
- package/dist/handlers/organizationContent/customRoles.js +51 -137
- package/dist/handlers/organizationContent/customRoles.test.js +13 -2
- package/dist/handlers/organizationContent/fileNames.d.ts.map +1 -1
- package/dist/handlers/organizationContent/fileNames.js +8 -29
- package/dist/handlers/organizationContent/groups.d.ts +4 -3
- package/dist/handlers/organizationContent/groups.d.ts.map +1 -1
- package/dist/handlers/organizationContent/groups.js +54 -139
- package/dist/handlers/organizationContent/groups.test.js +2 -1
- package/dist/handlers/organizationContent/index.d.ts.map +1 -1
- package/dist/handlers/organizationContent/index.js +7 -22
- package/dist/handlers/organizationContent/users.d.ts +4 -3
- package/dist/handlers/organizationContent/users.d.ts.map +1 -1
- package/dist/handlers/organizationContent/users.js +75 -146
- package/dist/handlers/organizationContent/users.test.js +4 -3
- package/dist/handlers/spacesAsCode.d.ts +1 -1
- package/dist/handlers/spacesAsCode.d.ts.map +1 -1
- package/dist/handlers/spacesAsCode.js +21 -26
- package/package.json +3 -3
|
@@ -1,16 +1,32 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.downloadUsers = exports.uploadUsers = exports.readUserFiles = exports.formatUserUploadSummary = exports.getUsersFolder = void 0;
|
|
3
|
+
exports.downloadUsers = exports.uploadUsers = exports.readUserFiles = exports.formatUserUploadSummary = exports.getUsersFolder = exports.USER_CODE_RESOURCE = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
/* eslint-disable no-await-in-loop */
|
|
6
6
|
const common_1 = require("@lightdash/common");
|
|
7
|
-
const fs_1 = require("fs");
|
|
8
|
-
const yaml = tslib_1.__importStar(require("js-yaml"));
|
|
9
|
-
const groupBy_1 = tslib_1.__importDefault(require("lodash/groupBy"));
|
|
10
7
|
const path = tslib_1.__importStar(require("path"));
|
|
8
|
+
const resource_1 = require("../contentAsCode/resource");
|
|
11
9
|
const apiClient_1 = require("../dbt/apiClient");
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
exports.USER_CODE_RESOURCE = {
|
|
11
|
+
kind: 'user',
|
|
12
|
+
displayLabel: 'user',
|
|
13
|
+
identityLabel: 'email',
|
|
14
|
+
scope: 'organization',
|
|
15
|
+
folderName: 'users',
|
|
16
|
+
acceptedExtensions: ['.yml'],
|
|
17
|
+
fileName: {
|
|
18
|
+
strategy: 'normalizedDisplayName',
|
|
19
|
+
fallbackPrefix: 'user',
|
|
20
|
+
extension: '.yml',
|
|
21
|
+
},
|
|
22
|
+
dependencies: ['custom_role'],
|
|
23
|
+
identity: ({ email }) => email,
|
|
24
|
+
normalizeIdentity: (email) => email.toLowerCase(),
|
|
25
|
+
displayName: ({ email }) => email.toLowerCase(),
|
|
26
|
+
parse: common_1.parseUserAsCode,
|
|
27
|
+
sort: (left, right) => left.email.localeCompare(right.email),
|
|
28
|
+
};
|
|
29
|
+
const getUsersFolder = (organizationContentPath) => path.join(organizationContentPath, exports.USER_CODE_RESOURCE.folderName);
|
|
14
30
|
exports.getUsersFolder = getUsersFolder;
|
|
15
31
|
const formatUserUploadSummary = (summary) => {
|
|
16
32
|
const invitationSummary = [
|
|
@@ -19,126 +35,49 @@ const formatUserUploadSummary = (summary) => {
|
|
|
19
35
|
summary.skippedDisabled +
|
|
20
36
|
summary.skippedValidInvite} invitation-skipped`,
|
|
21
37
|
];
|
|
22
|
-
return
|
|
38
|
+
return [
|
|
39
|
+
`${summary.created} created`,
|
|
40
|
+
`${summary.updated} updated`,
|
|
41
|
+
`${summary.unchanged} unchanged`,
|
|
42
|
+
`${summary.awaitingAuthentication} awaiting authentication`,
|
|
43
|
+
invitationSummary.join(', '),
|
|
44
|
+
`${summary.failed} failed`,
|
|
45
|
+
].join(', ');
|
|
23
46
|
};
|
|
24
47
|
exports.formatUserUploadSummary = formatUserUploadSummary;
|
|
25
|
-
const getUserEmail = (user) => {
|
|
26
|
-
if (typeof user !== 'object' || user === null || Array.isArray(user)) {
|
|
27
|
-
return undefined;
|
|
28
|
-
}
|
|
29
|
-
const { email } = user;
|
|
30
|
-
return typeof email === 'string' ? email.toLowerCase() : undefined;
|
|
31
|
-
};
|
|
32
|
-
const asUserAsCode = (user) => {
|
|
33
|
-
if (typeof user !== 'object' || user === null || Array.isArray(user)) {
|
|
34
|
-
return undefined;
|
|
35
|
-
}
|
|
36
|
-
const record = user;
|
|
37
|
-
if (typeof record.email !== 'string' ||
|
|
38
|
-
typeof record.disabled !== 'boolean' ||
|
|
39
|
-
typeof record.role !== 'object' ||
|
|
40
|
-
record.role === null ||
|
|
41
|
-
Array.isArray(record.role)) {
|
|
42
|
-
return undefined;
|
|
43
|
-
}
|
|
44
|
-
const role = record.role;
|
|
45
|
-
if ((role.type !== 'system' && role.type !== 'custom') ||
|
|
46
|
-
typeof role.name !== 'string') {
|
|
47
|
-
return undefined;
|
|
48
|
-
}
|
|
49
|
-
return user;
|
|
50
|
-
};
|
|
51
|
-
const readUserFileResults = async (organizationContentPath) => {
|
|
52
|
-
const folder = (0, exports.getUsersFolder)(organizationContentPath);
|
|
53
|
-
let entries;
|
|
54
|
-
try {
|
|
55
|
-
entries = await fs_1.promises.readdir(folder, { withFileTypes: true });
|
|
56
|
-
}
|
|
57
|
-
catch (error) {
|
|
58
|
-
if (error.code === 'ENOENT') {
|
|
59
|
-
return { userFiles: [], failures: [] };
|
|
60
|
-
}
|
|
61
|
-
throw error;
|
|
62
|
-
}
|
|
63
|
-
const files = entries
|
|
64
|
-
.filter((entry) => entry.isFile() && entry.name.endsWith('.yml'))
|
|
65
|
-
.sort((left, right) => left.name.localeCompare(right.name));
|
|
66
|
-
const results = await Promise.all(files.map(async (file) => {
|
|
67
|
-
const filePath = path.join(folder, file.name);
|
|
68
|
-
try {
|
|
69
|
-
return {
|
|
70
|
-
kind: 'user',
|
|
71
|
-
userFile: {
|
|
72
|
-
filePath,
|
|
73
|
-
user: yaml.load(await fs_1.promises.readFile(filePath, 'utf8')),
|
|
74
|
-
},
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
catch (error) {
|
|
78
|
-
return {
|
|
79
|
-
kind: 'failure',
|
|
80
|
-
failure: {
|
|
81
|
-
message: `Unable to parse user file "${filePath}": ${(0, common_1.getErrorMessage)(error)}`,
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
}));
|
|
86
|
-
const parsedUserFiles = results.flatMap((result) => result.kind === 'user' ? [result.userFile] : []);
|
|
87
|
-
const failures = results.flatMap((result) => result.kind === 'failure' ? [result.failure] : []);
|
|
88
|
-
const emailFiles = parsedUserFiles.flatMap((userFile) => {
|
|
89
|
-
const email = getUserEmail(userFile.user);
|
|
90
|
-
return email === undefined ? [] : [{ email, userFile }];
|
|
91
|
-
});
|
|
92
|
-
const filesByEmail = (0, groupBy_1.default)(emailFiles, ({ email }) => email);
|
|
93
|
-
const duplicateEmails = new Set(Object.entries(filesByEmail)
|
|
94
|
-
.filter(([, matchingFiles]) => matchingFiles.length > 1)
|
|
95
|
-
.map(([email]) => email));
|
|
96
|
-
for (const duplicateEmail of [...duplicateEmails].sort()) {
|
|
97
|
-
for (const { userFile } of filesByEmail[duplicateEmail]) {
|
|
98
|
-
failures.push({
|
|
99
|
-
message: `Duplicate user email "${duplicateEmail}" in "${userFile.filePath}"`,
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
return {
|
|
104
|
-
userFiles: parsedUserFiles.filter(({ user }) => !duplicateEmails.has(getUserEmail(user) ?? '')),
|
|
105
|
-
failures,
|
|
106
|
-
};
|
|
107
|
-
};
|
|
108
48
|
const readUserFiles = async (organizationContentPath) => {
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
49
|
+
const result = await (0, resource_1.readCodeResourceFiles)({
|
|
50
|
+
definition: exports.USER_CODE_RESOURCE,
|
|
51
|
+
basePath: organizationContentPath,
|
|
52
|
+
});
|
|
53
|
+
(0, resource_1.assertCodeResourceFilesValid)(result);
|
|
54
|
+
return result.files.map(({ document }) => document);
|
|
114
55
|
};
|
|
115
56
|
exports.readUserFiles = readUserFiles;
|
|
116
57
|
const isSystemAdmin = (user) => user.role.type === 'system' &&
|
|
117
58
|
user.role.name === common_1.OrganizationMemberRole.ADMIN;
|
|
118
59
|
const orderUserFiles = (userFiles, currentUsers) => {
|
|
119
60
|
const currentByEmail = new Map(currentUsers.map((user) => [user.email.toLowerCase(), user]));
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
return 1;
|
|
135
|
-
};
|
|
136
|
-
return (getPriority(left) - getPriority(right) ||
|
|
137
|
-
left.filePath.localeCompare(right.filePath));
|
|
138
|
-
});
|
|
61
|
+
const getPriority = ({ document: desired }) => {
|
|
62
|
+
const current = currentByEmail.get(desired.email.toLowerCase());
|
|
63
|
+
if (isSystemAdmin(desired) && !desired.disabled)
|
|
64
|
+
return 0;
|
|
65
|
+
if (current &&
|
|
66
|
+
isSystemAdmin(current) &&
|
|
67
|
+
!current.disabled &&
|
|
68
|
+
(!isSystemAdmin(desired) || desired.disabled)) {
|
|
69
|
+
return 2;
|
|
70
|
+
}
|
|
71
|
+
return 1;
|
|
72
|
+
};
|
|
73
|
+
return [...userFiles].sort((left, right) => getPriority(left) - getPriority(right) ||
|
|
74
|
+
left.filePath.localeCompare(right.filePath));
|
|
139
75
|
};
|
|
140
76
|
const uploadUsers = async (organizationUuid, organizationContentPath, sendInvites = false) => {
|
|
141
|
-
const {
|
|
77
|
+
const { files, failures } = await (0, resource_1.readCodeResourceFiles)({
|
|
78
|
+
definition: exports.USER_CODE_RESOURCE,
|
|
79
|
+
basePath: organizationContentPath,
|
|
80
|
+
});
|
|
142
81
|
const summary = {
|
|
143
82
|
created: 0,
|
|
144
83
|
updated: 0,
|
|
@@ -151,21 +90,21 @@ const uploadUsers = async (organizationUuid, organizationContentPath, sendInvite
|
|
|
151
90
|
failed: 0,
|
|
152
91
|
failures,
|
|
153
92
|
};
|
|
154
|
-
if (
|
|
155
|
-
summary.failed =
|
|
93
|
+
if (files.length === 0) {
|
|
94
|
+
summary.failed = failures.length;
|
|
156
95
|
return summary;
|
|
157
96
|
}
|
|
158
97
|
const { users: currentUsers } = await (0, apiClient_1.lightdashApi)({
|
|
159
98
|
method: 'GET',
|
|
160
|
-
url: `/api/v2/orgs/${organizationUuid}/users
|
|
99
|
+
url: `/api/v2/orgs/${organizationUuid}/code/users`,
|
|
161
100
|
body: undefined,
|
|
162
101
|
});
|
|
163
|
-
for (const { filePath,
|
|
102
|
+
for (const { filePath, document } of orderUserFiles(files, currentUsers)) {
|
|
164
103
|
try {
|
|
165
104
|
const result = await (0, apiClient_1.lightdashApi)({
|
|
166
105
|
method: 'POST',
|
|
167
|
-
url: `/api/v2/orgs/${organizationUuid}/users
|
|
168
|
-
body: JSON.stringify(
|
|
106
|
+
url: `/api/v2/orgs/${organizationUuid}/code/users${sendInvites ? '?sendInvite=true' : ''}`,
|
|
107
|
+
body: JSON.stringify(document),
|
|
169
108
|
});
|
|
170
109
|
switch (result.action) {
|
|
171
110
|
case common_1.PromotionAction.CREATE:
|
|
@@ -178,7 +117,7 @@ const uploadUsers = async (organizationUuid, organizationContentPath, sendInvite
|
|
|
178
117
|
summary.unchanged += 1;
|
|
179
118
|
break;
|
|
180
119
|
default:
|
|
181
|
-
|
|
120
|
+
(0, common_1.assertUnreachable)(result.action, 'Unsupported user promotion action');
|
|
182
121
|
}
|
|
183
122
|
switch (result.lifecycle) {
|
|
184
123
|
case common_1.UserAsCodeLifecycleStatus.READY:
|
|
@@ -187,7 +126,7 @@ const uploadUsers = async (organizationUuid, organizationContentPath, sendInvite
|
|
|
187
126
|
summary.awaitingAuthentication += 1;
|
|
188
127
|
break;
|
|
189
128
|
default:
|
|
190
|
-
|
|
129
|
+
(0, common_1.assertUnreachable)(result.lifecycle, 'Unsupported user lifecycle status');
|
|
191
130
|
}
|
|
192
131
|
switch (result.invitation) {
|
|
193
132
|
case common_1.UserAsCodeInvitationStatus.NOT_REQUESTED:
|
|
@@ -205,7 +144,7 @@ const uploadUsers = async (organizationUuid, organizationContentPath, sendInvite
|
|
|
205
144
|
summary.skippedValidInvite += 1;
|
|
206
145
|
break;
|
|
207
146
|
default:
|
|
208
|
-
|
|
147
|
+
(0, common_1.assertUnreachable)(result.invitation, 'Unsupported user invitation status');
|
|
209
148
|
}
|
|
210
149
|
}
|
|
211
150
|
catch (error) {
|
|
@@ -218,26 +157,16 @@ const uploadUsers = async (organizationUuid, organizationContentPath, sendInvite
|
|
|
218
157
|
return summary;
|
|
219
158
|
};
|
|
220
159
|
exports.uploadUsers = uploadUsers;
|
|
221
|
-
const downloadUsers = async (organizationUuid, organizationContentPath) => {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const sortedUsers = [...users].sort((left, right) => left.email.localeCompare(right.email));
|
|
234
|
-
const filenames = (0, fileNames_1.getOrganizationContentFileNames)({
|
|
235
|
-
values: sortedUsers.map(({ email }) => email.toLowerCase()),
|
|
236
|
-
fallbackPrefix: 'user',
|
|
237
|
-
});
|
|
238
|
-
await Promise.all(sortedUsers.map(async (user, index) => {
|
|
239
|
-
await fs_1.promises.writeFile(path.join(folder, filenames[index]), yaml.dump(user, { quotingType: '"', sortKeys: true }));
|
|
240
|
-
}));
|
|
241
|
-
return sortedUsers.length;
|
|
242
|
-
};
|
|
160
|
+
const downloadUsers = async (organizationUuid, organizationContentPath) => (0, resource_1.downloadCodeResource)({
|
|
161
|
+
definition: exports.USER_CODE_RESOURCE,
|
|
162
|
+
basePath: organizationContentPath,
|
|
163
|
+
list: async () => {
|
|
164
|
+
const { users } = await (0, apiClient_1.lightdashApi)({
|
|
165
|
+
method: 'GET',
|
|
166
|
+
url: `/api/v2/orgs/${organizationUuid}/code/users`,
|
|
167
|
+
body: undefined,
|
|
168
|
+
});
|
|
169
|
+
return users;
|
|
170
|
+
},
|
|
171
|
+
});
|
|
243
172
|
exports.downloadUsers = downloadUsers;
|
|
@@ -35,7 +35,7 @@ describe('users as code', () => {
|
|
|
35
35
|
vitest_1.vi.restoreAllMocks();
|
|
36
36
|
await fs_1.promises.rm(tmpDir, { recursive: true, force: true });
|
|
37
37
|
});
|
|
38
|
-
it('downloads deterministic portable users and
|
|
38
|
+
it('downloads deterministic portable users and preserves unknown YAML', async () => {
|
|
39
39
|
const folder = (0, users_1.getUsersFolder)(tmpDir);
|
|
40
40
|
await fs_1.promises.mkdir(folder, { recursive: true });
|
|
41
41
|
await fs_1.promises.writeFile(path.join(folder, 'stale.yml'), 'stale: true');
|
|
@@ -51,6 +51,7 @@ describe('users as code', () => {
|
|
|
51
51
|
await expect((0, users_1.downloadUsers)('organization-uuid', tmpDir)).resolves.toBe(2);
|
|
52
52
|
expect((await fs_1.promises.readdir(folder)).sort()).toStrictEqual([
|
|
53
53
|
'a-example-com.yml',
|
|
54
|
+
'stale.yml',
|
|
54
55
|
'z-example-com.yml',
|
|
55
56
|
]);
|
|
56
57
|
expect(yaml.load(await fs_1.promises.readFile(path.join(folder, 'z-example-com.yml'), 'utf8'))).toStrictEqual({
|
|
@@ -93,7 +94,7 @@ describe('users as code', () => {
|
|
|
93
94
|
});
|
|
94
95
|
expect(apiClient_1.lightdashApi).toHaveBeenNthCalledWith(2, {
|
|
95
96
|
method: 'POST',
|
|
96
|
-
url: '/api/v2/orgs/organization-uuid/users
|
|
97
|
+
url: '/api/v2/orgs/organization-uuid/code/users',
|
|
97
98
|
body: expect.any(String),
|
|
98
99
|
});
|
|
99
100
|
});
|
|
@@ -110,7 +111,7 @@ describe('users as code', () => {
|
|
|
110
111
|
expect(summary.invited).toBe(1);
|
|
111
112
|
expect(apiClient_1.lightdashApi).toHaveBeenNthCalledWith(2, {
|
|
112
113
|
method: 'POST',
|
|
113
|
-
url: '/api/v2/orgs/organization-uuid/users
|
|
114
|
+
url: '/api/v2/orgs/organization-uuid/code/users?sendInvite=true',
|
|
114
115
|
body: expect.any(String),
|
|
115
116
|
});
|
|
116
117
|
});
|
|
@@ -10,7 +10,7 @@ export declare const validateSpaceIdentity: (parsed: unknown, filePath: string)
|
|
|
10
10
|
export declare const assertUniqueSpacePaths: (files: SpaceCodeFile[]) => void;
|
|
11
11
|
export declare const sortSpaceFilesParentFirst: (files: SpaceCodeFile[]) => SpaceCodeFile[];
|
|
12
12
|
export declare const getUniqueExistingSpaceFilePathsBySlug: (baseDir: string) => Promise<Map<string, string[]>>;
|
|
13
|
-
/** Writes first-class space YAML files returned by GET /spaces
|
|
13
|
+
/** Writes first-class space YAML files returned by GET /code/spaces. */
|
|
14
14
|
export declare const writeSpaceFiles: (spaces: SpaceAsCode[], projectName: string, customPath?: string, folderScheme?: FolderScheme, flatSpaceLayout?: FlatSpaceLayout, preserveExistingPolicy?: boolean) => Promise<void>;
|
|
15
15
|
export declare const readSpaceFiles: (customPath?: string) => Promise<SpaceCodeFile[]>;
|
|
16
16
|
/** Reads legacy identity fields without validating unrelated access policy. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spacesAsCode.d.ts","sourceRoot":"","sources":["../../src/handlers/spacesAsCode.ts"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"spacesAsCode.d.ts","sourceRoot":"","sources":["../../src/handlers/spacesAsCode.ts"],"names":[],"mappings":"AAEA,OAAO,EASH,cAAc,EAId,KAAK,WAAW,EACnB,MAAM,mBAAmB,CAAC;AAgB3B,KAAK,YAAY,GAAG,MAAM,GAAG,QAAQ,CAAC;AACtC,KAAK,eAAe,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEzC,MAAM,MAAM,aAAa,GAAG;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,WAAW,CAAC;CACtB,CAAC;AAsDF,eAAO,MAAM,qBAAqB,GAAI,QAAQ,WAAW,EAAE,KAAG,MAAM,EAS9D,CAAC;AAuBP,eAAO,MAAM,qBAAqB,GAC9B,QAAQ,OAAO,EACf,UAAU,MAAM,KACjB,WAgMF,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,OAAO,aAAa,EAAE,KAAG,IAyB/D,CAAC;AAEF,eAAO,MAAM,yBAAyB,GAClC,OAAO,aAAa,EAAE,KACvB,aAAa,EAQV,CAAC;AA4EP,eAAO,MAAM,qCAAqC,GAC9C,SAAS,MAAM,KAChB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAY/B,CAAC;AAyDF,wEAAwE;AACxE,eAAO,MAAM,eAAe,GACxB,QAAQ,WAAW,EAAE,EACrB,aAAa,MAAM,EACnB,aAAa,MAAM,EACnB,eAAc,YAAqB,EACnC,kBAAiB,eAA0B,EAC3C,yBAAwB,OAAe,KACxC,OAAO,CAAC,IAAI,CAoHd,CAAC;AAEF,eAAO,MAAM,cAAc,GACvB,aAAa,MAAM,KACpB,OAAO,CAAC,aAAa,EAAE,CA0BzB,CAAC;AAEF,+EAA+E;AAC/E,eAAO,MAAM,cAAc,GACvB,aAAa,MAAM,KACpB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA8BhC,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,OAAO,aAAa,EAAE,KAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CACG,CAAC;AAqBhF,eAAO,MAAM,8BAA8B,GACvC,SAAS,MAAM,KAChB,cAIF,CAAC;AAEF,eAAO,MAAM,0BAA0B,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,KACS,CAAC;AAU/E,eAAO,MAAM,uBAAuB,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,KACS,CAAC;AAE5E,eAAO,MAAM,8BAA8B,GACvC,OAAO,OAAO,EACd,YAAY,OAAO,GAAG,SAAS,KAChC,OAAgE,CAAC;AAEpE,eAAO,MAAM,cAAc,GACvB,WAAW,MAAM,EACjB,aAAa,MAAM,EACnB,aAAa,MAAM,EACnB,SAAQ,OAAe,EACvB,aAAY,OAAe,KAC5B,OAAO,CAAC,MAAM,CAmDhB,CAAC;AAIF,eAAO,MAAM,4BAA4B,GACrC,SAAS,MAAM,KAChB,cAIF,CAAC;AAEF,eAAO,MAAM,wBAAwB,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,KACS,CAAC;AAE7E,eAAO,MAAM,gBAAgB,GAAI,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,SAY/D,CAAC;AAEF,eAAO,MAAM,YAAY,GACrB,WAAW,MAAM,EACjB,OAAO,aAAa,EAAE,EACtB,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,iBAAiB,OAAO,EACxB,mBAAmB,OAAO,KAC3B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAgGhC,CAAC"}
|
|
@@ -5,16 +5,16 @@ const tslib_1 = require("tslib");
|
|
|
5
5
|
/* eslint-disable no-await-in-loop */
|
|
6
6
|
/* eslint-disable no-param-reassign */
|
|
7
7
|
const common_1 = require("@lightdash/common");
|
|
8
|
-
const crypto_1 = require("crypto");
|
|
9
8
|
const fs_1 = require("fs");
|
|
10
9
|
const yaml = tslib_1.__importStar(require("js-yaml"));
|
|
11
10
|
const path = tslib_1.__importStar(require("path"));
|
|
12
11
|
const globalState_1 = tslib_1.__importDefault(require("../globalState"));
|
|
13
12
|
const styles = tslib_1.__importStar(require("../styles"));
|
|
14
13
|
const appsDownload_1 = require("./apps/appsDownload");
|
|
14
|
+
const fileNames_1 = require("./contentAsCode/fileNames");
|
|
15
15
|
const contentAsCodePaths_1 = require("./contentAsCodePaths");
|
|
16
16
|
const apiClient_1 = require("./dbt/apiClient");
|
|
17
|
-
const SPACE_FILENAME_MAX_LENGTH =
|
|
17
|
+
const SPACE_FILENAME_MAX_LENGTH = fileNames_1.CODE_FILENAME_MAX_STEM_LENGTH;
|
|
18
18
|
const SPACE_FILENAME_HASH_LENGTH = 8;
|
|
19
19
|
const isSpaceIdentity = (value) => typeof value === 'object' &&
|
|
20
20
|
value !== null &&
|
|
@@ -40,27 +40,22 @@ const hasRootSpaceFile = async (baseDir) => {
|
|
|
40
40
|
throw error;
|
|
41
41
|
}
|
|
42
42
|
};
|
|
43
|
-
const getStableSpaceHash = (value, length = SPACE_FILENAME_HASH_LENGTH) => (0,
|
|
44
|
-
const getSpaceFilenameBase = (space) => {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return `${base}.space.yml`;
|
|
60
|
-
const suffix = `-${getStableSpaceHash(space.slug)}`;
|
|
61
|
-
return `${base.slice(0, SPACE_FILENAME_MAX_LENGTH - suffix.length)}${suffix}.space.yml`;
|
|
62
|
-
});
|
|
63
|
-
};
|
|
43
|
+
const getStableSpaceHash = (value, length = SPACE_FILENAME_HASH_LENGTH) => (0, fileNames_1.getStableCodeHash)(value, length);
|
|
44
|
+
const getSpaceFilenameBase = (space) => (0, fileNames_1.getContentFileNameStem)({
|
|
45
|
+
value: space.spaceName,
|
|
46
|
+
fallbackPrefix: 'space',
|
|
47
|
+
fallbackHashValue: space.slug,
|
|
48
|
+
maxLength: SPACE_FILENAME_MAX_LENGTH,
|
|
49
|
+
});
|
|
50
|
+
const getFlatSpaceFileNames = (spaces) => (0, fileNames_1.allocateContentFileNames)({
|
|
51
|
+
items: spaces.map((space) => ({
|
|
52
|
+
identity: space.slug,
|
|
53
|
+
displayName: space.spaceName,
|
|
54
|
+
})),
|
|
55
|
+
fallbackPrefix: 'space',
|
|
56
|
+
extension: '.space.yml',
|
|
57
|
+
maxStemLength: SPACE_FILENAME_MAX_LENGTH,
|
|
58
|
+
});
|
|
64
59
|
exports.getFlatSpaceFileNames = getFlatSpaceFileNames;
|
|
65
60
|
const assertKnownKeys = (value, allowedKeys, field, filePath) => {
|
|
66
61
|
const unknownKeys = Object.keys(value).filter((key) => !allowedKeys.includes(key));
|
|
@@ -309,7 +304,7 @@ const allocateSpaceFileNames = async (spaces, outputDir, preferredFileNames) =>
|
|
|
309
304
|
return fileName;
|
|
310
305
|
});
|
|
311
306
|
};
|
|
312
|
-
/** Writes first-class space YAML files returned by GET /spaces
|
|
307
|
+
/** Writes first-class space YAML files returned by GET /code/spaces. */
|
|
313
308
|
const writeSpaceFiles = async (spaces, projectName, customPath, folderScheme = 'flat', flatSpaceLayout = 'folder', preserveExistingPolicy = false) => {
|
|
314
309
|
const baseDir = (0, contentAsCodePaths_1.getDownloadFolder)(customPath);
|
|
315
310
|
if (spaces.length === 0)
|
|
@@ -493,7 +488,7 @@ const downloadSpaces = async (projectId, projectName, customPath, nested = false
|
|
|
493
488
|
try {
|
|
494
489
|
results = await (0, apiClient_1.lightdashApi)({
|
|
495
490
|
method: 'GET',
|
|
496
|
-
url: `/api/v1/projects/${projectId}/spaces
|
|
491
|
+
url: `/api/v1/projects/${projectId}/code/spaces`,
|
|
497
492
|
body: undefined,
|
|
498
493
|
});
|
|
499
494
|
}
|
|
@@ -565,7 +560,7 @@ const upsertSpaces = async (projectId, files, changes, skipSpaceCreate, publicSp
|
|
|
565
560
|
});
|
|
566
561
|
const result = await (0, apiClient_1.lightdashApi)({
|
|
567
562
|
method: 'POST',
|
|
568
|
-
url: `/api/v1/projects/${projectId}/spaces
|
|
563
|
+
url: `/api/v1/projects/${projectId}/code/spaces?${params.toString()}`,
|
|
569
564
|
body: JSON.stringify(space),
|
|
570
565
|
});
|
|
571
566
|
const action = getSpacePromoteAction(result.action);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightdash/cli",
|
|
3
|
-
"version": "0.3406.
|
|
3
|
+
"version": "0.3406.1",
|
|
4
4
|
"description": "Lightdash CLI tool",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"unique-names-generator": "4.7.1",
|
|
42
42
|
"uuid": "11.1.1",
|
|
43
43
|
"yaml": "2.8.3",
|
|
44
|
-
"@lightdash/common": "0.3406.
|
|
45
|
-
"@lightdash/warehouses": "0.3406.
|
|
44
|
+
"@lightdash/common": "0.3406.1",
|
|
45
|
+
"@lightdash/warehouses": "0.3406.1"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/inquirer": "8.2.4",
|