@adminforth/import-export 1.4.9 → 1.4.10

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.
@@ -17,18 +17,18 @@ steps:
17
17
  - infisical export --domain https://vault.devforth.io/api --format=dotenv-export --env="prod" > /woodpecker/deploy.vault.env
18
18
 
19
19
  build:
20
- image: node:20
20
+ image: devforth/node20-pnpm:latest
21
21
  when:
22
22
  - event: push
23
23
  commands:
24
24
  - apt update && apt install -y rsync
25
25
  - . /woodpecker/deploy.vault.env
26
- - npm clean-install
26
+ - pnpm install
27
27
  - /bin/bash ./.woodpecker/buildRelease.sh
28
28
  - npm audit signatures
29
29
 
30
30
  release:
31
- image: node:20
31
+ image: devforth/node20-pnpm:latest
32
32
  when:
33
33
  - event:
34
34
  - push
@@ -36,7 +36,7 @@ steps:
36
36
  - main
37
37
  commands:
38
38
  - . /woodpecker/deploy.vault.env
39
- - npx semantic-release
39
+ - pnpm exec semantic-release
40
40
 
41
41
  slack-on-failure:
42
42
  image: curlimages/curl
package/dist/index.js CHANGED
@@ -89,41 +89,13 @@ export default class ImportExport extends AdminForthPlugin {
89
89
  noAuth: true,
90
90
  handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body }) {
91
91
  const { data } = body;
92
- const rows = [];
93
- const columns = Object.keys(data);
94
- // check column names are valid
95
- const errors = [];
96
- columns.forEach((col) => {
97
- if (!this.resourceConfig.columns.some((c) => c.name === col)) {
98
- const similar = suggestIfTypo(this.resourceConfig.columns.map((c) => c.name), col);
99
- errors.push(`Column '${col}' defined in CSV not found in resource '${this.resourceConfig.resourceId}'. ${similar ? `If you mean '${similar}', rename it in CSV` : 'If column is in database but not in resource configuration, add it with showIn:[]'}`);
100
- }
101
- });
92
+ const columns = this.getColumnNames(data);
93
+ const { errors, resourceColumns } = this.validateColumns(columns);
102
94
  if (errors.length > 0) {
103
95
  return { ok: false, errors };
104
96
  }
105
97
  const primaryKeyColumn = this.resourceConfig.columns.find(col => col.primaryKey);
106
- const resourceColumns = columns.map(colName => this.resourceConfig.columns.find(c => c.name === colName));
107
- const columnValues = Object.values(data);
108
- for (let i = 0; i < columnValues[0].length; i++) {
109
- const row = {};
110
- for (let j = 0; j < columns.length; j++) {
111
- const val = columnValues[j][i];
112
- const resourceCol = resourceColumns[j];
113
- if ((resourceCol.type === AdminForthDataTypes.INTEGER
114
- || resourceCol.type === AdminForthDataTypes.FLOAT) && val !== '') {
115
- // convert empty strings to null for numeric fields
116
- row[columns[j]] = +val;
117
- }
118
- else if (resourceCol.type === AdminForthDataTypes.BOOLEAN && val !== '') {
119
- row[columns[j]] = (val.toLowerCase() === 'true' || val === '1' || val === 1);
120
- }
121
- else {
122
- row[columns[j]] = val;
123
- }
124
- }
125
- rows.push(row);
126
- }
98
+ const rows = this.buildRowsFromData(data, columns, resourceColumns, { coerceTypes: true });
127
99
  console.log('Prepared rows for import:', rows);
128
100
  let importedCount = 0;
129
101
  let updatedCount = 0;
@@ -155,41 +127,13 @@ export default class ImportExport extends AdminForthPlugin {
155
127
  noAuth: true,
156
128
  handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body }) {
157
129
  const { data } = body;
158
- const rows = [];
159
- const columns = Object.keys(data);
160
- // check column names are valid
161
- const errors = [];
162
- columns.forEach((col) => {
163
- if (!this.resourceConfig.columns.some((c) => c.name === col)) {
164
- const similar = suggestIfTypo(this.resourceConfig.columns.map((c) => c.name), col);
165
- errors.push(`Column '${col}' defined in CSV not found in resource '${this.resourceConfig.resourceId}'. ${similar ? `If you mean '${similar}', rename it in CSV` : 'If column is in database but not in resource configuration, add it with showIn:[]'}`);
166
- }
167
- });
130
+ const columns = this.getColumnNames(data);
131
+ const { errors, resourceColumns } = this.validateColumns(columns);
168
132
  if (errors.length > 0) {
169
133
  return { ok: false, errors };
170
134
  }
171
135
  const primaryKeyColumn = this.resourceConfig.columns.find(col => col.primaryKey);
172
- const resourceColumns = columns.map(colName => this.resourceConfig.columns.find(c => c.name === colName));
173
- const columnValues = Object.values(data);
174
- for (let i = 0; i < columnValues[0].length; i++) {
175
- const row = {};
176
- for (let j = 0; j < columns.length; j++) {
177
- const val = columnValues[j][i];
178
- const resourceCol = resourceColumns[j];
179
- if ((resourceCol.type === AdminForthDataTypes.INTEGER
180
- || resourceCol.type === AdminForthDataTypes.FLOAT) && val !== '') {
181
- // convert empty strings to null for numeric fields
182
- row[columns[j]] = +val;
183
- }
184
- else if (resourceCol.type === AdminForthDataTypes.BOOLEAN && val !== '') {
185
- row[columns[j]] = (val.toLowerCase() === 'true' || val === '1' || val === 1);
186
- }
187
- else {
188
- row[columns[j]] = val;
189
- }
190
- }
191
- rows.push(row);
192
- }
136
+ const rows = this.buildRowsFromData(data, columns, resourceColumns, { coerceTypes: true });
193
137
  let importedCount = 0;
194
138
  yield Promise.all(rows.map((row) => __awaiter(this, void 0, void 0, function* () {
195
139
  try {
@@ -217,17 +161,10 @@ export default class ImportExport extends AdminForthPlugin {
217
161
  handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body }) {
218
162
  const { data } = body;
219
163
  const primaryKeyColumn = this.resourceConfig.columns.find(col => col.primaryKey);
220
- const columns = Object.keys(data);
221
- const columnValues = Object.values(data);
222
- const rows = Array.from({ length: columnValues[0].length }, (_, i) => {
223
- const row = {};
224
- for (let j = 0; j < columns.length; j++) {
225
- row[columns[j]] = columnValues[j][i];
226
- }
227
- return row;
228
- });
164
+ const columns = this.getColumnNames(data);
165
+ const rows = this.buildRowsFromData(data, columns, undefined, { coerceTypes: false });
229
166
  const primaryKeys = rows
230
- .map(row => row[primaryKeyColumn.name])
167
+ .map(row => primaryKeyColumn ? row[primaryKeyColumn.name] : undefined)
231
168
  .filter(key => key !== undefined && key !== null && key !== '');
232
169
  const existingRecords = yield this.adminforth
233
170
  .resource(this.resourceConfig.resourceId)
@@ -245,4 +182,59 @@ export default class ImportExport extends AdminForthPlugin {
245
182
  })
246
183
  });
247
184
  }
185
+ getColumnNames(data) {
186
+ return Object.keys(data !== null && data !== void 0 ? data : {});
187
+ }
188
+ validateColumns(columns) {
189
+ const errors = [];
190
+ const resourceColumns = [];
191
+ columns.forEach((col) => {
192
+ const resourceColumn = this.resourceConfig.columns.find((c) => c.name === col);
193
+ if (!resourceColumn) {
194
+ const similar = suggestIfTypo(this.resourceConfig.columns.map((c) => c.name), col);
195
+ errors.push(`Column '${col}' defined in CSV not found in resource '${this.resourceConfig.resourceId}'. ${similar
196
+ ? `If you mean '${similar}', rename it in CSV`
197
+ : 'If column is in database but not in resource configuration, add it with showIn:[]'}`);
198
+ return;
199
+ }
200
+ resourceColumns.push(resourceColumn);
201
+ });
202
+ return { errors, resourceColumns };
203
+ }
204
+ buildRowsFromData(data, columns, resourceColumns, { coerceTypes } = { coerceTypes: true }) {
205
+ const columnValues = Object.values(data !== null && data !== void 0 ? data : {});
206
+ if (columns.length === 0 || columnValues.length === 0) {
207
+ return [];
208
+ }
209
+ const rows = [];
210
+ const rowCount = columnValues[0].length;
211
+ for (let i = 0; i < rowCount; i++) {
212
+ const row = {};
213
+ for (let j = 0; j < columns.length; j++) {
214
+ const val = columnValues[j][i];
215
+ const resourceCol = resourceColumns ? resourceColumns[j] : undefined;
216
+ row[columns[j]] = coerceTypes
217
+ ? this.coerceValue(resourceCol, val)
218
+ : val;
219
+ }
220
+ rows.push(row);
221
+ }
222
+ return rows;
223
+ }
224
+ coerceValue(resourceCol, val) {
225
+ if (!resourceCol || val === '') {
226
+ return val;
227
+ }
228
+ if ((resourceCol.type === AdminForthDataTypes.INTEGER
229
+ || resourceCol.type === AdminForthDataTypes.FLOAT)) {
230
+ return +val;
231
+ }
232
+ if (resourceCol.type === AdminForthDataTypes.BOOLEAN) {
233
+ if (typeof val === 'string') {
234
+ return val.toLowerCase() === 'true' || val === '1';
235
+ }
236
+ return val === 1 || val === true;
237
+ }
238
+ return val;
239
+ }
248
240
  }
package/index.ts CHANGED
@@ -96,47 +96,13 @@ export default class ImportExport extends AdminForthPlugin {
96
96
  noAuth: true,
97
97
  handler: async ({ body }) => {
98
98
  const { data } = body;
99
- const rows = [];
100
- const columns = Object.keys(data);
101
-
102
- // check column names are valid
103
- const errors: string[] = [];
104
- columns.forEach((col) => {
105
- if (!this.resourceConfig.columns.some((c) => c.name === col)) {
106
- const similar = suggestIfTypo(this.resourceConfig.columns.map((c) => c.name), col);
107
- errors.push(`Column '${col}' defined in CSV not found in resource '${this.resourceConfig.resourceId}'. ${
108
- similar ? `If you mean '${similar}', rename it in CSV` : 'If column is in database but not in resource configuration, add it with showIn:[]'}`
109
- );
110
- }
111
- });
99
+ const columns = this.getColumnNames(data);
100
+ const { errors, resourceColumns } = this.validateColumns(columns);
112
101
  if (errors.length > 0) {
113
102
  return { ok: false, errors };
114
103
  }
115
-
116
104
  const primaryKeyColumn = this.resourceConfig.columns.find(col => col.primaryKey);
117
-
118
- const resourceColumns = columns.map(colName => this.resourceConfig.columns.find(c => c.name === colName));
119
-
120
- const columnValues: any[] = Object.values(data);
121
- for (let i = 0; i < columnValues[0].length; i++) {
122
- const row = {};
123
- for (let j = 0; j < columns.length; j++) {
124
- const val = columnValues[j][i];
125
- const resourceCol = resourceColumns[j];
126
-
127
- if ( (resourceCol.type === AdminForthDataTypes.INTEGER
128
- || resourceCol.type === AdminForthDataTypes.FLOAT) && val !== ''
129
- ) {
130
- // convert empty strings to null for numeric fields
131
- row[columns[j]] = +val;
132
- } else if (resourceCol.type === AdminForthDataTypes.BOOLEAN && val !== '') {
133
- row[columns[j]] = (val.toLowerCase() === 'true' || val === '1' || val === 1);
134
- } else {
135
- row[columns[j]] = val;
136
- }
137
- }
138
- rows.push(row);
139
- }
105
+ const rows = this.buildRowsFromData(data, columns, resourceColumns, { coerceTypes: true });
140
106
 
141
107
  console.log('Prepared rows for import:', rows);
142
108
 
@@ -173,45 +139,14 @@ export default class ImportExport extends AdminForthPlugin {
173
139
  noAuth: true,
174
140
  handler: async ({ body }) => {
175
141
  const { data } = body;
176
- const rows = [];
177
- const columns = Object.keys(data);
178
-
179
- // check column names are valid
180
- const errors: string[] = [];
181
- columns.forEach((col) => {
182
- if (!this.resourceConfig.columns.some((c) => c.name === col)) {
183
- const similar = suggestIfTypo(this.resourceConfig.columns.map((c) => c.name), col);
184
- errors.push(`Column '${col}' defined in CSV not found in resource '${this.resourceConfig.resourceId}'. ${
185
- similar ? `If you mean '${similar}', rename it in CSV` : 'If column is in database but not in resource configuration, add it with showIn:[]'}`
186
- );
187
- }
188
- });
142
+ const columns = this.getColumnNames(data);
143
+ const { errors, resourceColumns } = this.validateColumns(columns);
189
144
  if (errors.length > 0) {
190
145
  return { ok: false, errors };
191
146
  }
192
147
 
193
148
  const primaryKeyColumn = this.resourceConfig.columns.find(col => col.primaryKey);
194
- const resourceColumns = columns.map(colName => this.resourceConfig.columns.find(c => c.name === colName));
195
- const columnValues: any[] = Object.values(data);
196
- for (let i = 0; i < columnValues[0].length; i++) {
197
- const row = {};
198
- for (let j = 0; j < columns.length; j++) {
199
- const val = columnValues[j][i];
200
- const resourceCol = resourceColumns[j];
201
-
202
- if ( (resourceCol.type === AdminForthDataTypes.INTEGER
203
- || resourceCol.type === AdminForthDataTypes.FLOAT) && val !== ''
204
- ) {
205
- // convert empty strings to null for numeric fields
206
- row[columns[j]] = +val;
207
- } else if (resourceCol.type === AdminForthDataTypes.BOOLEAN && val !== '') {
208
- row[columns[j]] = (val.toLowerCase() === 'true' || val === '1' || val === 1);
209
- } else {
210
- row[columns[j]] = val;
211
- }
212
- }
213
- rows.push(row);
214
- }
149
+ const rows = this.buildRowsFromData(data, columns, resourceColumns, { coerceTypes: true });
215
150
 
216
151
  let importedCount = 0;
217
152
 
@@ -243,19 +178,11 @@ export default class ImportExport extends AdminForthPlugin {
243
178
  handler: async ({ body }) => {
244
179
  const { data } = body as { data: Record<string, unknown[]> };
245
180
  const primaryKeyColumn = this.resourceConfig.columns.find(col => col.primaryKey);
246
- const columns = Object.keys(data);
247
- const columnValues = Object.values(data);
248
-
249
- const rows = Array.from({ length: columnValues[0].length }, (_, i) => {
250
- const row = {};
251
- for (let j = 0; j < columns.length; j++) {
252
- row[columns[j]] = columnValues[j][i];
253
- }
254
- return row;
255
- });
181
+ const columns = this.getColumnNames(data);
182
+ const rows = this.buildRowsFromData(data, columns, undefined, { coerceTypes: false });
256
183
 
257
184
  const primaryKeys = rows
258
- .map(row => row[primaryKeyColumn.name])
185
+ .map(row => primaryKeyColumn ? row[primaryKeyColumn.name] : undefined)
259
186
  .filter(key => key !== undefined && key !== null && key !== '');
260
187
 
261
188
  const existingRecords = await this.adminforth
@@ -277,4 +204,85 @@ export default class ImportExport extends AdminForthPlugin {
277
204
  });
278
205
  }
279
206
 
207
+ private getColumnNames(data: Record<string, unknown[]>): string[] {
208
+ return Object.keys(data ?? {});
209
+ }
210
+
211
+ private validateColumns(columns: string[]): {
212
+ errors: string[];
213
+ resourceColumns: AdminForthResourceColumn[];
214
+ } {
215
+ const errors: string[] = [];
216
+ const resourceColumns: AdminForthResourceColumn[] = [];
217
+
218
+ columns.forEach((col) => {
219
+ const resourceColumn = this.resourceConfig.columns.find((c) => c.name === col);
220
+ if (!resourceColumn) {
221
+ const similar = suggestIfTypo(this.resourceConfig.columns.map((c) => c.name), col);
222
+ errors.push(
223
+ `Column '${col}' defined in CSV not found in resource '${this.resourceConfig.resourceId}'. ${
224
+ similar
225
+ ? `If you mean '${similar}', rename it in CSV`
226
+ : 'If column is in database but not in resource configuration, add it with showIn:[]'
227
+ }`
228
+ );
229
+ return;
230
+ }
231
+ resourceColumns.push(resourceColumn);
232
+ });
233
+
234
+ return { errors, resourceColumns };
235
+ }
236
+
237
+ private buildRowsFromData(
238
+ data: Record<string, unknown[]>,
239
+ columns: string[],
240
+ resourceColumns?: AdminForthResourceColumn[],
241
+ { coerceTypes }: { coerceTypes: boolean } = { coerceTypes: true }
242
+ ) {
243
+ const columnValues: unknown[][] = Object.values(data ?? {});
244
+ if (columns.length === 0 || columnValues.length === 0) {
245
+ return [];
246
+ }
247
+
248
+ const rows: Record<string, unknown>[] = [];
249
+ const rowCount = columnValues[0].length;
250
+
251
+ for (let i = 0; i < rowCount; i++) {
252
+ const row: Record<string, unknown> = {};
253
+ for (let j = 0; j < columns.length; j++) {
254
+ const val = columnValues[j][i];
255
+ const resourceCol = resourceColumns ? resourceColumns[j] : undefined;
256
+ row[columns[j]] = coerceTypes
257
+ ? this.coerceValue(resourceCol, val)
258
+ : val;
259
+ }
260
+ rows.push(row);
261
+ }
262
+
263
+ return rows;
264
+ }
265
+
266
+ private coerceValue(resourceCol: AdminForthResourceColumn | undefined, val: unknown): unknown {
267
+ if (!resourceCol || val === '') {
268
+ return val;
269
+ }
270
+
271
+ if (
272
+ (resourceCol.type === AdminForthDataTypes.INTEGER
273
+ || resourceCol.type === AdminForthDataTypes.FLOAT)
274
+ ) {
275
+ return +val;
276
+ }
277
+
278
+ if (resourceCol.type === AdminForthDataTypes.BOOLEAN) {
279
+ if (typeof val === 'string') {
280
+ return val.toLowerCase() === 'true' || val === '1';
281
+ }
282
+ return val === 1 || val === true;
283
+ }
284
+
285
+ return val;
286
+ }
287
+
280
288
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/import-export",
3
- "version": "1.4.9",
3
+ "version": "1.4.10",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -9,8 +9,7 @@
9
9
  },
10
10
  "homepage": "https://adminforth.dev/docs/tutorial/Plugins/import-export/",
11
11
  "scripts": {
12
- "build": "tsc && rsync -av --exclude 'node_modules' custom dist/",
13
- "prepare": "npm link adminforth"
12
+ "build": "tsc && rsync -av --exclude 'node_modules' custom dist/"
14
13
  },
15
14
  "repository": {
16
15
  "type": "git",
@@ -26,7 +25,7 @@
26
25
  "license": "ISC",
27
26
  "description": "CSV import/export plugin for adminforth",
28
27
  "peerDependencies": {
29
- "adminforth": "next"
28
+ "adminforth": "^2.24.0"
30
29
  },
31
30
  "devDependencies": {
32
31
  "@types/node": "^22.10.7",
@@ -43,6 +42,7 @@
43
42
  [
44
43
  "semantic-release-slack-bot",
45
44
  {
45
+ "packageName": "@adminforth/import-export",
46
46
  "notifyOnSuccess": true,
47
47
  "notifyOnFail": true,
48
48
  "slackIcon": ":package:",