@lightdash/cli 0.3405.1 → 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.
Files changed (44) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/handlers/contentAsCode/fileNames.d.ts +24 -0
  3. package/dist/handlers/contentAsCode/fileNames.d.ts.map +1 -0
  4. package/dist/handlers/contentAsCode/fileNames.js +67 -0
  5. package/dist/handlers/contentAsCode/fileNames.test.d.ts +2 -0
  6. package/dist/handlers/contentAsCode/fileNames.test.d.ts.map +1 -0
  7. package/dist/handlers/contentAsCode/fileNames.test.js +79 -0
  8. package/dist/handlers/contentAsCode/projectResources.d.ts +8 -0
  9. package/dist/handlers/contentAsCode/projectResources.d.ts.map +1 -0
  10. package/dist/handlers/contentAsCode/projectResources.js +95 -0
  11. package/dist/handlers/contentAsCode/registry.d.ts +9 -0
  12. package/dist/handlers/contentAsCode/registry.d.ts.map +1 -0
  13. package/dist/handlers/contentAsCode/registry.js +44 -0
  14. package/dist/handlers/contentAsCode/registry.test.d.ts +2 -0
  15. package/dist/handlers/contentAsCode/registry.test.d.ts.map +1 -0
  16. package/dist/handlers/contentAsCode/registry.test.js +18 -0
  17. package/dist/handlers/contentAsCode/resource.d.ts +67 -0
  18. package/dist/handlers/contentAsCode/resource.d.ts.map +1 -0
  19. package/dist/handlers/contentAsCode/resource.js +171 -0
  20. package/dist/handlers/contentAsCode/resource.test.d.ts +2 -0
  21. package/dist/handlers/contentAsCode/resource.test.d.ts.map +1 -0
  22. package/dist/handlers/contentAsCode/resource.test.js +39 -0
  23. package/dist/handlers/download.d.ts.map +1 -1
  24. package/dist/handlers/download.js +59 -109
  25. package/dist/handlers/organizationContent/customRoles.d.ts +4 -3
  26. package/dist/handlers/organizationContent/customRoles.d.ts.map +1 -1
  27. package/dist/handlers/organizationContent/customRoles.js +51 -137
  28. package/dist/handlers/organizationContent/customRoles.test.js +13 -2
  29. package/dist/handlers/organizationContent/fileNames.d.ts.map +1 -1
  30. package/dist/handlers/organizationContent/fileNames.js +8 -29
  31. package/dist/handlers/organizationContent/groups.d.ts +4 -3
  32. package/dist/handlers/organizationContent/groups.d.ts.map +1 -1
  33. package/dist/handlers/organizationContent/groups.js +54 -139
  34. package/dist/handlers/organizationContent/groups.test.js +2 -1
  35. package/dist/handlers/organizationContent/index.d.ts.map +1 -1
  36. package/dist/handlers/organizationContent/index.js +7 -22
  37. package/dist/handlers/organizationContent/users.d.ts +4 -3
  38. package/dist/handlers/organizationContent/users.d.ts.map +1 -1
  39. package/dist/handlers/organizationContent/users.js +75 -146
  40. package/dist/handlers/organizationContent/users.test.js +4 -3
  41. package/dist/handlers/spacesAsCode.d.ts +1 -1
  42. package/dist/handlers/spacesAsCode.d.ts.map +1 -1
  43. package/dist/handlers/spacesAsCode.js +21 -26
  44. 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
