@researchdatabox/redbox-dev-tools 0.0.1-beta.8936 → 0.0.1-beta.9020

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,6 +1,10 @@
1
1
  // This file is a helper for dev only. It makes it easier to run webpack when parts of the mono repo are in different stages of being built. Refer to the redbox-core webpack.ts for how the hook runs webpack
2
+ const path = require('path');
3
+ const { createRequire } = require('module');
2
4
  const redboxCore = require('@researchdatabox/redbox-core');
3
5
 
6
+ const requireFromHere = createRequire(__filename);
7
+
4
8
  const ensureTsNode = () => {
5
9
  try {
6
10
  require('ts-node/register/transpile-only');
@@ -18,7 +22,7 @@ if (!defineWebpackHook) {
18
22
  }
19
23
  if (!defineWebpackHook && ensureTsNode()) {
20
24
  try {
21
- defineWebpackHook = require('@researchdatabox/redbox-core/src/hooks/webpack.ts').defineWebpackHook;
25
+ defineWebpackHook = requireFromHere('@researchdatabox/redbox-core/src/hooks/webpack.ts').defineWebpackHook;
22
26
  } catch (e) {}
23
27
  }
24
28
 
@@ -54,7 +58,7 @@ if (!webpackConfig || (Array.isArray(webpackConfig) && webpackConfig.length ===
54
58
  if (!webpackConfig || (Array.isArray(webpackConfig) && webpackConfig.length === 0)) {
55
59
  if (ensureTsNode()) {
56
60
  try {
57
- const srcWebpack = require('@researchdatabox/redbox-core/src/config/webpack.config.ts');
61
+ const srcWebpack = requireFromHere('@researchdatabox/redbox-core/src/config/webpack.config.ts');
58
62
  webpackConfig = srcWebpack && srcWebpack.webpack ? srcWebpack.webpack.config : srcWebpack.config || srcWebpack;
59
63
  } catch (e) {}
60
64
  }
package/dist/cli.js CHANGED
@@ -128,6 +128,7 @@ program
128
128
  });
129
129
  (0, form_config_1.registerMigrateFormConfigCommand)(program);
130
130
  (0, form_config_1.registerMigrateDataClassificationCommand)(program);
131
+ (0, form_config_1.registerMigrateFigshareConfigCommand)(program);
131
132
  (0, form_config_1.registerClientFormConfigCommand)(program);
132
133
  (0, form_config_1.registerQuestionTreeDiagramCommand)(program);
133
134
  program
@@ -3,3 +3,4 @@ export declare function registerMigrateFormConfigCommand(program: Command): void
3
3
  export declare function registerMigrateDataClassificationCommand(program: Command): void;
4
4
  export declare function registerClientFormConfigCommand(program: Command): void;
5
5
  export declare function registerQuestionTreeDiagramCommand(program: Command): void;
6
+ export declare function registerMigrateFigshareConfigCommand(program: Command): void;
@@ -40,6 +40,7 @@ exports.registerMigrateFormConfigCommand = registerMigrateFormConfigCommand;
40
40
  exports.registerMigrateDataClassificationCommand = registerMigrateDataClassificationCommand;
41
41
  exports.registerClientFormConfigCommand = registerClientFormConfigCommand;
42
42
  exports.registerQuestionTreeDiagramCommand = registerQuestionTreeDiagramCommand;
43
+ exports.registerMigrateFigshareConfigCommand = registerMigrateFigshareConfigCommand;
43
44
  const fs_1 = __importDefault(require("fs"));
44
45
  const path = __importStar(require("path"));
45
46
  const redbox_core_1 = require("@researchdatabox/redbox-core");
@@ -192,3 +193,212 @@ function registerQuestionTreeDiagramCommand(program) {
192
193
  }
193
194
  });
194
195
  }
