@nocobase/cli 2.2.0-beta.17 → 2.2.0-beta.18

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.
@@ -1,10 +1,19 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
1
9
  import { Command } from '@oclif/core';
2
10
  import { buildCreateArgs, createFlags, runResourceCommand } from '../../../lib/resource-command.js';
3
11
  export default class ResourceCreate extends Command {
4
- static summary = 'Create a record in a resource';
5
- static description = 'Create a record in a generic resource. Pass record content through --values as a JSON object.';
12
+ static summary = 'Create one or more records in a resource';
13
+ static description = 'Create records in a generic resource. Pass record content through --values as a JSON object, or as a JSON array of objects to create multiple records in a single request.';
6
14
  static examples = [
7
15
  `<%= config.bin %> <%= command.id %> --resource users --values '{"nickname":"Ada"}'`,
16
+ `<%= config.bin %> <%= command.id %> --resource users --values '[{"nickname":"Ada"},{"nickname":"Grace"}]'`,
8
17
  `<%= config.bin %> <%= command.id %> --resource posts.comments --source-id 1 --values '{"content":"Hello"}'`,
9
18
  ];
10
19
  static flags = createFlags;
@@ -207,6 +207,29 @@ async function openPluginSource(source, npmRegistry, runFn = run) {
207
207
  }
208
208
  return await packNpmPluginSource(source, npmRegistry, runFn);
209
209
  }