- const fileNames_1 = require("./fileNames");
13
- const getUsersFolder = (organizationContentPath) => path.join(organizationContentPath, 'users');
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 `${summary.created} created, ${summary.updated} updated, ${summary.unchanged} unchanged, ${summary.awaitingAuthentication} awaiting authentication, ${invitationSummary.join(', ')}, ${summary.failed} failed`;
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 { userFiles, failures } = await readUserFileResults(organizationContentPath);
110
- if (failures.length > 0) {
111
- throw new common_1.ParameterError(failures.map(({ message }) => message).join('\n'));
112
- }
113
- return userFiles.map(({ user }) => user);
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
- return [...userFiles].sort((left, right) => {
121
- const getPriority = (userFile) => {
122
- const desired = asUserAsCode(userFile.user);
123
- if (!desired)
124
- return 1;
125
- const current = currentByEmail.get(desired.email.toLowerCase());
126
- if (isSystemAdmin(desired) && !desired.disabled)
127
- return 0;
128
- if (current &&
129
- isSystemAdmin(current) &&
130
- !current.disabled &&
131
- (!isSystemAdmin(desired) || desired.disabled)) {
132
- return 2;
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 { userFiles, failures } = await readUserFileResults(organizationContentPath);
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 (userFiles.length === 0) {
155
- summary.failed = summary.failures.length;
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/code`,
99
+ url: `/api/v2/orgs/${organizationUuid}/code/users`,
161
100
  body: undefined,
162
101
  });
163
- for (const { filePath, user } of orderUserFiles(userFiles, currentUsers)) {
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/code${sendInvites ? '?sendInvite=true' : ''}`,
168
- body: JSON.stringify(user),
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
- throw new common_1.ParameterError(`Unsupported user promotion action: ${result.action}`);
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
- return (0, common_1.assertUnreachable)(result.lifecycle, 'Unsupported user lifecycle status');
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
- return (0, common_1.assertUnreachable)(result.invitation, 'Unsupported user invitation status');
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
- const { users } = await (0, apiClient_1.lightdashApi)({
223
- method: 'GET',
224
- url: `/api/v2/orgs/${organizationUuid}/users/code`,
225
- body: undefined,
226
- });
227
- const folder = (0, exports.getUsersFolder)(organizationContentPath);
228
- await fs_1.promises.mkdir(folder, { recursive: true });
229
- const existingFiles = await fs_1.promises.readdir(folder, { withFileTypes: true });
230
- await Promise.all(existingFiles
231
- .filter((entry) => entry.isFile() && entry.name.endsWith('.yml'))
232
- .map((entry) => fs_1.promises.unlink(path.join(folder, entry.name))));
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 removes stale YAML', async () => {
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/code',
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/code?sendInvite=true',
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/code. */
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,EAUH,cAAc,EAId,KAAK,WAAW,EACnB,MAAM,mBAAmB,CAAC;AAW3B,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;AAyDF,eAAO,MAAM,qBAAqB,GAAI,QAAQ,WAAW,EAAE,KAAG,MAAM,EAiBnE,CAAC;AAuBF,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"}
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 = 200;
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, crypto_1.createHash)('sha256').update(value).digest('hex').slice(0, length);
44
- const getSpaceFilenameBase = (space) => {
45
- const normalizedName = space.spaceName
46
- .normalize('NFKD')
47
- .replace(/[\u0300-\u036f]/g, '');
48
- const base = /[a-z0-9]/i.test(normalizedName)
49
- ? (0, common_1.generateSlug)(normalizedName)
50
- : `space-${getStableSpaceHash(space.slug)}`;
51
- return base.slice(0, SPACE_FILENAME_MAX_LENGTH);
52
- };
53
- const getFlatSpaceFileNames = (spaces) => {
54
- const bases = spaces.map(getSpaceFilenameBase);
55
- const counts = bases.reduce((result, base) => result.set(base, (result.get(base) ?? 0) + 1), new Map());
56
- return spaces.map((space, index) => {
57
- const base = bases[index];
58
- if ((counts.get(base) ?? 0) === 1)
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/code. */
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/code`,
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/code?${params.toString()}`,
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.3405.1",
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.3405.1",
45
- "@lightdash/warehouses": "0.3405.1"
44
+ "@lightdash/common": "0.3406.1",
45
+ "@lightdash/warehouses": "0.3406.1"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/inquirer": "8.2.4",