@directus/extensions-sdk 10.0.0 → 10.1.0

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.
@@ -14,12 +14,12 @@ import { rollup, watch as rollupWatch } from 'rollup';
14
14
  import esbuildDefault from 'rollup-plugin-esbuild';
15
15
  import stylesDefault from 'rollup-plugin-styles';
16
16
  import vueDefault from 'rollup-plugin-vue';
17
- import { getLanguageFromPath, isLanguage } from '../utils/languages.js';
18
17
  import { clear, log } from '../utils/logger.js';
19
18
  import tryParseJson from '../utils/try-parse-json.js';
20
19
  import generateBundleEntrypoint from './helpers/generate-bundle-entrypoint.js';
21
20
  import loadConfig from './helpers/load-config.js';
22
21
  import { validateSplitEntrypointOption } from './helpers/validate-cli-options.js';
22
+ import { getFileExt } from '../utils/file.js';
23
23
  // Workaround for https://github.com/rollup/plugins/issues/1329
24
24
  const virtual = virtualDefault;
25
25
  const vue = vueDefault;
@@ -48,11 +48,13 @@ export default async function build(options) {
48
48
  process.exit(1);
49
49
  }
50
50
  const extensionOptions = extensionManifest[EXTENSION_PKG_KEY];
51
+ const format = extensionManifest.type === 'module' ? 'esm' : 'cjs';
51
52
  if (extensionOptions.type === 'bundle') {
52
53
  await buildBundleExtension({
53
54
  entries: extensionOptions.entries,
54
55
  outputApp: extensionOptions.path.app,
55
56
  outputApi: extensionOptions.path.api,
57
+ format,
56
58
  watch,
57
59
  sourcemap,
58
60
  minify,
@@ -64,6 +66,7 @@ export default async function build(options) {
64
66
  inputApi: extensionOptions.source.api,
65
67
  outputApp: extensionOptions.path.app,
66
68
  outputApi: extensionOptions.path.api,
69
+ format,
67
70
  watch,
68
71
  sourcemap,
69
72
  minify,
@@ -74,6 +77,7 @@ export default async function build(options) {
74
77
  type: extensionOptions.type,
75
78
  input: extensionOptions.source,
76
79
  output: extensionOptions.path,
80
+ format,
77
81
  watch,
78
82
  sourcemap,
79
83
  minify,
@@ -115,6 +119,7 @@ export default async function build(options) {
115
119
  entries: entries.data,
116
120
  outputApp: splitOutput.app,
117
121
  outputApi: splitOutput.api,
122
+ format: 'esm',
118
123
  watch,
119
124
  sourcemap,
120
125
  minify,
@@ -136,6 +141,7 @@ export default async function build(options) {
136
141
  inputApi: splitInput.api,
137
142
  outputApp: splitOutput.app,
138
143
  outputApi: splitOutput.api,
144
+ format: 'esm',
139
145
  watch,
140
146
  sourcemap,
141
147
  minify,
@@ -146,6 +152,7 @@ export default async function build(options) {
146
152
  type,
147
153
  input,
148
154
  output,
155
+ format: 'esm',
149
156
  watch,
150
157
  sourcemap,
151
158
  minify,
@@ -153,7 +160,7 @@ export default async function build(options) {
153
160
  }
154
161
  }
155
162
  }
156
- async function buildAppOrApiExtension({ type, input, output, watch, sourcemap, minify, }) {
163
+ async function buildAppOrApiExtension({ type, input, output, format, watch, sourcemap, minify, }) {
157
164
  if (!(await fse.pathExists(input)) || !(await fse.stat(input)).isFile()) {
158
165
  log(`Entrypoint ${chalk.bold(input)} does not exist.`, 'error');
159
166
  process.exit(1);
@@ -162,16 +169,11 @@ async function buildAppOrApiExtension({ type, input, output, watch, sourcemap, m
162
169
  log(`Output file can not be empty.`, 'error');
163
170
  process.exit(1);
164
171
  }
165
- const language = getLanguageFromPath(input);
166
- if (!isLanguage(language)) {
167
- log(`Language ${chalk.bold(language)} is not supported.`, 'error');
168
- process.exit(1);
169
- }
170
172
  const config = await loadConfig();
171
173
  const plugins = config.plugins ?? [];
172
174
  const mode = isIn(type, APP_EXTENSION_TYPES) ? 'browser' : 'node';
173
- const rollupOptions = getRollupOptions({ mode, input, language, sourcemap, minify, plugins });
174
- const rollupOutputOptions = getRollupOutputOptions({ mode, output, sourcemap });
175
+ const rollupOptions = getRollupOptions({ mode, input, sourcemap, minify, plugins });
176
+ const rollupOutputOptions = getRollupOutputOptions({ mode, output, format, sourcemap });
175
177
  if (watch) {
176
178
  await watchExtension({ rollupOptions, rollupOutputOptions });
177
179
  }
@@ -179,7 +181,7 @@ async function buildAppOrApiExtension({ type, input, output, watch, sourcemap, m
179
181
  await buildExtension({ rollupOptions, rollupOutputOptions });
180
182
  }
181
183
  }
182
- async function buildHybridExtension({ inputApp, inputApi, outputApp, outputApi, watch, sourcemap, minify, }) {
184
+ async function buildHybridExtension({ inputApp, inputApi, outputApp, outputApi, format, watch, sourcemap, minify, }) {
183
185
  if (!(await fse.pathExists(inputApp)) || !(await fse.stat(inputApp)).isFile()) {
184
186
  log(`App entrypoint ${chalk.bold(inputApp)} does not exist.`, 'error');
185
187
  process.exit(1);
@@ -196,22 +198,11 @@ async function buildHybridExtension({ inputApp, inputApi, outputApp, outputApi,
196
198
  log(`API output file can not be empty.`, 'error');
197
199
  process.exit(1);
198
200
  }
199
- const languageApp = getLanguageFromPath(inputApp);
200
- const languageApi = getLanguageFromPath(inputApi);
201
- if (!isLanguage(languageApp)) {
202
- log(`App language ${chalk.bold(languageApp)} is not supported.`, 'error');
203
- process.exit(1);
204
- }
205
- if (!isLanguage(languageApi)) {
206
- log(`API language ${chalk.bold(languageApi)} is not supported.`, 'error');
207
- process.exit(1);
208
- }
209
201
  const config = await loadConfig();
210
202
  const plugins = config.plugins ?? [];
211
203
  const rollupOptionsApp = getRollupOptions({
212
204
  mode: 'browser',
213
205
  input: inputApp,
214
- language: languageApp,
215
206
  sourcemap,
216
207
  minify,
217
208
  plugins,
@@ -219,13 +210,12 @@ async function buildHybridExtension({ inputApp, inputApi, outputApp, outputApi,
219
210
  const rollupOptionsApi = getRollupOptions({
220
211
  mode: 'node',
221
212
  input: inputApi,
222
- language: languageApi,
223
213
  sourcemap,
224
214
  minify,
225
215
  plugins,
226
216
  });
227
- const rollupOutputOptionsApp = getRollupOutputOptions({ mode: 'browser', output: outputApp, sourcemap });
228
- const rollupOutputOptionsApi = getRollupOutputOptions({ mode: 'node', output: outputApi, sourcemap });
217
+ const rollupOutputOptionsApp = getRollupOutputOptions({ mode: 'browser', output: outputApp, format, sourcemap });
218
+ const rollupOutputOptionsApi = getRollupOutputOptions({ mode: 'node', output: outputApi, format, sourcemap });
229
219
  const rollupOptionsAll = [
230
220
  { rollupOptions: rollupOptionsApp, rollupOutputOptions: rollupOutputOptionsApp },
231
221
  { rollupOptions: rollupOptionsApi, rollupOutputOptions: rollupOutputOptionsApi },
@@ -237,7 +227,7 @@ async function buildHybridExtension({ inputApp, inputApi, outputApp, outputApi,
237
227
  await buildExtension(rollupOptionsAll);
238
228
  }
239
229
  }
240
- async function buildBundleExtension({ entries, outputApp, outputApi, watch, sourcemap, minify, }) {
230
+ async function buildBundleExtension({ entries, outputApp, outputApi, format, watch, sourcemap, minify, }) {
241
231
  if (outputApp.length === 0) {
242
232
  log(`App output file can not be empty.`, 'error');
243
233
  process.exit(1);
@@ -246,52 +236,6 @@ async function buildBundleExtension({ entries, outputApp, outputApi, watch, sour
246
236
  log(`API output file can not be empty.`, 'error');
247
237
  process.exit(1);
248
238
  }
249
- const languagesApp = new Set();
250
- const languagesApi = new Set();
251
- for (const entry of entries) {
252
- if (isTypeIn(entry, HYBRID_EXTENSION_TYPES)) {
253
- const inputApp = entry.source.app;
254
- const inputApi = entry.source.api;
255
- if (!(await fse.pathExists(inputApp)) || !(await fse.stat(inputApp)).isFile()) {
256
- log(`App entrypoint ${chalk.bold(inputApp)} does not exist.`, 'error');
257
- process.exit(1);
258
- }
259
- if (!(await fse.pathExists(inputApi)) || !(await fse.stat(inputApi)).isFile()) {
260
- log(`API entrypoint ${chalk.bold(inputApi)} does not exist.`, 'error');
261
- process.exit(1);
262
- }
263
- const languageApp = getLanguageFromPath(inputApp);
264
- const languageApi = getLanguageFromPath(inputApi);
265
- if (!isLanguage(languageApp)) {
266
- log(`App language ${chalk.bold(languageApp)} is not supported.`, 'error');
267
- process.exit(1);
268
- }
269
- if (!isLanguage(languageApi)) {
270
- log(`API language ${chalk.bold(languageApi)} is not supported.`, 'error');
271
- process.exit(1);
272
- }
273
- languagesApp.add(languageApp);
274
- languagesApi.add(languageApi);
275
- }
276
- else {
277
- const input = entry.source;
278
- if (!(await fse.pathExists(input)) || !(await fse.stat(input)).isFile()) {
279
- log(`Entrypoint ${chalk.bold(input)} does not exist.`, 'error');
280
- process.exit(1);
281
- }
282
- const language = getLanguageFromPath(input);
283
- if (!isLanguage(language)) {
284
- log(`Language ${chalk.bold(language)} is not supported.`, 'error');
285
- process.exit(1);
286
- }
287
- if (isIn(entry.type, APP_EXTENSION_TYPES)) {
288
- languagesApp.add(language);
289
- }
290
- else {
291
- languagesApi.add(language);
292
- }
293
- }
294
- }
295
239
  const config = await loadConfig();
296
240
  const plugins = config.plugins ?? [];
297
241
  const entrypointApp = generateBundleEntrypoint('app', entries);
@@ -299,7 +243,6 @@ async function buildBundleExtension({ entries, outputApp, outputApi, watch, sour
299
243
  const rollupOptionsApp = getRollupOptions({
300
244
  mode: 'browser',
301
245
  input: { entry: entrypointApp },
302
- language: Array.from(languagesApp),
303
246
  sourcemap,
304
247
  minify,
305
248
  plugins,
@@ -307,13 +250,12 @@ async function buildBundleExtension({ entries, outputApp, outputApi, watch, sour
307
250
  const rollupOptionsApi = getRollupOptions({
308
251
  mode: 'node',
309
252
  input: { entry: entrypointApi },
310
- language: Array.from(languagesApi),
311
253
  sourcemap,
312
254
  minify,
313
255
  plugins,
314
256
  });
315
- const rollupOutputOptionsApp = getRollupOutputOptions({ mode: 'browser', output: outputApp, sourcemap });
316
- const rollupOutputOptionsApi = getRollupOutputOptions({ mode: 'node', output: outputApi, sourcemap });
257
+ const rollupOutputOptionsApp = getRollupOutputOptions({ mode: 'browser', output: outputApp, format, sourcemap });
258
+ const rollupOutputOptionsApi = getRollupOutputOptions({ mode: 'node', output: outputApi, format, sourcemap });
317
259
  const rollupOptionsAll = [
318
260
  { rollupOptions: rollupOptionsApp, rollupOutputOptions: rollupOutputOptionsApp },
319
261
  { rollupOptions: rollupOptionsApi, rollupOutputOptions: rollupOutputOptionsApi },
@@ -388,18 +330,17 @@ async function watchExtension(config) {
388
330
  });
389
331
  }
390
332
  }
391
- function getRollupOptions({ mode, input, language, sourcemap, minify, plugins, }) {
392
- const languages = Array.isArray(language) ? language : [language];
333
+ function getRollupOptions({ mode, input, sourcemap, minify, plugins, }) {
393
334
  return {
394
335
  input: typeof input !== 'string' ? 'entry' : input,
395
336
  external: mode === 'browser' ? APP_SHARED_DEPS : API_SHARED_DEPS,
396
337
  plugins: [
397
338
  typeof input !== 'string' ? virtual(input) : null,
398
339
  mode === 'browser' ? vue({ preprocessStyles: true }) : null,
399
- languages.includes('typescript') ? esbuild({ include: /\.tsx?$/, sourceMap: sourcemap }) : null,
340
+ esbuild({ include: /\.tsx?$/, sourceMap: sourcemap }),
400
341
  mode === 'browser' ? styles() : null,
401
342
  ...plugins,
402
- nodeResolve({ browser: mode === 'browser' }),
343
+ nodeResolve({ browser: mode === 'browser', preferBuiltins: mode === 'node' }),
403
344
  commonjs({ esmExternals: mode === 'browser', sourceMap: sourcemap }),
404
345
  json(),
405
346
  replace({
@@ -410,12 +351,25 @@ function getRollupOptions({ mode, input, language, sourcemap, minify, plugins, }
410
351
  }),
411
352
  minify ? terser() : null,
412
353
  ],
354
+ onwarn(warning, warn) {
355
+ if (warning.code === 'CIRCULAR_DEPENDENCY' && warning.ids?.every((id) => /\bnode_modules\b/.test(id)))
356
+ return;
357
+ warn(warning);
358
+ },
413
359
  };
414
360
  }
415
- function getRollupOutputOptions({ mode, output, sourcemap, }) {
361
+ function getRollupOutputOptions({ mode, output, format, sourcemap, }) {
362
+ const fileExtension = getFileExt(output);
363
+ let outputFormat = format;
364
+ if (mode === 'browser' || fileExtension === 'mjs') {
365
+ outputFormat = 'esm';
366
+ }
367
+ else if (fileExtension === 'cjs') {
368
+ outputFormat = 'cjs';
369
+ }
416
370
  return {
417
371
  file: output,
418
- format: mode === 'browser' ? 'es' : 'cjs',
372
+ format: outputFormat,
419
373
  exports: 'auto',
420
374
  inlineDynamicImports: true,
421
375
  sourcemap,
@@ -91,6 +91,7 @@ function getPackageManifest(name, options, deps) {
91
91
  icon: 'extension',
92
92
  version: '1.0.0',
93
93
  keywords: ['directus', 'directus-extension', `directus-custom-${options.type}`],
94
+ type: 'module',
94
95
  [EXTENSION_PKG_KEY]: options,
95
96
  scripts: {
96
97
  build: 'directus-extension build',
@@ -5,14 +5,29 @@ import path from 'path';
5
5
  export default function generateBundleEntrypoint(mode, entries) {
6
6
  const types = [...(mode === 'app' ? APP_EXTENSION_TYPES : API_EXTENSION_TYPES), ...HYBRID_EXTENSION_TYPES];
7
7
  const entriesForTypes = entries.filter((entry) => isIn(entry.type, types));
8
- const imports = entriesForTypes.map((entry, i) => `import e${i} from './${pathToRelativeUrl(path.resolve(isTypeIn(entry, HYBRID_EXTENSION_TYPES)
9
- ? mode === 'app'
10
- ? entry.source.app
11
- : entry.source.api
12
- : entry.source))}';`);
13
- const exports = types.map((type) => `export const ${pluralize(type)} = [${entriesForTypes
14
- .map((entry, i) => entry.type === type ? (mode === 'app' ? `e${i}` : `{name:'${entry.name}',config:e${i}}`) : null)
15
- .filter((e) => e !== null)
16
- .join(',')}];`);
8
+ const imports = entriesForTypes.map((entry, index) => {
9
+ let entryPath;
10
+ if (isTypeIn(entry, HYBRID_EXTENSION_TYPES)) {
11
+ entryPath = mode === 'app' ? entry.source.app : entry.source.api;
12
+ }
13
+ else {
14
+ entryPath = entry.source;
15
+ }
16
+ return `import e${index} from './${pathToRelativeUrl(path.resolve(entryPath))}';`;
17
+ });
18
+ const exports = types.map((type) => {
19
+ const entries = entriesForTypes.reduce((result, entry, index) => {
20
+ if (entry.type !== type)
21
+ return result;
22
+ if (mode === 'app') {
23
+ result.push(`e${index}`);
24
+ }
25
+ else {
26
+ result.push(`{name:'${entry.name}',config:e${index}}`);
27
+ }
28
+ return result;
29
+ }, []);
30
+ return `export const ${pluralize(type)} = [${entries.join(',')}];`;
31
+ });
17
32
  return `${imports.join('')}${exports.join('')}`;
18
33
  }
@@ -1,3 +1,2 @@
1
1
  import type { Config } from '../../types.js';
2
- export declare const CONFIG_FILE_NAMES: string[];
3
2
  export default function loadConfig(): Promise<Config>;
@@ -1,15 +1,14 @@
1
+ import { JAVASCRIPT_FILE_EXTS } from '@directus/constants';
1
2
  import { pathToRelativeUrl } from '@directus/utils/node';
2
3
  import fse from 'fs-extra';
3
4
  import path from 'path';
4
5
  import { fileURLToPath } from 'url';
5
- export const CONFIG_FILE_NAMES = ['extension.config.js', 'extension.config.mjs', 'extension.config.cjs'];
6
- // This is needed to work around Typescript always transpiling import() to require() for CommonJS targets.
7
- const _import = new Function('url', 'return import(url)');
8
6
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
7
  export default async function loadConfig() {
10
- for (const fileName of CONFIG_FILE_NAMES) {
8
+ for (const ext of JAVASCRIPT_FILE_EXTS) {
9
+ const fileName = `extension.config.${ext}`;
11
10
  if (await fse.pathExists(fileName)) {
12
- const configFile = await _import(pathToRelativeUrl(path.resolve(fileName), __dirname));
11
+ const configFile = await import(pathToRelativeUrl(path.resolve(fileName), __dirname));
13
12
  return configFile.default;
14
13
  }
15
14
  }
@@ -10,3 +10,4 @@ export type RollupConfig = {
10
10
  rollupOutputOptions: RollupOutputOptions;
11
11
  };
12
12
  export type RollupMode = 'browser' | 'node';
13
+ export type Format = 'esm' | 'cjs';
@@ -0,0 +1 @@
1
+ export declare function getFileExt(path: string): string;
@@ -0,0 +1,3 @@
1
+ export function getFileExt(path) {
2
+ return path.substring(path.lastIndexOf('.') + 1);
3
+ }
@@ -1,4 +1,5 @@
1
1
  import { EXTENSION_LANGUAGES } from '@directus/constants';
2
+ import { getFileExt } from './file.js';
2
3
  export function isLanguage(language) {
3
4
  return EXTENSION_LANGUAGES.includes(language);
4
5
  }
@@ -11,7 +12,7 @@ export function languageToShort(language) {
11
12
  }
12
13
  }
13
14
  export function getLanguageFromPath(path) {
14
- const fileExtension = path.substring(path.lastIndexOf('.') + 1);
15
+ const fileExtension = getFileExt(path);
15
16
  if (fileExtension === 'js') {
16
17
  return 'javascript';
17
18
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@directus/extensions-sdk",
3
- "version": "10.0.0",
3
+ "version": "10.1.0",
4
4
  "description": "A toolkit to develop extensions to extend Directus",
5
5
  "homepage": "https://directus.io",
6
6
  "type": "module",
@@ -25,8 +25,7 @@
25
25
  },
26
26
  "files": [
27
27
  "dist",
28
- "templates",
29
- "!**/*.test.{js,d.ts}"
28
+ "templates"
30
29
  ],
31
30
  "dependencies": {
32
31
  "@rollup/plugin-commonjs": "24.1.0",
@@ -47,18 +46,18 @@
47
46
  "rollup-plugin-esbuild": "5.0.0",
48
47
  "rollup-plugin-styles": "4.0.0",
49
48
  "rollup-plugin-vue": "6.0.0",
50
- "@directus/composables": "10.0.0",
51
- "@directus/constants": "10.0.0",
49
+ "@directus/composables": "10.0.1",
50
+ "@directus/constants": "10.1.0",
52
51
  "@directus/types": "10.0.0",
53
- "@directus/utils": "10.0.0"
52
+ "@directus/utils": "10.0.1"
54
53
  },
55
54
  "devDependencies": {
56
55
  "@directus/tsconfig": "0.0.7",
57
56
  "@types/fs-extra": "11.0.1",
58
57
  "@types/inquirer": "9.0.3",
59
- "@vitest/coverage-c8": "0.30.1",
58
+ "@vitest/coverage-c8": "0.31.0",
60
59
  "typescript": "5.0.4",
61
- "vitest": "0.30.1"
60
+ "vitest": "0.31.0"
62
61
  },
63
62
  "engines": {
64
63
  "node": ">=12.20.0"
@@ -68,7 +67,7 @@
68
67
  },
69
68
  "license": "MIT",
70
69
  "scripts": {
71
- "build": "tsc --build",
70
+ "build": "tsc --project tsconfig.prod.json",
72
71
  "dev": "tsc --watch",
73
72
  "test": "vitest --watch=false"
74
73
  }