196
+ function collectLegacyTemplates(value, pathPrefix = '') {
197
+ if (typeof value === 'string') {
198
+ return value.includes('<%') ? [pathPrefix] : [];
199
+ }
200
+ if (Array.isArray(value)) {
201
+ return value.flatMap((entry, index) => collectLegacyTemplates(entry, `${pathPrefix}[${index}]`));
202
+ }
203
+ if (value && typeof value === 'object') {
204
+ return Object.entries(value).flatMap(([key, entry]) => collectLegacyTemplates(entry, pathPrefix ? `${pathPrefix}.${key}` : key));
205
+ }
206
+ return [];
207
+ }
208
+ function loadLegacyModule(modulePath) {
209
+ const resolvedPath = path.resolve(modulePath);
210
+ const loaded = require(resolvedPath);
211
+ if (loaded == null || typeof loaded !== 'object') {
212
+ throw new Error(`Legacy config module '${resolvedPath}' did not export an object`);
213
+ }
214
+ return loaded;
215
+ }
216
+ function pickLegacyExport(moduleExports, exportName) {
217
+ const named = moduleExports[exportName];
218
+ if (named != null && typeof named === 'object') {
219
+ return named;
220
+ }
221
+ return moduleExports;
222
+ }
223
+ function toNumericStatusCodes(value) {
224
+ if (!Array.isArray(value)) {
225
+ return [408, 429, 500, 502, 503, 504];
226
+ }
227
+ return value
228
+ .map((entry) => Number(entry))
229
+ .filter((entry) => Number.isFinite(entry));
230
+ }
231
+ function createMigratedFigsharePublishingConfig(brand, figshareApi, figshareApiEnv = {}, figshareForMapping = {}) {
232
+ const overrideArtifacts = figshareApiEnv.overrideArtifacts ?? {};
233
+ const mapping = {
234
+ ...(figshareApi.mapping ?? {}),
235
+ ...(overrideArtifacts.mapping ?? {})
236
+ };
237
+ const resolvedBaseUrl = String(overrideArtifacts.baseURL ?? figshareApi.baseURL ?? '');
238
+ const resolvedFrontEndUrl = String(overrideArtifacts.frontEndURL ?? figshareApi.frontEndURL ?? '');
239
+ const resolvedToken = String(overrideArtifacts.APIToken ?? figshareApi.APIToken ?? '');
240
+ return {
241
+ enabled: Boolean(resolvedToken && resolvedBaseUrl && resolvedFrontEndUrl),
242
+ connection: {
243
+ baseUrl: resolvedBaseUrl,
244
+ frontEndUrl: resolvedFrontEndUrl,
245
+ token: resolvedToken,
246
+ timeoutMs: 30000,
247
+ operationTimeouts: {
248
+ metadataMs: 30000,
249
+ uploadInitMs: 30000,
250
+ uploadPartMs: 120000,
251
+ publishMs: 60000
252
+ },
253
+ retry: {
254
+ maxAttempts: Number(figshareApi.retry?.maxAttempts ?? 3),
255
+ baseDelayMs: Number(figshareApi.retry?.baseDelayMs ?? 500),
256
+ maxDelayMs: Number(figshareApi.retry?.maxDelayMs ?? 4000),
257
+ retryOnStatusCodes: toNumericStatusCodes(figshareApi.retry?.retryOnStatusCodes),
258
+ retryOnMethods: ['get', 'put', 'delete']
259
+ }
260
+ },
261
+ article: {
262
+ itemType: mapping.figshareItemType ?? 'dataset',
263
+ groupId: mapping.figshareItemGroupId,
264
+ publishMode: mapping.figshareNeedsPublishAfterFileUpload ? 'afterUploadsComplete' : 'immediate',
265
+ republishOnMetadataChange: true,
266
+ republishOnAssetChange: true
267
+ },
268
+ record: {
269
+ articleIdPath: mapping.recordFigArticleId ?? 'metadata.figshare_article_id',
270
+ articleUrlPaths: Array.isArray(mapping.recordFigArticleURL) ? mapping.recordFigArticleURL : [mapping.recordFigArticleURL ?? 'metadata.figshare_article_location'],
271
+ dataLocationsPath: mapping.recordDataLocations ?? 'metadata.dataLocations',
272
+ statusPath: 'metadata.figshareStatus',
273
+ errorPath: 'metadata.figshareError',
274
+ syncStatePath: 'metadata.figshareSyncState',
275
+ allFilesUploadedPath: typeof mapping.recordAllFilesUploaded === 'string' ? mapping.recordAllFilesUploaded : ''
276
+ },
277
+ selection: {
278
+ attachmentMode: mapping.figshareOnlyPublishSelectedAttachmentFiles ? 'selectedOnly' : 'all',
279
+ urlMode: mapping.figshareOnlyPublishSelectedLocationURLs ? 'selectedOnly' : 'all',
280
+ selectedFlagPath: 'selected'
281
+ },
282
+ authors: {
283
+ source: 'defaultRedboxContributors',
284
+ uniqueBy: mapping.recordAuthorUniqueBy ?? 'email',
285
+ externalNameField: mapping.recordAuthorExternalName ?? 'text_full_name',
286
+ maxInlineAuthors: 50,
287
+ contributorPaths: ['metadata.contributor_ci', 'metadata.contributors'],
288
+ lookup: []
289
+ },
290
+ metadata: {
291
+ title: { kind: 'path', path: 'metadata.title' },
292
+ description: { kind: 'path', path: 'metadata.description' },
293
+ keywords: { kind: 'path', path: 'metadata.finalKeywords', defaultValue: [] },
294
+ funding: { kind: 'path', path: 'metadata.funder' },
295
+ license: { source: { kind: 'path', path: mapping.recordLicensePath ?? 'metadata.license' }, matchBy: 'urlContains', required: true },
296
+ categories: { source: { kind: 'path', path: mapping.recordCategoryPath ?? 'metadata.forCodes', defaultValue: [] }, mappingStrategy: 'for2020Mapping' },
297
+ customFields: []
298
+ },
299
+ categories: {
300
+ strategy: 'for2020Mapping',
301
+ mappingTable: ((figshareForMapping.FORMapping ?? []).map((entry) => ({
302
+ sourceCode: String(entry.redboxCode ?? entry.sourceCode ?? ''),
303
+ figshareCategoryId: Number(entry.figshareCategoryId ?? entry.figCode ?? 0)
304
+ }))).filter((entry) => entry.sourceCode !== ''),
305
+ allowUnmapped: false
306
+ },
307
+ assets: {
308
+ enableHostedFiles: true,
309
+ enableLinkFiles: true,
310
+ dedupeStrategy: 'sourceId',
311
+ staging: {
312
+ tempDir: String(figshareApi.attachmentsFigshareTempDir ?? ''),
313
+ cleanupPolicy: 'deleteAfterSuccess',
314
+ diskSpaceThresholdBytes: 1073741824
315
+ }
316
+ },
317
+ embargo: {
318
+ mode: 'recordDriven',
319
+ forceSync: Boolean(mapping.figshareForceEmbargoUpdateAlways),
320
+ accessRights: {
321
+ accessRights: { kind: 'path', path: 'metadata.accessRights' },
322
+ fullEmbargoUntil: { kind: 'path', path: 'metadata.embargoUntil' },
323
+ fileEmbargoUntil: { kind: 'path', path: 'metadata.embargoUntil' },
324
+ reason: { kind: 'path', path: 'metadata.embargoReason' }
325
+ }
326
+ },
327
+ queue: {
328
+ publishAfterUploadDelay: typeof mapping.schedulePublishAfterUploadJob === 'string' ? mapping.schedulePublishAfterUploadJob : 'in 2 minutes',
329
+ uploadedFilesCleanupDelay: typeof mapping.scheduleUploadedFilesCleanupJob === 'string' ? mapping.scheduleUploadedFilesCleanupJob : 'in 5 minutes'
330
+ },
331
+ workflow: {
332
+ transitionRules: [],
333
+ transitionJob: {
334
+ enabled: String(mapping.figshareScheduledTransitionRecordWorkflowFromArticlePropertiesJob?.enabled ?? 'false') === 'true',
335
+ namedQuery: String(mapping.figshareScheduledTransitionRecordWorkflowFromArticlePropertiesJob?.namedQuery ?? ''),
336
+ targetStep: String(mapping.figshareScheduledTransitionRecordWorkflowFromArticlePropertiesJob?.targetStep ?? ''),
337
+ paramMap: mapping.figshareScheduledTransitionRecordWorkflowFromArticlePropertiesJob?.paramMap ?? {},
338
+ figshareTargetFieldKey: String(mapping.figshareScheduledTransitionRecordWorkflowFromArticlePropertiesJob?.figshareTargetFieldKey ?? ''),
339
+ figshareTargetFieldValue: String(mapping.figshareScheduledTransitionRecordWorkflowFromArticlePropertiesJob?.figshareTargetFieldValue ?? ''),
340
+ username: String(mapping.figshareScheduledTransitionRecordWorkflowFromArticlePropertiesJob?.username ?? ''),
341
+ userType: String(mapping.figshareScheduledTransitionRecordWorkflowFromArticlePropertiesJob?.userType ?? '')
342
+ }
343
+ },
344
+ testing: {
345
+ mode: figshareApi.testMode ? 'fixture' : 'live'
346
+ },
347
+ writeBack: {
348
+ articleId: 'metadata.figshare_article_id',
349
+ articleUrls: ['metadata.figshare_article_location'],
350
+ extraFields: []
351
+ },
352
+ migrationReport: {
353
+ brand,
354
+ unsupportedLegacyTemplates: collectLegacyTemplates(mapping)
355
+ }
356
+ };
357
+ }
358
+ function registerMigrateFigshareConfigCommand(program) {
359
+ program
360
+ .command('migrate-figshare-config')
361
+ .description('Migrate legacy Figshare config into figsharePublishing AppConfig JSON files')
362
+ .requiredOption('--figshare-api <path>', 'Path to the legacy figshareAPI config module')
363
+ .option('--figshare-api-env <path>', 'Path to the legacy figshareAPIEnv config module')
364
+ .option('--figshare-for-mapping <path>', 'Path to the legacy figshareReDBoxFORMapping config module')
365
+ .requiredOption('-o, --output <path>', 'Directory to write migrated figsharePublishing JSON files')
366
+ .option('-b, --brands <brands...>', 'Brands to generate config for', ['default'])
367
+ .action(async (options) => {
368
+ try {
369
+ const globalOptions = program.opts();
370
+ const outputDir = path.resolve(options.output);
371
+ const brands = options.brands;
372
+ const figshareApi = pickLegacyExport(loadLegacyModule(options.figshareApi), 'figshareAPI');
373
+ const figshareApiEnv = options.figshareApiEnv
374
+ ? pickLegacyExport(loadLegacyModule(options.figshareApiEnv), 'figshareAPIEnv')
375
+ : {};
376
+ const figshareForMapping = options.figshareForMapping
377
+ ? pickLegacyExport(loadLegacyModule(options.figshareForMapping), 'figshareReDBoxFORMapping')
378
+ : {};
379
+ if (!globalOptions.dryRun) {
380
+ fs_1.default.mkdirSync(outputDir, { recursive: true });
381
+ }
382
+ const report = brands.map((brand) => {
383
+ const migrated = createMigratedFigsharePublishingConfig(brand, figshareApi, figshareApiEnv, figshareForMapping);
384
+ const outputPath = path.join(outputDir, `${brand}.figsharePublishing.json`);
385
+ if (!globalOptions.dryRun) {
386
+ fs_1.default.writeFileSync(outputPath, JSON.stringify(migrated, null, 2) + '\n', 'utf8');
387
+ }
388
+ return {
389
+ brand,
390
+ outputPath,
391
+ unsupportedLegacyTemplates: migrated.migrationReport.unsupportedLegacyTemplates
392
+ };
393
+ });
394
+ if (globalOptions.dryRun) {
395
+ console.log('[dry-run] Migration report generated; no files written.');
396
+ }
397
+ console.log(JSON.stringify(report, null, 2));
398
+ }
399
+ catch (error) {
400
+ console.error(`\n❌ Error: `, error);
401
+ process.exit(1);
402
+ }
403
+ });
404
+ }
@@ -86,15 +86,58 @@ function toOutputRelativePath(templateRoot, filePath) {
86
86
  function shouldMarkExecutable(relativePath) {
87
87
  return relativePath.endsWith('.sh');
88
88
  }
89
- function readPackageVersion(packageRoot, packageName) {
90
- const packageJsonPath = path.join(packageRoot, '..', packageName, 'package.json');
91
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
92
- if (!packageJson.version) {
93
- throw new Error(`Could not determine version for ${packageName}`);
89
+ function readPackageJson(packageJsonPath) {
90
+ return JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
91
+ }
92
+ function tryReadPackageJson(packageJsonPath) {
93
+ return fs.existsSync(packageJsonPath)
94
+ ? readPackageJson(packageJsonPath)
95
+ : undefined;
96
+ }
97
+ function getScopedPackageName(packageName) {
98
+ return `@researchdatabox/${packageName}`;
99
+ }
100
+ function readOwnPackageVersion(packageRoot) {
101
+ const packageJson = tryReadPackageJson(path.join(packageRoot, 'package.json'));
102
+ if (!packageJson?.version) {
103
+ throw new Error('Could not determine version for @researchdatabox/redbox-dev-tools');
94
104
  }
95
105
  return packageJson.version;
96
106
  }
107
+ function tryReadInstalledPackageVersion(packageRoot, packageName) {
108
+ const packageJsonPath = path.join(packageRoot, 'node_modules', ...getScopedPackageName(packageName).split('/'), 'package.json');
109
+ return tryReadPackageJson(packageJsonPath)?.version;
110
+ }
111
+ function isReusableDependencySpecifier(versionSpecifier) {
112
+ return !/^(file:|workspace:|link:|portal:)/.test(versionSpecifier);
113
+ }
114
+ function readDeclaredDependencyVersion(packageRoot, packageName) {
115
+ const packageJson = tryReadPackageJson(path.join(packageRoot, 'package.json'));
116
+ const dependencyName = getScopedPackageName(packageName);
117
+ const versionSpecifier = packageJson?.dependencies?.[dependencyName]
118
+ ?? packageJson?.peerDependencies?.[dependencyName]
119
+ ?? packageJson?.devDependencies?.[dependencyName];
120
+ return versionSpecifier && isReusableDependencySpecifier(versionSpecifier)
121
+ ? versionSpecifier
122
+ : undefined;
123
+ }
124
+ function readDependencyPackageVersion(packageRoot, packageName) {
125
+ const installedVersion = tryReadInstalledPackageVersion(packageRoot, packageName);
126
+ if (installedVersion) {
127
+ return installedVersion;
128
+ }
129
+ const siblingPackageJson = tryReadPackageJson(path.join(packageRoot, '..', packageName, 'package.json'));
130
+ if (siblingPackageJson?.version) {
131
+ return siblingPackageJson.version;
132
+ }
133
+ const declaredVersion = readDeclaredDependencyVersion(packageRoot, packageName);
134
+ if (declaredVersion) {
135
+ return declaredVersion;
136
+ }
137
+ throw new Error(`Could not determine version for ${getScopedPackageName(packageName)}`);
138
+ }
97
139
  function buildTemplateContext(options) {
140
+ const packageRoot = getPackageRoot();
98
141
  const packageName = normalizePackageName(options.packageName);
99
142
  const hookShortName = packageName.replace(/^redbox-hook-/, '');
100
143
  const hookPascalName = toPascalCase(hookShortName);
@@ -104,8 +147,8 @@ function buildTemplateContext(options) {
104
147
  hookPascalName,
105
148
  description: options.description ?? `ReDBox hook for ${hookPascalName}`,
106
149
  integrationDirName: hookShortName,
107
- redboxCoreVersion: readPackageVersion(getPackageRoot(), 'redbox-core'),
108
- redboxDevToolsVersion: readPackageVersion(getPackageRoot(), 'redbox-dev-tools'),
150
+ redboxCoreVersion: readDependencyPackageVersion(packageRoot, 'redbox-core'),
151
+ redboxDevToolsVersion: readOwnPackageVersion(packageRoot),
109
152
  };
110
153
  }
111
154
  function generateHookArchetype(options) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@researchdatabox/redbox-dev-tools",
3
- "version": "0.0.1-beta.8936",
3
+ "version": "0.0.1-beta.9020",
4
4
  "description": "ReDBox customisation dev tools - shared configuration, generators, and migration tooling",
5
5
  "type": "commonjs",
6
6
  "main": "index.js",
@@ -44,8 +44,8 @@
44
44
  },
45
45
  "homepage": "https://github.com/redbox-mint/redbox-portal/tree/v4.5.1#readme",
46
46
  "dependencies": {
47
- "@researchdatabox/redbox-core": "0.0.1-beta.8936",
48
- "@researchdatabox/sails-ng-common": "0.0.1-beta.8936",
47
+ "@researchdatabox/redbox-core": "0.0.1-beta.9020",
48
+ "@researchdatabox/sails-ng-common": "0.0.1-beta.9020",
49
49
  "@tsconfig/node24": "24.0.1",
50
50
  "@types/chai": "5.2.3",
51
51
  "@types/lodash": "4.17.24",
@@ -64,8 +64,8 @@
64
64
  "zod": "3.25.28"
65
65
  },
66
66
  "devDependencies": {
67
- "@types/sinon": "17.0.3",
68
- "sinon": "17.0.1"
67
+ "@types/sinon": "21.0.1",
68
+ "sinon": "21.1.2"
69
69
  },
70
70
  "peerDependenciesMeta": {
71
71
  "@researchdatabox/redbox-core": {
@@ -73,13 +73,13 @@
73
73
  }
74
74
  },
75
75
  "overrides": {
76
+ "diff": "8.0.3",
76
77
  "mocha": {
77
78
  "serialize-javascript": "7.0.5"
78
79
  }
79
80
  },
80
81
  "publishConfig": {
81
82
  "access": "public",
82
- "registry": "https://registry.npmjs.org/",
83
- "provenance": false
83
+ "registry": "https://registry.npmjs.org/"
84
84
  }
85
85
  }
@@ -1,6 +1,6 @@
1
1
  save-exact=true
2
2
  save-prefix=
3
3
  ignore-scripts=true
4
- provenance=true
4
+ provenance=false
5
5
  registry=https://registry.npmjs.org/
6
6
  audit-level=moderate