210
+ /**
211
+ * Locate the directory holding the plugin's `package.json` inside a freshly extracted archive.
212
+ *
213
+ * Archives reach us in two shapes: npm-style tarballs (`npm pack`, registry downloads) wrap everything in a single
214
+ * top-level directory — conventionally `package/` — while NocoBase's own `yarn build <plugin> --tar` writes entries at
215
+ * the archive root. Extracting with a fixed `strip: 1` would silently discard the root-level files of the latter, so we
216
+ * extract verbatim and pick the package root here instead. When neither shape matches, return the extract root and let
217
+ * `readPluginMetadata` report the missing `package.json`.
218
+ */
219
+ async function resolveArchivePackageRoot(extractRoot) {
220
+ if (await pathExists(path.join(extractRoot, 'package.json'))) {
221
+ return extractRoot;
222
+ }
223
+ const entries = await fsp.readdir(extractRoot, { withFileTypes: true });
224
+ const directories = entries.filter((entry) => entry.isDirectory());
225
+ if (directories.length === 1) {
226
+ const nestedRoot = path.join(extractRoot, directories[0].name);
227
+ if (await pathExists(path.join(nestedRoot, 'package.json'))) {
228
+ return nestedRoot;
229
+ }
230
+ }
231
+ return extractRoot;
232
+ }
210
233
  async function readPluginMetadata(extractRoot, sourceLabel) {
211
234
  const packageJsonPath = path.join(extractRoot, 'package.json');
212
235
  let content;
@@ -241,22 +264,21 @@ export async function importPluginSource(source, options = {}) {
241
264
  await fsp.mkdir(storagePluginsPath, { recursive: true });
242
265
  const archive = await openPluginSource(normalizedSource, options.npmRegistry, options.runFn);
243
266
  const stageDir = await fsp.mkdtemp(path.join(storagePluginsPath, '.nb-plugin-import-'));
244
- let stageMoved = false;
245
267
  try {
246
268
  try {
247
- await pipeline(archive.stream, createGunzip(), tar.extract({ cwd: stageDir, strip: 1 }));
269
+ await pipeline(archive.stream, createGunzip(), tar.extract({ cwd: stageDir }));
248
270
  }
249
271
  catch (error) {
250
272
  const message = error instanceof Error ? error.message : String(error);
251
273
  throw new Error(`Failed to extract plugin archive from ${archive.source}: ${message}`);
252
274
  }
253
- const { packageName, packageVersion } = await readPluginMetadata(stageDir, archive.source);
275
+ const packageRoot = await resolveArchivePackageRoot(stageDir);
276
+ const { packageName, packageVersion } = await readPluginMetadata(packageRoot, archive.source);
254
277
  const outputDir = resolvePluginOutputDir(storagePluginsPath, packageName);
255
278
  const action = (await pathExists(outputDir)) ? 'updated' : 'installed';
256
279
  await fsp.mkdir(path.dirname(outputDir), { recursive: true });
257
280
  await fsp.rm(outputDir, { recursive: true, force: true });
258
- await fsp.rename(stageDir, outputDir);
259
- stageMoved = true;
281
+ await fsp.rename(packageRoot, outputDir);
260
282
  return {
261
283
  action,
262
284
  packageName,
@@ -268,9 +290,9 @@ export async function importPluginSource(source, options = {}) {
268
290
  };
269
291
  }
270
292
  finally {
271
- if (!stageMoved) {
272
- await fsp.rm(stageDir, { recursive: true, force: true });
273
- }
293
+ // Always removes the staging directory: it is either untouched (failure), or an empty wrapper left behind after the
294
+ // nested package root was renamed out of it.
295
+ await fsp.rm(stageDir, { recursive: true, force: true });
274
296
  await archive.cleanup();
275
297
  }
276
298
  }
@@ -41,6 +41,22 @@ function parseObjectFlag(value, flagName) {
41
41
  }
42
42
  return parsed;
43
43
  }
44
+ function parseValuesFlag(value, flagName) {
45
+ if (value === undefined) {
46
+ return undefined;
47
+ }
48
+ const parsed = parseJson(value, flagName);
49
+ if (!parsed || typeof parsed !== 'object') {
50
+ throw new Error(`--${flagName} must be a JSON object, or a JSON array of objects to create multiple records`);
51
+ }
52
+ if (Array.isArray(parsed)) {
53
+ const invalidIndex = parsed.findIndex((item) => !item || Array.isArray(item) || typeof item !== 'object');
54
+ if (invalidIndex !== -1) {
55
+ throw new Error(`--${flagName} array items must all be JSON objects, but item ${invalidIndex} is not`);
56
+ }
57
+ }
58
+ return parsed;
59
+ }
44
60
  function parseJsonArrayFlag(value, flagName) {
45
61
  if (value === undefined) {
46
62
  return undefined;
@@ -187,7 +203,7 @@ export const createFlags = {
187
203
  ...resourceBaseFlags,
188
204
  ...resourceAssociationFlags,
189
205
  values: Flags.string({
190
- description: 'Record values used by create as a JSON object.',
206
+ description: 'Record values used by create as a JSON object, or a JSON array of objects to create multiple records in one request.',
191
207
  required: true,
192
208
  }),
193
209
  whitelist: Flags.string({
@@ -298,7 +314,7 @@ export function buildGetArgs(flags) {
298
314
  export function buildCreateArgs(flags) {
299
315
  return {
300
316
  ...pickSharedArgs(flags),
301
- values: parseObjectFlag(flags.values, 'values'),
317
+ values: parseValuesFlag(flags.values, 'values'),
302
318
  whitelist: parseStringArrayFlags(flags.whitelist, 'whitelist'),
303
319
  blacklist: parseStringArrayFlags(flags.blacklist, 'blacklist'),
304
320
  };
@@ -1,3 +1,11 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
1
9
  import { executeRawApiRequest } from './api-client.js';
2
10
  function buildActionUrl(resource, action, sourceId) {
3
11
  if (typeof sourceId === 'undefined' || sourceId === null || !resource.includes('.')) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/cli",
3
- "version": "2.2.0-beta.17",
3
+ "version": "2.2.0-beta.18",
4
4
  "description": "NocoBase Command Line Tool",
5
5
  "type": "module",
6
6
  "main": "dist/generated/command-registry.js",
@@ -144,5 +144,5 @@
144
144
  "type": "git",
145
145
  "url": "git+https://github.com/nocobase/nocobase.git"
146
146
  },
147
- "gitHead": "32a74d4bdc538580581df7ac45f6ba9191fa2195"
147
+ "gitHead": "b74c962f7d35aa12012d908d16bd74fe6ac20a3d"
148
148
  }