From 22ce5183255a95dab420fde5c69bbaf4d676f2c2 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 22 Dec 2024 11:07:29 +0100 Subject: [PATCH 01/45] cli: added initial module hooks Signed-off-by: Patrik Oldsberg --- packages/cli/config/nodeTransform.cjs | 11 +- packages/cli/config/nodeTransformHooks.mjs | 281 +++++++++++++++++++++ 2 files changed, 291 insertions(+), 1 deletion(-) create mode 100644 packages/cli/config/nodeTransformHooks.mjs diff --git a/packages/cli/config/nodeTransform.cjs b/packages/cli/config/nodeTransform.cjs index 1cf79cb1bb..f54527b4a7 100644 --- a/packages/cli/config/nodeTransform.cjs +++ b/packages/cli/config/nodeTransform.cjs @@ -14,6 +14,7 @@ * limitations under the License. */ +const { pathToFileURL } = require('url'); const { transformSync } = require('@swc/core'); const { addHook } = require('pirates'); const { Module } = require('module'); @@ -55,7 +56,10 @@ addHook( const transformed = transformSync(code, { filename, sourceMaps: 'inline', - module: { type: 'commonjs' }, + module: { + type: 'commonjs', + ignoreDynamic: true, + }, jsc: { target: 'es2022', parser: { @@ -76,3 +80,8 @@ addHook( }, { extensions: ['.js', '.cjs'], ignoreNodeModules: true }, ); + +// Register module hooks, used by "type": "module" in package.json, .mjs and +// .mts files, as well as dynamic import(...)s, although dynamic imports will be +// handled be the CommonJS hooks in this file if what it points to is CommonJS. +Module.register('./nodeTransformHooks.mjs', pathToFileURL(__filename)); diff --git a/packages/cli/config/nodeTransformHooks.mjs b/packages/cli/config/nodeTransformHooks.mjs new file mode 100644 index 0000000000..5892b18ee4 --- /dev/null +++ b/packages/cli/config/nodeTransformHooks.mjs @@ -0,0 +1,281 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { dirname, extname, resolve as resolvePath } from 'path'; +import { fileURLToPath } from 'url'; +import { transformFile } from '@swc/core'; +import { isBuiltin } from 'node:module'; +import { readFile } from 'fs/promises'; +import { existsSync } from 'fs'; + +// @ts-check + +// No explicit file extension, no type in package.json +const DEFAULT_MODULE_FORMAT = 'commonjs'; + +// Source file extensions to look for when using bundle resolution strategy +const EXTS = ['.ts', '.js', '.mts', '.cts', '.mjs', '.cjs']; +const TS_EXTS = ['.ts', '.mts', '.cts']; +const moduleTypeTable = { + '.mjs': 'module', + '.mts': 'module', + '.cjs': 'commonjs', + '.cts': 'commonjs', + '.ts': undefined, + '.js': undefined, +}; + +/** @type {import('module').ResolveHook} */ +export async function resolve(specifier, context, nextResolve) { + // Built-in modules are handled by the default resolver + if (isBuiltin(specifier)) { + return nextResolve(specifier, context); + } + + const ext = extname(specifier); + + // Unless there's an explicit import attribute, JSON files are loaded with our custom loader that's defined below. + if (ext === '.json' && !context.importAttributes?.type) { + const jsonResult = await nextResolve(specifier, context); + return { + ...jsonResult, + format: 'commonjs', + importAttributes: { type: 'json' }, + }; + } + + // Anything else with an explicit extension is handled by the default + // resolver, except that we help determine the module type where needed. + if (ext !== '') { + return withDetectedModuleType(await nextResolve(specifier, context)); + } + + // Other external modules are handled by the default resolver, but again we + // help determine the module type where needed. + if (!specifier.startsWith('.')) { + return withDetectedModuleType(await nextResolve(specifier, context)); + } + + // Imports with exact file extensions are handled by the default resolver + // if (ext !== '') { + // return withDetectedModuleType(await nextResolve(specifier, context)); + // } + + // The rest of this function handles the case of resolving imports that do not + // specify any extension and might point to a directory with an `index.*` + // file. We resolve those using the same logic as most JS bundlers would, with + // the addition of checking if there's an explicit module format listed in the + // closest `package.json` file. + // + // We use a bundle resolution strategy in order to keep code consistent across + // Backstage codebases that contains code both for Web and Node.js, and to + // support packages with common code that can be used in both environments. + try { + // This is expected to throw, but in the event that this module specifier is + // supported we prefer to use the default resolver. + return await nextResolve(specifier, context); + } catch (error) { + if (error.code === 'ERR_UNSUPPORTED_DIR_IMPORT') { + const spec = `${specifier}${specifier.endsWith('/') ? '' : '/'}index`; + const resolved = await resolveWithoutExt(spec, context, nextResolve); + if (resolved) { + return withDetectedModuleType(resolved); + } + } else if (error.code === 'ERR_MODULE_NOT_FOUND') { + const resolved = await resolveWithoutExt(specifier, context, nextResolve); + if (resolved) { + return withDetectedModuleType(resolved); + } + } + + // Unexpected error or no resolution found + throw error; + } +} + +/** + * Populates the `format` field in the resolved object based on the closest `package.json` file. + * + * @param {import('module').ResolveFnOutput} resolved + * @returns {Promise} + */ +async function withDetectedModuleType(resolved) { + // Already has an explicit format + if (resolved.format) { + return resolved; + } + + const ext = extname(resolved.url); + + const explicitFormat = moduleTypeTable[ext]; + if (explicitFormat) { + return { + ...resolved, + format: explicitFormat, + }; + } + + // TODO(Rugvip): Afaik this should never happen and we can remove this check, but want it here for a little while to verify. + if (ext === '.js') { + throw new Error('Unexpected .js file without explicit format'); + } + + // TODO(Rugvip): Does this need caching? kept it simple for now but worth exploring + const packageJsonPath = await findPackageJSON(fileURLToPath(resolved.url)); + if (!packageJsonPath) { + return resolved; + } + + const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8')); + return { + ...resolved, + format: packageJson.type ?? DEFAULT_MODULE_FORMAT, + }; +} + +/** + * Find the closes package.json file from the given path. + * + * TODO(Rugvip): This can be replaced with the Node.js built-in with the same name once it is stable. + * @param {string} startPath + * @returns {Promise} + */ +async function findPackageJSON(startPath) { + let path = startPath; + + // Some confidence check to avoid infinite loop + for (let i = 0; i < 1000; i++) { + const packagePath = resolvePath(path, 'package.json'); + if (existsSync(packagePath)) { + return packagePath; + } + + const newPath = dirname(path); + if (newPath === path) { + return undefined; + } + path = newPath; + } + + throw new Error( + `Iteration limit reached when searching for package.json at ${startPath}`, + ); +} + +/** @type {import('module').ResolveHook} */ +async function resolveWithoutExt(specifier, context, nextResolve) { + for (const tryExt of EXTS) { + try { + const resolved = await nextResolve(specifier + tryExt, { + ...context, + format: 'commonjs', + }); + return { + ...resolved, + format: moduleTypeTable[tryExt] ?? resolved.format, + }; + } catch { + /* ignore */ + } + } + return undefined; +} + +/** @type {import('module').LoadHook} */ +export async function load(url, context, nextLoad) { + // Non-file URLs are handled by the default loader + if (!url.startsWith('file://')) { + return nextLoad(url, context); + } + + // JSON files loaded as CommonJS are handled by this custom loader, because + // the default one doesn't work. For JSON loading to work we'd need the + // synchronous hooks that aren't supported yet, or avoid using the CommonJS + // compatibility. + if ( + context.format === 'commonjs' && + context.importAttributes?.type === 'json' + ) { + try { + // TODO(Rugvip): Make sure this is valid JSON + const content = await readFile(fileURLToPath(url), 'utf8'); + return { + source: `module.exports = (${content})`, + format: 'commonjs', + shortCircuit: true, + }; + } catch { + // Let the default loader generate the error + return nextLoad(url, context); + } + } + + const ext = extname(url); + + // Non-TS files are handled by the default loader + if (!TS_EXTS.includes(ext)) { + return nextLoad(url, context); + } + + const format = context.format ?? DEFAULT_MODULE_FORMAT; + + // We have two choices at this point, we can either transform CommonJS files + // and return the transformed source code, or let the default loader handle + // them. If we transform them ourselves we will enter CommonJS compatibility + // mode in the new module system in Node.js, this effectively means all + // CommonJS loaded via `require` calls from this point will all be treated as + // if it was loaded via `import` calls from modules. + // + // The CommonJS compatibility layer will try to identify named exports and + // make them available directly, which is convenient as it avoids things like + // `import(...).then(m => m.default.foo)`, allowing you to instead write + // `import(...).then(m => m.foo)`. The compatibility layer doesn't always work + // all that well though, and can lead to module loading issues in many cases, + // especially for older code. + + // This `if` block opts-out of using CommonJS compatibility mode, and instead + // leaves it to our existing loader to transform CommonJS. + // + // TODO(Rugvip): Once the synchronous hooks API is available for us to use, we might be able to adopt that instead + if (format === 'commonjs') { + return nextLoad(url, { ...context, format }); + } + + const transformed = await transformFile(fileURLToPath(url), { + sourceMaps: 'inline', + module: { + type: format === 'module' ? 'nodenext' : 'commonjs', + ignoreDynamic: true, + + // This helps the Node.js CommonJS compat layer identify named exports. + exportInteropAnnotation: true, + }, + jsc: { + target: 'es2022', + parser: { + syntax: 'typescript', + }, + }, + }); + + return { + ...context, + shortCircuit: true, + source: transformed.code, + format, + responseURL: url, + }; +} From 8946eeb43799ed19b3d5f4cec30572b6590437b9 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 22 Dec 2024 11:28:12 +0100 Subject: [PATCH 02/45] cli: updated dynamic command imports to be compatible with module Signed-off-by: Patrik Oldsberg --- packages/cli/src/commands/index.ts | 58 +++++++++++++----------------- packages/cli/src/lib/lazy.ts | 18 ++++++++-- 2 files changed, 39 insertions(+), 37 deletions(-) diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 0f38fc1e8a..f1119053ec 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -43,7 +43,7 @@ export function registerRepoCommand(program: Command) { '--minify', 'Minify the generated code. Does not apply to app package (app is minified by default).', ) - .action(lazy(() => import('./repo/build').then(m => m.command))); + .action(lazy(() => import('./repo/build'), 'command')); command .command('lint') @@ -70,7 +70,7 @@ export function registerRepoCommand(program: Command) { 'Set the success cache location, (default: node_modules/.cache/backstage-cli)', ) .option('--fix', 'Attempt to automatically fix violations') - .action(lazy(() => import('./repo/lint').then(m => m.command))); + .action(lazy(() => import('./repo/lint'), 'command')); command .command('fix') @@ -83,20 +83,18 @@ export function registerRepoCommand(program: Command) { '--check', 'Fail if any packages would have been changed by the command', ) - .action(lazy(() => import('./repo/fix').then(m => m.command))); + .action(lazy(() => import('./repo/fix'), 'command')); command .command('clean') .description('Delete cache and output directories') - .action(lazy(() => import('./repo/clean').then(m => m.command))); + .action(lazy(() => import('./repo/clean'), 'command')); command .command('list-deprecations') .description('List deprecations') .option('--json', 'Output as JSON') - .action( - lazy(() => import('./repo/list-deprecations').then(m => m.command)), - ); + .action(lazy(() => import('./repo/list-deprecations'), 'command')); command .command('test') @@ -118,7 +116,7 @@ export function registerRepoCommand(program: Command) { 'Show help for Jest CLI options, which are passed through', ) .description('Run tests, forwarding args to Jest, defaulting to watch mode') - .action(lazy(() => import('./repo/test').then(m => m.command))); + .action(lazy(() => import('./repo/test'), 'command')); } export function registerScriptCommand(program: Command) { @@ -139,7 +137,7 @@ export function registerScriptCommand(program: Command) { ) .option('--require ', 'Add a --require argument to the node process') .option('--link ', 'Link an external workspace for module resolution') - .action(lazy(() => import('./start').then(m => m.command))); + .action(lazy(() => import('./start'), 'command')); command .command('build') @@ -163,7 +161,7 @@ export function registerScriptCommand(program: Command) { (opt: string, opts: string[]) => (opts ? [...opts, opt] : [opt]), Array(), ) - .action(lazy(() => import('./build').then(m => m.command))); + .action(lazy(() => import('./build'), 'command')); command .command('lint [directories...]') @@ -182,29 +180,29 @@ export function registerScriptCommand(program: Command) { 'Fail if more than this number of warnings. -1 allows warnings. (default: 0)', ) .description('Lint a package') - .action(lazy(() => import('./lint').then(m => m.default))); + .action(lazy(() => import('./lint'), 'default')); command .command('test') .allowUnknownOption(true) // Allows the command to run, but we still need to parse raw args .helpOption(', --backstage-cli-help') // Let Jest handle help .description('Run tests, forwarding args to Jest, defaulting to watch mode') - .action(lazy(() => import('./test').then(m => m.default))); + .action(lazy(() => import('./test'), 'default')); command .command('clean') .description('Delete cache directories') - .action(lazy(() => import('./clean/clean').then(m => m.default))); + .action(lazy(() => import('./clean/clean'), 'default')); command .command('prepack') .description('Prepares a package for packaging before publishing') - .action(lazy(() => import('./pack').then(m => m.pre))); + .action(lazy(() => import('./pack'), 'pre')); command .command('postpack') .description('Restores the changes made by the prepack command') - .action(lazy(() => import('./pack').then(m => m.post))); + .action(lazy(() => import('./pack'), 'post')); } export function registerMigrateCommand(program: Command) { @@ -215,39 +213,31 @@ export function registerMigrateCommand(program: Command) { command .command('package-roles') .description(`Add package role field to packages that don't have it`) - .action(lazy(() => import('./migrate/packageRole').then(m => m.default))); + .action(lazy(() => import('./migrate/packageRole'), 'default')); command .command('package-scripts') .description('Set package scripts according to each package role') - .action( - lazy(() => import('./migrate/packageScripts').then(m => m.command)), - ); + .action(lazy(() => import('./migrate/packageScripts'), 'command')); command .command('package-exports') .description('Synchronize package subpath export definitions') - .action( - lazy(() => import('./migrate/packageExports').then(m => m.command)), - ); + .action(lazy(() => import('./migrate/packageExports'), 'command')); command .command('package-lint-configs') .description( 'Migrates all packages to use @backstage/cli/config/eslint-factory', ) - .action( - lazy(() => import('./migrate/packageLintConfigs').then(m => m.command)), - ); + .action(lazy(() => import('./migrate/packageLintConfigs'), 'command')); command .command('react-router-deps') .description( 'Migrates the react-router dependencies for all packages to be peer dependencies', ) - .action( - lazy(() => import('./migrate/reactRouterDeps').then(m => m.command)), - ); + .action(lazy(() => import('./migrate/reactRouterDeps'), 'command')); } export function registerCommands(program: Command) { @@ -281,7 +271,7 @@ export function registerCommands(program: Command) { 'The license to use for any new packages (default: Apache-2.0)', ) .option('--no-private', 'Do not mark new packages as private') - .action(lazy(() => import('./new/new').then(m => m.default))); + .action(lazy(() => import('./new/new'), 'default')); registerConfigCommands(program); registerRepoCommand(program); @@ -302,7 +292,7 @@ export function registerCommands(program: Command) { .option('--skip-install', 'Skips yarn install step') .option('--skip-migrate', 'Skips migration of any moved packages') .description('Bump Backstage packages to the latest versions') - .action(lazy(() => import('./versions/bump').then(m => m.default))); + .action(lazy(() => import('./versions/bump'), 'default')); program .command('versions:migrate') @@ -317,7 +307,7 @@ export function registerCommands(program: Command) { .description( 'Migrate any plugins that have been moved to the @backstage-community namespace automatically', ) - .action(lazy(() => import('./versions/migrate').then(m => m.default))); + .action(lazy(() => import('./versions/migrate'), 'default')); program .command('build-workspace [packages...]') @@ -334,17 +324,17 @@ export function registerCommands(program: Command) { 'Force workspace output to be a result of running `yarn pack` on each package (warning: very slow)', ) .description('Builds a temporary dist workspace from the provided packages') - .action(lazy(() => import('./buildWorkspace').then(m => m.default))); + .action(lazy(() => import('./buildWorkspace'), 'default')); program .command('create-github-app ') .description('Create new GitHub App in your organization.') - .action(lazy(() => import('./create-github-app').then(m => m.default))); + .action(lazy(() => import('./create-github-app'), 'default')); program .command('info') .description('Show helpful information for debugging and reporting bugs') - .action(lazy(() => import('./info').then(m => m.default))); + .action(lazy(() => import('./info'), 'default')); // Notifications for removed commands program diff --git a/packages/cli/src/lib/lazy.ts b/packages/cli/src/lib/lazy.ts index 6d2cb1cd4f..d1255ea1bc 100644 --- a/packages/cli/src/lib/lazy.ts +++ b/packages/cli/src/lib/lazy.ts @@ -17,13 +17,25 @@ import { assertError } from '@backstage/errors'; import { exitWithError } from '../lib/errors'; +type ActionFunc = (...args: any[]) => Promise; +type ActionExports = { + [KName in keyof TModule as TModule[KName] extends ActionFunc + ? KName + : never]: TModule[KName]; +}; + // Wraps an action function so that it always exits and handles errors -export function lazy( - getActionFunc: () => Promise<(...args: any[]) => Promise>, +export function lazy( + moduleLoader: () => Promise, + exportName: keyof ActionExports, ): (...args: any[]) => Promise { return async (...args: any[]) => { try { - const actionFunc = await getActionFunc(); + const mod = await moduleLoader(); + const actualModule = ( + mod as unknown as { default: ActionExports } + ).default; + const actionFunc = actualModule[exportName] as ActionFunc; await actionFunc(...args); process.exit(0); From 38023ae743cd33926a9bd38c24630debe0711ad8 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 22 Dec 2024 11:41:11 +0100 Subject: [PATCH 03/45] cli: update alpha entry to support dynamic imports Signed-off-by: Patrik Oldsberg --- packages/cli/src/alpha.ts | 2 +- packages/cli/src/modules/config/alpha.ts | 2 +- packages/cli/src/modules/config/index.ts | 8 +++--- packages/cli/src/wiring/CliInitializer.ts | 32 ++++++++++++++++++++--- 4 files changed, 35 insertions(+), 9 deletions(-) diff --git a/packages/cli/src/alpha.ts b/packages/cli/src/alpha.ts index 4b2cfa4842..f6cd2157ae 100644 --- a/packages/cli/src/alpha.ts +++ b/packages/cli/src/alpha.ts @@ -24,6 +24,6 @@ import chalk from 'chalk'; ), ); const initializer = new CliInitializer(); - initializer.add(import('./modules/config/alpha').then(m => m.default)); + initializer.add(import('./modules/config/alpha')); await initializer.run(); })(); diff --git a/packages/cli/src/modules/config/alpha.ts b/packages/cli/src/modules/config/alpha.ts index c32982086a..9ce6080a4a 100644 --- a/packages/cli/src/modules/config/alpha.ts +++ b/packages/cli/src/modules/config/alpha.ts @@ -32,7 +32,7 @@ export default createCliPlugin({ 'Only include the schema that applies to the given package', ) .description('Browse the configuration reference documentation') - .action(lazy(() => import('./commands/docs').then(m => m.default))); + .action(lazy(() => import('./commands/docs'), 'default')); await defaultCommand.parseAsync(args, { from: 'user' }); }, diff --git a/packages/cli/src/modules/config/index.ts b/packages/cli/src/modules/config/index.ts index 0c6a79577d..a1436d0c21 100644 --- a/packages/cli/src/modules/config/index.ts +++ b/packages/cli/src/modules/config/index.ts @@ -32,7 +32,7 @@ export function registerCommands(program: Command) { 'Only include the schema that applies to the given package', ) .description('Browse the configuration reference documentation') - .action(lazy(() => import('./commands/docs').then(m => m.default))); + .action(lazy(() => import('./commands/docs'), 'default')); program .command('config:print') @@ -49,7 +49,7 @@ export function registerCommands(program: Command) { ) .option(...configOption) .description('Print the app configuration for the current package') - .action(lazy(() => import('./commands/print').then(m => m.default))); + .action(lazy(() => import('./commands/print'), 'default')); program .command('config:check') @@ -68,7 +68,7 @@ export function registerCommands(program: Command) { .description( 'Validate that the given configuration loads and matches schema', ) - .action(lazy(() => import('./commands/validate').then(m => m.default))); + .action(lazy(() => import('./commands/validate'), 'default')); program .command('config:schema') @@ -83,5 +83,5 @@ export function registerCommands(program: Command) { .option('--merge', 'Print the config schemas merged', true) .option('--no-merge', 'Print the config schemas not merged') .description('Print configuration schema') - .action(lazy(() => import('./commands/schema').then(m => m.default))); + .action(lazy(() => import('./commands/schema'), 'default')); } diff --git a/packages/cli/src/wiring/CliInitializer.ts b/packages/cli/src/wiring/CliInitializer.ts index 9fe8bb8084..5acb88151f 100644 --- a/packages/cli/src/wiring/CliInitializer.ts +++ b/packages/cli/src/wiring/CliInitializer.ts @@ -22,16 +22,23 @@ import { version } from '../lib/version'; import chalk from 'chalk'; import { exitWithError } from '../lib/errors'; import { assertError } from '@backstage/errors'; +import { isPromise } from 'util/types'; -type UninitializedFeature = CliFeature | Promise; +type UninitializedFeature = CliFeature | Promise<{ default: CliFeature }>; export class CliInitializer { private graph = new CommandGraph(); private commandRegistry = new CommandRegistry(this.graph); #uninitiazedFeatures: Promise[] = []; - add(module: UninitializedFeature) { - this.#uninitiazedFeatures.push(Promise.resolve(module)); + add(feature: UninitializedFeature) { + if (isPromise(feature)) { + this.#uninitiazedFeatures.push( + feature.then(f => unwrapFeature(f.default)), + ); + } else { + this.#uninitiazedFeatures.push(Promise.resolve(feature)); + } } async #register(feature: CliFeature) { @@ -136,3 +143,22 @@ function isCliPlugin(feature: CliFeature): feature is InternalCliPlugin { // Backwards compatibility for v1 registrations that use duck typing return 'plugin' in internal; } + +/** @internal */ +export function unwrapFeature( + feature: CliFeature | { default: CliFeature }, +): CliFeature { + if ('$$type' in feature) { + return feature; + } + + // This is a workaround where default exports get transpiled to `exports['default'] = ...` + // in CommonJS modules, which in turn results in a double `{ default: { default: ... } }` nesting + // when importing using a dynamic import. + // TODO: This is a broader issue than just this piece of code, and should move away from CommonJS. + if ('default' in feature) { + return feature.default; + } + + return feature; +} From cdfff7254dd5e9a388dc1f1e80ac878b081ea1cc Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 22 Dec 2024 12:18:21 +0100 Subject: [PATCH 04/45] cli: initial module transform tests Signed-off-by: Patrik Oldsberg --- .../tests/transforms/__fixtures__/.gitignore | 1 + .../node_modules/dep-commonjs/a-default.js | 1 + .../node_modules/dep-commonjs/a-named.js | 1 + .../node_modules/dep-commonjs/b-default.mjs | 1 + .../node_modules/dep-commonjs/b-named.mjs | 1 + .../node_modules/dep-commonjs/c-default.cjs | 1 + .../node_modules/dep-commonjs/c-named.cjs | 1 + .../node_modules/dep-commonjs/main.js | 14 +++ .../node_modules/dep-commonjs/package.json | 7 ++ .../node_modules/dep-default/a-default.js | 1 + .../node_modules/dep-default/a-named.js | 1 + .../node_modules/dep-default/b-default.mjs | 1 + .../node_modules/dep-default/b-named.mjs | 1 + .../node_modules/dep-default/c-default.cjs | 1 + .../node_modules/dep-default/c-named.cjs | 1 + .../node_modules/dep-default/main.js | 14 +++ .../node_modules/dep-default/package.json | 6 + .../node_modules/dep-module/a-default.js | 1 + .../node_modules/dep-module/a-named.js | 1 + .../node_modules/dep-module/b-default.mjs | 1 + .../node_modules/dep-module/b-named.mjs | 1 + .../node_modules/dep-module/c-default.cjs | 1 + .../node_modules/dep-module/c-named.cjs | 1 + .../node_modules/dep-module/main.js | 14 +++ .../node_modules/dep-module/package.json | 7 ++ .../__fixtures__/pkg-commonjs/a-default.ts | 16 +++ .../__fixtures__/pkg-commonjs/a-named.ts | 16 +++ .../__fixtures__/pkg-commonjs/b-default.mts | 16 +++ .../__fixtures__/pkg-commonjs/b-named.mts | 16 +++ .../__fixtures__/pkg-commonjs/c-default.cts | 16 +++ .../__fixtures__/pkg-commonjs/c-named.cts | 16 +++ .../__fixtures__/pkg-commonjs/main.ts | 70 ++++++++++++ .../__fixtures__/pkg-commonjs/package.json | 7 ++ .../__fixtures__/pkg-default/a-default.ts | 16 +++ .../__fixtures__/pkg-default/a-named.ts | 16 +++ .../__fixtures__/pkg-default/b-default.mts | 16 +++ .../__fixtures__/pkg-default/b-named.mts | 16 +++ .../__fixtures__/pkg-default/c-default.cts | 16 +++ .../__fixtures__/pkg-default/c-named.cts | 16 +++ .../__fixtures__/pkg-default/main.ts | 70 ++++++++++++ .../__fixtures__/pkg-default/package.json | 3 + .../__fixtures__/pkg-module/a-default.ts | 16 +++ .../__fixtures__/pkg-module/a-named.ts | 16 +++ .../__fixtures__/pkg-module/b-default.mts | 16 +++ .../__fixtures__/pkg-module/b-named.mts | 16 +++ .../__fixtures__/pkg-module/c-default.cts | 16 +++ .../__fixtures__/pkg-module/c-named.cts | 16 +++ .../__fixtures__/pkg-module/main.ts | 73 +++++++++++++ .../__fixtures__/pkg-module/package.json | 7 ++ .../src/tests/transforms/transforms.test.ts | 103 ++++++++++++++++++ 50 files changed, 702 insertions(+) create mode 100644 packages/cli/src/tests/transforms/__fixtures__/.gitignore create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/a-default.js create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/a-named.js create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/b-default.mjs create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/b-named.mjs create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/c-default.cjs create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/c-named.cjs create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/main.js create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/package.json create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/a-default.js create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/a-named.js create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/b-default.mjs create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/b-named.mjs create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/c-default.cjs create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/c-named.cjs create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/main.js create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/package.json create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/a-default.js create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/a-named.js create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/b-default.mjs create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/b-named.mjs create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/c-default.cjs create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/c-named.cjs create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/main.js create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/package.json create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/a-default.ts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/a-named.ts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/b-default.mts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/b-named.mts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/c-default.cts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/c-named.cts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/package.json create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-default/a-default.ts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-default/a-named.ts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-default/b-default.mts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-default/b-named.mts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-default/c-default.cts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-default/c-named.cts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-default/package.json create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-default.ts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-named.ts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-module/b-default.mts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-module/b-named.mts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-module/c-default.cts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-module/c-named.cts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-module/package.json create mode 100644 packages/cli/src/tests/transforms/transforms.test.ts diff --git a/packages/cli/src/tests/transforms/__fixtures__/.gitignore b/packages/cli/src/tests/transforms/__fixtures__/.gitignore new file mode 100644 index 0000000000..cf4bab9ddd --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/.gitignore @@ -0,0 +1 @@ +!node_modules diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/a-default.js b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/a-default.js new file mode 100644 index 0000000000..67606e3f86 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/a-default.js @@ -0,0 +1 @@ +module.exports = 'a' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/a-named.js b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/a-named.js new file mode 100644 index 0000000000..0710f9dbe6 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/a-named.js @@ -0,0 +1 @@ +exports.value = 'a' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/b-default.mjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/b-default.mjs new file mode 100644 index 0000000000..a3bb49043e --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/b-default.mjs @@ -0,0 +1 @@ +export default 'b' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/b-named.mjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/b-named.mjs new file mode 100644 index 0000000000..18049c8488 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/b-named.mjs @@ -0,0 +1 @@ +export const value = 'b' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/c-default.cjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/c-default.cjs new file mode 100644 index 0000000000..7212f4d5a7 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/c-default.cjs @@ -0,0 +1 @@ +module.exports = 'c' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/c-named.cjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/c-named.cjs new file mode 100644 index 0000000000..c1dcb4b923 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/c-named.cjs @@ -0,0 +1 @@ +exports.value = 'c' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/main.js b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/main.js new file mode 100644 index 0000000000..d59f7789bd --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/main.js @@ -0,0 +1,14 @@ +exports.namedA = require('./a-named').value; +// exports.namedB = require('./b-named.mjs').value; +exports.namedC = require('./c-named.cjs').value; +exports.defaultA = require('./a-default'); +// exports.defaultB = require('./b-default.mjs').default; +exports.defaultC = require('./c-default.cjs'); +exports.dyn = { + namedA: import('./a-named').then(m => m.value), + namedB: import('./b-named.mjs').then(m => m.value), + namedC: import('./c-named.cjs').then(m => m.value), + defaultA: import('./a-default').then(m => m.default), + defaultB: import('./b-default.mjs').then(m => m.default), + defaultC: import('./c-default.cjs').then(m => m.default), +} diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/package.json b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/package.json new file mode 100644 index 0000000000..348dc7e258 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/package.json @@ -0,0 +1,7 @@ +{ + "name": "dep-commonjs", + "type": "commonjs", + "exports": { + ".": "./main.js" + } +} diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/a-default.js b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/a-default.js new file mode 100644 index 0000000000..67606e3f86 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/a-default.js @@ -0,0 +1 @@ +module.exports = 'a' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/a-named.js b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/a-named.js new file mode 100644 index 0000000000..0710f9dbe6 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/a-named.js @@ -0,0 +1 @@ +exports.value = 'a' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/b-default.mjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/b-default.mjs new file mode 100644 index 0000000000..a3bb49043e --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/b-default.mjs @@ -0,0 +1 @@ +export default 'b' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/b-named.mjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/b-named.mjs new file mode 100644 index 0000000000..18049c8488 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/b-named.mjs @@ -0,0 +1 @@ +export const value = 'b' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/c-default.cjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/c-default.cjs new file mode 100644 index 0000000000..7212f4d5a7 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/c-default.cjs @@ -0,0 +1 @@ +module.exports = 'c' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/c-named.cjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/c-named.cjs new file mode 100644 index 0000000000..c1dcb4b923 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/c-named.cjs @@ -0,0 +1 @@ +exports.value = 'c' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/main.js b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/main.js new file mode 100644 index 0000000000..d59f7789bd --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/main.js @@ -0,0 +1,14 @@ +exports.namedA = require('./a-named').value; +// exports.namedB = require('./b-named.mjs').value; +exports.namedC = require('./c-named.cjs').value; +exports.defaultA = require('./a-default'); +// exports.defaultB = require('./b-default.mjs').default; +exports.defaultC = require('./c-default.cjs'); +exports.dyn = { + namedA: import('./a-named').then(m => m.value), + namedB: import('./b-named.mjs').then(m => m.value), + namedC: import('./c-named.cjs').then(m => m.value), + defaultA: import('./a-default').then(m => m.default), + defaultB: import('./b-default.mjs').then(m => m.default), + defaultC: import('./c-default.cjs').then(m => m.default), +} diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/package.json b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/package.json new file mode 100644 index 0000000000..1543cf8010 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/package.json @@ -0,0 +1,6 @@ +{ + "name": "dep-default", + "exports": { + ".": "./main.js" + } +} diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/a-default.js b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/a-default.js new file mode 100644 index 0000000000..90bd54cd7f --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/a-default.js @@ -0,0 +1 @@ +export default 'a' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/a-named.js b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/a-named.js new file mode 100644 index 0000000000..7fea2538a3 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/a-named.js @@ -0,0 +1 @@ +export const value = 'a' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/b-default.mjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/b-default.mjs new file mode 100644 index 0000000000..a3bb49043e --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/b-default.mjs @@ -0,0 +1 @@ +export default 'b' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/b-named.mjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/b-named.mjs new file mode 100644 index 0000000000..18049c8488 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/b-named.mjs @@ -0,0 +1 @@ +export const value = 'b' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/c-default.cjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/c-default.cjs new file mode 100644 index 0000000000..7212f4d5a7 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/c-default.cjs @@ -0,0 +1 @@ +module.exports = 'c' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/c-named.cjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/c-named.cjs new file mode 100644 index 0000000000..c1dcb4b923 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/c-named.cjs @@ -0,0 +1 @@ +exports.value = 'c' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/main.js b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/main.js new file mode 100644 index 0000000000..8b42a09d4a --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/main.js @@ -0,0 +1,14 @@ +export { value as namedA } from './a-named' +export { value as namedB } from './b-named.mjs' +export { value as namedC } from './c-named.cjs' +export { default as defaultA } from './a-default' +export { default as defaultB } from './b-default.mjs' +export { default as defaultC } from './c-default.cjs' +export const dyn = { + namedA: import('./a-named').then(m => m.value), + namedB: import('./b-named.mjs').then(m => m.value), + namedC: import('./c-named.cjs').then(m => m.value), + defaultA: import('./a-default').then(m => m.default), + defaultB: import('./b-default.mjs').then(m => m.default), + defaultC: import('./c-default.cjs').then(m => m.default), +} diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/package.json b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/package.json new file mode 100644 index 0000000000..b28b4b6562 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/package.json @@ -0,0 +1,7 @@ +{ + "name": "dep-module", + "type": "module", + "exports": { + ".": "./main.js" + } +} diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/a-default.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/a-default.ts new file mode 100644 index 0000000000..a3d64880a9 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/a-default.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export default 'a'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/a-named.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/a-named.ts new file mode 100644 index 0000000000..132570f2bb --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/a-named.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const value = 'a'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/b-default.mts b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/b-default.mts new file mode 100644 index 0000000000..57d07f2c30 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/b-default.mts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export default 'b'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/b-named.mts b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/b-named.mts new file mode 100644 index 0000000000..be9bb39348 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/b-named.mts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const value = 'b'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/c-default.cts b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/c-default.cts new file mode 100644 index 0000000000..48f5b4f136 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/c-default.cts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export default 'c'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/c-named.cts b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/c-named.cts new file mode 100644 index 0000000000..c3e5b58572 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/c-named.cts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const value = 'c'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts new file mode 100644 index 0000000000..4cb18038b3 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts @@ -0,0 +1,70 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-nocheck + +import * as depCommonJs from 'dep-commonjs'; +// import * as depModule from 'dep-module'; +import * as depDefault from 'dep-default'; +import { value as namedA } from './a-named'; +// import { value as namedB } from './b-named'; +import { value as namedC } from './c-named'; +import { default as defaultA } from './a-default'; +// import { default as defaultB } from './b-default'; +import { default as defaultC } from './c-default'; + +async function resolveAll(obj) { + const val = await obj; + if (typeof val !== 'object' || val === null) { + return val; + } + if (Array.isArray(val)) { + return await Promise.all(val.map(resolveAll)); + } + return Object.fromEntries( + await Promise.all( + Object.entries(obj).map(async ([key, value]) => [ + key, + await resolveAll(await value), + ]), + ), + ); +} + +resolveAll({ + depCommonJs, + // depModule, + depDefault, + dynCommonJs: import('dep-commonjs'), + dynModule: import('dep-module'), + dynDefault: import('dep-default'), + dep: { + namedA, + // namedB, + namedC, + defaultA, + // defaultB, + defaultC, + }, + dyn: { + namedA: import('./a-named').then(m => m.default.value), + namedB: import('./b-named').then(m => m.value), + namedC: import('./c-named').then(m => m.default.value), + defaultA: import('./a-default').then(m => m.default.default), + defaultB: import('./b-default').then(m => m.default), + defaultC: import('./c-default').then(m => m.default.default), + }, +}).then(obj => console.log(JSON.stringify(obj, null, 2))); diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/package.json b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/package.json new file mode 100644 index 0000000000..c622a8ba4d --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/package.json @@ -0,0 +1,7 @@ +{ + "name": "pkg-commonjs", + "type": "commonjs", + "exports": { + ".": "./main.ts" + } +} diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/a-default.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/a-default.ts new file mode 100644 index 0000000000..a3d64880a9 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/a-default.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export default 'a'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/a-named.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/a-named.ts new file mode 100644 index 0000000000..132570f2bb --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/a-named.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const value = 'a'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/b-default.mts b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/b-default.mts new file mode 100644 index 0000000000..57d07f2c30 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/b-default.mts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export default 'b'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/b-named.mts b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/b-named.mts new file mode 100644 index 0000000000..be9bb39348 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/b-named.mts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const value = 'b'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/c-default.cts b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/c-default.cts new file mode 100644 index 0000000000..48f5b4f136 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/c-default.cts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export default 'c'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/c-named.cts b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/c-named.cts new file mode 100644 index 0000000000..c3e5b58572 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/c-named.cts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const value = 'c'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts new file mode 100644 index 0000000000..4cb18038b3 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts @@ -0,0 +1,70 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-nocheck + +import * as depCommonJs from 'dep-commonjs'; +// import * as depModule from 'dep-module'; +import * as depDefault from 'dep-default'; +import { value as namedA } from './a-named'; +// import { value as namedB } from './b-named'; +import { value as namedC } from './c-named'; +import { default as defaultA } from './a-default'; +// import { default as defaultB } from './b-default'; +import { default as defaultC } from './c-default'; + +async function resolveAll(obj) { + const val = await obj; + if (typeof val !== 'object' || val === null) { + return val; + } + if (Array.isArray(val)) { + return await Promise.all(val.map(resolveAll)); + } + return Object.fromEntries( + await Promise.all( + Object.entries(obj).map(async ([key, value]) => [ + key, + await resolveAll(await value), + ]), + ), + ); +} + +resolveAll({ + depCommonJs, + // depModule, + depDefault, + dynCommonJs: import('dep-commonjs'), + dynModule: import('dep-module'), + dynDefault: import('dep-default'), + dep: { + namedA, + // namedB, + namedC, + defaultA, + // defaultB, + defaultC, + }, + dyn: { + namedA: import('./a-named').then(m => m.default.value), + namedB: import('./b-named').then(m => m.value), + namedC: import('./c-named').then(m => m.default.value), + defaultA: import('./a-default').then(m => m.default.default), + defaultB: import('./b-default').then(m => m.default), + defaultC: import('./c-default').then(m => m.default.default), + }, +}).then(obj => console.log(JSON.stringify(obj, null, 2))); diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/package.json b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/package.json new file mode 100644 index 0000000000..9881bbf3a8 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/package.json @@ -0,0 +1,3 @@ +{ + "name": "pkg-default" +} diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-default.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-default.ts new file mode 100644 index 0000000000..a3d64880a9 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-default.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export default 'a'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-named.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-named.ts new file mode 100644 index 0000000000..132570f2bb --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-named.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const value = 'a'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/b-default.mts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/b-default.mts new file mode 100644 index 0000000000..57d07f2c30 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/b-default.mts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export default 'b'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/b-named.mts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/b-named.mts new file mode 100644 index 0000000000..be9bb39348 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/b-named.mts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const value = 'b'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/c-default.cts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/c-default.cts new file mode 100644 index 0000000000..48f5b4f136 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/c-default.cts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export default 'c'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/c-named.cts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/c-named.cts new file mode 100644 index 0000000000..c3e5b58572 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/c-named.cts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const value = 'c'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts new file mode 100644 index 0000000000..4db74f0b86 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts @@ -0,0 +1,73 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-nocheck + +import * as depCommonJs from 'dep-commonjs'; +import * as depModule from 'dep-module'; +import * as depDefault from 'dep-default'; +import { value as namedA } from './a-named'; +import { value as namedB } from './b-named'; +import cNamed from './c-named'; +import defaultA from './a-default'; +import defaultB from './b-default'; +import cDefault from './c-default'; + +const { default: defaultC } = cDefault; +const { value: namedC } = cNamed; + +async function resolveAll(obj) { + const val = await obj; + if (typeof val !== 'object' || val === null) { + return val; + } + if (Array.isArray(val)) { + return await Promise.all(val.map(resolveAll)); + } + return Object.fromEntries( + await Promise.all( + Object.entries(obj).map(async ([key, value]) => [ + key, + await resolveAll(await value), + ]), + ), + ); +} + +resolveAll({ + depCommonJs, + depModule, + depDefault, + dynCommonJs: import('dep-commonjs'), + dynModule: import('dep-module'), + dynDefault: import('dep-default'), + dep: { + namedA, + namedB, + namedC, + defaultA, + defaultB, + defaultC, + }, + dyn: { + namedA: import('./a-named').then(m => m.value), + namedB: import('./b-named').then(m => m.value), + namedC: import('./c-named').then(m => m.default.value), + defaultA: import('./a-default').then(m => m.default), + defaultB: import('./b-default').then(m => m.default), + defaultC: import('./c-default').then(m => m.default.default), + }, +}).then(obj => console.log(JSON.stringify(obj, null, 2))); diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/package.json b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/package.json new file mode 100644 index 0000000000..a04ea067a6 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/package.json @@ -0,0 +1,7 @@ +{ + "name": "pkg-module", + "type": "module", + "exports": { + ".": "./main.ts" + } +} diff --git a/packages/cli/src/tests/transforms/transforms.test.ts b/packages/cli/src/tests/transforms/transforms.test.ts new file mode 100644 index 0000000000..b9180e69d6 --- /dev/null +++ b/packages/cli/src/tests/transforms/transforms.test.ts @@ -0,0 +1,103 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { execFileSync } from 'child_process'; +import { resolve as resolvePath } from 'path'; + +const exportValues = { + all: { + namedA: 'a', + namedB: 'b', + namedC: 'c', + defaultA: 'a', + defaultB: 'b', + defaultC: 'c', + }, + commonJs: { + namedA: 'a', + namedC: 'c', + defaultA: 'a', + defaultC: 'c', + }, +}; + +const expectedExports = { + commonJs: { + ...exportValues.commonJs, + dyn: exportValues.all, + default: { + ...exportValues.commonJs, + dyn: exportValues.all, + }, + }, + module: { + ...exportValues.all, + dyn: exportValues.all, + }, +}; + +function loadFixture(fixture: string) { + return JSON.parse( + execFileSync( + 'node', + [ + '--import', + '@backstage/cli/config/nodeTransform.cjs', + resolvePath(__dirname, `__fixtures__/${fixture}`), + ], + { encoding: 'utf8' }, + ), + ); +} + +describe('node runtime module transforms', () => { + it('should load from commonjs format', async () => { + expect(loadFixture('pkg-commonjs/main.ts')).toEqual({ + depCommonJs: expectedExports.commonJs, + depDefault: expectedExports.commonJs, + dynCommonJs: expectedExports.commonJs, + dynDefault: expectedExports.commonJs, + dynModule: expectedExports.module, + dep: exportValues.commonJs, + dyn: exportValues.all, + }); + }); + + it('should load from default format', async () => { + expect(loadFixture('pkg-default/main.ts')).toEqual({ + depCommonJs: expectedExports.commonJs, + depDefault: expectedExports.commonJs, + dynCommonJs: expectedExports.commonJs, + dynDefault: expectedExports.commonJs, + dynModule: expectedExports.module, + dep: exportValues.commonJs, + dyn: exportValues.all, + }); + }); + + it('should load from module format', async () => { + expect(loadFixture('pkg-module/main.ts')).toEqual({ + depCommonJs: expectedExports.commonJs, + depDefault: expectedExports.commonJs, + depModule: expectedExports.module, + dynCommonJs: expectedExports.commonJs, + dynDefault: expectedExports.commonJs, + dynModule: expectedExports.module, + dep: exportValues.all, + dyn: exportValues.all, + }); + }); +}); From ceda4097bccedaa3c7c95e95c7cb040330bd8243 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 22 Dec 2024 12:20:25 +0100 Subject: [PATCH 05/45] repo-tools: update to work with new dynamic imports Signed-off-by: Patrik Oldsberg --- packages/repo-tools/src/commands/index.ts | 93 +++++++------------ .../commands/repo/schema/openapi/verify.ts | 2 +- 2 files changed, 37 insertions(+), 58 deletions(-) diff --git a/packages/repo-tools/src/commands/index.ts b/packages/repo-tools/src/commands/index.ts index e3c4c9e41f..bb9fbf5087 100644 --- a/packages/repo-tools/src/commands/index.ts +++ b/packages/repo-tools/src/commands/index.ts @@ -39,9 +39,7 @@ function registerPackageCommand(program: Command) { 'Initialize any required files to use the OpenAPI tooling for this package.', ) .action( - lazy(() => - import('./package/schema/openapi/init').then(m => m.singleCommand), - ), + lazy(() => import('./package/schema/openapi/init'), 'singleCommand'), ); openApiCommand @@ -60,11 +58,7 @@ function registerPackageCommand(program: Command) { ) .option('--watch') .description('Watch the OpenAPI spec for changes and regenerate on save.') - .action( - lazy(() => - import('./package/schema/openapi/generate').then(m => m.command), - ), - ); + .action(lazy(() => import('./package/schema/openapi/generate'), 'command')); openApiCommand .command('fuzz') @@ -81,18 +75,14 @@ function registerPackageCommand(program: Command) { '--exclude-checks ', 'Exclude checks from schemathesis run', ) - .action( - lazy(() => import('./package/schema/openapi/fuzz').then(m => m.command)), - ); + .action(lazy(() => import('./package/schema/openapi/fuzz'), 'command')); openApiCommand .command('diff') .option('--ignore', 'Ignore linting failures and only log the results.') .option('--json', 'Output the results as JSON') .option('--since ', 'Diff the API against a specific ref') - .action( - lazy(() => import('./package/schema/openapi/diff').then(m => m.command)), - ); + .action(lazy(() => import('./package/schema/openapi/diff'), 'command')); } function registerRepoCommand(program: Command) { @@ -113,11 +103,7 @@ function registerRepoCommand(program: Command) { .description( 'Verify that all OpenAPI schemas are valid and set up correctly.', ) - .action( - lazy(() => - import('./repo/schema/openapi/verify').then(m => m.bulkCommand), - ), - ); + .action(lazy(() => import('./repo/schema/openapi/verify'), 'bulkCommand')); openApiCommand .command('lint [paths...]') @@ -126,17 +112,13 @@ function registerRepoCommand(program: Command) { '--strict', 'Fail on any linting severity messages, not just errors.', ) - .action( - lazy(() => import('./repo/schema/openapi/lint').then(m => m.bulkCommand)), - ); + .action(lazy(() => import('./repo/schema/openapi/lint'), 'bulkCommand')); openApiCommand .command('test [paths...]') .description('Test OpenAPI schemas against written tests') .option('--update', 'Update the spec on failure.') - .action( - lazy(() => import('./repo/schema/openapi/test').then(m => m.bulkCommand)), - ); + .action(lazy(() => import('./repo/schema/openapi/test'), 'bulkCommand')); openApiCommand .command('fuzz') @@ -145,9 +127,7 @@ function registerRepoCommand(program: Command) { '--since ', 'Only fuzz packages that have changed since the given ref', ) - .action( - lazy(() => import('./repo/schema/openapi/fuzz').then(m => m.command)), - ); + .action(lazy(() => import('./repo/schema/openapi/fuzz'), 'command')); openApiCommand .command('diff') @@ -159,9 +139,7 @@ function registerRepoCommand(program: Command) { 'Diff the API against a specific ref', 'origin/master', ) - .action( - lazy(() => import('./repo/schema/openapi/diff').then(m => m.command)), - ); + .action(lazy(() => import('./repo/schema/openapi/diff'), 'command')); } function registerLintCommand(program: Command) { @@ -174,10 +152,10 @@ function registerLintCommand(program: Command) { 'Lint backend plugin packages for legacy exports and make sure it conforms to the new export pattern', ) .action( - lazy(() => - import( - './lint-legacy-backend-exports/lint-legacy-backend-exports' - ).then(m => m.lint), + lazy( + () => + import('./lint-legacy-backend-exports/lint-legacy-backend-exports'), + 'lint', ), ); } @@ -215,16 +193,12 @@ export function registerCommands(program: Command) { 'Turn on release tag validation for the public, beta, and alpha APIs', ) .description('Generate an API report for selected packages') - .action( - lazy(() => - import('./api-reports/api-reports').then(m => m.buildApiReports), - ), - ); + .action(lazy(() => import('./api-reports/api-reports'), 'buildApiReports')); program .command('type-deps') .description('Find inconsistencies in types of all packages and plugins') - .action(lazy(() => import('./type-deps/type-deps').then(m => m.default))); + .action(lazy(() => import('./type-deps/type-deps'), 'default')); program .command('peer-deps') @@ -232,7 +206,7 @@ export function registerCommands(program: Command) { 'Ensure your packages are using the correct peer dependency format.', ) .option('--fix', 'Fix the issues found') - .action(lazy(() => import('./peer-deps/peer-deps').then(m => m.default))); + .action(lazy(() => import('./peer-deps/peer-deps'), 'default')); program .command('generate-catalog-info') @@ -246,10 +220,9 @@ export function registerCommands(program: Command) { ) .description('Create or fix info yaml files for all backstage packages') .action( - lazy(() => - import('./generate-catalog-info/generate-catalog-info').then( - m => m.default, - ), + lazy( + () => import('./generate-catalog-info/generate-catalog-info'), + 'default', ), ); @@ -278,20 +251,14 @@ export function registerCommands(program: Command) { .description( 'Generate a patch for the selected package in the target repository', ) - .action( - lazy(() => - import('./generate-patch/generate-patch').then(m => m.default), - ), - ); + .action(lazy(() => import('./generate-patch/generate-patch'), 'default')); program .command('knip-reports [paths...]') .option('--ci', 'CI run checks that there is no changes on knip reports') .description('Generate a knip report for selected packages') .action( - lazy(() => - import('./knip-reports/knip-reports').then(m => m.buildKnipReports), - ), + lazy(() => import('./knip-reports/knip-reports'), 'buildKnipReports'), ); registerPackageCommand(program); @@ -299,13 +266,25 @@ export function registerCommands(program: Command) { registerLintCommand(program); } +type ActionFunc = (...args: any[]) => Promise; +type ActionExports = { + [KName in keyof TModule as TModule[KName] extends ActionFunc + ? KName + : never]: TModule[KName]; +}; + // Wraps an action function so that it always exits and handles errors -function lazy( - getActionFunc: () => Promise<(...args: any[]) => Promise>, +export function lazy( + moduleLoader: () => Promise, + exportName: keyof ActionExports, ): (...args: any[]) => Promise { return async (...args: any[]) => { try { - const actionFunc = await getActionFunc(); + const mod = await moduleLoader(); + const actualModule = ( + mod as unknown as { default: ActionExports } + ).default; + const actionFunc = actualModule[exportName] as ActionFunc; await actionFunc(...args); process.exit(0); diff --git a/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts b/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts index 61aadc5d0c..9eab1c77f5 100644 --- a/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts +++ b/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts @@ -54,7 +54,7 @@ async function verify(directoryPath: string) { schemaPath = join(directoryPath, OLD_SCHEMA_PATH); } - const schema = await import(resolvePath(schemaPath)); + const { default: schema } = await import(resolvePath(schemaPath)); if (!schema.spec) { throw new Error(`\`${TS_SCHEMA_PATH}\` needs to have a 'spec' export.`); From f01b5739e6fe2b67abca8bb044387e86b5fb767d Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 23 Dec 2024 10:01:42 +0100 Subject: [PATCH 06/45] cli: refactor transform tests to separate out printing Signed-off-by: Patrik Oldsberg --- .../__fixtures__/pkg-commonjs/main.ts | 6 +++--- .../__fixtures__/pkg-commonjs/print.ts | 19 +++++++++++++++++++ .../__fixtures__/pkg-default/main.ts | 6 +++--- .../__fixtures__/pkg-default/print.ts | 19 +++++++++++++++++++ .../__fixtures__/pkg-module/main.ts | 6 +++--- .../__fixtures__/pkg-module/print.ts | 19 +++++++++++++++++++ .../src/tests/transforms/transforms.test.ts | 6 +++--- 7 files changed, 69 insertions(+), 12 deletions(-) create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/print.ts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-default/print.ts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-module/print.ts diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts index 4cb18038b3..c0494e86b6 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts @@ -26,7 +26,7 @@ import { default as defaultA } from './a-default'; // import { default as defaultB } from './b-default'; import { default as defaultC } from './c-default'; -async function resolveAll(obj) { +async function resolveAll(obj): Promise { const val = await obj; if (typeof val !== 'object' || val === null) { return val; @@ -44,7 +44,7 @@ async function resolveAll(obj) { ); } -resolveAll({ +export const values = resolveAll({ depCommonJs, // depModule, depDefault, @@ -67,4 +67,4 @@ resolveAll({ defaultB: import('./b-default').then(m => m.default), defaultC: import('./c-default').then(m => m.default.default), }, -}).then(obj => console.log(JSON.stringify(obj, null, 2))); +}); diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/print.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/print.ts new file mode 100644 index 0000000000..7c24f99f37 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/print.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { values } from './main'; + +values.then(obj => console.log(JSON.stringify(obj, null, 2))); diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts index 4cb18038b3..c0494e86b6 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts @@ -26,7 +26,7 @@ import { default as defaultA } from './a-default'; // import { default as defaultB } from './b-default'; import { default as defaultC } from './c-default'; -async function resolveAll(obj) { +async function resolveAll(obj): Promise { const val = await obj; if (typeof val !== 'object' || val === null) { return val; @@ -44,7 +44,7 @@ async function resolveAll(obj) { ); } -resolveAll({ +export const values = resolveAll({ depCommonJs, // depModule, depDefault, @@ -67,4 +67,4 @@ resolveAll({ defaultB: import('./b-default').then(m => m.default), defaultC: import('./c-default').then(m => m.default.default), }, -}).then(obj => console.log(JSON.stringify(obj, null, 2))); +}); diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/print.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/print.ts new file mode 100644 index 0000000000..7c24f99f37 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/print.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { values } from './main'; + +values.then(obj => console.log(JSON.stringify(obj, null, 2))); diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts index 4db74f0b86..7b40a259f6 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts @@ -29,7 +29,7 @@ import cDefault from './c-default'; const { default: defaultC } = cDefault; const { value: namedC } = cNamed; -async function resolveAll(obj) { +async function resolveAll(obj): Promise { const val = await obj; if (typeof val !== 'object' || val === null) { return val; @@ -47,7 +47,7 @@ async function resolveAll(obj) { ); } -resolveAll({ +export const values = resolveAll({ depCommonJs, depModule, depDefault, @@ -70,4 +70,4 @@ resolveAll({ defaultB: import('./b-default').then(m => m.default), defaultC: import('./c-default').then(m => m.default.default), }, -}).then(obj => console.log(JSON.stringify(obj, null, 2))); +}); diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/print.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/print.ts new file mode 100644 index 0000000000..7c24f99f37 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/print.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { values } from './main'; + +values.then(obj => console.log(JSON.stringify(obj, null, 2))); diff --git a/packages/cli/src/tests/transforms/transforms.test.ts b/packages/cli/src/tests/transforms/transforms.test.ts index b9180e69d6..83676d4136 100644 --- a/packages/cli/src/tests/transforms/transforms.test.ts +++ b/packages/cli/src/tests/transforms/transforms.test.ts @@ -65,7 +65,7 @@ function loadFixture(fixture: string) { describe('node runtime module transforms', () => { it('should load from commonjs format', async () => { - expect(loadFixture('pkg-commonjs/main.ts')).toEqual({ + expect(loadFixture('pkg-commonjs/print.ts')).toEqual({ depCommonJs: expectedExports.commonJs, depDefault: expectedExports.commonJs, dynCommonJs: expectedExports.commonJs, @@ -77,7 +77,7 @@ describe('node runtime module transforms', () => { }); it('should load from default format', async () => { - expect(loadFixture('pkg-default/main.ts')).toEqual({ + expect(loadFixture('pkg-default/print.ts')).toEqual({ depCommonJs: expectedExports.commonJs, depDefault: expectedExports.commonJs, dynCommonJs: expectedExports.commonJs, @@ -89,7 +89,7 @@ describe('node runtime module transforms', () => { }); it('should load from module format', async () => { - expect(loadFixture('pkg-module/main.ts')).toEqual({ + expect(loadFixture('pkg-module/print.ts')).toEqual({ depCommonJs: expectedExports.commonJs, depDefault: expectedExports.commonJs, depModule: expectedExports.module, From 47407fc34c9bf6a60565e050a393ac888737bcd4 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 23 Dec 2024 11:33:44 +0100 Subject: [PATCH 07/45] cli: add ESM support to Jest config + tests Signed-off-by: Patrik Oldsberg --- .github/workflows/ci.yml | 2 +- .github/workflows/verify_windows.yml | 2 +- package.json | 10 +- packages/cli/config/jest.js | 160 +++++++++++------- packages/cli/config/jestSwcTransform.js | 3 +- packages/cli/config/nodeTransformHooks.mjs | 2 +- .../pkg-module/a-default-explicit.mts | 16 ++ .../pkg-module/a-named-explicit.mts | 16 ++ .../__fixtures__/pkg-module/main-explicit.mts | 73 ++++++++ .../src/tests/transforms/transforms.test.ts | 75 ++++++-- 10 files changed, 278 insertions(+), 81 deletions(-) create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-default-explicit.mts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-named-explicit.mts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-module/main-explicit.mts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 24e74ff0f6..64926b5e8d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -202,7 +202,7 @@ jobs: env: CI: true - NODE_OPTIONS: --max-old-space-size=8192 --no-node-snapshot + NODE_OPTIONS: --max-old-space-size=8192 --no-node-snapshot --experimental-vm-modules INTEGRATION_TEST_GITHUB_TOKEN: ${{ secrets.INTEGRATION_TEST_GITHUB_TOKEN }} INTEGRATION_TEST_GITLAB_TOKEN: ${{ secrets.INTEGRATION_TEST_GITLAB_TOKEN }} INTEGRATION_TEST_BITBUCKET_TOKEN: ${{ secrets.INTEGRATION_TEST_BITBUCKET_TOKEN }} diff --git a/.github/workflows/verify_windows.yml b/.github/workflows/verify_windows.yml index e3f6959929..76ddd85a6b 100644 --- a/.github/workflows/verify_windows.yml +++ b/.github/workflows/verify_windows.yml @@ -21,7 +21,7 @@ jobs: env: CI: true - NODE_OPTIONS: --max-old-space-size=8192 --no-node-snapshot + NODE_OPTIONS: --max-old-space-size=8192 --no-node-snapshot --experimental-vm-modules INTEGRATION_TEST_GITHUB_TOKEN: ${{ secrets.INTEGRATION_TEST_GITHUB_TOKEN }} INTEGRATION_TEST_GITLAB_TOKEN: ${{ secrets.INTEGRATION_TEST_GITLAB_TOKEN }} INTEGRATION_TEST_BITBUCKET_TOKEN: ${{ secrets.INTEGRATION_TEST_BITBUCKET_TOKEN }} diff --git a/package.json b/package.json index b364b2986b..b17e604617 100644 --- a/package.json +++ b/package.json @@ -49,8 +49,8 @@ "storybook": "yarn ./storybook run storybook", "techdocs-cli": "node scripts/techdocs-cli.js", "techdocs-cli:dev": "cross-env TECHDOCS_CLI_DEV_MODE=true node scripts/techdocs-cli.js", - "test": "NODE_OPTIONS=--no-node-snapshot backstage-cli repo test", - "test:all": "NODE_OPTIONS=--no-node-snapshot backstage-cli repo test --coverage", + "test": "NODE_OPTIONS='--no-node-snapshot --experimental-vm-modules' backstage-cli repo test", + "test:all": "NODE_OPTIONS='--no-node-snapshot --experimental-vm-modules' backstage-cli repo test --coverage", "test:e2e": "NODE_OPTIONS=--no-node-snapshot playwright test", "tsc": "tsc", "tsc:full": "backstage-cli repo clean && tsc --skipLibCheck false --incremental false" @@ -84,6 +84,9 @@ ] }, "prettier": "@backstage/cli/config/prettier", + "jest": { + "rejectFrontendNetworkRequests": true + }, "resolutions": { "@changesets/assemble-release-plan@^6.0.0": "patch:@changesets/assemble-release-plan@npm%3A6.0.0#./.yarn/patches/@changesets-assemble-release-plan-npm-6.0.0-f7b3005037.patch", "@material-ui/pickers@^3.2.10": "patch:@material-ui/pickers@npm%3A3.3.11#./.yarn/patches/@material-ui-pickers-npm-3.3.11-1c8f68ea20.patch", @@ -134,9 +137,6 @@ "sort-package-json": "^2.8.0", "typescript": "~5.2.0" }, - "jest": { - "rejectFrontendNetworkRequests": true - }, "packageManager": "yarn@3.8.1", "engines": { "node": "20 || 22" diff --git a/packages/cli/config/jest.js b/packages/cli/config/jest.js index 45d744d7bc..560c9bf708 100644 --- a/packages/cli/config/jest.js +++ b/packages/cli/config/jest.js @@ -31,6 +31,14 @@ const FRONTEND_ROLES = [ 'frontend-plugin-module', ]; +const NODE_ROLES = [ + 'backend', + 'cli', + 'node-library', + 'backend-plugin', + 'backend-plugin-module', +]; + const envOptions = { oldTests: Boolean(process.env.BACKSTAGE_OLD_TESTS), }; @@ -130,11 +138,97 @@ const transformIgnorePattern = [ ].join('|'); // Provides additional config that's based on the role of the target package -function getRoleConfig(role) { +function getRoleConfig(role, pkgJson) { + // Only Node.js package roles support native ESM modules, frontend and common + // packages are always transpiled to CommonJS. + const moduleOpts = NODE_ROLES.includes(role) + ? { + module: { + ignoreDynamic: true, + exportInteropAnnotation: true, + }, + } + : undefined; + + const transform = { + '\\.(mjs|cjs|js)$': [ + require.resolve('./jestSwcTransform'), + { + ...moduleOpts, + jsc: { + parser: { + syntax: 'ecmascript', + }, + }, + }, + ], + '\\.jsx$': [ + require.resolve('./jestSwcTransform'), + { + jsc: { + parser: { + syntax: 'ecmascript', + jsx: true, + }, + transform: { + react: { + runtime: 'automatic', + }, + }, + }, + }, + ], + '\\.(mts|cts|ts)$': [ + require.resolve('./jestSwcTransform'), + { + ...moduleOpts, + jsc: { + parser: { + syntax: 'typescript', + }, + }, + }, + ], + '\\.tsx$': [ + require.resolve('./jestSwcTransform'), + { + jsc: { + parser: { + syntax: 'typescript', + tsx: true, + }, + transform: { + react: { + runtime: 'automatic', + }, + }, + }, + }, + ], + '\\.(bmp|gif|jpg|jpeg|png|ico|webp|frag|xml|svg|eot|woff|woff2|ttf)$': + require.resolve('./jestFileTransform.js'), + '\\.(yaml)$': require.resolve('./jestYamlTransform'), + }; if (FRONTEND_ROLES.includes(role)) { - return { testEnvironment: require.resolve('jest-environment-jsdom') }; + return { + testEnvironment: require.resolve('jest-environment-jsdom'), + transform, + }; } - return { testEnvironment: require.resolve('jest-environment-node') }; + return { + testEnvironment: require.resolve('jest-environment-node'), + moduleFileExtensions: [...SRC_EXTS, 'json', 'node'], + // Jest doesn't let us dynamically detect type=module per transformed file, + // so we have to assume that if the entry point is ESM, all TS files are + // ESM. + // + // This means you can't switch a package to type=module until all of its + // monorepo dependencies are also type=module or does not contain any .ts + // files. + extensionsToTreatAsEsm: + pkgJson.type === 'module' ? ['.ts', '.mts'] : ['.mts'], + transform, + }; } async function getProjectConfig(targetPath, extraConfig, extraOptions) { @@ -160,64 +254,6 @@ async function getProjectConfig(targetPath, extraConfig, extraOptions) { '\\.(css|less|scss|sss|styl)$': require.resolve('jest-css-modules'), }, - transform: { - '\\.(mjs|cjs|js)$': [ - require.resolve('./jestSwcTransform'), - { - jsc: { - parser: { - syntax: 'ecmascript', - }, - }, - }, - ], - '\\.jsx$': [ - require.resolve('./jestSwcTransform'), - { - jsc: { - parser: { - syntax: 'ecmascript', - jsx: true, - }, - transform: { - react: { - runtime: 'automatic', - }, - }, - }, - }, - ], - '\\.ts$': [ - require.resolve('./jestSwcTransform'), - { - jsc: { - parser: { - syntax: 'typescript', - }, - }, - }, - ], - '\\.tsx$': [ - require.resolve('./jestSwcTransform'), - { - jsc: { - parser: { - syntax: 'typescript', - tsx: true, - }, - transform: { - react: { - runtime: 'automatic', - }, - }, - }, - }, - ], - '\\.(bmp|gif|jpg|jpeg|png|ico|webp|frag|xml|svg|eot|woff|woff2|ttf)$': - require.resolve('./jestFileTransform.js'), - '\\.(yaml)$': require.resolve('./jestYamlTransform'), - }, - // A bit more opinionated testMatch: [`**/*.test.{${SRC_EXTS.join(',')}}`], @@ -226,7 +262,7 @@ async function getProjectConfig(targetPath, extraConfig, extraOptions) { : require.resolve('./jestCachingModuleLoader'), transformIgnorePatterns: [`/node_modules/(?:${transformIgnorePattern})/`], - ...getRoleConfig(pkgJson.backstage?.role), + ...getRoleConfig(pkgJson.backstage?.role, pkgJson), }; options.setupFilesAfterEnv = options.setupFilesAfterEnv || []; diff --git a/packages/cli/config/jestSwcTransform.js b/packages/cli/config/jestSwcTransform.js index 83abacc9b5..203bb73d80 100644 --- a/packages/cli/config/jestSwcTransform.js +++ b/packages/cli/config/jestSwcTransform.js @@ -18,12 +18,13 @@ const { createTransformer: createSwcTransformer } = require('@swc/jest'); const ESM_REGEX = /\b(?:import|export)\b/; function createTransformer(config) { + const useModules = Boolean(config?.module); const swcTransformer = createSwcTransformer({ inputSourceMap: false, ...config, }); const process = (source, filePath, jestOptions) => { - if (filePath.endsWith('.js') && !ESM_REGEX.test(source)) { + if (filePath.endsWith('.js') && (useModules || !ESM_REGEX.test(source))) { return { code: source }; } diff --git a/packages/cli/config/nodeTransformHooks.mjs b/packages/cli/config/nodeTransformHooks.mjs index 5892b18ee4..4a897ca6c7 100644 --- a/packages/cli/config/nodeTransformHooks.mjs +++ b/packages/cli/config/nodeTransformHooks.mjs @@ -257,7 +257,7 @@ export async function load(url, context, nextLoad) { const transformed = await transformFile(fileURLToPath(url), { sourceMaps: 'inline', module: { - type: format === 'module' ? 'nodenext' : 'commonjs', + type: format === 'module' ? 'es6' : 'commonjs', ignoreDynamic: true, // This helps the Node.js CommonJS compat layer identify named exports. diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-default-explicit.mts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-default-explicit.mts new file mode 100644 index 0000000000..a3d64880a9 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-default-explicit.mts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export default 'a'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-named-explicit.mts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-named-explicit.mts new file mode 100644 index 0000000000..132570f2bb --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-named-explicit.mts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const value = 'a'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main-explicit.mts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main-explicit.mts new file mode 100644 index 0000000000..7f45ea506c --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main-explicit.mts @@ -0,0 +1,73 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-nocheck + +import * as depCommonJs from 'dep-commonjs'; +import * as depModule from 'dep-module'; +import * as depDefault from 'dep-default'; +import { value as namedA } from './a-named-explicit'; +import { value as namedB } from './b-named'; +import cNamed from './c-named'; +import defaultA from './a-default-explicit'; +import defaultB from './b-default'; +import cDefault from './c-default'; + +const { default: defaultC } = cDefault; +const { value: namedC } = cNamed; + +async function resolveAll(obj): Promise { + const val = await obj; + if (typeof val !== 'object' || val === null) { + return val; + } + if (Array.isArray(val)) { + return await Promise.all(val.map(resolveAll)); + } + return Object.fromEntries( + await Promise.all( + Object.entries(obj).map(async ([key, value]) => [ + key, + await resolveAll(await value), + ]), + ), + ); +} + +export const values = resolveAll({ + depCommonJs, + depModule, + depDefault, + dynCommonJs: import('dep-commonjs'), + dynModule: import('dep-module'), + dynDefault: import('dep-default'), + dep: { + namedA, + namedB, + namedC, + defaultA, + defaultB, + defaultC, + }, + dyn: { + namedA: import('./a-named-explicit').then(m => m.value), + namedB: import('./b-named').then(m => m.value), + namedC: import('./c-named').then(m => m.default.value), + defaultA: import('./a-default-explicit').then(m => m.default), + defaultB: import('./b-default').then(m => m.default), + defaultC: import('./c-default').then(m => m.default.default), + }, +}); diff --git a/packages/cli/src/tests/transforms/transforms.test.ts b/packages/cli/src/tests/transforms/transforms.test.ts index 83676d4136..ac67069b77 100644 --- a/packages/cli/src/tests/transforms/transforms.test.ts +++ b/packages/cli/src/tests/transforms/transforms.test.ts @@ -50,17 +50,16 @@ const expectedExports = { }; function loadFixture(fixture: string) { - return JSON.parse( - execFileSync( - 'node', - [ - '--import', - '@backstage/cli/config/nodeTransform.cjs', - resolvePath(__dirname, `__fixtures__/${fixture}`), - ], - { encoding: 'utf8' }, - ), + const output = execFileSync( + 'node', + [ + '--import', + '@backstage/cli/config/nodeTransform.cjs', + resolvePath(__dirname, `__fixtures__/${fixture}`), + ], + { encoding: 'utf8' }, ); + return JSON.parse(output); } describe('node runtime module transforms', () => { @@ -101,3 +100,59 @@ describe('node runtime module transforms', () => { }); }); }); + +describe('Jest runtime module transforms', () => { + it('should load from commonjs format', async () => { + const values = await import('./__fixtures__/pkg-commonjs/main').then( + m => m.values, + ); + expect(values).toEqual({ + depCommonJs: expectedExports.commonJs, + depDefault: expectedExports.commonJs, + dynCommonJs: expectedExports.commonJs, + dynDefault: expectedExports.commonJs, + dynModule: expectedExports.module, + dep: exportValues.commonJs, + dyn: exportValues.all, + }); + }); + + it('should load from default format', async () => { + const values = await import('./__fixtures__/pkg-default/main').then( + m => m.values, + ); + expect(values).toEqual({ + depCommonJs: expectedExports.commonJs, + depDefault: expectedExports.commonJs, + dynCommonJs: expectedExports.commonJs, + dynDefault: expectedExports.commonJs, + dynModule: expectedExports.module, + dep: exportValues.commonJs, + dyn: exportValues.all, + }); + }); + + it('should load from module format', async () => { + // This uses a separate entry point with an explicit .mts extension. This is + // because we can't cleanly switch the Jest behavior based on type=module in + // package.json for .ts files in Jest. If a module type is detected we + // instead need to switch the transforms for the entire Jest project, which + // we can't do for this test. We instead use the explicit .mts extension to + // verify the transform behavior. + + // @ts-expect-error Cannot find module './__fixtures__/pkg-module/main-explicit' or its corresponding type declarations. + const values = await import('./__fixtures__/pkg-module/main-explicit').then( + m => m.values, + ); + expect(values).toEqual({ + depCommonJs: expectedExports.commonJs, + depDefault: expectedExports.commonJs, + depModule: expectedExports.module, + dynCommonJs: expectedExports.commonJs, + dynDefault: expectedExports.commonJs, + dynModule: expectedExports.module, + dep: exportValues.all, + dyn: exportValues.all, + }); + }); +}); From e1d50e2ce5f210862ad4e0240fe5b19316925e98 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 24 Dec 2024 12:35:52 +0100 Subject: [PATCH 08/45] cli: add module support for rollup build Signed-off-by: Patrik Oldsberg --- packages/cli-node/report.api.md | 2 + .../cli-node/src/monorepo/PackageGraph.ts | 2 + packages/cli/src/lib/builder/config.ts | 99 +++++++++++++++++-- packages/cli/src/lib/builder/packager.ts | 2 +- .../tests/transforms/__fixtures__/.gitignore | 1 + .../__fixtures__/pkg-default/package.json | 5 +- .../__fixtures__/pkg-module/main.ts | 4 +- .../src/tests/transforms/transforms.test.ts | 78 ++++++++++++++- 8 files changed, 180 insertions(+), 13 deletions(-) diff --git a/packages/cli-node/report.api.md b/packages/cli-node/report.api.md index 7c1a3bc744..bec25ea251 100644 --- a/packages/cli-node/report.api.md +++ b/packages/cli-node/report.api.md @@ -72,6 +72,8 @@ export interface BackstagePackageJson { [key: string]: string; }; // (undocumented) + type?: 'module' | 'commonjs'; + // (undocumented) types?: string; // (undocumented) typesVersions?: Record>; diff --git a/packages/cli-node/src/monorepo/PackageGraph.ts b/packages/cli-node/src/monorepo/PackageGraph.ts index 721aa79e50..1da896a057 100644 --- a/packages/cli-node/src/monorepo/PackageGraph.ts +++ b/packages/cli-node/src/monorepo/PackageGraph.ts @@ -43,6 +43,8 @@ export interface BackstagePackageJson { // that the package bundles all of its dependencies in its build output. bundled?: boolean; + type?: 'module' | 'commonjs'; + backstage?: { role?: PackageRole; moved?: string; diff --git a/packages/cli/src/lib/builder/config.ts b/packages/cli/src/lib/builder/config.ts index e419503649..c52efd6c2d 100644 --- a/packages/cli/src/lib/builder/config.ts +++ b/packages/cli/src/lib/builder/config.ts @@ -16,7 +16,11 @@ import chalk from 'chalk'; import fs from 'fs-extra'; -import { relative as relativePath, resolve as resolvePath } from 'path'; +import { + extname, + relative as relativePath, + resolve as resolvePath, +} from 'path'; import commonjs from '@rollup/plugin-commonjs'; import resolve from '@rollup/plugin-node-resolve'; import postcss from 'rollup-plugin-postcss'; @@ -29,6 +33,7 @@ import { RollupOptions, OutputOptions, WarningHandlerWithDefault, + OutputPlugin, } from 'rollup'; import { forwardFileImports } from './plugins'; @@ -40,6 +45,11 @@ import { readEntryPoints } from '../entryPoints'; const SCRIPT_EXTS = ['.js', '.jsx', '.ts', '.tsx']; +const MODULE_EXTS = ['.mjs', '.mts']; +const COMMONJS_EXTS = ['.cjs', '.cts']; +const MOD_EXT = '.mjs'; +const CJS_EXT = '.cjs'; + function isFileImport(source: string) { if (source.startsWith('.')) { return true; @@ -68,6 +78,39 @@ function buildInternalImportPattern(options: BuildOptions) { return new RegExp(`^(?:${names.join('|')})(?:$|/)`); } +// This Rollup output plugin enables support for mixed CommonJS and ESM output. +// It does it be filtering out the unwanted output files that don't match the +// input file format, allowing the rollup configuration to have overlapping +// output configurations for different formats. +function multiOutputFormat(): OutputPlugin { + return { + name: 'backstage-multi-output-format', + generateBundle(opts, bundle) { + const filter: (name: string) => boolean = + opts.format === 'cjs' + ? s => s.endsWith(MOD_EXT) + : s => !s.endsWith(MOD_EXT); + + // Delete any files that don't match the current output format + for (const name in bundle) { + if (filter(name)) { + delete bundle[name]; + delete bundle[`${name}.map`]; + } + } + }, + renderDynamicImport(opts) { + if (opts.format === 'cjs') { + return { + left: 'import(', + right: ')', + }; + } + return undefined; + }, + }; +} + export async function makeRollupConfigs( options: BuildOptions, ): Promise { @@ -120,18 +163,46 @@ export async function makeRollupConfigs( const rewriteNodeModules = (name: string) => name.replaceAll('node_modules', 'node_modules_dist'); + // For CommonJS we build both CommonJS and ESM output. Each of these outputs + // can output both .cjs and .mjs files. The files from each of these outputs + // will overlap, but we trim away files where the format doesn't match the + // file extensions. That way we are left with a combination of .cjs and .mjs + // files where the module format in the file matches the file extension. if (options.outputs.has(Output.cjs)) { - output.push({ + const defaultExt = targetPkg.type === 'module' ? MOD_EXT : CJS_EXT; + const outputOpts: OutputOptions = { dir: distDir, - entryFileNames: chunkInfo => - `${rewriteNodeModules(chunkInfo.name)}.cjs.js`, - chunkFileNames: `cjs/[name]-[hash].cjs.js`, - format: 'commonjs', - interop: 'compat', + entryFileNames(chunkInfo) { + const cleanName = rewriteNodeModules(chunkInfo.name); + + const inputId = chunkInfo.facadeModuleId; + if (!inputId) { + return cleanName + defaultExt; + } + + const inputExt = extname(inputId); + if (MODULE_EXTS.includes(inputExt)) { + return cleanName + MOD_EXT; + } + if (COMMONJS_EXTS.includes(inputExt)) { + return cleanName + CJS_EXT; + } + return cleanName + defaultExt; + }, sourcemap: true, preserveModules: true, preserveModulesRoot: `${targetDir}/src`, exports: 'named', + plugins: [multiOutputFormat()], + }; + + output.push({ + ...outputOpts, + format: 'cjs', + }); + output.push({ + ...outputOpts, + format: 'module', }); } if (options.outputs.has(Output.esm)) { @@ -160,7 +231,19 @@ export async function makeRollupConfigs( // All module imports are always marked as external external, plugins: [ - resolve({ mainFields }), + resolve({ + mainFields, + extensions: [ + '.ts', + '.js', + '.tsx', + '.jsx', + '.mts', + '.cts', + '.mjs', + '.cjs', + ], + }), commonjs({ include: /node_modules/, exclude: [/\/[^/]+\.(?:stories|test)\.[^/]+$/], diff --git a/packages/cli/src/lib/builder/packager.ts b/packages/cli/src/lib/builder/packager.ts index be9bc9a6ea..b709cb0408 100644 --- a/packages/cli/src/lib/builder/packager.ts +++ b/packages/cli/src/lib/builder/packager.ts @@ -107,7 +107,7 @@ export const buildPackage = async (options: BuildOptions) => { const rollupConfigs = await makeRollupConfigs(options); - await fs.remove(paths.resolveTarget('dist')); + await fs.remove(resolvePath(options.targetDir ?? paths.targetDir, 'dist')); const buildTasks = rollupConfigs.map(rollupBuild); diff --git a/packages/cli/src/tests/transforms/__fixtures__/.gitignore b/packages/cli/src/tests/transforms/__fixtures__/.gitignore index cf4bab9ddd..dd13a98e05 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/.gitignore +++ b/packages/cli/src/tests/transforms/__fixtures__/.gitignore @@ -1 +1,2 @@ !node_modules +dist diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/package.json b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/package.json index 9881bbf3a8..1bfa22188c 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/package.json +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/package.json @@ -1,3 +1,6 @@ { - "name": "pkg-default" + "name": "pkg-default", + "exports": { + ".": "./main.ts" + } } diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts index 7b40a259f6..4c6c8f0079 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts @@ -21,10 +21,10 @@ import * as depModule from 'dep-module'; import * as depDefault from 'dep-default'; import { value as namedA } from './a-named'; import { value as namedB } from './b-named'; -import cNamed from './c-named'; +import * as cNamed from './c-named'; import defaultA from './a-default'; import defaultB from './b-default'; -import cDefault from './c-default'; +import * as cDefault from './c-default'; const { default: defaultC } = cDefault; const { value: namedC } = cNamed; diff --git a/packages/cli/src/tests/transforms/transforms.test.ts b/packages/cli/src/tests/transforms/transforms.test.ts index ac67069b77..6f9c291bc7 100644 --- a/packages/cli/src/tests/transforms/transforms.test.ts +++ b/packages/cli/src/tests/transforms/transforms.test.ts @@ -16,6 +16,7 @@ import { execFileSync } from 'child_process'; import { resolve as resolvePath } from 'path'; +import { Output, buildPackage } from '../../lib/builder'; const exportValues = { all: { @@ -95,7 +96,12 @@ describe('node runtime module transforms', () => { dynCommonJs: expectedExports.commonJs, dynDefault: expectedExports.commonJs, dynModule: expectedExports.module, - dep: exportValues.all, + // TODO(Rugvip): Fix CommonJS import compat from modules + dep: { + ...exportValues.all, + defaultC: { default: 'c' }, + namedC: undefined, + }, dyn: exportValues.all, }); }); @@ -156,3 +162,73 @@ describe('Jest runtime module transforms', () => { }); }); }); + +describe('package build transforms', () => { + it('should build and load from commonjs format', async () => { + const pkgPath = resolvePath(__dirname, '__fixtures__/pkg-commonjs'); + + await buildPackage({ + targetDir: pkgPath, + outputs: new Set([Output.cjs]), + workspacePackages: [], + }); + const values = await import(resolvePath(pkgPath, 'dist/index.cjs')).then( + m => m.values, + ); + expect(values).toEqual({ + depCommonJs: expectedExports.commonJs, + depDefault: expectedExports.commonJs, + dynCommonJs: expectedExports.commonJs, + dynDefault: expectedExports.commonJs, + dynModule: expectedExports.module, + dep: exportValues.commonJs, + dyn: exportValues.all, + }); + }); + + it('should build and load from default format', async () => { + const pkgPath = resolvePath(__dirname, '__fixtures__/pkg-default'); + + await buildPackage({ + targetDir: pkgPath, + outputs: new Set([Output.cjs]), + workspacePackages: [], + }); + const values = await import(resolvePath(pkgPath, 'dist/index.cjs')).then( + m => m.values, + ); + expect(values).toEqual({ + depCommonJs: expectedExports.commonJs, + depDefault: expectedExports.commonJs, + dynCommonJs: expectedExports.commonJs, + dynDefault: expectedExports.commonJs, + dynModule: expectedExports.module, + dep: exportValues.commonJs, + dyn: exportValues.all, + }); + }); + + it('should build and load from module format', async () => { + const pkgPath = resolvePath(__dirname, '__fixtures__/pkg-module'); + + await buildPackage({ + targetDir: pkgPath, + outputs: new Set([Output.cjs]), + workspacePackages: [], + }); + const values = await import(resolvePath(pkgPath, 'dist/index.mjs')).then( + m => m.values, + ); + expect(values).toEqual({ + depCommonJs: expectedExports.commonJs, + depDefault: expectedExports.commonJs, + depModule: expectedExports.module, + dynCommonJs: expectedExports.commonJs, + dynDefault: expectedExports.commonJs, + dynModule: expectedExports.module, + // TODO(Rugvip): Fix CommonJS import compat from modules + dep: { ...exportValues.all, defaultC: { default: 'c' } }, + dyn: exportValues.all, + }); + }); +}); From d36f7fd6f865698801510f48b82a6d41b463f98a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 24 Dec 2024 12:41:26 +0100 Subject: [PATCH 09/45] cli: use commonjs compat mode for .cts files Signed-off-by: Patrik Oldsberg --- packages/cli/config/nodeTransformHooks.mjs | 8 +++++--- .../tests/transforms/__fixtures__/pkg-module/main.ts | 5 ++--- packages/cli/src/tests/transforms/transforms.test.ts | 10 ++-------- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/packages/cli/config/nodeTransformHooks.mjs b/packages/cli/config/nodeTransformHooks.mjs index 4a897ca6c7..c19738c1ed 100644 --- a/packages/cli/config/nodeTransformHooks.mjs +++ b/packages/cli/config/nodeTransformHooks.mjs @@ -246,11 +246,13 @@ export async function load(url, context, nextLoad) { // all that well though, and can lead to module loading issues in many cases, // especially for older code. - // This `if` block opts-out of using CommonJS compatibility mode, and instead - // leaves it to our existing loader to transform CommonJS. + // This `if` block opts-out of using CommonJS compatibility mode by default, + // and instead leaves it to our existing loader to transform CommonJS. We do + // however use compatibility mode for the more explicit .cts file extension, + // allows for a way to opt-in to the new behavior. // // TODO(Rugvip): Once the synchronous hooks API is available for us to use, we might be able to adopt that instead - if (format === 'commonjs') { + if (format === 'commonjs' && ext !== '.cts') { return nextLoad(url, { ...context, format }); } diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts index 4c6c8f0079..56ee59fdd7 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts @@ -21,13 +21,12 @@ import * as depModule from 'dep-module'; import * as depDefault from 'dep-default'; import { value as namedA } from './a-named'; import { value as namedB } from './b-named'; -import * as cNamed from './c-named'; +import { value as namedC } from './c-named'; import defaultA from './a-default'; import defaultB from './b-default'; -import * as cDefault from './c-default'; +import cDefault from './c-default'; const { default: defaultC } = cDefault; -const { value: namedC } = cNamed; async function resolveAll(obj): Promise { const val = await obj; diff --git a/packages/cli/src/tests/transforms/transforms.test.ts b/packages/cli/src/tests/transforms/transforms.test.ts index 6f9c291bc7..c15bd063d4 100644 --- a/packages/cli/src/tests/transforms/transforms.test.ts +++ b/packages/cli/src/tests/transforms/transforms.test.ts @@ -96,12 +96,7 @@ describe('node runtime module transforms', () => { dynCommonJs: expectedExports.commonJs, dynDefault: expectedExports.commonJs, dynModule: expectedExports.module, - // TODO(Rugvip): Fix CommonJS import compat from modules - dep: { - ...exportValues.all, - defaultC: { default: 'c' }, - namedC: undefined, - }, + dep: exportValues.all, dyn: exportValues.all, }); }); @@ -226,8 +221,7 @@ describe('package build transforms', () => { dynCommonJs: expectedExports.commonJs, dynDefault: expectedExports.commonJs, dynModule: expectedExports.module, - // TODO(Rugvip): Fix CommonJS import compat from modules - dep: { ...exportValues.all, defaultC: { default: 'c' } }, + dep: exportValues.all, dyn: exportValues.all, }); }); From e00a634225986d377fece3acdaf6365348a0d571 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 24 Dec 2024 13:14:39 +0100 Subject: [PATCH 10/45] cli: add type definitions for transform test fixtures Signed-off-by: Patrik Oldsberg --- .../node_modules/dep-commonjs/main.d.ts | 15 +++++++++++++++ .../node_modules/dep-commonjs/package.json | 5 +++++ .../node_modules/dep-default/main.d.ts | 15 +++++++++++++++ .../node_modules/dep-default/package.json | 5 +++++ .../node_modules/dep-module/main.d.ts | 15 +++++++++++++++ .../node_modules/dep-module/package.json | 5 +++++ 6 files changed, 60 insertions(+) create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/main.d.ts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/main.d.ts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/main.d.ts diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/main.d.ts b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/main.d.ts new file mode 100644 index 0000000000..bdc1e24a02 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/main.d.ts @@ -0,0 +1,15 @@ +export const namedA: string +export const namedB: string +export const namedC: string +export const defaultA: string +export const defaultB: string +export const defaultC: string + +export namespace dyn { + export const namedA: Promise + export const namedB: Promise + export const namedC: Promise + export const defaultA: Promise + export const defaultB: Promise + export const defaultC: Promise +} diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/package.json b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/package.json index 348dc7e258..c5b04671bb 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/package.json +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/package.json @@ -3,5 +3,10 @@ "type": "commonjs", "exports": { ".": "./main.js" + }, + "typesVersions": { + "*": { + "*": [ "main.d.ts" ] + } } } diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/main.d.ts b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/main.d.ts new file mode 100644 index 0000000000..bdc1e24a02 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/main.d.ts @@ -0,0 +1,15 @@ +export const namedA: string +export const namedB: string +export const namedC: string +export const defaultA: string +export const defaultB: string +export const defaultC: string + +export namespace dyn { + export const namedA: Promise + export const namedB: Promise + export const namedC: Promise + export const defaultA: Promise + export const defaultB: Promise + export const defaultC: Promise +} diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/package.json b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/package.json index 1543cf8010..8deb7ce3f4 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/package.json +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/package.json @@ -2,5 +2,10 @@ "name": "dep-default", "exports": { ".": "./main.js" + }, + "typesVersions": { + "*": { + "*": [ "main.d.ts" ] + } } } diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/main.d.ts b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/main.d.ts new file mode 100644 index 0000000000..bdc1e24a02 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/main.d.ts @@ -0,0 +1,15 @@ +export const namedA: string +export const namedB: string +export const namedC: string +export const defaultA: string +export const defaultB: string +export const defaultC: string + +export namespace dyn { + export const namedA: Promise + export const namedB: Promise + export const namedC: Promise + export const defaultA: Promise + export const defaultB: Promise + export const defaultC: Promise +} diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/package.json b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/package.json index b28b4b6562..29de5c862a 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/package.json +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/package.json @@ -3,5 +3,10 @@ "type": "module", "exports": { ".": "./main.js" + }, + "typesVersions": { + "*": { + "*": [ "main.d.ts" ] + } } } From d7b0ef9515977bfbb5e9369a6943d7afe10a475b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 24 Dec 2024 13:42:00 +0100 Subject: [PATCH 11/45] cli: remove support for implicit module extensions + allow .?ts Signed-off-by: Patrik Oldsberg --- packages/cli/config/nodeTransformHooks.mjs | 4 ++-- packages/cli/config/tsconfig.json | 1 + packages/cli/src/lib/builder/config.ts | 11 +---------- .../transforms/__fixtures__/pkg-commonjs/main.ts | 16 ++++++++-------- .../transforms/__fixtures__/pkg-default/main.ts | 16 ++++++++-------- .../transforms/__fixtures__/pkg-module/main.ts | 16 ++++++++-------- 6 files changed, 28 insertions(+), 36 deletions(-) diff --git a/packages/cli/config/nodeTransformHooks.mjs b/packages/cli/config/nodeTransformHooks.mjs index c19738c1ed..b7f3248546 100644 --- a/packages/cli/config/nodeTransformHooks.mjs +++ b/packages/cli/config/nodeTransformHooks.mjs @@ -27,7 +27,7 @@ import { existsSync } from 'fs'; const DEFAULT_MODULE_FORMAT = 'commonjs'; // Source file extensions to look for when using bundle resolution strategy -const EXTS = ['.ts', '.js', '.mts', '.cts', '.mjs', '.cjs']; +const SRC_EXTS = ['.ts', '.js']; const TS_EXTS = ['.ts', '.mts', '.cts']; const moduleTypeTable = { '.mjs': 'module', @@ -177,7 +177,7 @@ async function findPackageJSON(startPath) { /** @type {import('module').ResolveHook} */ async function resolveWithoutExt(specifier, context, nextResolve) { - for (const tryExt of EXTS) { + for (const tryExt of SRC_EXTS) { try { const resolved = await nextResolve(specifier + tryExt, { ...context, diff --git a/packages/cli/config/tsconfig.json b/packages/cli/config/tsconfig.json index aa25e59451..ac5e62b52e 100644 --- a/packages/cli/config/tsconfig.json +++ b/packages/cli/config/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "allowImportingTsExtensions": true, "allowJs": true, "declaration": true, "declarationMap": false, diff --git a/packages/cli/src/lib/builder/config.ts b/packages/cli/src/lib/builder/config.ts index c52efd6c2d..8bde17d7fe 100644 --- a/packages/cli/src/lib/builder/config.ts +++ b/packages/cli/src/lib/builder/config.ts @@ -233,16 +233,7 @@ export async function makeRollupConfigs( plugins: [ resolve({ mainFields, - extensions: [ - '.ts', - '.js', - '.tsx', - '.jsx', - '.mts', - '.cts', - '.mjs', - '.cjs', - ], + extensions: SCRIPT_EXTS, }), commonjs({ include: /node_modules/, diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts index c0494e86b6..5703eb1184 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts @@ -20,11 +20,11 @@ import * as depCommonJs from 'dep-commonjs'; // import * as depModule from 'dep-module'; import * as depDefault from 'dep-default'; import { value as namedA } from './a-named'; -// import { value as namedB } from './b-named'; -import { value as namedC } from './c-named'; +// import { value as namedB } from './b-named.mts'; +import { value as namedC } from './c-named.cts'; import { default as defaultA } from './a-default'; -// import { default as defaultB } from './b-default'; -import { default as defaultC } from './c-default'; +// import { default as defaultB } from './b-default.mts'; +import { default as defaultC } from './c-default.cts'; async function resolveAll(obj): Promise { const val = await obj; @@ -61,10 +61,10 @@ export const values = resolveAll({ }, dyn: { namedA: import('./a-named').then(m => m.default.value), - namedB: import('./b-named').then(m => m.value), - namedC: import('./c-named').then(m => m.default.value), + namedB: import('./b-named.mts').then(m => m.value), + namedC: import('./c-named.cts').then(m => m.default.value), defaultA: import('./a-default').then(m => m.default.default), - defaultB: import('./b-default').then(m => m.default), - defaultC: import('./c-default').then(m => m.default.default), + defaultB: import('./b-default.mts').then(m => m.default), + defaultC: import('./c-default.cts').then(m => m.default.default), }, }); diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts index c0494e86b6..5703eb1184 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts @@ -20,11 +20,11 @@ import * as depCommonJs from 'dep-commonjs'; // import * as depModule from 'dep-module'; import * as depDefault from 'dep-default'; import { value as namedA } from './a-named'; -// import { value as namedB } from './b-named'; -import { value as namedC } from './c-named'; +// import { value as namedB } from './b-named.mts'; +import { value as namedC } from './c-named.cts'; import { default as defaultA } from './a-default'; -// import { default as defaultB } from './b-default'; -import { default as defaultC } from './c-default'; +// import { default as defaultB } from './b-default.mts'; +import { default as defaultC } from './c-default.cts'; async function resolveAll(obj): Promise { const val = await obj; @@ -61,10 +61,10 @@ export const values = resolveAll({ }, dyn: { namedA: import('./a-named').then(m => m.default.value), - namedB: import('./b-named').then(m => m.value), - namedC: import('./c-named').then(m => m.default.value), + namedB: import('./b-named.mts').then(m => m.value), + namedC: import('./c-named.cts').then(m => m.default.value), defaultA: import('./a-default').then(m => m.default.default), - defaultB: import('./b-default').then(m => m.default), - defaultC: import('./c-default').then(m => m.default.default), + defaultB: import('./b-default.mts').then(m => m.default), + defaultC: import('./c-default.cts').then(m => m.default.default), }, }); diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts index 56ee59fdd7..bc8c9f3fdd 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts @@ -20,11 +20,11 @@ import * as depCommonJs from 'dep-commonjs'; import * as depModule from 'dep-module'; import * as depDefault from 'dep-default'; import { value as namedA } from './a-named'; -import { value as namedB } from './b-named'; -import { value as namedC } from './c-named'; +import { value as namedB } from './b-named.mts'; +import { value as namedC } from './c-named.cts'; import defaultA from './a-default'; -import defaultB from './b-default'; -import cDefault from './c-default'; +import defaultB from './b-default.mts'; +import cDefault from './c-default.cts'; const { default: defaultC } = cDefault; @@ -63,10 +63,10 @@ export const values = resolveAll({ }, dyn: { namedA: import('./a-named').then(m => m.value), - namedB: import('./b-named').then(m => m.value), - namedC: import('./c-named').then(m => m.default.value), + namedB: import('./b-named.mts').then(m => m.value), + namedC: import('./c-named.cts').then(m => m.default.value), defaultA: import('./a-default').then(m => m.default), - defaultB: import('./b-default').then(m => m.default), - defaultC: import('./c-default').then(m => m.default.default), + defaultB: import('./b-default.mts').then(m => m.default), + defaultC: import('./c-default.cts').then(m => m.default.default), }, }); From 5676658ca2faefd77174b711586b39f644cd0bf9 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 24 Dec 2024 13:51:44 +0100 Subject: [PATCH 12/45] cli: enable type checking in transform tests Signed-off-by: Patrik Oldsberg --- .../tests/transforms/__fixtures__/pkg-commonjs/main.ts | 9 +++++---- .../tests/transforms/__fixtures__/pkg-default/main.ts | 9 +++++---- .../src/tests/transforms/__fixtures__/pkg-module/main.ts | 8 ++++---- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts index 5703eb1184..d6bdfb30d1 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -// @ts-nocheck - import * as depCommonJs from 'dep-commonjs'; // import * as depModule from 'dep-module'; import * as depDefault from 'dep-default'; @@ -26,7 +24,7 @@ import { default as defaultA } from './a-default'; // import { default as defaultB } from './b-default.mts'; import { default as defaultC } from './c-default.cts'; -async function resolveAll(obj): Promise { +async function resolveAll(obj: object): Promise { const val = await obj; if (typeof val !== 'object' || val === null) { return val; @@ -60,11 +58,14 @@ export const values = resolveAll({ defaultC, }, dyn: { + // @ts-expect-error Default exports from CommonJS are not well supported namedA: import('./a-named').then(m => m.default.value), namedB: import('./b-named.mts').then(m => m.value), - namedC: import('./c-named.cts').then(m => m.default.value), + namedC: import('./c-named.cts').then(m => m.value), + // @ts-expect-error Default exports from CommonJS are not well supported defaultA: import('./a-default').then(m => m.default.default), defaultB: import('./b-default.mts').then(m => m.default), + // @ts-expect-error Default exports from CommonJS are not well supported defaultC: import('./c-default.cts').then(m => m.default.default), }, }); diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts index 5703eb1184..d6bdfb30d1 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -// @ts-nocheck - import * as depCommonJs from 'dep-commonjs'; // import * as depModule from 'dep-module'; import * as depDefault from 'dep-default'; @@ -26,7 +24,7 @@ import { default as defaultA } from './a-default'; // import { default as defaultB } from './b-default.mts'; import { default as defaultC } from './c-default.cts'; -async function resolveAll(obj): Promise { +async function resolveAll(obj: object): Promise { const val = await obj; if (typeof val !== 'object' || val === null) { return val; @@ -60,11 +58,14 @@ export const values = resolveAll({ defaultC, }, dyn: { + // @ts-expect-error Default exports from CommonJS are not well supported namedA: import('./a-named').then(m => m.default.value), namedB: import('./b-named.mts').then(m => m.value), - namedC: import('./c-named.cts').then(m => m.default.value), + namedC: import('./c-named.cts').then(m => m.value), + // @ts-expect-error Default exports from CommonJS are not well supported defaultA: import('./a-default').then(m => m.default.default), defaultB: import('./b-default.mts').then(m => m.default), + // @ts-expect-error Default exports from CommonJS are not well supported defaultC: import('./c-default.cts').then(m => m.default.default), }, }); diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts index bc8c9f3fdd..a4133495c7 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -// @ts-nocheck - import * as depCommonJs from 'dep-commonjs'; import * as depModule from 'dep-module'; import * as depDefault from 'dep-default'; @@ -26,9 +24,10 @@ import defaultA from './a-default'; import defaultB from './b-default.mts'; import cDefault from './c-default.cts'; +// @ts-expect-error Default exports from CommonJS are not well supported const { default: defaultC } = cDefault; -async function resolveAll(obj): Promise { +async function resolveAll(obj: object): Promise { const val = await obj; if (typeof val !== 'object' || val === null) { return val; @@ -64,9 +63,10 @@ export const values = resolveAll({ dyn: { namedA: import('./a-named').then(m => m.value), namedB: import('./b-named.mts').then(m => m.value), - namedC: import('./c-named.cts').then(m => m.default.value), + namedC: import('./c-named.cts').then(m => m.value), defaultA: import('./a-default').then(m => m.default), defaultB: import('./b-default.mts').then(m => m.default), + // @ts-expect-error Default exports from CommonJS are not well supported defaultC: import('./c-default.cts').then(m => m.default.default), }, }); From ce74d76262d487e8b75719f017957b12a941061f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 25 Dec 2024 11:05:27 +0100 Subject: [PATCH 13/45] cli: better runtime support for ESM dependencies in tests Signed-off-by: Patrik Oldsberg --- packages/cli/config/jestCachingModuleLoader.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/cli/config/jestCachingModuleLoader.js b/packages/cli/config/jestCachingModuleLoader.js index 95c6212123..3b7740a4f7 100644 --- a/packages/cli/config/jestCachingModuleLoader.js +++ b/packages/cli/config/jestCachingModuleLoader.js @@ -33,4 +33,22 @@ module.exports = class CachingJestRuntime extends JestRuntime { } return script; } + + // Notes(Rugvip): As far as I can tell this is the best we can currently do + // for runtime ESM support in Jest. What the below logic effectively does is + // to only allow packages to be loaded as ESM if all imports of that package + // are done in an ESM compatible way, as in either from ESM code or with a + // dynamic import. + cjsModules = new Set(); + _resolveCjsModule(...args) { + const path = super._resolveCjsModule(...args); + this.cjsModules.add(path); + return path; + } + unstable_shouldLoadAsEsm(path, ...restArgs) { + if (this.cjsModules.has(path)) { + return false; + } + return super.unstable_shouldLoadAsEsm(path, ...restArgs); + } }; From be9a1f8dc72d16b1845f139f3a40e6a38649ac8f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Dec 2024 11:31:03 +0100 Subject: [PATCH 14/45] cli: simplify Jest ESM support Signed-off-by: Patrik Oldsberg --- .../cli/config/jestCachingModuleLoader.js | 20 ++++++++----------- packages/cli/config/jestSwcTransform.js | 9 +++++++-- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/packages/cli/config/jestCachingModuleLoader.js b/packages/cli/config/jestCachingModuleLoader.js index 3b7740a4f7..dd22f25f6f 100644 --- a/packages/cli/config/jestCachingModuleLoader.js +++ b/packages/cli/config/jestCachingModuleLoader.js @@ -19,6 +19,11 @@ const { default: JestRuntime } = require('jest-runtime'); const scriptTransformCache = new Map(); module.exports = class CachingJestRuntime extends JestRuntime { + constructor(config, ...restAgs) { + super(config, ...restAgs); + this.allowLoadAsEsm = config.extensionsToTreatAsEsm.includes('.mts'); + } + // This may or may not be a good idea. Theoretically I don't know why this would impact // test correctness and flakiness, but it seems like it may introduce flakiness and strange failures. // It does seem to speed up test execution by a fair amount though. @@ -34,19 +39,10 @@ module.exports = class CachingJestRuntime extends JestRuntime { return script; } - // Notes(Rugvip): As far as I can tell this is the best we can currently do - // for runtime ESM support in Jest. What the below logic effectively does is - // to only allow packages to be loaded as ESM if all imports of that package - // are done in an ESM compatible way, as in either from ESM code or with a - // dynamic import. - cjsModules = new Set(); - _resolveCjsModule(...args) { - const path = super._resolveCjsModule(...args); - this.cjsModules.add(path); - return path; - } + // Unfortunately we need to use this unstable API to make sure that .js files + // are only loaded as modules where ESM is supported, i.e. Node.js packages. unstable_shouldLoadAsEsm(path, ...restArgs) { - if (this.cjsModules.has(path)) { + if (!this.allowLoadAsEsm) { return false; } return super.unstable_shouldLoadAsEsm(path, ...restArgs); diff --git a/packages/cli/config/jestSwcTransform.js b/packages/cli/config/jestSwcTransform.js index 203bb73d80..a77683b939 100644 --- a/packages/cli/config/jestSwcTransform.js +++ b/packages/cli/config/jestSwcTransform.js @@ -18,13 +18,18 @@ const { createTransformer: createSwcTransformer } = require('@swc/jest'); const ESM_REGEX = /\b(?:import|export)\b/; function createTransformer(config) { - const useModules = Boolean(config?.module); const swcTransformer = createSwcTransformer({ inputSourceMap: false, ...config, }); const process = (source, filePath, jestOptions) => { - if (filePath.endsWith('.js') && (useModules || !ESM_REGEX.test(source))) { + // Skip transformation of .js files without ESM syntax, we never transform from CJS to ESM + if (filePath.endsWith('.js') && !ESM_REGEX.test(source)) { + return { code: source }; + } + + // Skip transformation of .mjs files, they should only be used if ESM support is available + if (filePath.endsWith('.mjs')) { return { code: source }; } From f866b865212faee227d2c38df536d94ca2dd5304 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Dec 2024 11:52:09 +0100 Subject: [PATCH 15/45] switch to explicit require for lazy-loading dependencies in node packages Signed-off-by: Patrik Oldsberg --- .changeset/curly-humans-prove.md | 7 +++++++ .../src/entrypoints/database/connectors/postgres.ts | 2 +- packages/backend-test-utils/src/cache/memcache.ts | 3 ++- packages/backend-test-utils/src/cache/redis.ts | 3 ++- packages/backend-test-utils/src/database/mysql.ts | 3 ++- packages/backend-test-utils/src/database/postgres.ts | 3 ++- packages/config-loader/src/schema/collect.ts | 5 ++--- 7 files changed, 18 insertions(+), 8 deletions(-) create mode 100644 .changeset/curly-humans-prove.md diff --git a/.changeset/curly-humans-prove.md b/.changeset/curly-humans-prove.md new file mode 100644 index 0000000000..4e0ca9c90e --- /dev/null +++ b/.changeset/curly-humans-prove.md @@ -0,0 +1,7 @@ +--- +'@backstage/backend-test-utils': patch +'@backstage/backend-defaults': patch +'@backstage/config-loader': patch +--- + +Internal refactor to use explicit `require` for lazy-loading dependency. diff --git a/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts b/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts index e7e973ae4b..3583049a7d 100644 --- a/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts +++ b/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts @@ -103,7 +103,7 @@ export async function buildPgDatabaseConfig( Connector: CloudSqlConnector, IpAddressTypes, AuthTypes, - } = await import('@google-cloud/cloud-sql-connector'); + } = require('@google-cloud/cloud-sql-connector') as typeof import('@google-cloud/cloud-sql-connector'); const connector = new CloudSqlConnector(); const clientOpts = await connector.getOptions({ instanceConnectionName: config.connection.instance, diff --git a/packages/backend-test-utils/src/cache/memcache.ts b/packages/backend-test-utils/src/cache/memcache.ts index b7ac07cb13..e985ee1c7d 100644 --- a/packages/backend-test-utils/src/cache/memcache.ts +++ b/packages/backend-test-utils/src/cache/memcache.ts @@ -59,7 +59,8 @@ export async function startMemcachedContainer( image: string, ): Promise { // Lazy-load to avoid side-effect of importing testcontainers - const { GenericContainer } = await import('testcontainers'); + const { GenericContainer } = + require('testcontainers') as typeof import('testcontainers'); const container = await new GenericContainer(image) .withExposedPorts(11211) diff --git a/packages/backend-test-utils/src/cache/redis.ts b/packages/backend-test-utils/src/cache/redis.ts index 6185e4d076..cd6e5c2893 100644 --- a/packages/backend-test-utils/src/cache/redis.ts +++ b/packages/backend-test-utils/src/cache/redis.ts @@ -57,7 +57,8 @@ export async function connectToExternalRedis( export async function startRedisContainer(image: string): Promise { // Lazy-load to avoid side-effect of importing testcontainers - const { GenericContainer } = await import('testcontainers'); + const { GenericContainer } = + require('testcontainers') as typeof import('testcontainers'); const container = await new GenericContainer(image) .withExposedPorts(6379) diff --git a/packages/backend-test-utils/src/database/mysql.ts b/packages/backend-test-utils/src/database/mysql.ts index b27edb7f9d..a7c11d8452 100644 --- a/packages/backend-test-utils/src/database/mysql.ts +++ b/packages/backend-test-utils/src/database/mysql.ts @@ -72,7 +72,8 @@ export async function startMysqlContainer(image: string): Promise<{ const password = uuid(); // Lazy-load to avoid side-effect of importing testcontainers - const { GenericContainer } = await import('testcontainers'); + const { GenericContainer } = + require('testcontainers') as typeof import('testcontainers'); const container = await new GenericContainer(image) .withExposedPorts(3306) diff --git a/packages/backend-test-utils/src/database/postgres.ts b/packages/backend-test-utils/src/database/postgres.ts index c8a946d121..e6122b9ab7 100644 --- a/packages/backend-test-utils/src/database/postgres.ts +++ b/packages/backend-test-utils/src/database/postgres.ts @@ -72,7 +72,8 @@ export async function startPostgresContainer(image: string): Promise<{ const password = uuid(); // Lazy-load to avoid side-effect of importing testcontainers - const { GenericContainer } = await import('testcontainers'); + const { GenericContainer } = + require('testcontainers') as typeof import('testcontainers'); const container = await new GenericContainer(image) .withExposedPorts(5432) diff --git a/packages/config-loader/src/schema/collect.ts b/packages/config-loader/src/schema/collect.ts index 7120c12d90..1d134b2c15 100644 --- a/packages/config-loader/src/schema/collect.ts +++ b/packages/config-loader/src/schema/collect.ts @@ -164,9 +164,8 @@ async function compileTsSchemas(paths: string[]) { // Lazy loaded, because this brings up all of TypeScript and we don't // want that eagerly loaded in tests - const { getProgramFromFiles, buildGenerator } = await import( - 'typescript-json-schema' - ); + const { getProgramFromFiles, buildGenerator } = + require('typescript-json-schema') as typeof import('typescript-json-schema'); const program = getProgramFromFiles(paths, { incremental: false, From fb051f274eb2736d1e6bc5728b22e7ff4efc3864 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Dec 2024 12:12:04 +0100 Subject: [PATCH 16/45] backend-test-utils: sync feature compat unwrapping Signed-off-by: Patrik Oldsberg --- .changeset/dry-horses-report.md | 5 +++++ .../src/next/wiring/TestBackend.ts | 13 +++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 .changeset/dry-horses-report.md diff --git a/.changeset/dry-horses-report.md b/.changeset/dry-horses-report.md new file mode 100644 index 0000000000..b7a0b4cd46 --- /dev/null +++ b/.changeset/dry-horses-report.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-test-utils': patch +--- + +Sync feature installation compatibility logic with `@backstage/backend-app-api`. diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.ts index 4471f6ed24..36e7073e4d 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.ts @@ -209,10 +209,19 @@ function isPromise(value: unknown | Promise): value is Promise { ); } +// Same as in the backend-app-api, handles double defaults from dynamic imports function unwrapFeature( - feature: BackendFeature | (() => BackendFeature), + feature: BackendFeature | { default: BackendFeature }, ): BackendFeature { - return typeof feature === 'function' ? feature() : feature; + if ('$$type' in feature) { + return feature; + } + + if ('default' in feature) { + return feature.default; + } + + return feature; } const backendInstancesToCleanUp = new Array(); From 96c20cd9cf6db6b5f0cf1d3c2611595e283d8466 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Dec 2024 12:45:45 +0100 Subject: [PATCH 17/45] backend-dynamic-feature-service: wait for changes to be tracked Signed-off-by: Patrik Oldsberg --- .changeset/twelve-eyes-stare.md | 5 +++++ .../src/manager/plugin-manager.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/twelve-eyes-stare.md diff --git a/.changeset/twelve-eyes-stare.md b/.changeset/twelve-eyes-stare.md new file mode 100644 index 0000000000..bd2db50c9b --- /dev/null +++ b/.changeset/twelve-eyes-stare.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-dynamic-feature-service': patch +--- + +Make sure changes are successfully tracked before starting up scanner. diff --git a/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts index c64fca053c..398e9fb14c 100644 --- a/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts +++ b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts @@ -68,7 +68,7 @@ export class DynamicPluginManager implements DynamicPluginProvider { preferAlpha: options.preferAlpha, }); const scannedPlugins = (await scanner.scanRoot()).packages; - scanner.trackChanges(); + await scanner.trackChanges(); const moduleLoader = options.moduleLoader || new CommonJSModuleLoader({ logger: options.logger }); From 45f09403e5a898b23869a1bfac98a69651796b85 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Dec 2024 13:41:02 +0100 Subject: [PATCH 18/45] scaffolder-backend: fix loading of ESM-only module in test Signed-off-by: Patrik Oldsberg --- .../src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts index 3990ac14b6..5a931f2da0 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts @@ -38,7 +38,6 @@ import { mockCredentials, mockServices, } from '@backstage/backend-test-utils'; -import stripAnsi from 'strip-ansi'; import { loggerToWinstonLogger } from '@backstage/backend-common'; import { LoggerService } from '@backstage/backend-plugin-api'; @@ -48,6 +47,7 @@ describe('NunjucksWorkflowRunner', () => { let runner: NunjucksWorkflowRunner; let fakeActionHandler: jest.Mock; let fakeTaskLog: jest.Mock; + let stripAnsi: typeof import('strip-ansi').default; const mockDir = createMockDirectory(); @@ -92,9 +92,12 @@ describe('NunjucksWorkflowRunner', () => { ); } - beforeEach(() => { + beforeEach(async () => { mockDir.clear(); + // This one is ESM-only + stripAnsi = await import('strip-ansi').then(m => m.default); + jest.resetAllMocks(); logger = mockServices.logger.mock(); actionRegistry = new TemplateActionRegistry(); From ecea2074576c8212bccf530ab46d868d0cd892e5 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Dec 2024 18:16:38 +0100 Subject: [PATCH 19/45] cli: also verify print output in build transform tests Signed-off-by: Patrik Oldsberg --- .../__fixtures__/pkg-commonjs/package.json | 3 +- .../__fixtures__/pkg-default/package.json | 3 +- .../__fixtures__/pkg-module/package.json | 3 +- .../src/tests/transforms/transforms.test.ts | 31 +++++++++++++++++++ 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/package.json b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/package.json index c622a8ba4d..b39408ee58 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/package.json +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/package.json @@ -2,6 +2,7 @@ "name": "pkg-commonjs", "type": "commonjs", "exports": { - ".": "./main.ts" + ".": "./main.ts", + "./print": "./print.ts" } } diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/package.json b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/package.json index 1bfa22188c..5b0b075a3c 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/package.json +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/package.json @@ -1,6 +1,7 @@ { "name": "pkg-default", "exports": { - ".": "./main.ts" + ".": "./main.ts", + "./print": "./print.ts" } } diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/package.json b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/package.json index a04ea067a6..f7b7e1ab30 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/package.json +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/package.json @@ -2,6 +2,7 @@ "name": "pkg-module", "type": "module", "exports": { - ".": "./main.ts" + ".": "./main.ts", + "./print": "./print.ts" } } diff --git a/packages/cli/src/tests/transforms/transforms.test.ts b/packages/cli/src/tests/transforms/transforms.test.ts index c15bd063d4..7538ed3b89 100644 --- a/packages/cli/src/tests/transforms/transforms.test.ts +++ b/packages/cli/src/tests/transforms/transforms.test.ts @@ -179,6 +179,16 @@ describe('package build transforms', () => { dep: exportValues.commonJs, dyn: exportValues.all, }); + + expect(loadFixture('pkg-commonjs/dist/print.cjs')).toEqual({ + depCommonJs: expectedExports.commonJs, + depDefault: expectedExports.commonJs, + dynCommonJs: expectedExports.commonJs, + dynDefault: expectedExports.commonJs, + dynModule: expectedExports.module, + dep: exportValues.commonJs, + dyn: exportValues.all, + }); }); it('should build and load from default format', async () => { @@ -201,6 +211,16 @@ describe('package build transforms', () => { dep: exportValues.commonJs, dyn: exportValues.all, }); + + expect(loadFixture('pkg-default/dist/print.cjs')).toEqual({ + depCommonJs: expectedExports.commonJs, + depDefault: expectedExports.commonJs, + dynCommonJs: expectedExports.commonJs, + dynDefault: expectedExports.commonJs, + dynModule: expectedExports.module, + dep: exportValues.commonJs, + dyn: exportValues.all, + }); }); it('should build and load from module format', async () => { @@ -224,5 +244,16 @@ describe('package build transforms', () => { dep: exportValues.all, dyn: exportValues.all, }); + + expect(loadFixture('pkg-module/dist/print.mjs')).toEqual({ + depCommonJs: expectedExports.commonJs, + depDefault: expectedExports.commonJs, + depModule: expectedExports.module, + dynCommonJs: expectedExports.commonJs, + dynDefault: expectedExports.commonJs, + dynModule: expectedExports.module, + dep: exportValues.all, + dyn: exportValues.all, + }); }); }); From ac27fdb5b5246dd43b1e9e1ece54e115f4c6877c Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Dec 2024 18:49:16 +0100 Subject: [PATCH 20/45] cli: revert default cjs build extension to .cjs.js Signed-off-by: Patrik Oldsberg --- packages/cli/src/lib/builder/config.ts | 3 ++- packages/cli/src/tests/transforms/transforms.test.ts | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/lib/builder/config.ts b/packages/cli/src/lib/builder/config.ts index 8bde17d7fe..3126383308 100644 --- a/packages/cli/src/lib/builder/config.ts +++ b/packages/cli/src/lib/builder/config.ts @@ -49,6 +49,7 @@ const MODULE_EXTS = ['.mjs', '.mts']; const COMMONJS_EXTS = ['.cjs', '.cts']; const MOD_EXT = '.mjs'; const CJS_EXT = '.cjs'; +const CJS_JS_EXT = '.cjs.js'; function isFileImport(source: string) { if (source.startsWith('.')) { @@ -169,7 +170,7 @@ export async function makeRollupConfigs( // file extensions. That way we are left with a combination of .cjs and .mjs // files where the module format in the file matches the file extension. if (options.outputs.has(Output.cjs)) { - const defaultExt = targetPkg.type === 'module' ? MOD_EXT : CJS_EXT; + const defaultExt = targetPkg.type === 'module' ? MOD_EXT : CJS_JS_EXT; const outputOpts: OutputOptions = { dir: distDir, entryFileNames(chunkInfo) { diff --git a/packages/cli/src/tests/transforms/transforms.test.ts b/packages/cli/src/tests/transforms/transforms.test.ts index 7538ed3b89..52af9a47d3 100644 --- a/packages/cli/src/tests/transforms/transforms.test.ts +++ b/packages/cli/src/tests/transforms/transforms.test.ts @@ -167,7 +167,7 @@ describe('package build transforms', () => { outputs: new Set([Output.cjs]), workspacePackages: [], }); - const values = await import(resolvePath(pkgPath, 'dist/index.cjs')).then( + const values = await import(resolvePath(pkgPath, 'dist/index.cjs.js')).then( m => m.values, ); expect(values).toEqual({ @@ -180,7 +180,7 @@ describe('package build transforms', () => { dyn: exportValues.all, }); - expect(loadFixture('pkg-commonjs/dist/print.cjs')).toEqual({ + expect(loadFixture('pkg-commonjs/dist/print.cjs.js')).toEqual({ depCommonJs: expectedExports.commonJs, depDefault: expectedExports.commonJs, dynCommonJs: expectedExports.commonJs, @@ -199,7 +199,7 @@ describe('package build transforms', () => { outputs: new Set([Output.cjs]), workspacePackages: [], }); - const values = await import(resolvePath(pkgPath, 'dist/index.cjs')).then( + const values = await import(resolvePath(pkgPath, 'dist/index.cjs.js')).then( m => m.values, ); expect(values).toEqual({ @@ -212,7 +212,7 @@ describe('package build transforms', () => { dyn: exportValues.all, }); - expect(loadFixture('pkg-default/dist/print.cjs')).toEqual({ + expect(loadFixture('pkg-default/dist/print.cjs.js')).toEqual({ depCommonJs: expectedExports.commonJs, depDefault: expectedExports.commonJs, dynCommonJs: expectedExports.commonJs, From deee6d584dcd948c031c47f090a2794070969b42 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Dec 2024 18:49:32 +0100 Subject: [PATCH 21/45] docs/tooling/cli: initial ESM docs Signed-off-by: Patrik Oldsberg --- docs/tooling/cli/02-build-system.md | 56 +++++++++++++++++------------ 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/docs/tooling/cli/02-build-system.md b/docs/tooling/cli/02-build-system.md index 468e848694..c9b2fec5ec 100644 --- a/docs/tooling/cli/02-build-system.md +++ b/docs/tooling/cli/02-build-system.md @@ -483,29 +483,39 @@ of the build system, including the bundling, tests, builds, and type checking. Loaders are always selected based on the file extension. The following is a list of all supported file extensions: -| Extension | Exports | Purpose | -| --------- | ------------- | ------------------ | -| `.ts` | Script Module | TypeScript | -| `.tsx` | Script Module | TypeScript and XML | -| `.js` | Script Module | JavaScript | -| `.jsx` | Script Module | JavaScript and XML | -| `.mjs` | Script Module | ECMAScript Module | -| `.cjs` | Script Module | CommonJS Module | -| `.json` | JSON Data | JSON Data | -| `.yml` | JSON Data | YAML Data | -| `.yaml` | JSON Data | YAML Data | -| `.css` | classes | Style sheet | -| `.eot` | URL Path | Font | -| `.ttf` | URL Path | Font | -| `.woff2` | URL Path | Font | -| `.woff` | URL Path | Font | -| `.bmp` | URL Path | Image | -| `.gif` | URL Path | Image | -| `.jpeg` | URL Path | Image | -| `.jpg` | URL Path | Image | -| `.png` | URL Path | Image | -| `.svg` | URL Path | Image | -| `.md` | URL Path | Markdown File | +| Extension | Exports | Purpose | +| --------- | ------------- | ---------------------------- | +| `.ts` | Script Module | TypeScript | +| `.tsx` | Script Module | TypeScript and XML | +| `.mts` | Script Module | ECMAScript Module TypeScript | +| `.cts` | Script Module | CommonJS TypeScript | +| `.js` | Script Module | JavaScript | +| `.jsx` | Script Module | JavaScript and XML | +| `.mjs` | Script Module | ECMAScript Module | +| `.cjs` | Script Module | CommonJS Module | +| `.json` | JSON Data | JSON Data | +| `.yml` | JSON Data | YAML Data | +| `.yaml` | JSON Data | YAML Data | +| `.css` | classes | Style sheet | +| `.eot` | URL Path | Font | +| `.ttf` | URL Path | Font | +| `.woff2` | URL Path | Font | +| `.woff` | URL Path | Font | +| `.bmp` | URL Path | Image | +| `.gif` | URL Path | Image | +| `.jpeg` | URL Path | Image | +| `.jpg` | URL Path | Image | +| `.png` | URL Path | Image | +| `.svg` | URL Path | Image | +| `.md` | URL Path | Markdown File | + +## ECMAScript Modules + +The Backstage tooling supports [ECMAScript modules (ESM)](https://nodejs.org/docs/latest-v22.x/api/esm.html) in Node.js packages. This includes support for all the script module file extensions listed above during local development, in built packages, in tests, and during type checking. [Dynamic imports](https://nodejs.org/docs/latest-v22.x/api/esm.html#import-expressions) can be used to load ESM-only packages from CommonJS and vice versa. There are however a couple of limitations to be aware of: + +- Declaring a package as `"type": "module"` in `package.json` is supported, but in tests it will cause all local transitive dependencies to also be treated as ESM, regardless of whether they declare `"type": "module"` or not. +- Node.js has an [ESM interoperability layer with CommonJS](https://nodejs.org/docs/latest-v22.x/api/esm.html#interoperability-with-commonjs) that allows for imports from ESM to identify named exports in CommonJS packages. This interoperability layer is **only** enabled when importing packages with a `.cts` or `.cjs` extension. This is because the interoperability layer is not fully compatible with the NPM ecosystem, and would break package if it was enabled for `.js` files. +- Dynamic imports of CommonJS packages will vary in shape depending on the runtime, i.e. test vs local development, etc. It is therefore recommended to avoid dynamic imports of CommonJS packages and instead use `require`, or to use the explicit CommonJS extensions as mentioned above. If you do need to dynamically import CommonJS packages, avoid using `default` exports, as the shape of them vary across different environments and you would otherwise need to manually unwrap the import based on the shape of the module object. ## Jest Configuration From 479f9a068d8b77b0a33531f623dcd06cd3f00341 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Dec 2024 18:52:41 +0100 Subject: [PATCH 22/45] cli: restore interop compat mode for cjs build Signed-off-by: Patrik Oldsberg --- packages/cli/src/lib/builder/config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cli/src/lib/builder/config.ts b/packages/cli/src/lib/builder/config.ts index 3126383308..3b55a5a451 100644 --- a/packages/cli/src/lib/builder/config.ts +++ b/packages/cli/src/lib/builder/config.ts @@ -193,6 +193,7 @@ export async function makeRollupConfigs( sourcemap: true, preserveModules: true, preserveModulesRoot: `${targetDir}/src`, + interop: 'compat', exports: 'named', plugins: [multiOutputFormat()], }; From cb76663f9a13bc1eab49167de5fee25f8005414e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Dec 2024 21:25:37 +0100 Subject: [PATCH 23/45] .changesets: add changesets for ESM support Signed-off-by: Patrik Oldsberg --- .changeset/afraid-experts-explain.md | 5 +++++ .changeset/beige-dingos-destroy.md | 17 +++++++++++++++++ .changeset/quiet-phones-sell.md | 5 +++++ 3 files changed, 27 insertions(+) create mode 100644 .changeset/afraid-experts-explain.md create mode 100644 .changeset/beige-dingos-destroy.md create mode 100644 .changeset/quiet-phones-sell.md diff --git a/.changeset/afraid-experts-explain.md b/.changeset/afraid-experts-explain.md new file mode 100644 index 0000000000..38eb3709d0 --- /dev/null +++ b/.changeset/afraid-experts-explain.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli-node': patch +--- + +Added `type` field to `BackstagePackageJson` type. diff --git a/.changeset/beige-dingos-destroy.md b/.changeset/beige-dingos-destroy.md new file mode 100644 index 0000000000..917f03bb8d --- /dev/null +++ b/.changeset/beige-dingos-destroy.md @@ -0,0 +1,17 @@ +--- +'@backstage/cli': minor +--- + +**BREAKING**: Add support for native ESM in Node.js code. This changes the behavior of dynamic import expressions in Node.js code. Typically this can be fixed by replacing `import(...)` with `require(...)`, with an `as typeof import(...)` cast if needed for types. This is because dynamic imports will no longer be transformed to `require(...)` calls, but instead be left as this. This in turn allows you to load ESM modules from CommonJS code using `import(...)`. + +This change adds support for the following in Node.js packages, across type checking, package builds, runtime transforms and Jest tests: + +- Dynamic imports that load ESM modules from CommonJS code. +- Both `.mjs` and `.mts` files as explicit ESM files, as well as `.cjs` and `.cts` as explicit CommonJS files. +- Support for the `"type": "module"` field in `package.json` to indicate that the package is an ESM package. + +There are a few caveats to be aware of: + +- Declaring a package as `"type": "module"` in `package.json` is supported, but in tests it will cause all local transitive dependencies to also be treated as ESM, regardless of whether they declare `"type": "module"` or not. +- Node.js has an [ESM interoperability layer with CommonJS](https://nodejs.org/docs/latest-v22.x/api/esm.html#interoperability-with-commonjs) that allows for imports from ESM to identify named exports in CommonJS packages. This interoperability layer is **only** enabled when importing packages with a `.cts` or `.cjs` extension. This is because the interoperability layer is not fully compatible with the NPM ecosystem, and would break package if it was enabled for `.js` files. +- Dynamic imports of CommonJS packages will vary in shape depending on the runtime, i.e. test vs local development, etc. It is therefore recommended to avoid dynamic imports of CommonJS packages and instead use `require`, or to use the explicit CommonJS extensions as mentioned above. If you do need to dynamically import CommonJS packages, avoid using `default` exports, as the shape of them vary across different environments and you would otherwise need to manually unwrap the import based on the shape of the module object. diff --git a/.changeset/quiet-phones-sell.md b/.changeset/quiet-phones-sell.md new file mode 100644 index 0000000000..f41f5d685e --- /dev/null +++ b/.changeset/quiet-phones-sell.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': patch +--- + +Internal refactor to support native ESM. From c1775a1b3c0f5aa21c4d1475788bb8cc90c76242 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Dec 2024 21:35:40 +0100 Subject: [PATCH 24/45] document need to run ESM tests with --experimental-vm-modules Signed-off-by: Patrik Oldsberg --- .changeset/beige-dingos-destroy.md | 1 + docs/tooling/cli/02-build-system.md | 1 + 2 files changed, 2 insertions(+) diff --git a/.changeset/beige-dingos-destroy.md b/.changeset/beige-dingos-destroy.md index 917f03bb8d..fdd8bfe415 100644 --- a/.changeset/beige-dingos-destroy.md +++ b/.changeset/beige-dingos-destroy.md @@ -12,6 +12,7 @@ This change adds support for the following in Node.js packages, across type chec There are a few caveats to be aware of: +- To enable support for native ESM in tests, you need to run the tests with the `--experimental-vm-module` flag enabled, typically via `NODE_OPTIONS='--experimental-vm-modules'`. - Declaring a package as `"type": "module"` in `package.json` is supported, but in tests it will cause all local transitive dependencies to also be treated as ESM, regardless of whether they declare `"type": "module"` or not. - Node.js has an [ESM interoperability layer with CommonJS](https://nodejs.org/docs/latest-v22.x/api/esm.html#interoperability-with-commonjs) that allows for imports from ESM to identify named exports in CommonJS packages. This interoperability layer is **only** enabled when importing packages with a `.cts` or `.cjs` extension. This is because the interoperability layer is not fully compatible with the NPM ecosystem, and would break package if it was enabled for `.js` files. - Dynamic imports of CommonJS packages will vary in shape depending on the runtime, i.e. test vs local development, etc. It is therefore recommended to avoid dynamic imports of CommonJS packages and instead use `require`, or to use the explicit CommonJS extensions as mentioned above. If you do need to dynamically import CommonJS packages, avoid using `default` exports, as the shape of them vary across different environments and you would otherwise need to manually unwrap the import based on the shape of the module object. diff --git a/docs/tooling/cli/02-build-system.md b/docs/tooling/cli/02-build-system.md index c9b2fec5ec..5e82da4f9b 100644 --- a/docs/tooling/cli/02-build-system.md +++ b/docs/tooling/cli/02-build-system.md @@ -513,6 +513,7 @@ of all supported file extensions: The Backstage tooling supports [ECMAScript modules (ESM)](https://nodejs.org/docs/latest-v22.x/api/esm.html) in Node.js packages. This includes support for all the script module file extensions listed above during local development, in built packages, in tests, and during type checking. [Dynamic imports](https://nodejs.org/docs/latest-v22.x/api/esm.html#import-expressions) can be used to load ESM-only packages from CommonJS and vice versa. There are however a couple of limitations to be aware of: +- To enable support for native ESM in tests, you need to run the tests with the `--experimental-vm-module` flag enabled, typically via `NODE_OPTIONS='--experimental-vm-modules'`. - Declaring a package as `"type": "module"` in `package.json` is supported, but in tests it will cause all local transitive dependencies to also be treated as ESM, regardless of whether they declare `"type": "module"` or not. - Node.js has an [ESM interoperability layer with CommonJS](https://nodejs.org/docs/latest-v22.x/api/esm.html#interoperability-with-commonjs) that allows for imports from ESM to identify named exports in CommonJS packages. This interoperability layer is **only** enabled when importing packages with a `.cts` or `.cjs` extension. This is because the interoperability layer is not fully compatible with the NPM ecosystem, and would break package if it was enabled for `.js` files. - Dynamic imports of CommonJS packages will vary in shape depending on the runtime, i.e. test vs local development, etc. It is therefore recommended to avoid dynamic imports of CommonJS packages and instead use `require`, or to use the explicit CommonJS extensions as mentioned above. If you do need to dynamically import CommonJS packages, avoid using `default` exports, as the shape of them vary across different environments and you would otherwise need to manually unwrap the import based on the shape of the module object. From e6c049add0ad0e6f5d8e097b4a80571e703223ce Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Dec 2024 21:44:19 +0100 Subject: [PATCH 25/45] cil: removed dead code in node transform Signed-off-by: Patrik Oldsberg --- packages/cli/config/nodeTransformHooks.mjs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/cli/config/nodeTransformHooks.mjs b/packages/cli/config/nodeTransformHooks.mjs index b7f3248546..06bfcf4e30 100644 --- a/packages/cli/config/nodeTransformHooks.mjs +++ b/packages/cli/config/nodeTransformHooks.mjs @@ -69,11 +69,6 @@ export async function resolve(specifier, context, nextResolve) { return withDetectedModuleType(await nextResolve(specifier, context)); } - // Imports with exact file extensions are handled by the default resolver - // if (ext !== '') { - // return withDetectedModuleType(await nextResolve(specifier, context)); - // } - // The rest of this function handles the case of resolving imports that do not // specify any extension and might point to a directory with an `index.*` // file. We resolve those using the same logic as most JS bundlers would, with From a8dc7f2a7a431e66787b9b70ede7587291e6b14a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Dec 2024 21:51:05 +0100 Subject: [PATCH 26/45] cli: explicit module type fallback for Node.js 22 Signed-off-by: Patrik Oldsberg --- packages/cli/config/nodeTransformHooks.mjs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/cli/config/nodeTransformHooks.mjs b/packages/cli/config/nodeTransformHooks.mjs index 06bfcf4e30..7d1186561f 100644 --- a/packages/cli/config/nodeTransformHooks.mjs +++ b/packages/cli/config/nodeTransformHooks.mjs @@ -112,6 +112,10 @@ async function withDetectedModuleType(resolved) { if (resolved.format) { return resolved; } + // Happens in Node.js v22 when there's a package.json without an explicit "type" field. Use the default. + if (resolved.format === null) { + return { ...resolved, format: DEFAULT_MODULE_FORMAT }; + } const ext = extname(resolved.url); From eee8d76a4ff118da7a1fe0223d58d280b5a17335 Mon Sep 17 00:00:00 2001 From: tong min Date: Sun, 5 Jan 2025 03:28:05 +0000 Subject: [PATCH 27/45] udpate plugins search-backend-module-catalog encoding the entity property values with encodeURIComponent for location Signed-off-by: tong min --- .changeset/sharp-years-drive.md | 5 +++++ .../src/collators/DefaultCatalogCollatorFactory.ts | 8 +++++--- 2 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 .changeset/sharp-years-drive.md diff --git a/.changeset/sharp-years-drive.md b/.changeset/sharp-years-drive.md new file mode 100644 index 0000000000..39220e4d69 --- /dev/null +++ b/.changeset/sharp-years-drive.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend-module-catalog': patch +--- + +Modified the logic for generating the location URL by encoding the entity property values with encodeURIComponent. This enhancement improves the safety and reliability of the URL. diff --git a/plugins/search-backend-module-catalog/src/collators/DefaultCatalogCollatorFactory.ts b/plugins/search-backend-module-catalog/src/collators/DefaultCatalogCollatorFactory.ts index 1cef2e21df..f023efbf54 100644 --- a/plugins/search-backend-module-catalog/src/collators/DefaultCatalogCollatorFactory.ts +++ b/plugins/search-backend-module-catalog/src/collators/DefaultCatalogCollatorFactory.ts @@ -120,9 +120,11 @@ export class DefaultCatalogCollatorFactory implements DocumentCollatorFactory { resourceRef: stringifyEntityRef(entity), }, location: this.applyArgsToFormat(this.locationTemplate, { - namespace: entity.metadata.namespace || 'default', - kind: entity.kind, - name: entity.metadata.name, + namespace: encodeURIComponent( + entity.metadata.namespace || 'default', + ), + kind: encodeURIComponent(entity.kind), + name: encodeURIComponent(entity.metadata.name), }), }; } From d0b993dbea223697dacb06089e51fa2e56659958 Mon Sep 17 00:00:00 2001 From: tong min Date: Mon, 6 Jan 2025 10:38:21 +0800 Subject: [PATCH 28/45] Update .changeset/sharp-years-drive.md Co-authored-by: Yasser Hennawi Signed-off-by: tong min --- .changeset/sharp-years-drive.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/sharp-years-drive.md b/.changeset/sharp-years-drive.md index 39220e4d69..92ade2b972 100644 --- a/.changeset/sharp-years-drive.md +++ b/.changeset/sharp-years-drive.md @@ -2,4 +2,4 @@ '@backstage/plugin-search-backend-module-catalog': patch --- -Modified the logic for generating the location URL by encoding the entity property values with encodeURIComponent. This enhancement improves the safety and reliability of the URL. +Modified the logic for generating the location URL by encoding the entity property values with `encodeURIComponent`. This enhancement improves the safety and reliability of the URL. From b81776dfaa6dffa1ea332692796fe75cd6030507 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Sun, 12 Jan 2025 15:06:54 +0000 Subject: [PATCH 29/45] First pass at custom theme Signed-off-by: Charles de Dreuille --- canon-docs/package.json | 2 + canon-docs/src/app/globals.css | 34 +++ canon-docs/src/app/layout.tsx | 3 +- .../components/CustomTheme/customTheme.tsx | 94 ++++++ .../src/components/CustomTheme/index.ts | 1 + .../components/CustomTheme/styles.module.css | 41 +++ canon-docs/src/components/Sidebar/docs.tsx | 3 +- .../src/components/Toolbar/theme-name.tsx | 5 +- canon-docs/src/mdx-components.tsx | 1 - canon-docs/src/utils/playground-context.tsx | 23 +- canon-docs/yarn.lock | 271 ++++++++++++++++++ 11 files changed, 463 insertions(+), 15 deletions(-) create mode 100644 canon-docs/src/components/CustomTheme/customTheme.tsx create mode 100644 canon-docs/src/components/CustomTheme/index.ts create mode 100644 canon-docs/src/components/CustomTheme/styles.module.css diff --git a/canon-docs/package.json b/canon-docs/package.json index 824a645f99..af440bf9d5 100644 --- a/canon-docs/package.json +++ b/canon-docs/package.json @@ -9,11 +9,13 @@ "watch-css": "node scripts/watch-css.js" }, "dependencies": { + "@codemirror/lang-sass": "^6.0.2", "@mdx-js/loader": "^3.1.0", "@mdx-js/react": "^3.1.0", "@next/mdx": "^15.1.4", "@storybook/react": "^8.4.7", "@types/mdx": "^2.0.13", + "@uiw/react-codemirror": "^4.23.7", "next": "14.2.23", "react": "^18", "react-dom": "^18", diff --git a/canon-docs/src/app/globals.css b/canon-docs/src/app/globals.css index 4b39735a22..e9710c6576 100644 --- a/canon-docs/src/app/globals.css +++ b/canon-docs/src/app/globals.css @@ -35,3 +35,37 @@ iframe { font-weight: var(--shiki-dark-font-weight) !important; text-decoration: var(--shiki-dark-text-decoration) !important; } + +.ͼ2 .cm-gutters { + background-color: var(--canon-surface-2); + border-right: 1px solid var(--canon-border-base); + transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out; +} + +.ͼ1 .cm-lineNumbers .cm-gutterElement { + padding-right: 8px; + padding-left: 8px; + font-size: 0.875rem; + display: flex; + align-items: center; + justify-content: flex-end; +} + +.cm-line { + font-size: 0.875rem; +} + +.ͼ1 .cm-line { + padding-left: 12px; + padding-top: 1px; + padding-bottom: 1px; +} + +.cm-focused { + outline: none !important; +} + +.cm-editor { + background-color: var(--canon-surface-1); + transition: background-color 0.2s ease-in-out; +} diff --git a/canon-docs/src/app/layout.tsx b/canon-docs/src/app/layout.tsx index 6eb1856a1d..90cd6a9d0d 100644 --- a/canon-docs/src/app/layout.tsx +++ b/canon-docs/src/app/layout.tsx @@ -2,7 +2,7 @@ import type { Metadata } from 'next'; import { Sidebar } from '../components/Sidebar'; import { Toolbar } from '@/components/Toolbar'; import { Providers } from './providers'; - +import { CustomTheme } from '@/components/CustomTheme'; import styles from './page.module.css'; import './globals.css'; @@ -35,6 +35,7 @@ export default function RootLayout({ {children} + diff --git a/canon-docs/src/components/CustomTheme/customTheme.tsx b/canon-docs/src/components/CustomTheme/customTheme.tsx new file mode 100644 index 0000000000..d02f96131b --- /dev/null +++ b/canon-docs/src/components/CustomTheme/customTheme.tsx @@ -0,0 +1,94 @@ +'use client'; + +import { useEffect, useState, useCallback } from 'react'; +import CodeMirror from '@uiw/react-codemirror'; +import { sass } from '@codemirror/lang-sass'; +import styles from './styles.module.css'; +import { usePlayground } from '@/utils/playground-context'; +import { AnimatePresence, motion } from 'framer-motion'; + +export const CustomTheme = () => { + const [customTheme, setCustomTheme] = useState(undefined); + const { selectedThemeName } = usePlayground(); + const [isClient, setIsClient] = useState(false); + + useEffect(() => { + if (selectedThemeName === 'custom') { + const storedTheme = localStorage.getItem('customThemeCss') || ''; + setCustomTheme(storedTheme); + + let styleElement = document.getElementById( + 'custom-theme-style', + ) as HTMLStyleElement; + + if (!styleElement) { + styleElement = document.createElement('style'); + styleElement.id = 'custom-theme-style'; + document.head.appendChild(styleElement); + } + + styleElement.textContent = storedTheme; + } else { + const styleElement = document.getElementById( + 'custom-theme-style', + ) as HTMLStyleElement; + if (styleElement) { + styleElement.remove(); + } + } + }, [selectedThemeName]); + + useEffect(() => { + setIsClient(true); + }, []); + + const handleSave = () => { + if (customTheme) { + localStorage.setItem('customThemeCss', customTheme); + + let styleElement = document.getElementById( + 'custom-theme-style', + ) as HTMLStyleElement; + + if (!styleElement) { + styleElement = document.createElement('style'); + styleElement.id = 'custom-theme-style'; + document.head.appendChild(styleElement); + } + + styleElement.textContent = customTheme; + } + }; + + const onChange = useCallback((val: string) => { + setCustomTheme(val); + }, []); + + return ( + + {isClient && selectedThemeName === 'custom' && ( + +
+
Custom Theme
+ +
+ +
+ )} +
+ ); +}; diff --git a/canon-docs/src/components/CustomTheme/index.ts b/canon-docs/src/components/CustomTheme/index.ts new file mode 100644 index 0000000000..6805b2c087 --- /dev/null +++ b/canon-docs/src/components/CustomTheme/index.ts @@ -0,0 +1 @@ +export { CustomTheme } from './customTheme'; diff --git a/canon-docs/src/components/CustomTheme/styles.module.css b/canon-docs/src/components/CustomTheme/styles.module.css new file mode 100644 index 0000000000..17e6ef18e4 --- /dev/null +++ b/canon-docs/src/components/CustomTheme/styles.module.css @@ -0,0 +1,41 @@ +.container { + position: fixed; + bottom: 16px; + right: 16px; + width: 36%; + height: 348px; + background-color: var(--canon-surface-1); + border-radius: 0.375rem; + border: 1px solid var(--canon-border-base); + display: flex; + flex-direction: column; + overflow: hidden; + transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out; +} + +.editor { + flex: 1; +} + +.header { + height: 46px; + flex-shrink: 0; + border-bottom: 1px solid var(--canon-border-base); + background-color: var(--canon-surface-1); + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 12px 0 16px; + transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out; +} + +.button { + all: unset; + height: 28px; + padding: 0 8px; + color: #fff; + background-color: #000; + border-radius: 0.25rem; + cursor: pointer; + font-size: 0.875rem; +} diff --git a/canon-docs/src/components/Sidebar/docs.tsx b/canon-docs/src/components/Sidebar/docs.tsx index 13c1675c20..f207036cfa 100644 --- a/canon-docs/src/components/Sidebar/docs.tsx +++ b/canon-docs/src/components/Sidebar/docs.tsx @@ -2,8 +2,7 @@ import Link from 'next/link'; import { components, overview, layoutComponents, theme } from '@/utils/data'; -import { Box } from '../../../../packages/canon/src/components/Box'; -import { Text } from '../../../../packages/canon/src/components/Text'; +import { Box } from '../../../../packages/canon'; import { motion } from 'framer-motion'; import styles from './Sidebar.module.css'; import { usePathname } from 'next/navigation'; diff --git a/canon-docs/src/components/Toolbar/theme-name.tsx b/canon-docs/src/components/Toolbar/theme-name.tsx index 57c858dd44..07b734de34 100644 --- a/canon-docs/src/components/Toolbar/theme-name.tsx +++ b/canon-docs/src/components/Toolbar/theme-name.tsx @@ -6,8 +6,9 @@ import { Icon } from '@backstage/canon'; import { usePlayground } from '@/utils/playground-context'; const themes = [ - { name: 'Backstage Legacy', value: 'legacy' }, { name: 'Backstage Default', value: 'default' }, + { name: 'Backstage Legacy', value: 'legacy' }, + { name: 'Custom theme', value: 'custom' }, ]; export const ThemeNameSelector = () => { @@ -31,7 +32,7 @@ export const ThemeNameSelector = () => { {themes.map(({ name, value }) => ( - + diff --git a/canon-docs/src/mdx-components.tsx b/canon-docs/src/mdx-components.tsx index 308356bc67..071fa7580c 100644 --- a/canon-docs/src/mdx-components.tsx +++ b/canon-docs/src/mdx-components.tsx @@ -2,7 +2,6 @@ import React, { ReactNode } from 'react'; import type { MDXComponents } from 'mdx/types'; import Image, { ImageProps } from 'next/image'; import { CodeBlock } from '@/components/CodeBlock'; -import { Heading } from '../../packages/canon/src/components/Heading'; import { Box } from '../../packages/canon/src/components/Box'; export function useMDXComponents(components: MDXComponents): MDXComponents { diff --git a/canon-docs/src/utils/playground-context.tsx b/canon-docs/src/utils/playground-context.tsx index 198c5a9aab..721851c3e4 100644 --- a/canon-docs/src/utils/playground-context.tsx +++ b/canon-docs/src/utils/playground-context.tsx @@ -7,16 +7,19 @@ import React, { } from 'react'; import { components } from './data'; +type Theme = 'light' | 'dark'; +type ThemeName = 'legacy' | 'default' | 'custom'; + // Create a context with an empty array as the default value const PlaygroundContext = createContext<{ selectedScreenSizes: string[]; setSelectedScreenSizes: (screenSizes: string[]) => void; selectedComponents: string[]; setSelectedComponents: (components: string[]) => void; - selectedTheme: string | null; - setSelectedTheme: (theme: string) => void; - selectedThemeName: string; - setSelectedThemeName: (themeName: string) => void; + selectedTheme: Theme; + setSelectedTheme: (theme: Theme) => void; + selectedThemeName: ThemeName; + setSelectedThemeName: (themeName: ThemeName) => void; }>({ selectedScreenSizes: [], setSelectedScreenSizes: () => {}, @@ -37,12 +40,14 @@ export const PlaygroundProvider = ({ children }: { children: ReactNode }) => { const [selectedComponents, setSelectedComponents] = useState( components.map(component => component.slug), ); - const [selectedTheme, setSelectedTheme] = useState(() => { - return isBrowser ? localStorage.getItem('theme') : 'light'; - }); - const [selectedThemeName, setSelectedThemeName] = useState(() => { + const [selectedTheme, setSelectedTheme] = useState(() => { return isBrowser - ? localStorage.getItem('theme-name') || 'default' + ? (localStorage.getItem('theme') as Theme) || 'light' + : 'light'; + }); + const [selectedThemeName, setSelectedThemeName] = useState(() => { + return isBrowser + ? (localStorage.getItem('theme-name') as ThemeName) || 'default' : 'default'; }); diff --git a/canon-docs/yarn.lock b/canon-docs/yarn.lock index 9eb2b969f2..f7a3d103a4 100644 --- a/canon-docs/yarn.lock +++ b/canon-docs/yarn.lock @@ -5,6 +5,133 @@ __metadata: version: 6 cacheKey: 8 +"@babel/runtime@npm:^7.18.6": + version: 7.26.0 + resolution: "@babel/runtime@npm:7.26.0" + dependencies: + regenerator-runtime: ^0.14.0 + checksum: c8e2c0504ab271b3467a261a8f119bf2603eb857a0d71e37791f4e3fae00f681365073cc79f141ddaa90c6077c60ba56448004ad5429d07ac73532be9f7cf28a + languageName: node + linkType: hard + +"@codemirror/autocomplete@npm:^6.0.0": + version: 6.18.4 + resolution: "@codemirror/autocomplete@npm:6.18.4" + dependencies: + "@codemirror/language": ^6.0.0 + "@codemirror/state": ^6.0.0 + "@codemirror/view": ^6.17.0 + "@lezer/common": ^1.0.0 + checksum: 4216f45a17f6cfd8d33df53f940396f7d3707662570bf3a79d8d333f926e273a265fac13c362e29e3fa57ccdf444f1a047862f5f56c672cfc669c87ee975858f + languageName: node + linkType: hard + +"@codemirror/commands@npm:^6.0.0, @codemirror/commands@npm:^6.1.0": + version: 6.8.0 + resolution: "@codemirror/commands@npm:6.8.0" + dependencies: + "@codemirror/language": ^6.0.0 + "@codemirror/state": ^6.4.0 + "@codemirror/view": ^6.27.0 + "@lezer/common": ^1.1.0 + checksum: 7d819bab4830ec7b8c5dffdec4b035dfa664bfd1d2675e639e08a459df65f45be111e1b8b569b1a8a3253d5980cf2ecf4394d8a13509996cca1b65cc16d47a4e + languageName: node + linkType: hard + +"@codemirror/lang-css@npm:^6.2.0": + version: 6.3.1 + resolution: "@codemirror/lang-css@npm:6.3.1" + dependencies: + "@codemirror/autocomplete": ^6.0.0 + "@codemirror/language": ^6.0.0 + "@codemirror/state": ^6.0.0 + "@lezer/common": ^1.0.2 + "@lezer/css": ^1.1.7 + checksum: ed175d75d75bc0a059d1e60b3dcd8464d570da14fc97388439943c9c43e1e9146e37b83fe2ccaad9cd387420b7b411ea1d24ede78ecd1f2045a38acbb4dd36bc + languageName: node + linkType: hard + +"@codemirror/lang-sass@npm:^6.0.2": + version: 6.0.2 + resolution: "@codemirror/lang-sass@npm:6.0.2" + dependencies: + "@codemirror/lang-css": ^6.2.0 + "@codemirror/language": ^6.0.0 + "@codemirror/state": ^6.0.0 + "@lezer/common": ^1.0.2 + "@lezer/sass": ^1.0.0 + checksum: e7665aaab70476a952522b143fd7bd59f6c025746cbf7b542f6965f94eecac483b4afd03f6da98aaa1572e379194309b241c5264eff05c681c637aa26651b9ab + languageName: node + linkType: hard + +"@codemirror/language@npm:^6.0.0": + version: 6.10.8 + resolution: "@codemirror/language@npm:6.10.8" + dependencies: + "@codemirror/state": ^6.0.0 + "@codemirror/view": ^6.23.0 + "@lezer/common": ^1.1.0 + "@lezer/highlight": ^1.0.0 + "@lezer/lr": ^1.0.0 + style-mod: ^4.0.0 + checksum: 679b69d69faa94f028f996a7005d0c6c2a2e4cd7a7a2614f615c23d7b642c31fc1837915248e864cb1ad59a2f032d1a7a8ef486b5f9904e5f6fbe6f7d2882c38 + languageName: node + linkType: hard + +"@codemirror/lint@npm:^6.0.0": + version: 6.8.4 + resolution: "@codemirror/lint@npm:6.8.4" + dependencies: + "@codemirror/state": ^6.0.0 + "@codemirror/view": ^6.35.0 + crelt: ^1.0.5 + checksum: 640e3dd44eb167d952eb5c5b8518919ba46e164aa3471776342f7f9361e676b4627a76a9f01d51b22127b97413f2bc9b8c60299d8dfdd5fc8ad0225d42de7669 + languageName: node + linkType: hard + +"@codemirror/search@npm:^6.0.0": + version: 6.5.8 + resolution: "@codemirror/search@npm:6.5.8" + dependencies: + "@codemirror/state": ^6.0.0 + "@codemirror/view": ^6.0.0 + crelt: ^1.0.5 + checksum: 0f9633037492a7b647b606c30255ea42c4327319e643be7ea3aa2913ed8e4aa662589f457e376636521c7d4d1215fae0e8939f127db9c0790b19ae3b654c3bc4 + languageName: node + linkType: hard + +"@codemirror/state@npm:^6.0.0, @codemirror/state@npm:^6.1.1, @codemirror/state@npm:^6.4.0, @codemirror/state@npm:^6.5.0": + version: 6.5.1 + resolution: "@codemirror/state@npm:6.5.1" + dependencies: + "@marijn/find-cluster-break": ^1.0.0 + checksum: b7d6de9a87d5b55dadfadaeb6e1c991e0a91845d3cdd0bd953391f05363fcbaf21de79a4eec2816ab8c3e31293faeca82cc2bb729d080779df94b14e28ae0d8a + languageName: node + linkType: hard + +"@codemirror/theme-one-dark@npm:^6.0.0": + version: 6.1.2 + resolution: "@codemirror/theme-one-dark@npm:6.1.2" + dependencies: + "@codemirror/language": ^6.0.0 + "@codemirror/state": ^6.0.0 + "@codemirror/view": ^6.0.0 + "@lezer/highlight": ^1.0.0 + checksum: 29bc09f79534115f62658caf3d0db527fe347d058b69a8c7f580ae636827377aadd0606fd0d83dbab8d6f3b0a5df53d3253c619341b5fb93d2c8291a8efb9556 + languageName: node + linkType: hard + +"@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.27.0, @codemirror/view@npm:^6.35.0": + version: 6.36.2 + resolution: "@codemirror/view@npm:6.36.2" + dependencies: + "@codemirror/state": ^6.5.0 + style-mod: ^4.1.0 + w3c-keyname: ^2.2.4 + checksum: a58c64b623ddc65bb864917297f3b37f8e95280deec442024c43a9513b26352c829665c5d98e4dfcae104e8ecdfdb774d94a395a29da98a919c83482d2c14152 + languageName: node + linkType: hard + "@esbuild/aix-ppc64@npm:0.24.2": version: 0.24.2 resolution: "@esbuild/aix-ppc64@npm:0.24.2" @@ -261,6 +388,60 @@ __metadata: languageName: node linkType: hard +"@lezer/common@npm:^1.0.0, @lezer/common@npm:^1.0.2, @lezer/common@npm:^1.1.0, @lezer/common@npm:^1.2.0": + version: 1.2.3 + resolution: "@lezer/common@npm:1.2.3" + checksum: 9b5f52d949adae69d077f56c0b1c2295923108c3dfb241dd9f17654ff708f3eab81ff9fa7f0d0e4a668eabdcb9d961c73e75caca87c966ca1436e30e49130fcb + languageName: node + linkType: hard + +"@lezer/css@npm:^1.1.7": + version: 1.1.9 + resolution: "@lezer/css@npm:1.1.9" + dependencies: + "@lezer/common": ^1.2.0 + "@lezer/highlight": ^1.0.0 + "@lezer/lr": ^1.0.0 + checksum: 25c63475061a3c9f87961a7f85c5f547f14fb7e81b0864675d2206999a874a0559d676145c74c6ccde39519dbc8aa33e216265f5366d08060507b6c9e875fe0f + languageName: node + linkType: hard + +"@lezer/highlight@npm:^1.0.0": + version: 1.2.1 + resolution: "@lezer/highlight@npm:1.2.1" + dependencies: + "@lezer/common": ^1.0.0 + checksum: a8822d7e37f79ff64669eb2df4a9f9d16580e88f2b276a646092e19a9bdccac304e92510e200e35869a8b1f6c27eba5972c508d347a277e9b722d582ab7a23d5 + languageName: node + linkType: hard + +"@lezer/lr@npm:^1.0.0": + version: 1.4.2 + resolution: "@lezer/lr@npm:1.4.2" + dependencies: + "@lezer/common": ^1.0.0 + checksum: 94318ad046c7dfcc8d37e26cb85b99623c39aef60aa51ec2abb30928e7a649f38fa5520f34bd5b356f1db11b6991999589f039e87c8949b0f163be3764f029d8 + languageName: node + linkType: hard + +"@lezer/sass@npm:^1.0.0": + version: 1.0.7 + resolution: "@lezer/sass@npm:1.0.7" + dependencies: + "@lezer/common": ^1.2.0 + "@lezer/highlight": ^1.0.0 + "@lezer/lr": ^1.0.0 + checksum: 32d37aa5d3143033f0b886c33a293b1d28b7eecdf2cb979cf7b4cbba0587be227dfed246c27116060ac9b2b2c0fe4b09df4b672690dd0593e876529583e7ef2a + languageName: node + linkType: hard + +"@marijn/find-cluster-break@npm:^1.0.0": + version: 1.0.2 + resolution: "@marijn/find-cluster-break@npm:1.0.2" + checksum: 0d836de25e04d58325813401ef3c2d34caf040da985a5935fcbc9d84e7b47a21bdb15f57d70c2bf0960bd29ed3dbbb1afd00cdd0fc4fafbee7fd0ffe7d508ae1 + languageName: node + linkType: hard + "@mdx-js/loader@npm:^3.1.0": version: 3.1.0 resolution: "@mdx-js/loader@npm:3.1.0" @@ -904,6 +1085,51 @@ __metadata: languageName: node linkType: hard +"@uiw/codemirror-extensions-basic-setup@npm:4.23.7": + version: 4.23.7 + resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.23.7" + dependencies: + "@codemirror/autocomplete": ^6.0.0 + "@codemirror/commands": ^6.0.0 + "@codemirror/language": ^6.0.0 + "@codemirror/lint": ^6.0.0 + "@codemirror/search": ^6.0.0 + "@codemirror/state": ^6.0.0 + "@codemirror/view": ^6.0.0 + peerDependencies: + "@codemirror/autocomplete": ">=6.0.0" + "@codemirror/commands": ">=6.0.0" + "@codemirror/language": ">=6.0.0" + "@codemirror/lint": ">=6.0.0" + "@codemirror/search": ">=6.0.0" + "@codemirror/state": ">=6.0.0" + "@codemirror/view": ">=6.0.0" + checksum: 656c5f78db4c6216a9a7eeb65455f52b002b7a5426bb1b05f11b896d5dba9887c7b9ee5aee9fe369d6c7b3c69128ee842cb5c8fe029cd07c2e48ec9358039a30 + languageName: node + linkType: hard + +"@uiw/react-codemirror@npm:^4.23.7": + version: 4.23.7 + resolution: "@uiw/react-codemirror@npm:4.23.7" + dependencies: + "@babel/runtime": ^7.18.6 + "@codemirror/commands": ^6.1.0 + "@codemirror/state": ^6.1.1 + "@codemirror/theme-one-dark": ^6.0.0 + "@uiw/codemirror-extensions-basic-setup": 4.23.7 + codemirror: ^6.0.0 + peerDependencies: + "@babel/runtime": ">=7.11.0" + "@codemirror/state": ">=6.0.0" + "@codemirror/theme-one-dark": ">=6.0.0" + "@codemirror/view": ">=6.0.0" + codemirror: ">=6.0.0" + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 5f6318b290f479c4718129578bdccbbdf64e3b588fdd79e3d80224779219eae7e249d40e4f88c864ed6ae40ea93036042313e983a022349563698593a2bf2e6c + languageName: node + linkType: hard + "@ungap/structured-clone@npm:^1.0.0, @ungap/structured-clone@npm:^1.2.0": version: 1.2.1 resolution: "@ungap/structured-clone@npm:1.2.1" @@ -1254,6 +1480,7 @@ __metadata: version: 0.0.0-use.local resolution: "canon-docs@workspace:." dependencies: + "@codemirror/lang-sass": ^6.0.2 "@mdx-js/loader": ^3.1.0 "@mdx-js/react": ^3.1.0 "@next/mdx": ^15.1.4 @@ -1262,6 +1489,7 @@ __metadata: "@types/node": ^20 "@types/react": ^18 "@types/react-dom": ^18 + "@uiw/react-codemirror": ^4.23.7 concurrently: ^9.1.2 eslint: ^8 eslint-config-next: 14.2.23 @@ -1339,6 +1567,21 @@ __metadata: languageName: node linkType: hard +"codemirror@npm:^6.0.0": + version: 6.0.1 + resolution: "codemirror@npm:6.0.1" + dependencies: + "@codemirror/autocomplete": ^6.0.0 + "@codemirror/commands": ^6.0.0 + "@codemirror/language": ^6.0.0 + "@codemirror/lint": ^6.0.0 + "@codemirror/search": ^6.0.0 + "@codemirror/state": ^6.0.0 + "@codemirror/view": ^6.0.0 + checksum: 1a78f7077ac5801bdbff162aa0c61bf2b974603c7e9a477198c3ce50c789af674a061d7c293c58b73807eda345c2b5228c38ad2aabb9319d552d5486f785cbef + languageName: node + linkType: hard + "collapse-white-space@npm:^2.0.0": version: 2.1.0 resolution: "collapse-white-space@npm:2.1.0" @@ -1394,6 +1637,13 @@ __metadata: languageName: node linkType: hard +"crelt@npm:^1.0.5": + version: 1.0.6 + resolution: "crelt@npm:1.0.6" + checksum: dad842093371ad702afbc0531bfca2b0a8dd920b23a42f26e66dabbed9aad9acd5b9030496359545ef3937c3aced0fd4ac39f7a2d280a23ddf9eb7fdcb94a69f + languageName: node + linkType: hard + "cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" @@ -4380,6 +4630,13 @@ __metadata: languageName: node linkType: hard +"regenerator-runtime@npm:^0.14.0": + version: 0.14.1 + resolution: "regenerator-runtime@npm:0.14.1" + checksum: 9f57c93277b5585d3c83b0cf76be47b473ae8c6d9142a46ce8b0291a04bb2cf902059f0f8445dcabb3fb7378e5fe4bb4ea1e008876343d42e46d3b484534ce38 + languageName: node + linkType: hard + "regex-recursion@npm:^5.1.1": version: 5.1.1 resolution: "regex-recursion@npm:5.1.1" @@ -4971,6 +5228,13 @@ __metadata: languageName: node linkType: hard +"style-mod@npm:^4.0.0, style-mod@npm:^4.1.0": + version: 4.1.2 + resolution: "style-mod@npm:4.1.2" + checksum: 7c5c3e82747f9bcf5f288d8d07f50848e4630fe5ff7bfe4d94cc87d6b6a2588227cbf21b4c792ac6406e5852293300a75e710714479a5c59a06af677f0825ef8 + languageName: node + linkType: hard + "style-to-object@npm:^1.0.0": version: 1.0.8 resolution: "style-to-object@npm:1.0.8" @@ -5331,6 +5595,13 @@ __metadata: languageName: node linkType: hard +"w3c-keyname@npm:^2.2.4": + version: 2.2.8 + resolution: "w3c-keyname@npm:2.2.8" + checksum: 95bafa4c04fa2f685a86ca1000069c1ec43ace1f8776c10f226a73296caeddd83f893db885c2c220ebeb6c52d424e3b54d7c0c1e963bbf204038ff1a944fbb07 + languageName: node + linkType: hard + "which-boxed-primitive@npm:^1.1.0, which-boxed-primitive@npm:^1.1.1": version: 1.1.1 resolution: "which-boxed-primitive@npm:1.1.1" From 7bc5aaab834c87f8a25a88446c6bd61670c62e9f Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Sun, 12 Jan 2025 15:45:27 +0000 Subject: [PATCH 30/45] Improve customTheme functionalities Signed-off-by: Charles de Dreuille --- canon-docs/src/app/globals.css | 1 + canon-docs/src/app/layout.tsx | 1 + .../components/CustomTheme/customTheme.tsx | 99 +++++++++++-------- .../components/CustomTheme/styles.module.css | 46 ++++++++- 4 files changed, 102 insertions(+), 45 deletions(-) diff --git a/canon-docs/src/app/globals.css b/canon-docs/src/app/globals.css index e9710c6576..249f05ea8c 100644 --- a/canon-docs/src/app/globals.css +++ b/canon-docs/src/app/globals.css @@ -49,6 +49,7 @@ iframe { display: flex; align-items: center; justify-content: flex-end; + min-width: 34px; } .cm-line { diff --git a/canon-docs/src/app/layout.tsx b/canon-docs/src/app/layout.tsx index 90cd6a9d0d..99b5f22739 100644 --- a/canon-docs/src/app/layout.tsx +++ b/canon-docs/src/app/layout.tsx @@ -13,6 +13,7 @@ import '/public/backstage.css'; export const metadata: Metadata = { title: 'Canon', description: 'UI library for Backstage', + metadataBase: new URL('https://canon.backstage.io'), }; export default function RootLayout({ diff --git a/canon-docs/src/components/CustomTheme/customTheme.tsx b/canon-docs/src/components/CustomTheme/customTheme.tsx index d02f96131b..b29b5458cd 100644 --- a/canon-docs/src/components/CustomTheme/customTheme.tsx +++ b/canon-docs/src/components/CustomTheme/customTheme.tsx @@ -6,28 +6,42 @@ import { sass } from '@codemirror/lang-sass'; import styles from './styles.module.css'; import { usePlayground } from '@/utils/playground-context'; import { AnimatePresence, motion } from 'framer-motion'; +import { Icon } from '../../../../packages/canon'; + +const defaultTheme = `:root { + --canon-accent: #000; +}`; export const CustomTheme = () => { + const [isClient, setIsClient] = useState(false); + const [open, setOpen] = useState(true); const [customTheme, setCustomTheme] = useState(undefined); const { selectedThemeName } = usePlayground(); - const [isClient, setIsClient] = useState(false); + const [savedMessage, setSavedMessage] = useState('Save'); + + const updateStyleElement = (theme: string) => { + let styleElement = document.getElementById( + 'custom-theme-style', + ) as HTMLStyleElement; + + if (!styleElement) { + styleElement = document.createElement('style'); + styleElement.id = 'custom-theme-style'; + document.head.appendChild(styleElement); + } + + styleElement.textContent = theme; + }; useEffect(() => { if (selectedThemeName === 'custom') { - const storedTheme = localStorage.getItem('customThemeCss') || ''; - setCustomTheme(storedTheme); - - let styleElement = document.getElementById( - 'custom-theme-style', - ) as HTMLStyleElement; - - if (!styleElement) { - styleElement = document.createElement('style'); - styleElement.id = 'custom-theme-style'; - document.head.appendChild(styleElement); + let storedTheme = localStorage.getItem('customThemeCss'); + if (!storedTheme) { + storedTheme = defaultTheme; + localStorage.setItem('customThemeCss', storedTheme); } - - styleElement.textContent = storedTheme; + setCustomTheme(storedTheme); + updateStyleElement(storedTheme); } else { const styleElement = document.getElementById( 'custom-theme-style', @@ -45,48 +59,53 @@ export const CustomTheme = () => { const handleSave = () => { if (customTheme) { localStorage.setItem('customThemeCss', customTheme); - - let styleElement = document.getElementById( - 'custom-theme-style', - ) as HTMLStyleElement; - - if (!styleElement) { - styleElement = document.createElement('style'); - styleElement.id = 'custom-theme-style'; - document.head.appendChild(styleElement); - } - - styleElement.textContent = customTheme; + updateStyleElement(customTheme); + setSavedMessage('Saved!'); + setTimeout(() => setSavedMessage('Save'), 1000); } }; - const onChange = useCallback((val: string) => { + const handleChange = useCallback((val: string) => { setCustomTheme(val); }, []); + if (isClient === false) return null; + return ( - {isClient && selectedThemeName === 'custom' && ( + {selectedThemeName === 'custom' && (
Custom Theme
- +
+ {open && ( + + )} + +
+
+
+
-
)}
diff --git a/canon-docs/src/components/CustomTheme/styles.module.css b/canon-docs/src/components/CustomTheme/styles.module.css index 17e6ef18e4..a4f51c8a1d 100644 --- a/canon-docs/src/components/CustomTheme/styles.module.css +++ b/canon-docs/src/components/CustomTheme/styles.module.css @@ -2,21 +2,32 @@ position: fixed; bottom: 16px; right: 16px; - width: 36%; - height: 348px; + width: 240px; + height: 47px; background-color: var(--canon-surface-1); border-radius: 0.375rem; border: 1px solid var(--canon-border-base); display: flex; flex-direction: column; overflow: hidden; - transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out; + transition-property: background-color, border-color, height, width; + transition-duration: 0.2s; + transition-timing-function: ease-in-out; +} + +.open { + width: 36%; + height: 348px; } .editor { flex: 1; } +.editorContainer { + overflow: hidden; +} + .header { height: 46px; flex-shrink: 0; @@ -29,7 +40,16 @@ transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out; } -.button { +.headerLeft { + font-size: 0.875rem; +} + +.headerRight { + display: flex; + gap: 8px; +} + +.buttonSave { all: unset; height: 28px; padding: 0 8px; @@ -37,5 +57,21 @@ background-color: #000; border-radius: 0.25rem; cursor: pointer; - font-size: 0.875rem; + font-size: 0.75rem; +} + +.buttonClose { + all: unset; + height: 28px; + padding: 0 8px; + color: #fff; + background-color: var(--canon-surface-2); + color: var(--canon-text-primary); + transition: background-color 0.2s ease-in-out; + border-radius: 0.25rem; + cursor: pointer; + font-size: 0.875rem; + display: flex; + align-items: center; + justify-content: center; } From 6de1d5fbae34fab1f3c42c1aa9eebe2a7e53458d Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Sun, 12 Jan 2025 22:28:07 +0000 Subject: [PATCH 31/45] Fix some styling Signed-off-by: Charles de Dreuille --- canon-docs/package.json | 2 ++ canon-docs/src/app/globals.css | 9 +++-- .../components/CustomTheme/customTheme.tsx | 34 +++++++++++++++++++ .../src/components/Sidebar/Sidebar.module.css | 14 ++++---- .../components/Toolbar/theme-name.module.css | 2 +- canon-docs/yarn.lock | 19 ++++++++++- 6 files changed, 65 insertions(+), 15 deletions(-) diff --git a/canon-docs/package.json b/canon-docs/package.json index af440bf9d5..9fddf08c20 100644 --- a/canon-docs/package.json +++ b/canon-docs/package.json @@ -10,11 +10,13 @@ }, "dependencies": { "@codemirror/lang-sass": "^6.0.2", + "@lezer/highlight": "^1.2.1", "@mdx-js/loader": "^3.1.0", "@mdx-js/react": "^3.1.0", "@next/mdx": "^15.1.4", "@storybook/react": "^8.4.7", "@types/mdx": "^2.0.13", + "@uiw/codemirror-themes": "^4.23.7", "@uiw/react-codemirror": "^4.23.7", "next": "14.2.23", "react": "^18", diff --git a/canon-docs/src/app/globals.css b/canon-docs/src/app/globals.css index 249f05ea8c..5efc0407e7 100644 --- a/canon-docs/src/app/globals.css +++ b/canon-docs/src/app/globals.css @@ -36,6 +36,10 @@ iframe { text-decoration: var(--shiki-dark-text-decoration) !important; } +.cm-editor { + transition: background-color 0.2s ease-in-out; +} + .ͼ2 .cm-gutters { background-color: var(--canon-surface-2); border-right: 1px solid var(--canon-border-base); @@ -65,8 +69,3 @@ iframe { .cm-focused { outline: none !important; } - -.cm-editor { - background-color: var(--canon-surface-1); - transition: background-color 0.2s ease-in-out; -} diff --git a/canon-docs/src/components/CustomTheme/customTheme.tsx b/canon-docs/src/components/CustomTheme/customTheme.tsx index b29b5458cd..3d8ed394bd 100644 --- a/canon-docs/src/components/CustomTheme/customTheme.tsx +++ b/canon-docs/src/components/CustomTheme/customTheme.tsx @@ -7,11 +7,44 @@ import styles from './styles.module.css'; import { usePlayground } from '@/utils/playground-context'; import { AnimatePresence, motion } from 'framer-motion'; import { Icon } from '../../../../packages/canon'; +import { createTheme } from '@uiw/codemirror-themes'; +import { tags as t } from '@lezer/highlight'; const defaultTheme = `:root { --canon-accent: #000; }`; +const myTheme = createTheme({ + theme: 'light', + settings: { + background: 'var(--canon-surface-1)', + backgroundImage: '', + foreground: '#6182B8', + caret: '#5d00ff', + selection: '#036dd626', + selectionMatch: '#036dd626', + lineHighlight: '#8a91991a', + gutterBackground: '#fff', + gutterForeground: '#8a919966', + }, + styles: [ + { tag: t.comment, color: '#787b8099' }, + { tag: t.variableName, color: '#0080ff' }, + { tag: [t.string, t.special(t.brace)], color: '#6182B8' }, + { tag: t.number, color: '#6182B8' }, + { tag: t.bool, color: '#6182B8' }, + { tag: t.null, color: '#6182B8' }, + { tag: t.keyword, color: '#6182B8' }, + { tag: t.operator, color: '#6182B8' }, + { tag: t.className, color: '#6182B8' }, + { tag: t.definition(t.typeName), color: '#6182B8' }, + { tag: t.typeName, color: '#6182B8' }, + { tag: t.angleBracket, color: '#6182B8' }, + { tag: t.tagName, color: '#6182B8' }, + { tag: t.attributeName, color: '#6182B8' }, + ], +}); + export const CustomTheme = () => { const [isClient, setIsClient] = useState(false); const [open, setOpen] = useState(true); @@ -104,6 +137,7 @@ export const CustomTheme = () => { onChange={handleChange} className={styles.editor} basicSetup={{ foldGutter: false }} + theme={myTheme} /> diff --git a/canon-docs/src/components/Sidebar/Sidebar.module.css b/canon-docs/src/components/Sidebar/Sidebar.module.css index 870f504d28..f3bdd3f2e7 100644 --- a/canon-docs/src/components/Sidebar/Sidebar.module.css +++ b/canon-docs/src/components/Sidebar/Sidebar.module.css @@ -35,15 +35,11 @@ position: relative; } -.menu a { - display: flex; - text-decoration: none; - height: 28px; - align-items: center; -} - .section { width: 100%; + display: flex; + flex-direction: column; + gap: 2px; } .sectionTitle { @@ -54,12 +50,14 @@ } .line { + text-decoration: none; + align-items: center; width: 100%; display: flex; flex-direction: row; justify-content: space-between; align-items: center; - height: 28px; + height: 26px; padding: 0 12px; border-radius: 4px; transition: background-color 0.2s ease-in-out; diff --git a/canon-docs/src/components/Toolbar/theme-name.module.css b/canon-docs/src/components/Toolbar/theme-name.module.css index 6f2ceb427f..c9700c571b 100644 --- a/canon-docs/src/components/Toolbar/theme-name.module.css +++ b/canon-docs/src/components/Toolbar/theme-name.module.css @@ -43,7 +43,7 @@ box-sizing: border-box; padding-block: 0.25rem; border-radius: 0.375rem; - background-color: canvas; + background-color: var(--canon-surface-1); color: var(--color-gray-900); border: 1px solid var(--canon-border-base); padding-inline: 0.25rem; diff --git a/canon-docs/yarn.lock b/canon-docs/yarn.lock index f7a3d103a4..90107a541b 100644 --- a/canon-docs/yarn.lock +++ b/canon-docs/yarn.lock @@ -406,7 +406,7 @@ __metadata: languageName: node linkType: hard -"@lezer/highlight@npm:^1.0.0": +"@lezer/highlight@npm:^1.0.0, @lezer/highlight@npm:^1.2.1": version: 1.2.1 resolution: "@lezer/highlight@npm:1.2.1" dependencies: @@ -1108,6 +1108,21 @@ __metadata: languageName: node linkType: hard +"@uiw/codemirror-themes@npm:^4.23.7": + version: 4.23.7 + resolution: "@uiw/codemirror-themes@npm:4.23.7" + dependencies: + "@codemirror/language": ^6.0.0 + "@codemirror/state": ^6.0.0 + "@codemirror/view": ^6.0.0 + peerDependencies: + "@codemirror/language": ">=6.0.0" + "@codemirror/state": ">=6.0.0" + "@codemirror/view": ">=6.0.0" + checksum: 8c2a9a7ee8df2de3f09ad1f18979c4b407f7dd03e1f6ddfe47837b9e4769b566a70259ec8c398473b6b6db79cdb8809d4acc2f2ed7851743e13ff95308f52e33 + languageName: node + linkType: hard + "@uiw/react-codemirror@npm:^4.23.7": version: 4.23.7 resolution: "@uiw/react-codemirror@npm:4.23.7" @@ -1481,6 +1496,7 @@ __metadata: resolution: "canon-docs@workspace:." dependencies: "@codemirror/lang-sass": ^6.0.2 + "@lezer/highlight": ^1.2.1 "@mdx-js/loader": ^3.1.0 "@mdx-js/react": ^3.1.0 "@next/mdx": ^15.1.4 @@ -1489,6 +1505,7 @@ __metadata: "@types/node": ^20 "@types/react": ^18 "@types/react-dom": ^18 + "@uiw/codemirror-themes": ^4.23.7 "@uiw/react-codemirror": ^4.23.7 concurrently: ^9.1.2 eslint: ^8 From c09845bb4e550e2a66255d216d0a34f4a1e075f1 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 15 Jan 2025 17:38:56 +0100 Subject: [PATCH 32/45] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Signed-off-by: Patrik Oldsberg --- .changeset/beige-dingos-destroy.md | 4 ++-- docs/tooling/cli/02-build-system.md | 2 +- packages/cli/config/nodeTransformHooks.mjs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.changeset/beige-dingos-destroy.md b/.changeset/beige-dingos-destroy.md index fdd8bfe415..325c150119 100644 --- a/.changeset/beige-dingos-destroy.md +++ b/.changeset/beige-dingos-destroy.md @@ -2,7 +2,7 @@ '@backstage/cli': minor --- -**BREAKING**: Add support for native ESM in Node.js code. This changes the behavior of dynamic import expressions in Node.js code. Typically this can be fixed by replacing `import(...)` with `require(...)`, with an `as typeof import(...)` cast if needed for types. This is because dynamic imports will no longer be transformed to `require(...)` calls, but instead be left as this. This in turn allows you to load ESM modules from CommonJS code using `import(...)`. +**BREAKING**: Add support for native ESM in Node.js code. This changes the behavior of dynamic import expressions in Node.js code. Typically this can be fixed by replacing `import(...)` with `require(...)`, with an `as typeof import(...)` cast if needed for types. This is because dynamic imports will no longer be transformed to `require(...)` calls, but instead be left as-is. This in turn allows you to load ESM modules from CommonJS code using `import(...)`. This change adds support for the following in Node.js packages, across type checking, package builds, runtime transforms and Jest tests: @@ -12,7 +12,7 @@ This change adds support for the following in Node.js packages, across type chec There are a few caveats to be aware of: -- To enable support for native ESM in tests, you need to run the tests with the `--experimental-vm-module` flag enabled, typically via `NODE_OPTIONS='--experimental-vm-modules'`. +- To enable support for native ESM in tests, you need to run the tests with the `--experimental-vm-modules` flag enabled, typically via `NODE_OPTIONS='--experimental-vm-modules'`. - Declaring a package as `"type": "module"` in `package.json` is supported, but in tests it will cause all local transitive dependencies to also be treated as ESM, regardless of whether they declare `"type": "module"` or not. - Node.js has an [ESM interoperability layer with CommonJS](https://nodejs.org/docs/latest-v22.x/api/esm.html#interoperability-with-commonjs) that allows for imports from ESM to identify named exports in CommonJS packages. This interoperability layer is **only** enabled when importing packages with a `.cts` or `.cjs` extension. This is because the interoperability layer is not fully compatible with the NPM ecosystem, and would break package if it was enabled for `.js` files. - Dynamic imports of CommonJS packages will vary in shape depending on the runtime, i.e. test vs local development, etc. It is therefore recommended to avoid dynamic imports of CommonJS packages and instead use `require`, or to use the explicit CommonJS extensions as mentioned above. If you do need to dynamically import CommonJS packages, avoid using `default` exports, as the shape of them vary across different environments and you would otherwise need to manually unwrap the import based on the shape of the module object. diff --git a/docs/tooling/cli/02-build-system.md b/docs/tooling/cli/02-build-system.md index 5e82da4f9b..8f9b086c33 100644 --- a/docs/tooling/cli/02-build-system.md +++ b/docs/tooling/cli/02-build-system.md @@ -513,7 +513,7 @@ of all supported file extensions: The Backstage tooling supports [ECMAScript modules (ESM)](https://nodejs.org/docs/latest-v22.x/api/esm.html) in Node.js packages. This includes support for all the script module file extensions listed above during local development, in built packages, in tests, and during type checking. [Dynamic imports](https://nodejs.org/docs/latest-v22.x/api/esm.html#import-expressions) can be used to load ESM-only packages from CommonJS and vice versa. There are however a couple of limitations to be aware of: -- To enable support for native ESM in tests, you need to run the tests with the `--experimental-vm-module` flag enabled, typically via `NODE_OPTIONS='--experimental-vm-modules'`. +- To enable support for native ESM in tests, you need to run the tests with the `--experimental-vm-modules` flag enabled, typically via `NODE_OPTIONS='--experimental-vm-modules'`. - Declaring a package as `"type": "module"` in `package.json` is supported, but in tests it will cause all local transitive dependencies to also be treated as ESM, regardless of whether they declare `"type": "module"` or not. - Node.js has an [ESM interoperability layer with CommonJS](https://nodejs.org/docs/latest-v22.x/api/esm.html#interoperability-with-commonjs) that allows for imports from ESM to identify named exports in CommonJS packages. This interoperability layer is **only** enabled when importing packages with a `.cts` or `.cjs` extension. This is because the interoperability layer is not fully compatible with the NPM ecosystem, and would break package if it was enabled for `.js` files. - Dynamic imports of CommonJS packages will vary in shape depending on the runtime, i.e. test vs local development, etc. It is therefore recommended to avoid dynamic imports of CommonJS packages and instead use `require`, or to use the explicit CommonJS extensions as mentioned above. If you do need to dynamically import CommonJS packages, avoid using `default` exports, as the shape of them vary across different environments and you would otherwise need to manually unwrap the import based on the shape of the module object. diff --git a/packages/cli/config/nodeTransformHooks.mjs b/packages/cli/config/nodeTransformHooks.mjs index 7d1186561f..5d37ac05c0 100644 --- a/packages/cli/config/nodeTransformHooks.mjs +++ b/packages/cli/config/nodeTransformHooks.mjs @@ -146,7 +146,7 @@ async function withDetectedModuleType(resolved) { } /** - * Find the closes package.json file from the given path. + * Find the closest package.json file from the given path. * * TODO(Rugvip): This can be replaced with the Node.js built-in with the same name once it is stable. * @param {string} startPath From 1e0be5e1830f603a1188deece76816ae8158df15 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Wed, 15 Jan 2025 17:27:19 +0000 Subject: [PATCH 33/45] First pass at bundling CSS Signed-off-by: Charles de Dreuille --- packages/canon/package.json | 4 +- packages/canon/scripts/build-css.mjs | 56 ++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 packages/canon/scripts/build-css.mjs diff --git a/packages/canon/package.json b/packages/canon/package.json index d08f660e7d..0929111546 100644 --- a/packages/canon/package.json +++ b/packages/canon/package.json @@ -27,8 +27,10 @@ "dist" ], "scripts": { - "build": "backstage-cli package build", + "build": "yarn build:app && yarn build:css", "build-storybook": "storybook build", + "build:app": "backstage-cli package build", + "build:css": "node scripts/build-css.mjs", "clean": "backstage-cli package clean", "lint": "backstage-cli package lint", "prepack": "backstage-cli package prepack", diff --git a/packages/canon/scripts/build-css.mjs b/packages/canon/scripts/build-css.mjs new file mode 100644 index 0000000000..221d483e03 --- /dev/null +++ b/packages/canon/scripts/build-css.mjs @@ -0,0 +1,56 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* eslint-disable no-restricted-imports */ +import { transform, bundle } from 'lightningcss'; +import fs from 'fs'; +import path from 'path'; +/* eslint-enable no-restricted-imports */ + +// Check if core.css and components.css exist +const cssFiles = ['src/css/core.css', 'src/css/components.css']; +const distDir = 'dist/css'; + +cssFiles.forEach(file => { + if (!fs.existsSync(file)) { + console.error(`${file} does not exist`); + process.exit(1); + } +}); + +// Ensure the dist/css directory exists +if (!fs.existsSync(distDir)) { + fs.mkdirSync(distDir, { recursive: true }); +} + +cssFiles.forEach(file => { + let { code: bundleCode } = bundle({ + filename: file, + }); + + let { code, map } = transform({ + filename: `${distDir}/${path.basename(file)}`, + code: bundleCode, + minify: true, + sourceMap: true, + }); + + fs.writeFileSync(`${distDir}/${path.basename(file)}`, code); + fs.writeFileSync(`${distDir}/${path.basename(file)}.map`, map); + + // Clear the content of the original CSS file + fs.writeFileSync(file, ''); +}); From daadfe8035b015f0d7596d54604ff4a335206f15 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Wed, 15 Jan 2025 18:13:43 +0000 Subject: [PATCH 34/45] Improve the script to include all css files Signed-off-by: Charles de Dreuille --- packages/canon/package.json | 3 + packages/canon/scripts/build-css.mjs | 41 +++++-- yarn.lock | 175 ++++++++++++++++++++++++++- 3 files changed, 209 insertions(+), 10 deletions(-) diff --git a/packages/canon/package.json b/packages/canon/package.json index 0929111546..0dfea024cc 100644 --- a/packages/canon/package.json +++ b/packages/canon/package.json @@ -58,8 +58,11 @@ "@testing-library/jest-dom": "^6.0.0", "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", + "chalk": "^5.4.1", "eslint-plugin-storybook": "^0.11.1", + "glob": "^11.0.1", "globals": "^15.11.0", + "lightningcss": "^1.29.1", "mini-css-extract-plugin": "^2.9.2", "react": "^18.0.2", "react-dom": "^18.0.2", diff --git a/packages/canon/scripts/build-css.mjs b/packages/canon/scripts/build-css.mjs index 221d483e03..f0ca17c73e 100644 --- a/packages/canon/scripts/build-css.mjs +++ b/packages/canon/scripts/build-css.mjs @@ -18,15 +18,36 @@ import { transform, bundle } from 'lightningcss'; import fs from 'fs'; import path from 'path'; +import chalk from 'chalk'; +import { glob } from 'glob'; /* eslint-enable no-restricted-imports */ // Check if core.css and components.css exist -const cssFiles = ['src/css/core.css', 'src/css/components.css']; +const cssDir = 'src/css'; const distDir = 'dist/css'; +const componentsDir = 'src/components'; +// Core files +const cssFiles = [ + { path: `${cssDir}/core.css`, newName: 'core.css' }, + { path: `${cssDir}/components.css`, newName: 'components.css' }, +]; + +// Components files +const componentsFiles = glob + .sync('**/*.css', { cwd: componentsDir }) + .map(file => { + const folderName = file.split('/')[0].toLocaleLowerCase('en-US'); + return { path: `${componentsDir}/${file}`, newName: `${folderName}.css` }; + }); + +// Combine core and components files +cssFiles.push(...componentsFiles); + +// Check if files exist cssFiles.forEach(file => { - if (!fs.existsSync(file)) { - console.error(`${file} does not exist`); + if (!fs.existsSync(file.path)) { + console.error(`${file.originalName} does not exist`); process.exit(1); } }); @@ -36,21 +57,23 @@ if (!fs.existsSync(distDir)) { fs.mkdirSync(distDir, { recursive: true }); } +// Bundle and transform files cssFiles.forEach(file => { let { code: bundleCode } = bundle({ - filename: file, + filename: file.path, }); let { code, map } = transform({ - filename: `${distDir}/${path.basename(file)}`, + filename: `${distDir}/${file.newName}`, code: bundleCode, minify: true, sourceMap: true, }); - fs.writeFileSync(`${distDir}/${path.basename(file)}`, code); - fs.writeFileSync(`${distDir}/${path.basename(file)}.map`, map); + fs.writeFileSync(`${distDir}/${file.newName}`, code); + fs.writeFileSync(`${distDir}/${file.newName}.map`, map); - // Clear the content of the original CSS file - fs.writeFileSync(file, ''); + console.log(chalk.blue('CSS bundled: ') + file.newName); }); + +console.log(chalk.green('CSS files bundled successfully!')); diff --git a/yarn.lock b/yarn.lock index af6c15c0fc..d1dd4667e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3802,9 +3802,12 @@ __metadata: "@testing-library/jest-dom": ^6.0.0 "@types/react": ^18.0.0 "@types/react-dom": ^18.0.0 + chalk: ^5.4.1 clsx: ^2.1.1 eslint-plugin-storybook: ^0.11.1 + glob: ^11.0.1 globals: ^15.11.0 + lightningcss: ^1.29.1 mini-css-extract-plugin: ^2.9.2 react: ^18.0.2 react-dom: ^18.0.2 @@ -24242,7 +24245,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:~5.4.1": +"chalk@npm:^5.4.1, chalk@npm:~5.4.1": version: 5.4.1 resolution: "chalk@npm:5.4.1" checksum: 0c656f30b782fed4d99198825c0860158901f449a6b12b818b0aabad27ec970389e7e8767d0e00762175b23620c812e70c4fd92c0210e55fc2d993638b74e86e @@ -26554,6 +26557,15 @@ __metadata: languageName: node linkType: hard +"detect-libc@npm:^1.0.3": + version: 1.0.3 + resolution: "detect-libc@npm:1.0.3" + bin: + detect-libc: ./bin/detect-libc.js + checksum: daaaed925ffa7889bd91d56e9624e6c8033911bb60f3a50a74a87500680652969dbaab9526d1e200a4c94acf80fc862a22131841145a0a8482d60a99c24f4a3e + languageName: node + linkType: hard + "detect-libc@npm:^2.0.0": version: 2.0.1 resolution: "detect-libc@npm:2.0.1" @@ -30110,6 +30122,22 @@ __metadata: languageName: node linkType: hard +"glob@npm:^11.0.1": + version: 11.0.1 + resolution: "glob@npm:11.0.1" + dependencies: + foreground-child: ^3.1.0 + jackspeak: ^4.0.1 + minimatch: ^10.0.0 + minipass: ^7.1.2 + package-json-from-dist: ^1.0.0 + path-scurry: ^2.0.0 + bin: + glob: dist/esm/bin.mjs + checksum: ffbbafe1d2dae2fa68f190ac76df7254e840b27f59df34129fd658bd9da0c50b538d144eb0962dc7fa71cdaccf3fe108f045d4a15b3f5815e465749a6bf00965 + languageName: node + linkType: hard + "glob@npm:^7.0.0, glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6, glob@npm:^7.1.7": version: 7.2.3 resolution: "glob@npm:7.2.3" @@ -32656,6 +32684,15 @@ __metadata: languageName: node linkType: hard +"jackspeak@npm:^4.0.1": + version: 4.0.2 + resolution: "jackspeak@npm:4.0.2" + dependencies: + "@isaacs/cliui": ^8.0.2 + checksum: 210030029edfa1658328799ad88c3d0fc057c4cb8a069fc4137cc8d2cc4b65c9721c6e749e890f9ca77a954bb54f200f715b8896e50d330e5f3e902e72b40974 + languageName: node + linkType: hard + "jake@npm:^10.8.5": version: 10.8.5 resolution: "jake@npm:10.8.5" @@ -34305,6 +34342,116 @@ __metadata: languageName: node linkType: hard +"lightningcss-darwin-arm64@npm:1.29.1": + version: 1.29.1 + resolution: "lightningcss-darwin-arm64@npm:1.29.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-darwin-x64@npm:1.29.1": + version: 1.29.1 + resolution: "lightningcss-darwin-x64@npm:1.29.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"lightningcss-freebsd-x64@npm:1.29.1": + version: 1.29.1 + resolution: "lightningcss-freebsd-x64@npm:1.29.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"lightningcss-linux-arm-gnueabihf@npm:1.29.1": + version: 1.29.1 + resolution: "lightningcss-linux-arm-gnueabihf@npm:1.29.1" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"lightningcss-linux-arm64-gnu@npm:1.29.1": + version: 1.29.1 + resolution: "lightningcss-linux-arm64-gnu@npm:1.29.1" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-arm64-musl@npm:1.29.1": + version: 1.29.1 + resolution: "lightningcss-linux-arm64-musl@npm:1.29.1" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-linux-x64-gnu@npm:1.29.1": + version: 1.29.1 + resolution: "lightningcss-linux-x64-gnu@npm:1.29.1" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-x64-musl@npm:1.29.1": + version: 1.29.1 + resolution: "lightningcss-linux-x64-musl@npm:1.29.1" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-win32-arm64-msvc@npm:1.29.1": + version: 1.29.1 + resolution: "lightningcss-win32-arm64-msvc@npm:1.29.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-win32-x64-msvc@npm:1.29.1": + version: 1.29.1 + resolution: "lightningcss-win32-x64-msvc@npm:1.29.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"lightningcss@npm:^1.29.1": + version: 1.29.1 + resolution: "lightningcss@npm:1.29.1" + dependencies: + detect-libc: ^1.0.3 + lightningcss-darwin-arm64: 1.29.1 + lightningcss-darwin-x64: 1.29.1 + lightningcss-freebsd-x64: 1.29.1 + lightningcss-linux-arm-gnueabihf: 1.29.1 + lightningcss-linux-arm64-gnu: 1.29.1 + lightningcss-linux-arm64-musl: 1.29.1 + lightningcss-linux-x64-gnu: 1.29.1 + lightningcss-linux-x64-musl: 1.29.1 + lightningcss-win32-arm64-msvc: 1.29.1 + lightningcss-win32-x64-msvc: 1.29.1 + dependenciesMeta: + lightningcss-darwin-arm64: + optional: true + lightningcss-darwin-x64: + optional: true + lightningcss-freebsd-x64: + optional: true + lightningcss-linux-arm-gnueabihf: + optional: true + lightningcss-linux-arm64-gnu: + optional: true + lightningcss-linux-arm64-musl: + optional: true + lightningcss-linux-x64-gnu: + optional: true + lightningcss-linux-x64-musl: + optional: true + lightningcss-win32-arm64-msvc: + optional: true + lightningcss-win32-x64-msvc: + optional: true + checksum: d1c4dba66dfe7f6a76532bdb84c35742bee61149550e5eb5b0e84e282f21aecd335f917ca9619bb7ca95fc1eb3092dc7e22f2c16b01e9a0ee472b76452343cce + languageName: node + linkType: hard + "lilconfig@npm:^2.0.3": version: 2.1.0 resolution: "lilconfig@npm:2.1.0" @@ -34873,6 +35020,13 @@ __metadata: languageName: node linkType: hard +"lru-cache@npm:^11.0.0": + version: 11.0.2 + resolution: "lru-cache@npm:11.0.2" + checksum: f9c27c58919a30f42834de9444de9f75bcbbb802c459239f96dd449ad880d8f9a42f51556d13659864dc94ab2dbded9c4a4f42a3e25a45b6da01bb86111224df + languageName: node + linkType: hard + "lru-cache@npm:^5.1.1": version: 5.1.1 resolution: "lru-cache@npm:5.1.1" @@ -36057,6 +36211,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:^10.0.0": + version: 10.0.1 + resolution: "minimatch@npm:10.0.1" + dependencies: + brace-expansion: ^2.0.1 + checksum: f5b63c2f30606091a057c5f679b067f84a2cd0ffbd2dbc9143bda850afd353c7be81949ff11ae0c86988f07390eeca64efd7143ee05a0dab37f6c6b38a2ebb6c + languageName: node + linkType: hard + "minimatch@npm:^5.0.1, minimatch@npm:^5.1.0": version: 5.1.6 resolution: "minimatch@npm:5.1.6" @@ -38522,6 +38685,16 @@ __metadata: languageName: node linkType: hard +"path-scurry@npm:^2.0.0": + version: 2.0.0 + resolution: "path-scurry@npm:2.0.0" + dependencies: + lru-cache: ^11.0.0 + minipass: ^7.1.2 + checksum: 9953ce3857f7e0796b187a7066eede63864b7e1dfc14bf0484249801a5ab9afb90d9a58fc533ebb1b552d23767df8aa6a2c6c62caf3f8a65f6ce336a97bbb484 + languageName: node + linkType: hard + "path-to-regexp@npm:0.1.12": version: 0.1.12 resolution: "path-to-regexp@npm:0.1.12" From 63a5f0a410b084475187d6567ccb34a32b060227 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Wed, 15 Jan 2025 18:32:53 +0000 Subject: [PATCH 35/45] Removing docs on Storybook Signed-off-by: Charles de Dreuille --- packages/canon/.storybook/main.ts | 6 +- packages/canon/.storybook/preview.tsx | 3 - packages/canon/docs/Home.mdx | 91 ----- packages/canon/docs/Iconography.mdx | 22 -- packages/canon/docs/Layout.mdx | 54 --- packages/canon/docs/Responsive.mdx | 150 ------- packages/canon/docs/Theme.mdx | 231 ----------- packages/canon/docs/Typography.mdx | 48 --- .../canon/docs/components/Banner/Banner.tsx | 41 -- .../canon/docs/components/Banner/index.ts | 16 - .../canon/docs/components/Banner/styles.css | 37 -- packages/canon/docs/components/Chip/Chip.tsx | 27 -- packages/canon/docs/components/Chip/index.ts | 16 - .../canon/docs/components/Chip/styles.css | 33 -- .../canon/docs/components/Columns/Columns.tsx | 31 -- .../canon/docs/components/Columns/index.ts | 16 - .../canon/docs/components/Columns/styles.css | 22 -- .../ComponentStatus/ComponentStatus.tsx | 48 --- .../docs/components/ComponentStatus/index.ts | 16 - .../components/ComponentStatus/styles.css | 71 ---- .../components/IconLibrary/IconLibrary.tsx | 37 -- .../docs/components/IconLibrary/index.ts | 16 - .../docs/components/IconLibrary/styles.css | 22 -- .../LayoutComponents/LayoutComponents.tsx | 64 --- .../docs/components/LayoutComponents/index.ts | 16 - .../components/LayoutComponents/styles.css | 58 --- .../components/LayoutComponents/svgs/box.tsx | 54 --- .../LayoutComponents/svgs/container.tsx | 373 ------------------ .../components/LayoutComponents/svgs/grid.tsx | 91 ----- .../LayoutComponents/svgs/inline.tsx | 92 ----- .../LayoutComponents/svgs/stack.tsx | 82 ---- .../docs/components/PropsTable/PropsTable.tsx | 57 --- .../docs/components/PropsTable/getProps.ts | 43 -- .../canon/docs/components/PropsTable/index.ts | 17 - .../canon/docs/components/Roadmap/Roadmap.tsx | 53 --- .../canon/docs/components/Roadmap/index.ts | 16 - .../canon/docs/components/Roadmap/list.ts | 62 --- .../canon/docs/components/Roadmap/styles.css | 100 ----- .../canon/docs/components/Table/Table.tsx | 49 --- packages/canon/docs/components/Table/index.ts | 16 - .../canon/docs/components/Table/styles.css | 72 ---- packages/canon/docs/components/Text/Text.tsx | 31 -- packages/canon/docs/components/Text/index.ts | 16 - .../canon/docs/components/Text/styles.css | 43 -- .../canon/docs/components/Title/Title.tsx | 38 -- packages/canon/docs/components/Title/index.ts | 16 - .../canon/docs/components/Title/styles.css | 42 -- packages/canon/docs/components/index.ts | 27 -- packages/canon/docs/components/styles.css | 15 - packages/canon/docs/spaceProps.ts | 41 -- packages/canon/src/components/Box/Docs.mdx | 112 ------ packages/canon/src/components/Button/Docs.mdx | 135 ------- .../canon/src/components/Checkbox/Docs.mdx | 70 ---- .../canon/src/components/Container/Docs.mdx | 99 ----- packages/canon/src/components/Grid/Docs.mdx | 199 ---------- .../canon/src/components/Heading/Docs.mdx | 83 ---- packages/canon/src/components/Icon/Docs.mdx | 48 --- packages/canon/src/components/Inline/Docs.mdx | 124 ------ packages/canon/src/components/Stack/Docs.mdx | 126 ------ packages/canon/src/components/Table/Docs.mdx | 5 - packages/canon/src/components/Text/Docs.mdx | 101 ----- 61 files changed, 1 insertion(+), 3739 deletions(-) delete mode 100644 packages/canon/docs/Home.mdx delete mode 100644 packages/canon/docs/Iconography.mdx delete mode 100644 packages/canon/docs/Layout.mdx delete mode 100644 packages/canon/docs/Responsive.mdx delete mode 100644 packages/canon/docs/Theme.mdx delete mode 100644 packages/canon/docs/Typography.mdx delete mode 100644 packages/canon/docs/components/Banner/Banner.tsx delete mode 100644 packages/canon/docs/components/Banner/index.ts delete mode 100644 packages/canon/docs/components/Banner/styles.css delete mode 100644 packages/canon/docs/components/Chip/Chip.tsx delete mode 100644 packages/canon/docs/components/Chip/index.ts delete mode 100644 packages/canon/docs/components/Chip/styles.css delete mode 100644 packages/canon/docs/components/Columns/Columns.tsx delete mode 100644 packages/canon/docs/components/Columns/index.ts delete mode 100644 packages/canon/docs/components/Columns/styles.css delete mode 100644 packages/canon/docs/components/ComponentStatus/ComponentStatus.tsx delete mode 100644 packages/canon/docs/components/ComponentStatus/index.ts delete mode 100644 packages/canon/docs/components/ComponentStatus/styles.css delete mode 100644 packages/canon/docs/components/IconLibrary/IconLibrary.tsx delete mode 100644 packages/canon/docs/components/IconLibrary/index.ts delete mode 100644 packages/canon/docs/components/IconLibrary/styles.css delete mode 100644 packages/canon/docs/components/LayoutComponents/LayoutComponents.tsx delete mode 100644 packages/canon/docs/components/LayoutComponents/index.ts delete mode 100644 packages/canon/docs/components/LayoutComponents/styles.css delete mode 100644 packages/canon/docs/components/LayoutComponents/svgs/box.tsx delete mode 100644 packages/canon/docs/components/LayoutComponents/svgs/container.tsx delete mode 100644 packages/canon/docs/components/LayoutComponents/svgs/grid.tsx delete mode 100644 packages/canon/docs/components/LayoutComponents/svgs/inline.tsx delete mode 100644 packages/canon/docs/components/LayoutComponents/svgs/stack.tsx delete mode 100644 packages/canon/docs/components/PropsTable/PropsTable.tsx delete mode 100644 packages/canon/docs/components/PropsTable/getProps.ts delete mode 100644 packages/canon/docs/components/PropsTable/index.ts delete mode 100644 packages/canon/docs/components/Roadmap/Roadmap.tsx delete mode 100644 packages/canon/docs/components/Roadmap/index.ts delete mode 100644 packages/canon/docs/components/Roadmap/list.ts delete mode 100644 packages/canon/docs/components/Roadmap/styles.css delete mode 100644 packages/canon/docs/components/Table/Table.tsx delete mode 100644 packages/canon/docs/components/Table/index.ts delete mode 100644 packages/canon/docs/components/Table/styles.css delete mode 100644 packages/canon/docs/components/Text/Text.tsx delete mode 100644 packages/canon/docs/components/Text/index.ts delete mode 100644 packages/canon/docs/components/Text/styles.css delete mode 100644 packages/canon/docs/components/Title/Title.tsx delete mode 100644 packages/canon/docs/components/Title/index.ts delete mode 100644 packages/canon/docs/components/Title/styles.css delete mode 100644 packages/canon/docs/components/index.ts delete mode 100644 packages/canon/docs/components/styles.css delete mode 100644 packages/canon/docs/spaceProps.ts delete mode 100644 packages/canon/src/components/Box/Docs.mdx delete mode 100644 packages/canon/src/components/Button/Docs.mdx delete mode 100644 packages/canon/src/components/Checkbox/Docs.mdx delete mode 100644 packages/canon/src/components/Container/Docs.mdx delete mode 100644 packages/canon/src/components/Grid/Docs.mdx delete mode 100644 packages/canon/src/components/Heading/Docs.mdx delete mode 100644 packages/canon/src/components/Icon/Docs.mdx delete mode 100644 packages/canon/src/components/Inline/Docs.mdx delete mode 100644 packages/canon/src/components/Stack/Docs.mdx delete mode 100644 packages/canon/src/components/Table/Docs.mdx delete mode 100644 packages/canon/src/components/Text/Docs.mdx diff --git a/packages/canon/.storybook/main.ts b/packages/canon/.storybook/main.ts index cf7921268e..8bf24a7e23 100644 --- a/packages/canon/.storybook/main.ts +++ b/packages/canon/.storybook/main.ts @@ -9,11 +9,7 @@ function getAbsolutePath(value: string): any { return dirname(require.resolve(join(value, 'package.json'))); } const config: StorybookConfig = { - stories: [ - '../docs/**/*.mdx', - '../src/components/**/*.mdx', - '../src/components/**/*.stories.@(js|jsx|mjs|ts|tsx)', - ], + stories: ['../src/components/**/*.stories.@(js|jsx|mjs|ts|tsx)'], staticDirs: ['../static'], addons: [ getAbsolutePath('@storybook/addon-webpack5-compiler-swc'), diff --git a/packages/canon/.storybook/preview.tsx b/packages/canon/.storybook/preview.tsx index 1ac53acd86..867461818b 100644 --- a/packages/canon/.storybook/preview.tsx +++ b/packages/canon/.storybook/preview.tsx @@ -2,9 +2,6 @@ import React from 'react'; import type { Preview, ReactRenderer } from '@storybook/react'; import { withThemeByDataAttribute } from '@storybook/addon-themes'; -// Storybook specific styles -import '../docs/components/styles.css'; - // Canon specific styles import '../src/css/core.css'; import '../src/css/components.css'; diff --git a/packages/canon/docs/Home.mdx b/packages/canon/docs/Home.mdx deleted file mode 100644 index 5b85ab15ad..0000000000 --- a/packages/canon/docs/Home.mdx +++ /dev/null @@ -1,91 +0,0 @@ -import { Unstyled } from '@storybook/blocks'; -import { - Columns, - Text, - ComponentStatus, - Banner, - Title, - Roadmap, -} from './components'; -import { list } from './components/Roadmap/list'; - - - - - - - Welcome to the Canon, the new design library for Backstage plugins. This - project is still under active development but we will make sure to document - the API as we go. We are aiming to improve the general UI of Backstage and - plugins across Backstage. This new library will take time to build but we are - building it incrementally with not conflict with the existing theming system. - - - - This library is still under heavy construction. Please be aware that the API - will change until we reach a stable release. - - - - Component Status - - - We are still in the process of documenting the API and building the - components. You can use the statuses below to see what is ready and what is - coming soon. If there is a component missing that you need, please let us know - by opening an issue on GitHub. - - - - - - - - - - - - - - - - - - - - - - Roadmap - - - - - diff --git a/packages/canon/docs/Iconography.mdx b/packages/canon/docs/Iconography.mdx deleted file mode 100644 index 83d181af12..0000000000 --- a/packages/canon/docs/Iconography.mdx +++ /dev/null @@ -1,22 +0,0 @@ -import { Unstyled, Source, Meta } from '@storybook/blocks'; -import { Title, Text, IconLibrary } from './components'; - - - - - -Iconography - - - All our default icons are provided by [Remix Icon](https://remixicon.com/). We - don't import all icons to reduce the bundle size but we cherry pick a nice - selection for you to use in your application. The list of names is set down - below. To use an icon, you can use the `Icon` component and pass the name of - the icon you want to use. - - -`} language="tsx" dark /> - - - - diff --git a/packages/canon/docs/Layout.mdx b/packages/canon/docs/Layout.mdx deleted file mode 100644 index 9145f71aed..0000000000 --- a/packages/canon/docs/Layout.mdx +++ /dev/null @@ -1,54 +0,0 @@ -import { Unstyled, Source, Meta } from '@storybook/blocks'; -import { Title, Text, LayoutComponents } from './components'; -import * as Table from './components/Table'; - - - - - -Layout - - Canon is made for extensibility. We built this library to make it easy for any - Backstage plugin creator to be able to build their ideas at speed ensuring - consistency across the rest of your ecosystem. Each component is designed to - be editable to match your need but sometimes you want to have more control - over the layout of your page. To help you with that, we created a set of - layout components that you can use to build your own layouts. All of these - components are built to extend on our theming system, making it easy for you - to build your own layouts. Sometimes these components are not enough so we - created a set of helpers to be used with any CSS-in-JS library. - - -Layout Components - - - We built a couple of layout components to help you build responsive elements - that will be consistent with the rest of your Backstage instance. These - components are opinionated and use TypeScript to ensure that the props you - provide are the ones coming from the theme. - - - - Hello World - - Project 1 - Project 2 - -`} - language="tsx" - dark -/> - - - -Layout Helpers - - - Sometimes you want to use global tokens dynamically outside of React - components. To help you with that we would like to provide a set of helpers - that you can use in your code. These helpers are not available just yet but we - are working on it. - - - diff --git a/packages/canon/docs/Responsive.mdx b/packages/canon/docs/Responsive.mdx deleted file mode 100644 index 85292ee555..0000000000 --- a/packages/canon/docs/Responsive.mdx +++ /dev/null @@ -1,150 +0,0 @@ -import { Unstyled, Meta, Canvas, Source } from '@storybook/blocks'; -import { - Columns, - Text, - ComponentStatus, - Banner, - Title, - Roadmap, -} from './components'; -import { list } from './components/Roadmap/list'; -import * as HeadingStories from '../src/components/Heading/Heading.stories'; -import * as TextStories from '../src/components/Text/Text.stories'; -import * as Table from './components/Table'; -import { Chip } from './components/Chip'; - - - - - -Responsive - - Canon is built on a responsive design system, meaning that the components are - designed to adapt to different screen sizes. By default we offer a set of - breakpoints that you can use to create responsive components. - - - - Breakpoints - - - - - - Breakpoint prefix - Minimum width - CSS - - - - - - xs - - - 0px - - - {`{ ... }`} - - - - - sm - - - 640px - - - {`@media (min-width: 640px) { ... }`} - - - - - md - - - 768px - - - {`@media (min-width: 768px) { ... }`} - - - - - lg - - - 1024px - - - {`@media (min-width: 1024px) { ... }`} - - - - - xl - - - 1280px - - - {`@media (min-width: 1280px) { ... }`} - - - - - 2xl - - - 1536px - - - {`@media (min-width: 1536px) { ... }`} - - - - - -Responsive components - - - Canon components are designed to be responsive, meaning that they will adapt - to different screen sizes. Not every component is responsive, but the ones - that are will have a prop to control the responsive behavior. - - - - The behaviour is the same for each component. For each prop, instead of adding - the value, you add an object with the value and the breakpoint prefix. - - -Button - -// Responsive value - -`} dark /> - -How to update breakpoints - - - The set of keys are not to be changed, but you can update the minimum width of - each breakpoint in the theme provider. - - -`} - dark -/> - - diff --git a/packages/canon/docs/Theme.mdx b/packages/canon/docs/Theme.mdx deleted file mode 100644 index 1ea7c5c4c6..0000000000 --- a/packages/canon/docs/Theme.mdx +++ /dev/null @@ -1,231 +0,0 @@ -import { Unstyled, Source, Meta } from '@storybook/blocks'; -import { Title, Text, Chip } from './components'; -import * as Table from './components/Table'; - -{' '} - - - -Theming - - Backstage ships with a default theme with a light and dark mode variant. The - themes are provided as a part of the `@backstage/canon` package, which also - includes utilities for customizing the default theme, or creating completely - new themes. - - -Light & Dark modes - - By default we are supporting both light and dark modes. Each user can opt to - choose what theme they want to use or to use their system decide what theme to - use. If you want to create your own theme, you will have to set both light and - dark themes following the instructions below. If you only set one of them, the - other mode will fallback to the default theme. - - -How to create your own theme - - To create your own theme, you will have to define the variables below. To do - that, create a theme.css file and import it in your application. Here's an - example below on how to set your light and dark mode. - - - -Colors - - We provide a set of generic colours tokens that we use across Canon. By - changing these colours you can easily change the look and feel of your - application to match your brand. - - - - - Prop - Description - - - - - - --canon-accent - - The accent color for the theme. - - - - --canon-bg - - The background color for the theme. - - - - --canon-surface-1 - - The first surface color for the theme. - - - - --canon-surface-2 - - The second surface color for the theme. - - - - --canon-outline - - The outline color for the theme. - - - - --canon-outline-focus - - The outline focus color for the theme. - - - - --canon-text-primary - - The primary text color for the theme. - - - - --canon-text-secondary - - The secondary text color for the theme. - - - - -Typography - - We have two fonts that we use across Canon. The first one is the sans-serif - font that we use for the body of the application. The second one is the - monospace font that we use for code blocks and tables. - - - - - - Prop - Description - - - - - - --canon-font-regular - - The sans-serif font for the theme. - - - - --canon-font-mono - - The monospace font for the theme. - - - - -Spacing - - Our default spacing system is made to work in most scenarios. We have 7 scale - values from `xxs` to `xxl`. We use the values on padding and margin in our - layout components mostly. If you prefer to use a different spacing system, you - can do that by changing the values below. - - - - - - Prop - Description - - - - - - --canon-space-unit - - - The base unit for the spacing system. Default value is `1em` - - - - - --canon-space-xxs - - Default value is `0.25 x space unit` - - - - --canon-space-xs - - Default value is `0.5 x space unit` - - - - --canon-space-sm - - Default value is `0.75 x space unit` - - - - --canon-space-md - - Default value is `1.25 x space unit` - - - - --canon-space-lg - - Default value is `2 x space unit` - - - - --canon-space-xl - - Default value is `3.25 x space unit` - - - - --canon-space-xxl - - Default value is `5.25 x space unit` - - - - - diff --git a/packages/canon/docs/Typography.mdx b/packages/canon/docs/Typography.mdx deleted file mode 100644 index 802b9881be..0000000000 --- a/packages/canon/docs/Typography.mdx +++ /dev/null @@ -1,48 +0,0 @@ -import { Unstyled, Meta, Canvas } from '@storybook/blocks'; -import { - Columns, - Text, - ComponentStatus, - Banner, - Title, - Roadmap, -} from './components'; -import { list } from './components/Roadmap/list'; -import * as HeadingStories from '../src/components/Heading/Heading.stories'; -import * as TextStories from '../src/components/Text/Text.stories'; - - - - - -Typography - - Canon offers a suite of typography components designed to seamlessly align - with the rest of your Backstage instance. While you can customize their - appearance to match your brand, the underlying API remains consistent and - unchanged. Each component is built on a responsive structure, allowing you to - define different typography values for various breakpoints. - - -Heading - - Headings are used to structure the content of your page. They are used to - create a hierarchy of information and to make the content more readable. The - best way to use add these headings to your page is to import the [Heading - component](?path=/docs/components-heading--docs). - - - - -Text - - Canon provides four distinct text variants, each offering different font sizes - carefully designed to cover the majority of use cases. These variants are - versatile and can be paired with regular and bold of font weights. You can use - the [Text component](?path=/docs/components-text--docs) to add text to your - page. - - - - - diff --git a/packages/canon/docs/components/Banner/Banner.tsx b/packages/canon/docs/components/Banner/Banner.tsx deleted file mode 100644 index 3199e65bc0..0000000000 --- a/packages/canon/docs/components/Banner/Banner.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import React from 'react'; - -export const Banner = ({ - children, - variant = 'info', -}: { - children: React.ReactNode; - variant?: 'info' | 'warning'; -}) => { - return ( -
-
- - - -
- {children} -
- ); -}; diff --git a/packages/canon/docs/components/Banner/index.ts b/packages/canon/docs/components/Banner/index.ts deleted file mode 100644 index 31f4aa88b0..0000000000 --- a/packages/canon/docs/components/Banner/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export { Banner } from './Banner'; diff --git a/packages/canon/docs/components/Banner/styles.css b/packages/canon/docs/components/Banner/styles.css deleted file mode 100644 index d20e14ce0b..0000000000 --- a/packages/canon/docs/components/Banner/styles.css +++ /dev/null @@ -1,37 +0,0 @@ -.banner { - display: flex; - align-items: center; - font-size: 16px; - line-height: 28px; - padding: 16px; - border-radius: 6px; - margin-bottom: 16px; - border: 1px solid #e0e0e0; - - & > p { - margin: 0; - } - - &.info { - background-color: #f2f2f2; - border-color: #cdcdcd; - color: #888888; - } - - &.warning { - background-color: #fff2b9; - border-color: #ffd000; - color: #d79927; - } - - & .icon { - width: 32px; - height: 32px; - background-color: rgba(215, 153, 39, 0.2); - border-radius: 6px; - margin-right: 16px; - display: flex; - align-items: center; - justify-content: center; - } -} diff --git a/packages/canon/docs/components/Chip/Chip.tsx b/packages/canon/docs/components/Chip/Chip.tsx deleted file mode 100644 index e6e1d1b1c4..0000000000 --- a/packages/canon/docs/components/Chip/Chip.tsx +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; - -export const Chip = ({ - children, - head = false, -}: { - children: React.ReactNode; - head?: boolean; -}) => { - return {children}; -}; diff --git a/packages/canon/docs/components/Chip/index.ts b/packages/canon/docs/components/Chip/index.ts deleted file mode 100644 index ff9e4794c5..0000000000 --- a/packages/canon/docs/components/Chip/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export { Chip } from './Chip'; diff --git a/packages/canon/docs/components/Chip/styles.css b/packages/canon/docs/components/Chip/styles.css deleted file mode 100644 index 95cf7cef34..0000000000 --- a/packages/canon/docs/components/Chip/styles.css +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -.chip { - display: inline-flex; - align-items: center; - font-family: monospace; - font-size: 13px; - border-radius: 6px; - padding: 0px 8px; - height: 24px; - margin-right: 4px; - background-color: #f0f0f0; - color: #5d5d5d; - - &.head { - background-color: #eaf2fd; - color: #2563eb; - } -} diff --git a/packages/canon/docs/components/Columns/Columns.tsx b/packages/canon/docs/components/Columns/Columns.tsx deleted file mode 100644 index 115ec50010..0000000000 --- a/packages/canon/docs/components/Columns/Columns.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; - -export const Columns = ({ - children, - style, -}: { - children: React.ReactNode; - style?: React.CSSProperties; -}) => { - return ( -
- {children} -
- ); -}; diff --git a/packages/canon/docs/components/Columns/index.ts b/packages/canon/docs/components/Columns/index.ts deleted file mode 100644 index 05de0850be..0000000000 --- a/packages/canon/docs/components/Columns/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export { Columns } from './Columns'; diff --git a/packages/canon/docs/components/Columns/styles.css b/packages/canon/docs/components/Columns/styles.css deleted file mode 100644 index c34acfc406..0000000000 --- a/packages/canon/docs/components/Columns/styles.css +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -.columns { - display: grid; - grid-template-columns: repeat(3, 1fr); - row-gap: 20px; - column-gap: 80px; -} diff --git a/packages/canon/docs/components/ComponentStatus/ComponentStatus.tsx b/packages/canon/docs/components/ComponentStatus/ComponentStatus.tsx deleted file mode 100644 index 0159e27b32..0000000000 --- a/packages/canon/docs/components/ComponentStatus/ComponentStatus.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; - -export const ComponentStatus = ({ - name, - status = 'notStarted', - style, - link, -}: { - name: string; - status: 'notStarted' | 'inProgress' | 'alpha' | 'beta' | 'stable'; - style?: React.CSSProperties; - link?: string; -}) => { - return ( -
- {link ? ( - - {name} - - ) : ( - {name} - )} - - {status === 'notStarted' && 'Not Started'} - {status === 'inProgress' && 'In Progress'} - {status === 'alpha' && 'Alpha'} - {status === 'beta' && 'Beta'} - {status === 'stable' && 'Stable'} - -
- ); -}; diff --git a/packages/canon/docs/components/ComponentStatus/index.ts b/packages/canon/docs/components/ComponentStatus/index.ts deleted file mode 100644 index 238c001289..0000000000 --- a/packages/canon/docs/components/ComponentStatus/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export { ComponentStatus } from './ComponentStatus'; diff --git a/packages/canon/docs/components/ComponentStatus/styles.css b/packages/canon/docs/components/ComponentStatus/styles.css deleted file mode 100644 index 6ef02ccf3f..0000000000 --- a/packages/canon/docs/components/ComponentStatus/styles.css +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -.component-status { - display: flex; - justify-content: space-between; - - & .title { - color: #3b59ff; - font-size: 16px; - font-weight: 400; - text-decoration: none; - } - - & .pill { - display: inline-flex; - align-items: center; - color: #000; - border-radius: 40px; - padding: 0px 8px; - height: 24px; - font-size: 12px; - font-weight: 600; - border-style: solid; - border-width: 1px; - margin-left: 8px; - - &.notStarted { - background-color: #f2f2f2; - border-color: #cdcdcd; - color: #888888; - } - - &.inProgress { - background-color: #fff2b9; - border-color: #ffd000; - color: #d79927; - } - - &.alpha { - background-color: #d7f9d7; - border-color: #4ed14a; - color: #3a9837; - } - - &.beta { - background-color: #d7f9d7; - border-color: #4ed14a; - color: #3a9837; - } - - &.stable { - background-color: #d7f9d7; - border-color: #4ed14a; - color: #3a9837; - } - } -} diff --git a/packages/canon/docs/components/IconLibrary/IconLibrary.tsx b/packages/canon/docs/components/IconLibrary/IconLibrary.tsx deleted file mode 100644 index 1d517f7a98..0000000000 --- a/packages/canon/docs/components/IconLibrary/IconLibrary.tsx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import React from 'react'; -import { Icon } from '@backstage/canon'; -import type { IconNames } from '@backstage/canon'; -import { icons } from '../../../src/components/Icon/icons'; -import { Text } from '../Text/Text'; - -export const IconLibrary = () => { - const iconsList = Object.keys(icons); - - return ( -
- {iconsList.map(icon => ( -
-
- -
- {icon} -
- ))} -
- ); -}; diff --git a/packages/canon/docs/components/IconLibrary/index.ts b/packages/canon/docs/components/IconLibrary/index.ts deleted file mode 100644 index 071c80db85..0000000000 --- a/packages/canon/docs/components/IconLibrary/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export { IconLibrary } from './IconLibrary'; diff --git a/packages/canon/docs/components/IconLibrary/styles.css b/packages/canon/docs/components/IconLibrary/styles.css deleted file mode 100644 index 1a6f63ca79..0000000000 --- a/packages/canon/docs/components/IconLibrary/styles.css +++ /dev/null @@ -1,22 +0,0 @@ -.icon-library { - display: grid; - grid-template-columns: repeat(6, 1fr); - gap: 1rem; -} - -.icon-library-item { - display: flex; - flex-direction: column; - align-items: center; - gap: 8px; -} - -.icon-library-item-icon { - display: flex; - width: 100%; - justify-content: center; - align-items: center; - height: 80px; - border: 1px solid #d3d3d3; - border-radius: 0.5rem; -} diff --git a/packages/canon/docs/components/LayoutComponents/LayoutComponents.tsx b/packages/canon/docs/components/LayoutComponents/LayoutComponents.tsx deleted file mode 100644 index 56f389a389..0000000000 --- a/packages/canon/docs/components/LayoutComponents/LayoutComponents.tsx +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; -import { BoxSvg } from './svgs/box'; -import { StackSvg } from './svgs/stack'; -import { GridSvg } from './svgs/grid'; -import { InlineSvg } from './svgs/inline'; -import { ContainerSvg } from './svgs/container'; - -export const LayoutComponents = () => { - return ( -
-
- - - -
Box
-
The most basic layout component
-
-
- - - -
Stack
-
Arrange your components vertically
-
-
- - - -
Grid
-
Arrange your components in a grid
-
-
- - - -
Inline
-
Arrange your components in a row
-
-
- - - -
Container
-
A container for your components
-
-
- ); -}; diff --git a/packages/canon/docs/components/LayoutComponents/index.ts b/packages/canon/docs/components/LayoutComponents/index.ts deleted file mode 100644 index 55cc9f25ef..0000000000 --- a/packages/canon/docs/components/LayoutComponents/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export { LayoutComponents } from './LayoutComponents'; diff --git a/packages/canon/docs/components/LayoutComponents/styles.css b/packages/canon/docs/components/LayoutComponents/styles.css deleted file mode 100644 index 9319121224..0000000000 --- a/packages/canon/docs/components/LayoutComponents/styles.css +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -.layout-components { - display: flex; - justify-content: flex-start; - gap: 1rem; - flex-wrap: wrap; - - & .box { - display: flex; - flex-direction: column; - width: calc(33.33% - 0.67rem); - margin-bottom: 1rem; - align-items: flex-start; - } - - & .content { - flex: none; - background: linear-gradient(180deg, #f3f3f3 0%, #fff 100%); - border-radius: 4px; - width: 100%; - height: 180px; - transition: all 0.2s ease-in-out; - margin-bottom: 0.75rem; - display: flex; - align-items: center; - justify-content: center; - - &:hover { - transform: translateY(-4px); - } - } - - & .title { - font-size: 16px; - transition: color 0.2s ease-in-out; - margin-bottom: 0.25rem; - } - - & .description { - font-size: 16px; - color: #9e9e9e; - } -} diff --git a/packages/canon/docs/components/LayoutComponents/svgs/box.tsx b/packages/canon/docs/components/LayoutComponents/svgs/box.tsx deleted file mode 100644 index 1749b38c6f..0000000000 --- a/packages/canon/docs/components/LayoutComponents/svgs/box.tsx +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import React from 'react'; - -export const BoxSvg = () => { - return ( - - - - - - - - ); -}; diff --git a/packages/canon/docs/components/LayoutComponents/svgs/container.tsx b/packages/canon/docs/components/LayoutComponents/svgs/container.tsx deleted file mode 100644 index 62fe4ce266..0000000000 --- a/packages/canon/docs/components/LayoutComponents/svgs/container.tsx +++ /dev/null @@ -1,373 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import React from 'react'; - -export const ContainerSvg = () => { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -}; diff --git a/packages/canon/docs/components/LayoutComponents/svgs/grid.tsx b/packages/canon/docs/components/LayoutComponents/svgs/grid.tsx deleted file mode 100644 index 5527d1f3e2..0000000000 --- a/packages/canon/docs/components/LayoutComponents/svgs/grid.tsx +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import React from 'react'; - -export const GridSvg = () => { - return ( - - - - - - - - - - - - ); -}; diff --git a/packages/canon/docs/components/LayoutComponents/svgs/inline.tsx b/packages/canon/docs/components/LayoutComponents/svgs/inline.tsx deleted file mode 100644 index fadb9bfc1b..0000000000 --- a/packages/canon/docs/components/LayoutComponents/svgs/inline.tsx +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import React from 'react'; - -export const InlineSvg = () => { - return ( - - - - - - - - - - - - ); -}; diff --git a/packages/canon/docs/components/LayoutComponents/svgs/stack.tsx b/packages/canon/docs/components/LayoutComponents/svgs/stack.tsx deleted file mode 100644 index aeda998d2e..0000000000 --- a/packages/canon/docs/components/LayoutComponents/svgs/stack.tsx +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import React from 'react'; - -export const StackSvg = () => { - return ( - - - - - - - - - - - - ); -}; diff --git a/packages/canon/docs/components/PropsTable/PropsTable.tsx b/packages/canon/docs/components/PropsTable/PropsTable.tsx deleted file mode 100644 index a3dc9e271c..0000000000 --- a/packages/canon/docs/components/PropsTable/PropsTable.tsx +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; -import * as Table from '..'; -import { Chip } from '..'; - -// Modify the PropsTable component to accept a generic type -export const PropsTable = >({ - data, -}: { - data: T; -}) => { - return ( - - - - Prop - Type - Responsive - - - - {Object.keys(data).map(n => ( - - - {n} - - - {Array.isArray(data[n].type) ? ( - data[n].type.map((t: any) => {t}) - ) : ( - {data[n].type} - )} - - - {data[n].responsive ? 'Yes' : 'No'} - - - ))} - - - ); -}; diff --git a/packages/canon/docs/components/PropsTable/getProps.ts b/packages/canon/docs/components/PropsTable/getProps.ts deleted file mode 100644 index 5773eaeb27..0000000000 --- a/packages/canon/docs/components/PropsTable/getProps.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export function getProps(styles: Record) { - return Object.keys(styles).reduce( - (acc: Record, n) => { - const style = styles[n]; - - let values: string[] = []; - - if (style.values) { - // If values exist, use them - values = Object.keys(style.values); - } else if (style.mappings && style.mappings.length > 0) { - // If mappings exist, use the first mapping's values - const firstMapping = style.mappings[0]; - values = Object.keys(styles[firstMapping].values); - } else { - // Default to an empty array if neither values nor mappings exist - values = []; - } - - acc[n] = { - type: values, - responsive: true, - }; - return acc; - }, - {} as Record, - ); -} diff --git a/packages/canon/docs/components/PropsTable/index.ts b/packages/canon/docs/components/PropsTable/index.ts deleted file mode 100644 index 181933f0f8..0000000000 --- a/packages/canon/docs/components/PropsTable/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export { PropsTable } from './PropsTable'; -export { getProps } from './getProps'; diff --git a/packages/canon/docs/components/Roadmap/Roadmap.tsx b/packages/canon/docs/components/Roadmap/Roadmap.tsx deleted file mode 100644 index 2177ea558f..0000000000 --- a/packages/canon/docs/components/Roadmap/Roadmap.tsx +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import React from 'react'; -import { RoadmapItem } from './list'; - -export const Roadmap = ({ list }: { list: RoadmapItem[] }) => { - const orderList = ['inProgress', 'notStarted', 'completed']; - return ( -
- {list - .sort( - (a, b) => orderList.indexOf(a.status) - orderList.indexOf(b.status), - ) - .map(Item)} -
- ); -}; - -const Item = ({ - title, - status = 'notStarted', -}: { - title: string; - status: 'notStarted' | 'inProgress' | 'inReview' | 'completed'; -}) => { - return ( -
-
-
-
{title}
-
- - {status === 'notStarted' && 'Not Started'} - {status === 'inProgress' && 'In Progress'} - {status === 'inReview' && 'Ready for Review'} - {status === 'completed' && 'Completed'} - -
- ); -}; diff --git a/packages/canon/docs/components/Roadmap/index.ts b/packages/canon/docs/components/Roadmap/index.ts deleted file mode 100644 index ac51dfa1ed..0000000000 --- a/packages/canon/docs/components/Roadmap/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export { Roadmap } from './Roadmap'; diff --git a/packages/canon/docs/components/Roadmap/list.ts b/packages/canon/docs/components/Roadmap/list.ts deleted file mode 100644 index d5f436281a..0000000000 --- a/packages/canon/docs/components/Roadmap/list.ts +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export type RoadmapItem = { - title: string; - status: 'notStarted' | 'inProgress' | 'inReview' | 'completed'; -}; - -export const list: RoadmapItem[] = [ - { - title: 'Remove Vanilla Extract and use pure CSS instead', - status: 'inProgress', - }, - { - title: 'Add collapsing across breakpoints for the Inline component', - status: 'notStarted', - }, - { - title: 'Add reversing the order for the Inline component', - status: 'notStarted', - }, - { - title: 'Set up Storybook', - status: 'completed', - }, - { - title: 'Set up iconography', - status: 'completed', - }, - { - title: 'Set up global tokens', - status: 'inProgress', - }, - { - title: 'Set up theming system', - status: 'inProgress', - }, - { - title: 'Create first pass at box component', - status: 'completed', - }, - { - title: 'Create first pass at stack component', - status: 'completed', - }, - { - title: 'Create first pass at inline component', - status: 'completed', - }, -]; diff --git a/packages/canon/docs/components/Roadmap/styles.css b/packages/canon/docs/components/Roadmap/styles.css deleted file mode 100644 index 63f32312e7..0000000000 --- a/packages/canon/docs/components/Roadmap/styles.css +++ /dev/null @@ -1,100 +0,0 @@ -.roadmap { - display: flex; - flex-direction: column; -} - -.roadmap .roadmap-item { - display: flex; - align-items: center; - justify-content: space-between; - border-bottom: 1px solid #e0e0e0; - padding: 8px 0px; -} - -.roadmap .roadmap-item .left { - display: flex; - align-items: center; - padding-left: 12px; - gap: 12px; -} - -.roadmap .roadmap-item .dot { - width: 8px; - height: 8px; - border-radius: 50%; - background-color: #e0e0e0; -} - -.roadmap .roadmap-item.notStarted { - color: #000; -} - -.roadmap .roadmap-item.inProgress { - color: #000; -} - -.roadmap .roadmap-item.inReview { - color: #000; -} - -.roadmap .roadmap-item.completed .title { - color: #a2a2a2; - text-decoration: line-through; -} - -.roadmap .roadmap-item.notStarted .dot { - background-color: #d1d1d1; -} - -.roadmap .roadmap-item.inProgress .dot { - background-color: #ffd000; -} - -.roadmap .roadmap-item.inReview .dot { - background-color: #4ed14a; -} - -.roadmap .roadmap-item.completed .dot { - background-color: #4ed14a; -} - -.roadmap .roadmap-item .title { - font-size: 16px; -} - -.roadmap .roadmap-item .pill { - display: inline-flex; - align-items: center; - height: 24px; - padding: 0px 8px; - border-radius: 40px; - font-size: 12px; - font-weight: 600; - margin-left: 8px; - border-style: solid; - border-width: 1px; -} - -.roadmap .roadmap-item.notStarted .pill { - background-color: #f2f2f2; - border-color: #cdcdcd; - color: #888888; -} - -.roadmap .roadmap-item.inProgress .pill { - background-color: #fff2b9; - border-color: #ffd000; - color: #d79927; -} - -.roadmap .roadmap-item.inReview .pill { - background-color: #d7f9d7; - border-color: #4ed14a; - color: #3a9837; -} - -.roadmap .roadmap-item.completed .pill { - background-color: #d7f9d7; - border-color: #4ed14a; - color: #3a9837; -} diff --git a/packages/canon/docs/components/Table/Table.tsx b/packages/canon/docs/components/Table/Table.tsx deleted file mode 100644 index 74a26a89af..0000000000 --- a/packages/canon/docs/components/Table/Table.tsx +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; - -export const Root = ({ children }: { children: React.ReactNode }) => { - return ( -
- {children}
-
- ); -}; - -export const Header = ({ children }: { children: React.ReactNode }) => { - return {children}; -}; - -export const Body = ({ children }: { children: React.ReactNode }) => { - return {children}; -}; - -export const HeaderRow = ({ children }: { children: React.ReactNode }) => { - return {children}; -}; - -export const HeaderCell = ({ children }: { children: React.ReactNode }) => { - return {children}; -}; - -export const Row = ({ children }: { children: React.ReactNode }) => { - return {children}; -}; - -export const Cell = ({ children }: { children: React.ReactNode }) => { - return {children}; -}; diff --git a/packages/canon/docs/components/Table/index.ts b/packages/canon/docs/components/Table/index.ts deleted file mode 100644 index 7419aa83a8..0000000000 --- a/packages/canon/docs/components/Table/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export { Root, Header, Body, Row, Cell, HeaderRow, HeaderCell } from './Table'; diff --git a/packages/canon/docs/components/Table/styles.css b/packages/canon/docs/components/Table/styles.css deleted file mode 100644 index ed56774a85..0000000000 --- a/packages/canon/docs/components/Table/styles.css +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -.sb-table-wrapper { - border: 1px solid #e7e7e7; - border-radius: 4px; - overflow: hidden; - - & .sb-table { - width: 100%; - margin: 0 !important; - padding: 0 !important; - border-spacing: 0px; - border-collapse: collapse; - } - - & .sb-table-cell { - padding: 12px 16px !important; - border: none !important; - text-align: left; - background-color: white !important; - font-size: 16px; - - & p { - margin: 0; - } - } - - & .sb-table-header-cell { - background-color: #f5f5f5 !important; - border-bottom: 1px solid #e7e7e7 !important; - font-weight: 500; - font-size: 14px; - } - - & .sb-table-row { - border: none; - border-bottom: 1px solid #e7e7e7; - &:last-child { - border-bottom: none; - } - } - - & .sb-table-chip { - display: inline-block; - font-size: 14px !important; - border: 1px solid #e7e7e7; - border-radius: 6px; - padding: 0px 6px; - height: 24px; - } - - & .sb-table-type { - display: flex; - flex-wrap: wrap; - flex-direction: row; - gap: 8px; - } -} diff --git a/packages/canon/docs/components/Text/Text.tsx b/packages/canon/docs/components/Text/Text.tsx deleted file mode 100644 index 90804b7da7..0000000000 --- a/packages/canon/docs/components/Text/Text.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; - -export const Text = ({ - children, - style, -}: { - children: React.ReactNode; - style?: React.CSSProperties; -}) => { - return ( -
- {children} -
- ); -}; diff --git a/packages/canon/docs/components/Text/index.ts b/packages/canon/docs/components/Text/index.ts deleted file mode 100644 index 873fca2c76..0000000000 --- a/packages/canon/docs/components/Text/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export { Text } from './Text'; diff --git a/packages/canon/docs/components/Text/styles.css b/packages/canon/docs/components/Text/styles.css deleted file mode 100644 index b1bc40a1ac..0000000000 --- a/packages/canon/docs/components/Text/styles.css +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -.sb-text { - font-size: 16px; - line-height: 28px; - margin: 0; - margin-bottom: 16px; - color: #4f4f4f; - - & p { - font-size: 16px; - line-height: 28px; - margin: 0; - } - - & code { - font-size: 13px; - color: #215cff; - background-color: #f1f3fc; - padding: 4px 4px; - border-radius: 4px; - } - - & a { - color: #215cff; - text-decoration: none; - border-bottom: 1px solid #215cff; - } -} diff --git a/packages/canon/docs/components/Title/Title.tsx b/packages/canon/docs/components/Title/Title.tsx deleted file mode 100644 index 59fefad8f9..0000000000 --- a/packages/canon/docs/components/Title/Title.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; - -export const Title = ({ - children, - style, - type = 'h1', -}: { - children: React.ReactNode; - style?: React.CSSProperties; - type?: 'h1' | 'h2' | 'h3'; -}) => { - let Component = 'h1'; - if (type === 'h1') Component = 'h1'; - if (type === 'h2') Component = 'h2'; - if (type === 'h3') Component = 'h3'; - - return React.createElement(Component, { - className: `sb-title ${type}`, - style, - children, - }); -}; diff --git a/packages/canon/docs/components/Title/index.ts b/packages/canon/docs/components/Title/index.ts deleted file mode 100644 index 998dc81882..0000000000 --- a/packages/canon/docs/components/Title/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export { Title } from './Title'; diff --git a/packages/canon/docs/components/Title/styles.css b/packages/canon/docs/components/Title/styles.css deleted file mode 100644 index 61e99e20b3..0000000000 --- a/packages/canon/docs/components/Title/styles.css +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -.sb-title { - margin: 0; - font-weight: 500; - border: none; - - &.h1 { - font-size: 36px; - line-height: 44px; - margin-bottom: 24px; - margin-top: 0px; - } - - &.h2 { - font-size: 24px; - line-height: 32px; - margin-bottom: 12px; - margin-top: 52px; - } - - &.h3 { - font-size: 20px; - line-height: 28px; - margin-bottom: 12px; - margin-top: 40px; - } -} diff --git a/packages/canon/docs/components/index.ts b/packages/canon/docs/components/index.ts deleted file mode 100644 index 0e5ff2e325..0000000000 --- a/packages/canon/docs/components/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export * from './Banner'; -export * from './Chip'; -export * from './Columns'; -export * from './ComponentStatus'; -export * from './IconLibrary'; -export * from './LayoutComponents'; -export * from './PropsTable'; -export * from './Roadmap'; -export * from './Table'; -export * from './Text'; -export * from './Title'; diff --git a/packages/canon/docs/components/styles.css b/packages/canon/docs/components/styles.css deleted file mode 100644 index 706b1626b3..0000000000 --- a/packages/canon/docs/components/styles.css +++ /dev/null @@ -1,15 +0,0 @@ -.sbdocs-content { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, - Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; -} - -@import './Banner/styles.css'; -@import './Chip/styles.css'; -@import './Columns/styles.css'; -@import './ComponentStatus/styles.css'; -@import './IconLibrary/styles.css'; -@import './LayoutComponents/styles.css'; -@import './Roadmap/styles.css'; -@import './Table/styles.css'; -@import './Text/styles.css'; -@import './Title/styles.css'; diff --git a/packages/canon/docs/spaceProps.ts b/packages/canon/docs/spaceProps.ts deleted file mode 100644 index 3eb6961bc1..0000000000 --- a/packages/canon/docs/spaceProps.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export const spacePropsList = [ - 'margin', - 'marginBottom', - 'marginLeft', - 'marginRight', - 'marginTop', - 'marginX', - 'marginY', - 'padding', - 'paddingBottom', - 'paddingLeft', - 'paddingRight', - 'paddingTop', - 'paddingX', - 'paddingY', -].reduce( - (acc: { [key: string]: { type: string[]; responsive: boolean } }, prop) => { - acc[prop] = { - type: ['2xs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl', '4xl', '5xl'], - responsive: true, - }; - return acc; - }, - {}, -); diff --git a/packages/canon/src/components/Box/Docs.mdx b/packages/canon/src/components/Box/Docs.mdx deleted file mode 100644 index ee30e17305..0000000000 --- a/packages/canon/src/components/Box/Docs.mdx +++ /dev/null @@ -1,112 +0,0 @@ -import { Meta, Unstyled, Source, Canvas } from '@storybook/blocks'; -import * as BoxStories from './Box.stories'; -import { Title, Text } from '../../../docs/components'; -import { PropsTable } from '../../../docs/components'; -import { spacePropsList } from '../../../docs/spaceProps'; - - - - - -Box - - Box is the lowest-level component in Canon. We use it internally to build all - of our components. It provides a consistent API for styling and layout. - - -Usage -Hello World! -`} language="tsx" dark /> - -API reference - -Box - - This is the Box component, our lowest-level component. Here are all the - available properties. - - - - - - Padding and margin are used to create space around your component using our - predefined spacing tokens. We would recommend to use padding over margin to - avoid collapsing margins but both are available. - - - - - - -Examples -Here are some examples of how you can use the Box component. - -Simple example -A simple example of how to use the Box component. -Hello World`} - language="tsx" - dark -/> - -Responsive - - Most of the values can be defined per breakpoint, making it easy to create - responsive designs. - -Hello World`} - language="tsx" - dark -/> - - diff --git a/packages/canon/src/components/Button/Docs.mdx b/packages/canon/src/components/Button/Docs.mdx deleted file mode 100644 index ae90b5fd80..0000000000 --- a/packages/canon/src/components/Button/Docs.mdx +++ /dev/null @@ -1,135 +0,0 @@ -import { Canvas, Meta, Unstyled, Source } from '@storybook/blocks'; -import * as ButtonStories from './Button.stories'; -import { Title, Text } from '../../../docs/components'; -import { PropsTable } from '../../../docs/components/PropsTable/PropsTable'; - - - - - -Button -A button component that can be used to trigger actions. - - - -Usage -Click me -`} language="tsx" dark /> - - - API reference - - - - -Examples - -Variants -Here's a view when buttons have different variants. - - - - - - - -`} - language="tsx" - dark -/> - -Sizes -Here's a view when buttons have different sizes. - - - - - - -`} - language="tsx" - dark -/> - -With Icons -Here's a view when buttons have icons. - - - - - - - -`} - language="tsx" - dark -/> - -Full width -Here's a view when buttons are full width. - - - - - - - -`} - language="tsx" - dark -/> - -Disabled -Here's a view when buttons are disabled. - - - -Button`} language="tsx" dark /> - -Responsive -Here's a view when buttons are responsive. - - - - - Button -`} - language="tsx" - dark -/> - - diff --git a/packages/canon/src/components/Checkbox/Docs.mdx b/packages/canon/src/components/Checkbox/Docs.mdx deleted file mode 100644 index 9826708888..0000000000 --- a/packages/canon/src/components/Checkbox/Docs.mdx +++ /dev/null @@ -1,70 +0,0 @@ -import { Canvas, Meta, Unstyled, Source } from '@storybook/blocks'; -import * as CheckboxStories from './Checkbox.stories'; -import { Title, Text } from '../../../docs/components'; -import { PropsTable } from '../../../docs/components/PropsTable'; - - - - - -Checkbox -A checkbox component that can be used to trigger actions. - - - -Usage - -`} language="tsx" dark /> - - - API reference - - - void", - responsive: false, - }, - disabled: { - type: 'boolean', - responsive: false, - }, - required: { - type: 'boolean', - responsive: false, - }, - name: { - type: 'string', - responsive: false, - }, - value: { - type: 'string', - responsive: false, - }, - className: { - type: 'string', - responsive: false, - }, - style: { - type: 'CSSProperties', - responsive: false, - }, - }} -/> - - diff --git a/packages/canon/src/components/Container/Docs.mdx b/packages/canon/src/components/Container/Docs.mdx deleted file mode 100644 index 05a0600813..0000000000 --- a/packages/canon/src/components/Container/Docs.mdx +++ /dev/null @@ -1,99 +0,0 @@ -import { Meta, Unstyled, Source } from '@storybook/blocks'; -import * as ContainerStories from './Container.stories'; -import { Title, Text, PropsTable, getProps } from '../../../docs/components'; -import { spacePropsList } from '../../../docs/spaceProps'; - - - - - -Container - - - The container component let you use our default max-width and center the - content on the page. - - -Usage -Hello World! -`} language="tsx" dark /> - - - API reference - - - - -Examples - -Simple -A simple example of how to use the Container component. - - - Hello World - Hello World - Hello World -`} - language="tsx" - dark -/> - -Responsive padding & margin - - The Container component also supports responsive values, making it easy to - create responsive designs. - - - Hello World - Hello World - Hello World -`} - language="tsx" - dark -/> - - diff --git a/packages/canon/src/components/Grid/Docs.mdx b/packages/canon/src/components/Grid/Docs.mdx deleted file mode 100644 index c1a0237279..0000000000 --- a/packages/canon/src/components/Grid/Docs.mdx +++ /dev/null @@ -1,199 +0,0 @@ -import { Canvas, Meta, Unstyled, Source } from '@storybook/blocks'; -import * as GridStories from './Grid.stories'; -import { Title, Text, PropsTable } from '../../../docs/components'; -import { spacePropsList } from '../../../docs/spaceProps'; - - - - - -Grid - - A layout component that helps to create simple column-based layouts as well as - more complex ones. - - -Usage - - Hello World - -`} language="tsx" dark /> - -API reference - -Grid - - This is the grid container component. It will help to define the number of - columns that will be used in the grid. You can also define the gap between the - columns. All values are responsive. - - - - - - The grid component also accepts all the spacing props from the Box component. - - - - -Grid Item - - If you need more control over the columns, you can use the grid item - component. This will give you access to `rowSpan`, `colSpan`, `start` and - `end`. All values are responsive. This component is optional, you can use any - elements directly if you prefer. - - - - -Examples - -Simple grid -This is a simple grid with 3 columns and a gap of md. - - Hello World - Hello World - Hello World - -`} - language="tsx" - dark -/> - -Complex grid - - You can also use the grid item to create more complex layouts. In this example - the first column will span 1 column and the second column will span 2 columns. - - - - - Hello World - - - Hello World - - -`} - language="tsx" - dark -/> - -Mixing rows and columns - - The grid item component also supports the `rowSpan` prop, which allows you to - span multiple rows within the grid layout. In this example, the first item - will span 2 rows to achieve a dynamic and flexible grid structure. - - - - - Hello World - - - Hello World - - - Hello World - - -`} - language="tsx" - dark -/> - -Responsive - - The grid component also supports responsive values. In this example the grid - will have 1 column on small screens and 3 on large screens, with a gap of xs - on small screens and md on large screens. - - - - Hello World - - - Hello World - - -`} - language="tsx" - dark -/> - -Start and End - - The start and end props can be used to position the item in the grid. - - - - Hello World - - -`} - language="tsx" - dark -/> - - diff --git a/packages/canon/src/components/Heading/Docs.mdx b/packages/canon/src/components/Heading/Docs.mdx deleted file mode 100644 index dd64bd59b2..0000000000 --- a/packages/canon/src/components/Heading/Docs.mdx +++ /dev/null @@ -1,83 +0,0 @@ -import { Canvas, Meta, Unstyled, Source } from '@storybook/blocks'; -import * as HeadingStories from './Heading.stories'; -import { Title, Text } from '../../../docs/components'; -import { PropsTable } from '../../../docs/components/PropsTable/PropsTable'; - - - - - -Heading - -Headings are used to structure the content of your page. - - - -Usage -Hello World! -`} language="tsx" dark /> - - - API reference - - - - -Examples -Here are some examples of how you can use the Heading component. - -All variants - - The `Heading` component has a `variant` prop that can be used to change the - appearance of the heading. - - - - - - Title 1 Lorem ipsum dolor sit amet consectetur... - Title 2 Lorem ipsum dolor sit amet consectetur... - Title 3 Lorem ipsum dolor sit amet consectetur... - Title 4 Lorem ipsum dolor sit amet consectetur... - Title 5 Lorem ipsum dolor sit amet consectetur... - Display Lorem ipsum dolor sit amet consectetur... -`} - language="tsx" - dark -/> - -Responsive - - You can also use the `variant` prop to change the appearance of the text based - on the screen size. - - -Responsive Lorem ipsum dolor sit amet consectetur...`} - language="tsx" - dark -/> - - diff --git a/packages/canon/src/components/Icon/Docs.mdx b/packages/canon/src/components/Icon/Docs.mdx deleted file mode 100644 index 41c296424c..0000000000 --- a/packages/canon/src/components/Icon/Docs.mdx +++ /dev/null @@ -1,48 +0,0 @@ -import { Canvas, Meta, Unstyled, Source } from '@storybook/blocks'; -import * as IconStories from './Icon.stories'; -import { Title, Text } from '../../../docs/components'; -import { PropsTable } from '../../../docs/components/PropsTable/PropsTable'; -import { icons } from './icons'; - - - - - -Icon - -Icons are used to represent an action or a state. - - - -Usage - -`} language="tsx" dark /> - - - API reference - - - - - diff --git a/packages/canon/src/components/Inline/Docs.mdx b/packages/canon/src/components/Inline/Docs.mdx deleted file mode 100644 index 7671122966..0000000000 --- a/packages/canon/src/components/Inline/Docs.mdx +++ /dev/null @@ -1,124 +0,0 @@ -import { Meta, Unstyled, Source } from '@storybook/blocks'; -import * as InlineStories from './Inline.stories'; -import { Title, Text, PropsTable, getProps } from '../../../docs/components'; -import { spacePropsList } from '../../../docs/spaceProps'; - - - - - -Inline - - - The Inline component is used to create a horizontal layout of elements. By - default it uses flex and flexWrap to make sure that your content always flows - responsively. - - -Usage - - Hello World - Hello World - Hello World - -`} language="tsx" dark /> - - - API reference - - - - - - The grid component also accepts all the spacing props from the Box component. - - - - -Examples - -Simple -A simple example of how to use the Inline component. - - - Hello World - Hello World - Hello World -`} - language="tsx" - dark -/> - -Responsive - - The Inline component also supports responsive values, making it easy to create - responsive designs. - - - Hello World - Hello World - Hello World -`} - language="tsx" - dark -/> - -Align - - The Inline component also supports responsive alignment, making it easy to - create responsive designs. - - - Hello World - Hello World - Hello World -`} - language="tsx" - dark -/> - -Align vertically - - The Inline component also supports responsive vertical alignment, making it - easy to create responsive designs. - - - Hello World - Hello World - Hello World -`} - language="tsx" - dark -/> - - diff --git a/packages/canon/src/components/Stack/Docs.mdx b/packages/canon/src/components/Stack/Docs.mdx deleted file mode 100644 index 921618f49f..0000000000 --- a/packages/canon/src/components/Stack/Docs.mdx +++ /dev/null @@ -1,126 +0,0 @@ -import { Meta, Unstyled, Source } from '@storybook/blocks'; -import * as StackStories from './Stack.stories'; -import { Title, Text, PropsTable, getProps } from '../../../docs/components'; -import { spacePropsList } from '../../../docs/spaceProps'; - - - - - -Stack - - - This is the stack container component. It will help to define the number of - columns that will be used in the grid. You can also define the gap between the - columns. All values are responsive. - - -Usage - - Hello World - Hello World - Hello World - -`} language="tsx" dark /> - - - API reference - - - - - - The grid component also accepts all the spacing props from the Box component. - - - - -Common questions - -Can I stack horizontally? - - The Stack component only allows for stacking elements vertically. If you want - to create a column layout, please use the Grid component. - - - - Hello World - Hello World - Hello World - -`} - language="tsx" - dark -/> - -Examples - -Simple - - A simple example of how to use the Stack component with a medium gap. - - - - Hello World - Hello World - Hello World -`} - language="tsx" - dark -/> - -Responsive - - The Stack component also supports responsive values, making it easy to create - responsive designs. - - - Hello World - Hello World - Hello World -`} - language="tsx" - dark -/> - -Align - - The Stack component also supports responsive alignment, making it easy to - create responsive designs. - - - Hello World - Hello World - Hello World -`} - language="tsx" - dark -/> - - diff --git a/packages/canon/src/components/Table/Docs.mdx b/packages/canon/src/components/Table/Docs.mdx deleted file mode 100644 index 6a6e9c8a59..0000000000 --- a/packages/canon/src/components/Table/Docs.mdx +++ /dev/null @@ -1,5 +0,0 @@ -import { Meta, Controls } from '@storybook/blocks'; - - - -# Table diff --git a/packages/canon/src/components/Text/Docs.mdx b/packages/canon/src/components/Text/Docs.mdx deleted file mode 100644 index 685a00a33f..0000000000 --- a/packages/canon/src/components/Text/Docs.mdx +++ /dev/null @@ -1,101 +0,0 @@ -import { Canvas, Meta, Unstyled, Source } from '@storybook/blocks'; -import * as TextStories from './Text.stories'; -import { Title, Text } from '../../../docs/components'; -import { PropsTable } from '../../../docs/components/PropsTable/PropsTable'; - - - - - -Text - -The `Text` component is used to display content on your page. - - - -Usage -Hello World!`} language="tsx" dark /> - - - API reference - - - - -Examples -Here are some examples of how you can use the Text component. - -All variants - - The `Text` component has a `variant` prop that can be used to change the - appearance of the text. - - - - - - Subtitle Lorem ipsum dolor sit amet consectetur... - Body Lorem ipsum dolor sit amet consectetur... - Caption Lorem ipsum dolor sit amet consectetur... - Label Lorem ipsum dolor sit amet consectetur... -`} - language="tsx" - dark -/> - -All weights - - The `Text` component has a `weight` prop that can be used to change the - appearance of the text. - - - - - - Regular Lorem ipsum dolor sit amet consectetur... - Bold Lorem ipsum dolor sit amet consectetur... -`} - language="tsx" - dark -/> - -Responsive - - You can also use the `variant` prop to change the appearance of the text based - on the screen size. - - -Responsive Lorem ipsum dolor sit amet consectetur...`} - language="tsx" - dark -/> - - From 71c9fbb6a90e3aae590be1ef8e33ac716a264d96 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Wed, 15 Jan 2025 18:33:08 +0000 Subject: [PATCH 36/45] Update dependencies Signed-off-by: Charles de Dreuille --- packages/canon/package.json | 20 +- yarn.lock | 372 ++++++++++++++++++------------------ 2 files changed, 194 insertions(+), 198 deletions(-) diff --git a/packages/canon/package.json b/packages/canon/package.json index d08f660e7d..9c18f857ee 100644 --- a/packages/canon/package.json +++ b/packages/canon/package.json @@ -37,22 +37,22 @@ "test": "backstage-cli package test" }, "dependencies": { - "@base-ui-components/react": "^1.0.0-alpha.4", + "@base-ui-components/react": "^1.0.0-alpha.5", "@remixicon/react": "^4.5.0", "clsx": "^2.1.1" }, "devDependencies": { "@backstage/cli": "workspace:^", "@chromatic-com/storybook": "^3.2.2", - "@storybook/addon-essentials": "^8.4.7", - "@storybook/addon-interactions": "^8.4.7", + "@storybook/addon-essentials": "^8.5.0", + "@storybook/addon-interactions": "^8.5.0", "@storybook/addon-styling-webpack": "^1.0.1", - "@storybook/addon-themes": "^8.4.7", - "@storybook/addon-webpack5-compiler-swc": "^1.0.5", - "@storybook/blocks": "^8.4.7", - "@storybook/react": "^8.4.7", - "@storybook/react-webpack5": "^8.4.7", - "@storybook/test": "^8.4.7", + "@storybook/addon-themes": "^8.5.0", + "@storybook/addon-webpack5-compiler-swc": "^2.0.0", + "@storybook/blocks": "^8.5.0", + "@storybook/react": "^8.5.0", + "@storybook/react-webpack5": "^8.5.0", + "@storybook/test": "^8.5.0", "@testing-library/jest-dom": "^6.0.0", "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", @@ -62,7 +62,7 @@ "react": "^18.0.2", "react-dom": "^18.0.2", "react-router-dom": "^6.3.0", - "storybook": "^8.4.7" + "storybook": "^8.5.0" }, "peerDependencies": { "@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0", diff --git a/yarn.lock b/yarn.lock index 7e21cdcad1..8f483f177e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3787,18 +3787,18 @@ __metadata: resolution: "@backstage/canon@workspace:packages/canon" dependencies: "@backstage/cli": "workspace:^" - "@base-ui-components/react": ^1.0.0-alpha.4 + "@base-ui-components/react": ^1.0.0-alpha.5 "@chromatic-com/storybook": ^3.2.2 "@remixicon/react": ^4.5.0 - "@storybook/addon-essentials": ^8.4.7 - "@storybook/addon-interactions": ^8.4.7 + "@storybook/addon-essentials": ^8.5.0 + "@storybook/addon-interactions": ^8.5.0 "@storybook/addon-styling-webpack": ^1.0.1 - "@storybook/addon-themes": ^8.4.7 - "@storybook/addon-webpack5-compiler-swc": ^1.0.5 - "@storybook/blocks": ^8.4.7 - "@storybook/react": ^8.4.7 - "@storybook/react-webpack5": ^8.4.7 - "@storybook/test": ^8.4.7 + "@storybook/addon-themes": ^8.5.0 + "@storybook/addon-webpack5-compiler-swc": ^2.0.0 + "@storybook/blocks": ^8.5.0 + "@storybook/react": ^8.5.0 + "@storybook/react-webpack5": ^8.5.0 + "@storybook/test": ^8.5.0 "@testing-library/jest-dom": ^6.0.0 "@types/react": ^18.0.0 "@types/react-dom": ^18.0.0 @@ -3809,7 +3809,7 @@ __metadata: react: ^18.0.2 react-dom: ^18.0.2 react-router-dom: ^6.3.0 - storybook: ^8.4.7 + storybook: ^8.5.0 peerDependencies: "@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0 react: ^16.13.1 || ^17.0.0 || ^18.0.0 @@ -8467,7 +8467,7 @@ __metadata: languageName: node linkType: hard -"@base-ui-components/react@npm:^1.0.0-alpha.4": +"@base-ui-components/react@npm:^1.0.0-alpha.5": version: 1.0.0-alpha.5 resolution: "@base-ui-components/react@npm:1.0.0-alpha.5" dependencies: @@ -17131,9 +17131,9 @@ __metadata: languageName: node linkType: hard -"@storybook/addon-actions@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/addon-actions@npm:8.4.7" +"@storybook/addon-actions@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/addon-actions@npm:8.5.0" dependencies: "@storybook/global": ^5.0.0 "@types/uuid": ^9.0.1 @@ -17141,121 +17141,121 @@ __metadata: polished: ^4.2.2 uuid: ^9.0.0 peerDependencies: - storybook: ^8.4.7 - checksum: 7cc48f1ebd137f815b907255e8201e49df5a5fa37fade9fa1a8d2bcaeaf4922e777a20e5e2b84500b85a4976a95b81ab960d6208e1c1d44be1e6fb7a6b93fc6c + storybook: ^8.5.0 + checksum: 88c279bf90edd818017f18925efcaf2b2ccd42f9247832a26dc36a7e73db012b9784889f64986527a55ed9a3746fc5fe229d26de8c2b2dbfb670494213bf688b languageName: node linkType: hard -"@storybook/addon-backgrounds@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/addon-backgrounds@npm:8.4.7" +"@storybook/addon-backgrounds@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/addon-backgrounds@npm:8.5.0" dependencies: "@storybook/global": ^5.0.0 memoizerific: ^1.11.3 ts-dedent: ^2.0.0 peerDependencies: - storybook: ^8.4.7 - checksum: 67cf32c3f9d2797158679156fb7250b94e72d692375505aa641d856b89c6340501d8c62653c1bb613aa1920d9115ae56566e1d2866a454515d2a6b639df94856 + storybook: ^8.5.0 + checksum: 6a9b1f1b4d5db5c2f9a21477c3708f4d80e4dae4a33acee0c3130d698664ed180da4ae426708f0a929ba5b4a7daa9ed3d9d9273ca21b898abd535d56e39d701c languageName: node linkType: hard -"@storybook/addon-controls@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/addon-controls@npm:8.4.7" +"@storybook/addon-controls@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/addon-controls@npm:8.5.0" dependencies: "@storybook/global": ^5.0.0 dequal: ^2.0.2 ts-dedent: ^2.0.0 peerDependencies: - storybook: ^8.4.7 - checksum: 5d91916bfc767c119b4d938bf8ecaee8a8d661068f11b5f8d73027626340a972eaa503d8ef75c64aa61da4e8b846c3bc3f66529e75f76d830811ce04b17bc367 + storybook: ^8.5.0 + checksum: 8e370b6add3dbbe430f2f72fa9383d872ecbe9e88f0190b560c545620fc21292417d9a042c0aa496640f98b2e5621a46a34da23406848d86c25bf0638812a2de languageName: node linkType: hard -"@storybook/addon-docs@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/addon-docs@npm:8.4.7" +"@storybook/addon-docs@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/addon-docs@npm:8.5.0" dependencies: "@mdx-js/react": ^3.0.0 - "@storybook/blocks": 8.4.7 - "@storybook/csf-plugin": 8.4.7 - "@storybook/react-dom-shim": 8.4.7 + "@storybook/blocks": 8.5.0 + "@storybook/csf-plugin": 8.5.0 + "@storybook/react-dom-shim": 8.5.0 react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 ts-dedent: ^2.0.0 peerDependencies: - storybook: ^8.4.7 - checksum: b4442198f6931b8d1be15b3846898d1e1bda7c36f331f7f5b75dd36b69c1f22eee755633343ba78b7decc5aec45dd50a88375686b10462c0dc79fead9cf66309 + storybook: ^8.5.0 + checksum: 47f46a80929485a3941e69dfd3079cb7d43702e9a5080f788f0971637fc906af5b353446a89f2ebdaa501739a7e778803ad93fec8787de8ba493ebe6e802a725 languageName: node linkType: hard -"@storybook/addon-essentials@npm:^8.4.7": - version: 8.4.7 - resolution: "@storybook/addon-essentials@npm:8.4.7" +"@storybook/addon-essentials@npm:^8.5.0": + version: 8.5.0 + resolution: "@storybook/addon-essentials@npm:8.5.0" dependencies: - "@storybook/addon-actions": 8.4.7 - "@storybook/addon-backgrounds": 8.4.7 - "@storybook/addon-controls": 8.4.7 - "@storybook/addon-docs": 8.4.7 - "@storybook/addon-highlight": 8.4.7 - "@storybook/addon-measure": 8.4.7 - "@storybook/addon-outline": 8.4.7 - "@storybook/addon-toolbars": 8.4.7 - "@storybook/addon-viewport": 8.4.7 + "@storybook/addon-actions": 8.5.0 + "@storybook/addon-backgrounds": 8.5.0 + "@storybook/addon-controls": 8.5.0 + "@storybook/addon-docs": 8.5.0 + "@storybook/addon-highlight": 8.5.0 + "@storybook/addon-measure": 8.5.0 + "@storybook/addon-outline": 8.5.0 + "@storybook/addon-toolbars": 8.5.0 + "@storybook/addon-viewport": 8.5.0 ts-dedent: ^2.0.0 peerDependencies: - storybook: ^8.4.7 - checksum: d8731c18935fbc130beee7236b4e80c1621c6964a4109741512b50f065cd8d322446f8ecd84b4120ad1ce2ea829d0d3b5b764cca19c1bd8b73fc77d04dc13f17 + storybook: ^8.5.0 + checksum: 704f626070eeeb52029798eb5a191408fb87e4e284a97b4b17918cb7a945f25d805b2e704978829d5a3b5a4853b78aab068a18bbd5696ce8a80559fe778e0867 languageName: node linkType: hard -"@storybook/addon-highlight@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/addon-highlight@npm:8.4.7" +"@storybook/addon-highlight@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/addon-highlight@npm:8.5.0" dependencies: "@storybook/global": ^5.0.0 peerDependencies: - storybook: ^8.4.7 - checksum: 8b4ad5df3227441e3442d6c49a0be7b14b9b12f722897d45d90ee405ac791e549a41b30f9f6ab3c89f39c89294f7933449338ddcf3c4bdc1e6f3f42a48093826 + storybook: ^8.5.0 + checksum: f29f39e13d798da0f1ddf4f80d1853e44435380a2d1b5e9e4097dbd3ff948ac4fa45ae1f1f95605a7eaa8da5c972bda20dfe6696e989413e8f0449bb98ab3b80 languageName: node linkType: hard -"@storybook/addon-interactions@npm:^8.4.7": - version: 8.4.7 - resolution: "@storybook/addon-interactions@npm:8.4.7" +"@storybook/addon-interactions@npm:^8.5.0": + version: 8.5.0 + resolution: "@storybook/addon-interactions@npm:8.5.0" dependencies: "@storybook/global": ^5.0.0 - "@storybook/instrumenter": 8.4.7 - "@storybook/test": 8.4.7 + "@storybook/instrumenter": 8.5.0 + "@storybook/test": 8.5.0 polished: ^4.2.2 ts-dedent: ^2.2.0 peerDependencies: - storybook: ^8.4.7 - checksum: 9130d38a49cfc858b262faf19214eee83c1a89c012477821bd8371fa0ea952e3de0317d7e1bab78e4945a64b977bbf1b4092bbfe1d1d29bed5d6a117861cdba8 + storybook: ^8.5.0 + checksum: e34efa2baeb02728ddb8d878aff0d3c8f1f6c5f03db21224ad1710d62722aeef7155d828dfc37aa4edce8da2aafef7e824d0755e76f3fe432cc80a2abcc26bb8 languageName: node linkType: hard -"@storybook/addon-measure@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/addon-measure@npm:8.4.7" +"@storybook/addon-measure@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/addon-measure@npm:8.5.0" dependencies: "@storybook/global": ^5.0.0 tiny-invariant: ^1.3.1 peerDependencies: - storybook: ^8.4.7 - checksum: 16bc6a5ece783a99eb14cfd705c7664582b640dc81c0226d7f8e649c611082c00b348cc834b43cb5be26b478afc16f3673607f07cccdd04b6dfde7faae99c220 + storybook: ^8.5.0 + checksum: e0d976318b809fdd9573f323d43e4551fd3079c5f74f61dc666a9f2da00350b68e76a0f78c523d308c0165226f1721a41059abb4d9c650be2a002784a165ea59 languageName: node linkType: hard -"@storybook/addon-outline@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/addon-outline@npm:8.4.7" +"@storybook/addon-outline@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/addon-outline@npm:8.5.0" dependencies: "@storybook/global": ^5.0.0 ts-dedent: ^2.0.0 peerDependencies: - storybook: ^8.4.7 - checksum: caea4da246bca6d31a217b2f004daa416998c505648f9ed3768d094d4950d1b9f014da70e0f31b92cb6851e92e171cec55ebccf80600902019102c8c997c1228 + storybook: ^8.5.0 + checksum: 8d5323168932700a06e115c74c1c2a867bd32acbfc41d1cb2d0bde5fc4b0beaed5848136d8f2f9e7c4577c1a482cc88f6ddf312b842cea12eb8cc4b13f910018 languageName: node linkType: hard @@ -17270,73 +17270,72 @@ __metadata: languageName: node linkType: hard -"@storybook/addon-themes@npm:^8.4.7": - version: 8.4.7 - resolution: "@storybook/addon-themes@npm:8.4.7" +"@storybook/addon-themes@npm:^8.5.0": + version: 8.5.0 + resolution: "@storybook/addon-themes@npm:8.5.0" dependencies: ts-dedent: ^2.0.0 peerDependencies: - storybook: ^8.4.7 - checksum: a9ba38e765564b20691525d78354ee135c3722ffffb8735df21da8afa33a529f82dd59ae1041f21c3572bf066784f58529ab0a96e1414b2eb61d7916528f9ba1 + storybook: ^8.5.0 + checksum: bdfdd1813bcefe50873fa1d39aa53811596197ade52710cd3abeec0b2bcc2187b38b549ed1be8b7cddd7abf25c63fd00967439cba013c08720560dd3176c1380 languageName: node linkType: hard -"@storybook/addon-toolbars@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/addon-toolbars@npm:8.4.7" +"@storybook/addon-toolbars@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/addon-toolbars@npm:8.5.0" peerDependencies: - storybook: ^8.4.7 - checksum: e94f2a47d7c59f19f43b2f345a39e7a0071f80e2b9c636e82b814707b92c86578dba8e0b82a31a8f62806bbfb16d5491f3e784b6ed27a9b5af76c6723bcdfd59 + storybook: ^8.5.0 + checksum: 467d7914411b8be08591c6f91e3fb603ec3cd49eabb1a604cc3d9197337b17aad02d82a46d1b90d749b22066d49ec78c2b889ea4aa9e2e86e81b6af67851c993 languageName: node linkType: hard -"@storybook/addon-viewport@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/addon-viewport@npm:8.4.7" +"@storybook/addon-viewport@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/addon-viewport@npm:8.5.0" dependencies: memoizerific: ^1.11.3 peerDependencies: - storybook: ^8.4.7 - checksum: 87c4384293d2adea8a3267560fd72070d2185dfa5e4dc3c5325ccb412d6d5c0bcc6d2c825d92c83e099a8187bddcb7db7f635107308952ad120b057f1a099443 + storybook: ^8.5.0 + checksum: 974c28ce0a6ce69972192cbd2d0f62fd170e638804bc0680ab2045034491eee25877190033138e970d406dd3408a79f5a49c792a97d72cd8dac7fdc25559e25d languageName: node linkType: hard -"@storybook/addon-webpack5-compiler-swc@npm:^1.0.5": - version: 1.0.6 - resolution: "@storybook/addon-webpack5-compiler-swc@npm:1.0.6" +"@storybook/addon-webpack5-compiler-swc@npm:^2.0.0": + version: 2.0.0 + resolution: "@storybook/addon-webpack5-compiler-swc@npm:2.0.0" dependencies: "@swc/core": ^1.7.3 swc-loader: ^0.2.3 - checksum: c70389a664f1862d4939dca645e0f5680f759bcd15d629004a7a7186e2a96993a84f41535f63a610b753c1a625427bc5af5a0a7976f31f0aac81fb80f47523af + checksum: abc904d79fc0ddf1e9207d6012ebc4c9288f966a3821edd6c20732ee6c3c3a4cad44010090bd0d354386089fd59ff0f668e4305e2493ab582a1200b75a67baab languageName: node linkType: hard -"@storybook/blocks@npm:8.4.7, @storybook/blocks@npm:^8.4.7": - version: 8.4.7 - resolution: "@storybook/blocks@npm:8.4.7" +"@storybook/blocks@npm:8.5.0, @storybook/blocks@npm:^8.5.0": + version: 8.5.0 + resolution: "@storybook/blocks@npm:8.5.0" dependencies: - "@storybook/csf": ^0.1.11 + "@storybook/csf": 0.1.12 "@storybook/icons": ^1.2.12 ts-dedent: ^2.0.0 peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.4.7 + storybook: ^8.5.0 peerDependenciesMeta: react: optional: true react-dom: optional: true - checksum: 4fd794e48efd809dd7588c65f721f3d3674554d18ffef380a0b8431af654e995f54a4a2ab6e7a1b6f092ddf614a4701db361175912d61ece9599a1e1cbce6c83 + checksum: 6b0288254d4411320f79bdcc26719413e8bc71a63c0333af5cafb526a104c9b25b254ed65fc51760f6efe05120d846ae71b1b4a76b00262579440afa948e9155 languageName: node linkType: hard -"@storybook/builder-webpack5@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/builder-webpack5@npm:8.4.7" +"@storybook/builder-webpack5@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/builder-webpack5@npm:8.5.0" dependencies: - "@storybook/core-webpack": 8.4.7 - "@types/node": ^22.0.0 + "@storybook/core-webpack": 8.5.0 "@types/semver": ^7.3.4 browser-assert: ^1.2.1 case-sensitive-paths-webpack-plugin: ^2.4.0 @@ -17361,40 +17360,39 @@ __metadata: webpack-hot-middleware: ^2.25.1 webpack-virtual-modules: ^0.6.0 peerDependencies: - storybook: ^8.4.7 + storybook: ^8.5.0 peerDependenciesMeta: typescript: optional: true - checksum: e1a48c3b03a4bac12ec99d41de3107246f4940bf7c67f2294db49b3b4e92cb65166bfde32184372bde758a4386206b2013b2b56683e684a2f2d096814bd4120a + checksum: 21cc74a46e5bb70126e44df09173ae6d01756e7d4cb86ff70da72066f8543c73d424560ee47cec5b46157c487fb6e7c63220f48a133cef2088f3a32ef313dd7e languageName: node linkType: hard -"@storybook/components@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/components@npm:8.4.7" +"@storybook/components@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/components@npm:8.5.0" peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: e39fb81e8386db4f3f76cbf4f82e50512fed2f65a581951c0b61e00c9834c20cfff7f717e936353275dadfe6a25ffaac5d47151adbe1e3be85e709f8a64f6a15 + checksum: 2436cd632134a5e6e1733d2081898d41bd7ff328f30a4173d80a27f27b8e989d2b29ee70df4caae54040122381b376d461d9bbfc372d72e98cfceeaf60d0b724 languageName: node linkType: hard -"@storybook/core-webpack@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/core-webpack@npm:8.4.7" +"@storybook/core-webpack@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/core-webpack@npm:8.5.0" dependencies: - "@types/node": ^22.0.0 ts-dedent: ^2.0.0 peerDependencies: - storybook: ^8.4.7 - checksum: 9b89842f51f391502e8e466747f68bcfb285691d958b8cbf6e57e691108c9c199430d43be90a558987c88e95bff6daa5129af6ad0b785752e2538c09f0d1a438 + storybook: ^8.5.0 + checksum: 18e3551a3503316a738390f76bd68a6d119ab3bedf57d0ffa008d0d5823958856b2ed5f29e5a86d869202b10f9d78b7fcb5346c8c6a9c5950cf7f3571b7802c7 languageName: node linkType: hard -"@storybook/core@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/core@npm:8.4.7" +"@storybook/core@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/core@npm:8.5.0" dependencies: - "@storybook/csf": ^0.1.11 + "@storybook/csf": 0.1.12 better-opn: ^3.0.2 browser-assert: ^1.2.1 esbuild: ^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 @@ -17410,27 +17408,27 @@ __metadata: peerDependenciesMeta: prettier: optional: true - checksum: 969cde2203c9c2c744f2a1d2858b2adeb7d57fc75e703f64187fcf0056eb7da48d0507d919718c0866952dda084eb51d79e65a90abec8bb0dcb404b542a6872f + checksum: 362e48c30c4b08505d40d605fcf490a42b73069b1d297ef385ccadc16a481582d4a28c1518a50ced649a957cbc561d09e19be7d9cd5c69172a33e8012f99643d languageName: node linkType: hard -"@storybook/csf-plugin@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/csf-plugin@npm:8.4.7" +"@storybook/csf-plugin@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/csf-plugin@npm:8.5.0" dependencies: unplugin: ^1.3.1 peerDependencies: - storybook: ^8.4.7 - checksum: d9006d1a506796717528ee81948be89c8ca7e4a4ad463e024936d828b8e91e12940a41f054db4d5b1f1b058146113aaeb415eca87ca94142c3ef1ef501aead17 + storybook: ^8.5.0 + checksum: 4d81e94dbd7aea3dd7da36c9ec80559d5c5d3597aae3f3a59472b803fda3eb8a33c35dcc6a4d238f1daf1d39d1f135e0daa26d3d1b29feabf942b6138d3b5054 languageName: node linkType: hard -"@storybook/csf@npm:^0.1.11": - version: 0.1.11 - resolution: "@storybook/csf@npm:0.1.11" +"@storybook/csf@npm:0.1.12, @storybook/csf@npm:^0.1.11": + version: 0.1.12 + resolution: "@storybook/csf@npm:0.1.12" dependencies: type-fest: ^2.19.0 - checksum: ba2a265f62ad82a2853b069f77e974efe31bed263a640ca1dd8e6d7e194022018a67ad4a2587ae928f33ae45aaf6ffedd5925ba3fcf3fe5b7996667a918e22eb + checksum: 85ef7d481a2d3329b481013a7c16e847aa732b96aea74efd40f95a84a2692d19ab4260f9603028cd3369303d95210497ff1dfc8cd9d6a26ad76f82e676f34a80 languageName: node linkType: hard @@ -17451,24 +17449,24 @@ __metadata: languageName: node linkType: hard -"@storybook/instrumenter@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/instrumenter@npm:8.4.7" +"@storybook/instrumenter@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/instrumenter@npm:8.5.0" dependencies: "@storybook/global": ^5.0.0 "@vitest/utils": ^2.1.1 peerDependencies: - storybook: ^8.4.7 - checksum: 8e3316a42c172099b3a27bedbfde4da45e76d2f7508ff20e9a259322a35a52c69cc0fb74a3611d10f5963ff165e83c6ae0b78b1e0e5f77a1aa0d70ac16f7be83 + storybook: ^8.5.0 + checksum: a5d4fc43f797db0654b5207f4dd2512b2cbca02ca3d31872ced05955d4568d01e58adaaebf44db5c03414bfacf83b4de26fd1cd4dda00d20d2981cd526a098a2 languageName: node linkType: hard -"@storybook/manager-api@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/manager-api@npm:8.4.7" +"@storybook/manager-api@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/manager-api@npm:8.5.0" peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 2b826ec55de7ea0b5b5151dfa896f3e7eddfd36ede61f8a7ad14a37733d5d5645565f863dbde7e2272f1e9b5717f26de7802ae60e297a2647ee2c4c072ed3069 + checksum: d0e343579430e1896b6cf5fe5a81d5784a9fcad2725d6cc3012d05f6195c45cbe39bc5fcae21b334dd701cf6ab4d805261f513394687734e3a987906449f18d4 languageName: node linkType: hard @@ -17481,14 +17479,13 @@ __metadata: languageName: node linkType: hard -"@storybook/preset-react-webpack@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/preset-react-webpack@npm:8.4.7" +"@storybook/preset-react-webpack@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/preset-react-webpack@npm:8.5.0" dependencies: - "@storybook/core-webpack": 8.4.7 - "@storybook/react": 8.4.7 + "@storybook/core-webpack": 8.5.0 + "@storybook/react": 8.5.0 "@storybook/react-docgen-typescript-plugin": 1.0.6--canary.9.0c3f3b7.0 - "@types/node": ^22.0.0 "@types/semver": ^7.3.4 find-up: ^5.0.0 magic-string: ^0.30.5 @@ -17500,20 +17497,20 @@ __metadata: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.4.7 + storybook: ^8.5.0 peerDependenciesMeta: typescript: optional: true - checksum: 0e2a3934e0b1b225adaa12ab1a44822a8dd3b0ce23be0fbcf923bdf555e6ff3008123f55d31825548cbc4a5f76a7adadd3b4d933289aaa94f7d2e2404d761f51 + checksum: f14ebe8281e3a1296359fa59e0feff93bf9d2f67d9e6e16bdba1e00f3f979dc9abffdef1c3dc152b673e5edf6246f8613a5f27ad8ecf26cfcbfd0d9b594c3f99 languageName: node linkType: hard -"@storybook/preview-api@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/preview-api@npm:8.4.7" +"@storybook/preview-api@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/preview-api@npm:8.5.0" peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 1c467bb2c16c5998b9bc4c2c013e6786936d5f6a373ad8d8ab1beb626616c3187329fdfc3a709663b4af963c7e5789a1401166c6e2a3a66a12f66e858aa94e91 + checksum: 8e13270e970a36c935eb4ee926cfc8d187f33d55fc121793845c0b6e37eba7e2581b2dba5dea6bffb843d202f61f20aed71e6e9dceae3e5438b017acdb32609a languageName: node linkType: hard @@ -17535,86 +17532,85 @@ __metadata: languageName: node linkType: hard -"@storybook/react-dom-shim@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/react-dom-shim@npm:8.4.7" +"@storybook/react-dom-shim@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/react-dom-shim@npm:8.5.0" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.4.7 - checksum: 4de29cbb990bfb2f310440aa995b024faa93fd1d5c7c942d6d661d590693eb56e0567a141c14faca63e8b24fc2f6b6b44c02af37cd2d5b469c1129b0e78fc79d + storybook: ^8.5.0 + checksum: d41521f4d965c954fd9a6517ee334801db98c6558ca985d80ae6586bab07aa5f73986711d49429d329fdcb835dc04417b8721600df77a2341b7524e5e2cab9ab languageName: node linkType: hard -"@storybook/react-webpack5@npm:^8.4.7": - version: 8.4.7 - resolution: "@storybook/react-webpack5@npm:8.4.7" +"@storybook/react-webpack5@npm:^8.5.0": + version: 8.5.0 + resolution: "@storybook/react-webpack5@npm:8.5.0" dependencies: - "@storybook/builder-webpack5": 8.4.7 - "@storybook/preset-react-webpack": 8.4.7 - "@storybook/react": 8.4.7 - "@types/node": ^22.0.0 + "@storybook/builder-webpack5": 8.5.0 + "@storybook/preset-react-webpack": 8.5.0 + "@storybook/react": 8.5.0 peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.4.7 + storybook: ^8.5.0 typescript: ">= 4.2.x" peerDependenciesMeta: typescript: optional: true - checksum: 5be2491f5fe2525dee96a86d1fe7775bbaaf5352cfb538aa8e91bab7bdbea47597670761921436ea7041fc6277cb33ee680bad6efd7bfd84fc38d66942aa4001 + checksum: ea50f906c8f7ee2a786f25a41d1f29d624239447c0183e54641a361ac37b51e0423fdaa9807fdb488b3aa7518fcd41924766219b02c23112e6ce7bd8d49053b1 languageName: node linkType: hard -"@storybook/react@npm:8.4.7, @storybook/react@npm:^8.4.7": - version: 8.4.7 - resolution: "@storybook/react@npm:8.4.7" +"@storybook/react@npm:8.5.0, @storybook/react@npm:^8.5.0": + version: 8.5.0 + resolution: "@storybook/react@npm:8.5.0" dependencies: - "@storybook/components": 8.4.7 + "@storybook/components": 8.5.0 "@storybook/global": ^5.0.0 - "@storybook/manager-api": 8.4.7 - "@storybook/preview-api": 8.4.7 - "@storybook/react-dom-shim": 8.4.7 - "@storybook/theming": 8.4.7 + "@storybook/manager-api": 8.5.0 + "@storybook/preview-api": 8.5.0 + "@storybook/react-dom-shim": 8.5.0 + "@storybook/theming": 8.5.0 peerDependencies: - "@storybook/test": 8.4.7 + "@storybook/test": 8.5.0 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.4.7 + storybook: ^8.5.0 typescript: ">= 4.2.x" peerDependenciesMeta: "@storybook/test": optional: true typescript: optional: true - checksum: 5ad2137f8f5f0a34cb90e2582fd574aff888c544f0f9907472d930d4fb1f444124aa84188a0b5d661cc6c4c0bf7210b1e616a53b4be3f2df2479165571fa9085 + checksum: 8f37ce63666b85193acd61dc5dd1b8464445a7d5841e99c1887de287865a455868545eba78647a1c28a52c8b1232bdd1fdebfa2a339e6d027f723aa360d6c0bd languageName: node linkType: hard -"@storybook/test@npm:8.4.7, @storybook/test@npm:^8.4.7": - version: 8.4.7 - resolution: "@storybook/test@npm:8.4.7" +"@storybook/test@npm:8.5.0, @storybook/test@npm:^8.5.0": + version: 8.5.0 + resolution: "@storybook/test@npm:8.5.0" dependencies: - "@storybook/csf": ^0.1.11 + "@storybook/csf": 0.1.12 "@storybook/global": ^5.0.0 - "@storybook/instrumenter": 8.4.7 + "@storybook/instrumenter": 8.5.0 "@testing-library/dom": 10.4.0 "@testing-library/jest-dom": 6.5.0 "@testing-library/user-event": 14.5.2 "@vitest/expect": 2.0.5 "@vitest/spy": 2.0.5 peerDependencies: - storybook: ^8.4.7 - checksum: ef1147edb55be9770004d3194dcbeef650768659af0704cd50e9d14f3c647f28855270e5151e609775770c30bf5d42fbd33ad3bc383d25846bc1e5e9e4f490f7 + storybook: ^8.5.0 + checksum: 26b4978a93baf1e31deab709179c2c4181d2019eb249db55f8d48f387861cb48a7235a73dea90dece30491cc31067f51c5601f6d7227b2519b42db30c1cf648c languageName: node linkType: hard -"@storybook/theming@npm:8.4.7": - version: 8.4.7 - resolution: "@storybook/theming@npm:8.4.7" +"@storybook/theming@npm:8.5.0": + version: 8.5.0 + resolution: "@storybook/theming@npm:8.5.0" peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 47d29993c33bb29994d227af30e099579b7cf760652ed743020f5d7e5a5974f59a6ebeb1cc8995e6158da9cf768a8d2f559d1d819cc082d0bcdb056d85fdcb29 + checksum: d93b5d104c500b674353b53ba6d5b0ad86e83206e84587a44c2eac561713bccaf53017cbb6d498d540e8eb4a21781e6494a86a0d22246404bad2dc78f1f99df6 languageName: node linkType: hard @@ -43380,11 +43376,11 @@ __metadata: languageName: node linkType: hard -"storybook@npm:^8.4.7": - version: 8.4.7 - resolution: "storybook@npm:8.4.7" +"storybook@npm:^8.5.0": + version: 8.5.0 + resolution: "storybook@npm:8.5.0" dependencies: - "@storybook/core": 8.4.7 + "@storybook/core": 8.5.0 peerDependencies: prettier: ^2 || ^3 peerDependenciesMeta: @@ -43394,7 +43390,7 @@ __metadata: getstorybook: ./bin/index.cjs sb: ./bin/index.cjs storybook: ./bin/index.cjs - checksum: 6cd44f8d51b68f2c2363d5e996bc8bf1e47a5acc2050da351d0fb49acd3d99ed093eef1b148145a1af9c08ead8592a87ee569c4456941a37dc374f9f21ea45c3 + checksum: 1557e9397a663b21221cdbea616461897cd6a6a3387d8e5f8c0a6dd7a1b6c0f73ba358fbed9d5aa747375546be1694bf7b860d1240f10951c08bd5915fca3a12 languageName: node linkType: hard From 629fd78f1d31c414d8bd7c1147318dacb9b9d116 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 05:23:43 +0000 Subject: [PATCH 37/45] fix(deps): update dependency @module-federation/enhanced to v0.8.9 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 194 +++++++++++++++++++++++++++--------------------------- 1 file changed, 97 insertions(+), 97 deletions(-) diff --git a/yarn.lock b/yarn.lock index 7e21cdcad1..33fc09a872 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11458,39 +11458,39 @@ __metadata: languageName: node linkType: hard -"@module-federation/bridge-react-webpack-plugin@npm:0.8.7": - version: 0.8.7 - resolution: "@module-federation/bridge-react-webpack-plugin@npm:0.8.7" +"@module-federation/bridge-react-webpack-plugin@npm:0.8.9": + version: 0.8.9 + resolution: "@module-federation/bridge-react-webpack-plugin@npm:0.8.9" dependencies: - "@module-federation/sdk": 0.8.7 + "@module-federation/sdk": 0.8.9 "@types/semver": 7.5.8 semver: 7.6.3 - checksum: de47a0bc01830138afafffcb093886dec175f1bee8f0c6ffe3d62744de2e34b379afdc50193520b9613a7b942a0729d31d204c1f2e04044e85613527b2cbeeb5 + checksum: f731e42ca14af20634d5a3b41af12f0969a4df5d48270206524fe5a79f1603235a397419c9ba0343735f5c345b1ac67f5c88172d56ed58953b20beb40bea37a0 languageName: node linkType: hard -"@module-federation/data-prefetch@npm:0.8.7": - version: 0.8.7 - resolution: "@module-federation/data-prefetch@npm:0.8.7" +"@module-federation/data-prefetch@npm:0.8.9": + version: 0.8.9 + resolution: "@module-federation/data-prefetch@npm:0.8.9" dependencies: - "@module-federation/runtime": 0.8.7 - "@module-federation/sdk": 0.8.7 + "@module-federation/runtime": 0.8.9 + "@module-federation/sdk": 0.8.9 fs-extra: 9.1.0 peerDependencies: react: ">=16.9.0" react-dom: ">=16.9.0" - checksum: 208e9cb08de88bfd6fad13dbc97f582d3be6c3a7245dd963e82e35810bfa441d07aeb53f2a3cdee693146d21d9fdf7b7df550c9e138c417db285f0bd285376e2 + checksum: 1b1a078eec13cc0f7e9ca2f3346326c1e4f19cc899e124fd0c351ad717094b2772995660cd622f01a2eb22ad795bb03369699a2dabe0e23ebc7f26109d104458 languageName: node linkType: hard -"@module-federation/dts-plugin@npm:0.8.7": - version: 0.8.7 - resolution: "@module-federation/dts-plugin@npm:0.8.7" +"@module-federation/dts-plugin@npm:0.8.9": + version: 0.8.9 + resolution: "@module-federation/dts-plugin@npm:0.8.9" dependencies: - "@module-federation/error-codes": 0.8.7 - "@module-federation/managers": 0.8.7 - "@module-federation/sdk": 0.8.7 - "@module-federation/third-party-dts-extractor": 0.8.7 + "@module-federation/error-codes": 0.8.9 + "@module-federation/managers": 0.8.9 + "@module-federation/sdk": 0.8.9 + "@module-federation/third-party-dts-extractor": 0.8.9 adm-zip: ^0.5.10 ansi-colors: ^4.1.3 axios: ^1.7.4 @@ -11509,24 +11509,24 @@ __metadata: peerDependenciesMeta: vue-tsc: optional: true - checksum: 82d0bccb7f1f159b57faa55d80a5ebff411439271b5dc60823558118a9106a6dc9195f5b0cd23a6018533903f7bf5ee0578cfcb3d6f212a8c448e93c53b1570e + checksum: e5e09c08ca186559a06f539272d2ff9005cc24ee69dd207c992ce9fcf8496640cbbdd022574e8bbdd4d21df886d71975215c3c5b2ae946defaa33f2156cd8bc8 languageName: node linkType: hard "@module-federation/enhanced@npm:^0.8.0": - version: 0.8.7 - resolution: "@module-federation/enhanced@npm:0.8.7" + version: 0.8.9 + resolution: "@module-federation/enhanced@npm:0.8.9" dependencies: - "@module-federation/bridge-react-webpack-plugin": 0.8.7 - "@module-federation/data-prefetch": 0.8.7 - "@module-federation/dts-plugin": 0.8.7 - "@module-federation/error-codes": 0.8.7 - "@module-federation/inject-external-runtime-core-plugin": 0.8.7 - "@module-federation/managers": 0.8.7 - "@module-federation/manifest": 0.8.7 - "@module-federation/rspack": 0.8.7 - "@module-federation/runtime-tools": 0.8.7 - "@module-federation/sdk": 0.8.7 + "@module-federation/bridge-react-webpack-plugin": 0.8.9 + "@module-federation/data-prefetch": 0.8.9 + "@module-federation/dts-plugin": 0.8.9 + "@module-federation/error-codes": 0.8.9 + "@module-federation/inject-external-runtime-core-plugin": 0.8.9 + "@module-federation/managers": 0.8.9 + "@module-federation/manifest": 0.8.9 + "@module-federation/rspack": 0.8.9 + "@module-federation/runtime-tools": 0.8.9 + "@module-federation/sdk": 0.8.9 btoa: ^1.2.1 upath: 2.0.1 peerDependencies: @@ -11540,61 +11540,61 @@ __metadata: optional: true webpack: optional: true - checksum: 6438b023bc56dea85dad5c5b840778bda75508976bba7d922ae8692a331b1b98041f3cd8f4a8a1189ea21ec2731d061b97b6644e2611437e90411efe90abda50 + checksum: 12b0cafc428bc65e06cbab407e369fe24739923e1555005b6954cebc379c8c2f8c10758081e479293687fe6c2dba437204f3f8fe7f83545a565a80484c322f82 languageName: node linkType: hard -"@module-federation/error-codes@npm:0.8.7": - version: 0.8.7 - resolution: "@module-federation/error-codes@npm:0.8.7" - checksum: e2d0448a85881d824fd2ca3ef9a04523d06eff4dd421962e8f86c34ef46a1cd48602d917eac3d57ecdc22ee5373232bb599a2ab66bd48a9b50b885d01f77053c +"@module-federation/error-codes@npm:0.8.9": + version: 0.8.9 + resolution: "@module-federation/error-codes@npm:0.8.9" + checksum: afeacc27a133e7867db4df6f8245b5d5c465fdd2faaec8608aea2b34bd922f38f23c02ed6dcd95303dddb2735b8fcc1bc137a08d470e605ca17807b705053578 languageName: node linkType: hard -"@module-federation/inject-external-runtime-core-plugin@npm:0.8.7": - version: 0.8.7 - resolution: "@module-federation/inject-external-runtime-core-plugin@npm:0.8.7" +"@module-federation/inject-external-runtime-core-plugin@npm:0.8.9": + version: 0.8.9 + resolution: "@module-federation/inject-external-runtime-core-plugin@npm:0.8.9" peerDependencies: - "@module-federation/runtime-tools": 0.8.7 - checksum: 28e4e447a79bb0de2ae270e0aabacaf1f2533a9d30d5cc445a81bddc796895c1f1fe895da611689d18912e52a0aba1b4b605bb2f54d517e8ce81bf44e5d8f7ac + "@module-federation/runtime-tools": 0.8.9 + checksum: 77d8361051d54345d346cf3a3d967ed71d9a0bf83182a101c0dfa5c1f6593219521e80a8fb048a4a47e40a3fc4cd552e388728c853fcdcd52bb8870c7e345069 languageName: node linkType: hard -"@module-federation/managers@npm:0.8.7": - version: 0.8.7 - resolution: "@module-federation/managers@npm:0.8.7" +"@module-federation/managers@npm:0.8.9": + version: 0.8.9 + resolution: "@module-federation/managers@npm:0.8.9" dependencies: - "@module-federation/sdk": 0.8.7 + "@module-federation/sdk": 0.8.9 find-pkg: 2.0.0 fs-extra: 9.1.0 - checksum: 1b38ba2dcde89a268b2f4de8fcdc4ddbf2efd5c1f1ba641e58be2f72aa62b27584c01b46261dc65747301a238f4fafbc22ac2c8ede071e11172318084a3a8522 + checksum: b477542ad7faf02c5fce1c50a5d49ed704aebf11091c942c8cfc8615cced35efd1f3180afd0d281959a6deae792b0f6910d2da9187a83dd7761042a56dfd27b4 languageName: node linkType: hard -"@module-federation/manifest@npm:0.8.7": - version: 0.8.7 - resolution: "@module-federation/manifest@npm:0.8.7" +"@module-federation/manifest@npm:0.8.9": + version: 0.8.9 + resolution: "@module-federation/manifest@npm:0.8.9" dependencies: - "@module-federation/dts-plugin": 0.8.7 - "@module-federation/managers": 0.8.7 - "@module-federation/sdk": 0.8.7 + "@module-federation/dts-plugin": 0.8.9 + "@module-federation/managers": 0.8.9 + "@module-federation/sdk": 0.8.9 chalk: 3.0.0 find-pkg: 2.0.0 - checksum: 702b721308d938758f1d57d5f0d3ef42bc2005e5441f6ccfdc92c7171fa6fdfcbd268f7054dc530dbf3d60b187ba394c4bfa38f3ba17baede5a492ef67b845e3 + checksum: a5221fccfc626250f5256a656b55d693077076fcb0b88d103b97c6e4d18f4656752734f5966ded0ac1e1a5a501629d179d23017857da640af0fe46ad2fecdb33 languageName: node linkType: hard -"@module-federation/rspack@npm:0.8.7": - version: 0.8.7 - resolution: "@module-federation/rspack@npm:0.8.7" +"@module-federation/rspack@npm:0.8.9": + version: 0.8.9 + resolution: "@module-federation/rspack@npm:0.8.9" dependencies: - "@module-federation/bridge-react-webpack-plugin": 0.8.7 - "@module-federation/dts-plugin": 0.8.7 - "@module-federation/inject-external-runtime-core-plugin": 0.8.7 - "@module-federation/managers": 0.8.7 - "@module-federation/manifest": 0.8.7 - "@module-federation/runtime-tools": 0.8.7 - "@module-federation/sdk": 0.8.7 + "@module-federation/bridge-react-webpack-plugin": 0.8.9 + "@module-federation/dts-plugin": 0.8.9 + "@module-federation/inject-external-runtime-core-plugin": 0.8.9 + "@module-federation/managers": 0.8.9 + "@module-federation/manifest": 0.8.9 + "@module-federation/runtime-tools": 0.8.9 + "@module-federation/sdk": 0.8.9 peerDependencies: "@rspack/core": ">=0.7" typescript: ^4.9.0 || ^5.0.0 @@ -11604,17 +11604,17 @@ __metadata: optional: true vue-tsc: optional: true - checksum: 933adfab6b62885e57da33cf453d450eb7f7ce983e54e2ae10ef4cb1b8c3ce333fec41428defd389b722d5323b355e9c374d42db00fd51faf2609fb8cdc29511 + checksum: 027b759e0c2b05f55b50835e3dad556ad4b889885fe64de3ee55d62850d3cff7fa746f517f8bc127be3a6d5ac4b6d78bc5c639ed378afe29d3917e4dfef67a8a languageName: node linkType: hard -"@module-federation/runtime-core@npm:0.6.15": - version: 0.6.15 - resolution: "@module-federation/runtime-core@npm:0.6.15" +"@module-federation/runtime-core@npm:0.6.17": + version: 0.6.17 + resolution: "@module-federation/runtime-core@npm:0.6.17" dependencies: - "@module-federation/error-codes": 0.8.7 - "@module-federation/sdk": 0.8.7 - checksum: 6a2acbdfd2fdb39dc9c8e6e0edaf371437405eb25800709ed94b2a3c5f36e3918d77da6bd17751635a9fe9ead6993aa343d4a9590d0797d28d9c017293782865 + "@module-federation/error-codes": 0.8.9 + "@module-federation/sdk": 0.8.9 + checksum: c7da28d11137b1d0925461d77660b4e431b2a6f0d66e851f34609c9d7acf515f2db9d993b6c2e993e3721753fb2df8fcd4d0bf62ee7b84cde9367b3bfce873e9 languageName: node linkType: hard @@ -11628,13 +11628,13 @@ __metadata: languageName: node linkType: hard -"@module-federation/runtime-tools@npm:0.8.7": - version: 0.8.7 - resolution: "@module-federation/runtime-tools@npm:0.8.7" +"@module-federation/runtime-tools@npm:0.8.9": + version: 0.8.9 + resolution: "@module-federation/runtime-tools@npm:0.8.9" dependencies: - "@module-federation/runtime": 0.8.7 - "@module-federation/webpack-bundler-runtime": 0.8.7 - checksum: 39ccdcdd6a178a3be2a574fe38ac063096a3c39ef5dc5f772120134205134ab0617c5d7f979dcf25f4bff18ed0538923544bc402231b19cf39d1912440473b8b + "@module-federation/runtime": 0.8.9 + "@module-federation/webpack-bundler-runtime": 0.8.9 + checksum: d59ba1620d7da5ee8ce2b7cebc4f813efe202b3af1bce9a395409a17f08633f864c0980744069c378896ef5f8bfd68574110873d42d615c36b51bd36e2e48c74 languageName: node linkType: hard @@ -11647,14 +11647,14 @@ __metadata: languageName: node linkType: hard -"@module-federation/runtime@npm:0.8.7": - version: 0.8.7 - resolution: "@module-federation/runtime@npm:0.8.7" +"@module-federation/runtime@npm:0.8.9": + version: 0.8.9 + resolution: "@module-federation/runtime@npm:0.8.9" dependencies: - "@module-federation/error-codes": 0.8.7 - "@module-federation/runtime-core": 0.6.15 - "@module-federation/sdk": 0.8.7 - checksum: 033dff2955290c29023593314de463744bee0eacce0bc3d0d1b9223ce2356f5d92554703e9f46066b269f24e3f16caa0cb675362d0ad88dfd9cae3b2a0e8b54c + "@module-federation/error-codes": 0.8.9 + "@module-federation/runtime-core": 0.6.17 + "@module-federation/sdk": 0.8.9 + checksum: 20495c029401431c43f5c5744d83d18c2f8ecafcc97cb960c3973119ec9d8ae313c5ceacbd1ae181331ccf51afb4185a2c8cde6e81ae372d7fa2d42f5af9de0b languageName: node linkType: hard @@ -11665,23 +11665,23 @@ __metadata: languageName: node linkType: hard -"@module-federation/sdk@npm:0.8.7": - version: 0.8.7 - resolution: "@module-federation/sdk@npm:0.8.7" +"@module-federation/sdk@npm:0.8.9": + version: 0.8.9 + resolution: "@module-federation/sdk@npm:0.8.9" dependencies: isomorphic-rslog: 0.0.7 - checksum: d78fffcf5413877047e05cc82bdf31810ad46f669a9393105280e4eae7fcaf80aaf29dd239ce6c7be61b59e31e93d6531a9afc6f89e4c9dcdb6a35e5cf01f408 + checksum: 74effd5aa86b3c9d7a08efc435a8b0bb244010de05b8e84a6269931bc72d59a467654b57c7870380502a1999bcd22c5c138b3558ab26663f366b6cec6b0fe225 languageName: node linkType: hard -"@module-federation/third-party-dts-extractor@npm:0.8.7": - version: 0.8.7 - resolution: "@module-federation/third-party-dts-extractor@npm:0.8.7" +"@module-federation/third-party-dts-extractor@npm:0.8.9": + version: 0.8.9 + resolution: "@module-federation/third-party-dts-extractor@npm:0.8.9" dependencies: find-pkg: 2.0.0 fs-extra: 9.1.0 resolve: 1.22.8 - checksum: 50ba3a7b507029291567c0334b257c6ba7ffc502627da958ff2247571a01d013d209143fb76557c9817aa62cd56a4ede69663866742063c97d2e14b3decf22ec + checksum: d5e67563ff80a107ba811477271d12bbc5c89869a13499d278961c1f13947f6fb85d9f5316fc9017be77692d876e8b7304136d4154a313023486b521548abafd languageName: node linkType: hard @@ -11695,13 +11695,13 @@ __metadata: languageName: node linkType: hard -"@module-federation/webpack-bundler-runtime@npm:0.8.7": - version: 0.8.7 - resolution: "@module-federation/webpack-bundler-runtime@npm:0.8.7" +"@module-federation/webpack-bundler-runtime@npm:0.8.9": + version: 0.8.9 + resolution: "@module-federation/webpack-bundler-runtime@npm:0.8.9" dependencies: - "@module-federation/runtime": 0.8.7 - "@module-federation/sdk": 0.8.7 - checksum: a32718e9ce51a8cc0c5a822b92507cfe087a75d9a979d12e1158018cb6abc469e5aee6c7d18bd0ded786600232056c57fc589101bda4f54b1708c6549ead7864 + "@module-federation/runtime": 0.8.9 + "@module-federation/sdk": 0.8.9 + checksum: 1ca36a54976207682cbac0e74eae7ceb7be102aff5008ed5b5f77a8aeab7e74198cd4c26a7c521ab6b38d4e7c31c86115901aa69753e76094012d171a1f79e4d languageName: node linkType: hard From 438c36c55496b3e907f28d15a692bd09a902755c Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Sat, 21 Dec 2024 21:53:05 +0100 Subject: [PATCH 38/45] feat(notifications): added the topic filter for notifications Signed-off-by: Marek Libra --- .changeset/clean-squids-build.md | 6 ++ .../DatabaseNotificationsStore.test.ts | 48 +++++++++++++ .../database/DatabaseNotificationsStore.ts | 10 +++ .../src/database/NotificationsStore.ts | 12 ++++ .../src/service/router.ts | 68 +++++++++++++------ plugins/notifications/report.api.md | 25 +++++-- .../notifications/src/api/NotificationsApi.ts | 25 +++++-- .../src/api/NotificationsClient.test.ts | 51 ++++++++++++++ .../src/api/NotificationsClient.ts | 50 ++++++++++---- .../NotificationsFilters.tsx | 40 +++++++++++ .../NotificationsPage/NotificationsPage.tsx | 23 ++++++- 11 files changed, 310 insertions(+), 48 deletions(-) create mode 100644 .changeset/clean-squids-build.md diff --git a/.changeset/clean-squids-build.md b/.changeset/clean-squids-build.md new file mode 100644 index 0000000000..3c1e0b56a7 --- /dev/null +++ b/.changeset/clean-squids-build.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-notifications-backend': patch +'@backstage/plugin-notifications': patch +--- + +added topic filter for notifications diff --git a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts index e0536c50ac..d4edcd1e89 100644 --- a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts +++ b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts @@ -742,5 +742,53 @@ describe.each(databases.eachSupportedId())( expect(settings).toEqual(notificationSettings); }); }); + + describe('topics', () => { + it('should return all topics for user', async () => { + await storage.saveNotification(testNotification1); + await storage.saveNotification(testNotification2); + await storage.saveBroadcast(testNotification3); + await storage.saveNotification(otherUserNotification); + + const topics = await storage.getTopics({ user }); + expect(topics.topics.sort()).toEqual([ + testNotification1.payload.topic, + testNotification3.payload.topic, + testNotification2.payload.topic, + ]); + }); + + it('should return filtered topics for user by title', async () => { + await storage.saveNotification(testNotification1); + await storage.saveNotification(testNotification2); + await storage.saveBroadcast(testNotification3); + await storage.saveBroadcast(testNotification4); + await storage.saveNotification(otherUserNotification); + + const topics = await storage.getTopics({ + user, + search: 'Notification 3', + }); + expect(topics).toEqual({ + topics: [testNotification3.payload.topic], + }); + }); + + it('should return filtered topics for user by severity', async () => { + await storage.saveNotification(testNotification1); + await storage.saveNotification(testNotification2); + await storage.saveBroadcast(testNotification3); + await storage.saveBroadcast(testNotification4); + await storage.saveNotification(otherUserNotification); + + const topics = await storage.getTopics({ + user, + minimumSeverity: 'critical', + }); + expect(topics).toEqual({ + topics: [testNotification1.payload.topic], + }); + }); + }); }, ); diff --git a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts index 3a00d1c9b5..cf19874ff2 100644 --- a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts +++ b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts @@ -21,6 +21,7 @@ import { NotificationGetOptions, NotificationModifyOptions, NotificationsStore, + TopicGetOptions, } from './NotificationsStore'; import { Notification, @@ -563,4 +564,13 @@ export class DatabaseNotificationsStore implements NotificationsStore { .delete(); await this.db('user_settings').insert(rows); } + + async getTopics(options: TopicGetOptions): Promise<{ topics: string[] }> { + const notificationQuery = this.getNotificationsBaseQuery({ + ...options, + orderField: [{ field: 'topic', order: 'asc' }], + }); + const topics = await notificationQuery.distinct(['topic']); + return { topics: topics.map(row => row.topic) }; + } } diff --git a/plugins/notifications-backend/src/database/NotificationsStore.ts b/plugins/notifications-backend/src/database/NotificationsStore.ts index 2560406e20..a1f4eb6999 100644 --- a/plugins/notifications-backend/src/database/NotificationsStore.ts +++ b/plugins/notifications-backend/src/database/NotificationsStore.ts @@ -48,6 +48,16 @@ export type NotificationModifyOptions = { ids: string[]; } & NotificationGetOptions; +/** @internal */ +export type TopicGetOptions = { + user: string; + search?: string; + read?: boolean; + saved?: boolean; + createdAfter?: Date; + minimumSeverity?: NotificationSeverity; +}; + /** @internal */ export interface NotificationsStore { getNotifications(options: NotificationGetOptions): Promise; @@ -97,4 +107,6 @@ export interface NotificationsStore { user: string; settings: NotificationSettings; }): Promise; + + getTopics(options: TopicGetOptions): Promise<{ topics: string[] }>; } diff --git a/plugins/notifications-backend/src/service/router.ts b/plugins/notifications-backend/src/service/router.ts index 8a12a53be5..06a0a5ebb0 100644 --- a/plugins/notifications-backend/src/service/router.ts +++ b/plugins/notifications-backend/src/service/router.ts @@ -20,6 +20,7 @@ import { DatabaseNotificationsStore, normalizeSeverity, NotificationGetOptions, + TopicGetOptions, } from '../database'; import { v4 as uuid } from 'uuid'; import { CatalogApi } from '@backstage/catalog-client'; @@ -246,24 +247,10 @@ export async function createRouter( } }; - // TODO: Move to use OpenAPI router instead - const router = Router(); - router.use(express.json()); - - const listNotificationsHandler = async (req: Request, res: Response) => { - const user = await getUser(req); - const opts: NotificationGetOptions = { - user: user, - }; - if (req.query.offset) { - opts.offset = Number.parseInt(req.query.offset.toString(), 10); - } - if (req.query.limit) { - opts.limit = Number.parseInt(req.query.limit.toString(), 10); - } - if (req.query.orderField) { - opts.orderField = parseEntityOrderFieldParams(req.query); - } + const appendCommonOptions = ( + req: Request, + opts: NotificationGetOptions | TopicGetOptions, + ) => { if (req.query.search) { opts.search = req.query.search.toString(); } @@ -274,10 +261,6 @@ export async function createRouter( // or keep undefined } - if (req.query.topic) { - opts.topic = req.query.topic.toString(); - } - if (req.query.saved === 'true') { opts.saved = true; } else if (req.query.saved === 'false') { @@ -296,6 +279,32 @@ export async function createRouter( req.query.minimumSeverity.toString(), ); } + }; + + // TODO: Move to use OpenAPI router instead + const router = Router(); + router.use(express.json()); + + const listNotificationsHandler = async (req: Request, res: Response) => { + const user = await getUser(req); + const opts: NotificationGetOptions = { + user: user, + }; + if (req.query.offset) { + opts.offset = Number.parseInt(req.query.offset.toString(), 10); + } + if (req.query.limit) { + opts.limit = Number.parseInt(req.query.limit.toString(), 10); + } + if (req.query.orderField) { + opts.orderField = parseEntityOrderFieldParams(req.query); + } + + if (req.query.topic) { + opts.topic = req.query.topic.toString(); + } + + appendCommonOptions(req, opts); const [notifications, totalCount] = await Promise.all([ store.getNotifications(opts), @@ -357,6 +366,21 @@ export async function createRouter( res.json(notifications[0]); }; + // Get topics + const listTopicsHandler = async (req: Request, res: Response) => { + const user = await getUser(req); + const opts: TopicGetOptions = { + user: user, + }; + + appendCommonOptions(req, opts); + + const topics = await store.getTopics(opts); + res.json(topics); + }; + + router.get('/topics', listTopicsHandler); + // Make sure this is the last "GET" handler router.get('/:id', getNotificationHandler); // Deprecated endpoint router.get('/notifications/:id', getNotificationHandler); diff --git a/plugins/notifications/report.api.md b/plugins/notifications/report.api.md index 17c1e7e338..8b4553cf42 100644 --- a/plugins/notifications/report.api.md +++ b/plugins/notifications/report.api.md @@ -20,16 +20,21 @@ import { RouteRef } from '@backstage/core-plugin-api'; import { TableProps } from '@backstage/core-components'; // @public (undocumented) -export type GetNotificationsOptions = { - offset?: number; - limit?: number; +export type GetNotificationsCommonOptions = { search?: string; read?: boolean; saved?: boolean; createdAfter?: Date; + minimumSeverity?: NotificationSeverity; +}; + +// @public (undocumented) +export type GetNotificationsOptions = GetNotificationsCommonOptions & { + offset?: number; + limit?: number; sort?: 'created' | 'topic' | 'origin'; sortOrder?: 'asc' | 'desc'; - minimumSeverity?: NotificationSeverity; + topic?: string; }; // @public (undocumented) @@ -38,6 +43,14 @@ export type GetNotificationsResponse = { totalCount: number; }; +// @public (undocumented) +export type GetTopicsOptions = GetNotificationsCommonOptions; + +// @public (undocumented) +export type GetTopicsResponse = { + topics: string[]; +}; + // @public (undocumented) export interface NotificationsApi { // (undocumented) @@ -51,6 +64,8 @@ export interface NotificationsApi { // (undocumented) getStatus(): Promise; // (undocumented) + getTopics(options?: GetTopicsOptions): Promise; + // (undocumented) updateNotifications( options: UpdateNotificationsOptions, ): Promise; @@ -77,6 +92,8 @@ export class NotificationsClient implements NotificationsApi { // (undocumented) getStatus(): Promise; // (undocumented) + getTopics(options?: GetTopicsOptions): Promise; + // (undocumented) updateNotifications( options: UpdateNotificationsOptions, ): Promise; diff --git a/plugins/notifications/src/api/NotificationsApi.ts b/plugins/notifications/src/api/NotificationsApi.ts index e24e5cad64..6bf53c2864 100644 --- a/plugins/notifications/src/api/NotificationsApi.ts +++ b/plugins/notifications/src/api/NotificationsApi.ts @@ -27,18 +27,26 @@ export const notificationsApiRef = createApiRef({ }); /** @public */ -export type GetNotificationsOptions = { - offset?: number; - limit?: number; +export type GetNotificationsCommonOptions = { search?: string; read?: boolean; saved?: boolean; createdAfter?: Date; - sort?: 'created' | 'topic' | 'origin'; - sortOrder?: 'asc' | 'desc'; minimumSeverity?: NotificationSeverity; }; +/** @public */ +export type GetNotificationsOptions = GetNotificationsCommonOptions & { + offset?: number; + limit?: number; + sort?: 'created' | 'topic' | 'origin'; + sortOrder?: 'asc' | 'desc'; + topic?: string; +}; + +/** @public */ +export type GetTopicsOptions = GetNotificationsCommonOptions; + /** @public */ export type UpdateNotificationsOptions = { ids: string[]; @@ -52,6 +60,11 @@ export type GetNotificationsResponse = { totalCount: number; }; +/** @public */ +export type GetTopicsResponse = { + topics: string[]; +}; + /** @public */ export interface NotificationsApi { getNotifications( @@ -71,4 +84,6 @@ export interface NotificationsApi { updateNotificationSettings( settings: NotificationSettings, ): Promise; + + getTopics(options?: GetTopicsOptions): Promise; } diff --git a/plugins/notifications/src/api/NotificationsClient.test.ts b/plugins/notifications/src/api/NotificationsClient.test.ts index 8248ea9331..8856e529e6 100644 --- a/plugins/notifications/src/api/NotificationsClient.test.ts +++ b/plugins/notifications/src/api/NotificationsClient.test.ts @@ -22,6 +22,8 @@ import { Notification } from '@backstage/plugin-notifications-common'; const server = setupServer(); +const testTopic = 'test-topic'; + const testNotification: Partial = { user: 'user:default/john.doe', origin: 'plugin-test', @@ -29,6 +31,7 @@ const testNotification: Partial = { title: 'Notification 1', link: '/catalog', severity: 'normal', + topic: testTopic, }, }; @@ -75,6 +78,22 @@ describe('NotificationsClient', () => { expect(response).toEqual(expectedResp); }); + it('should fetch notifications of the topic', async () => { + server.use( + rest.get(`${mockBaseUrl}/notifications`, (req, res, ctx) => { + expect(req.url.search).toBe(`?limit=10&offset=0&topic=${testTopic}`); + return res(ctx.json(expectedResp)); + }), + ); + + const response = await client.getNotifications({ + limit: 10, + offset: 0, + topic: testTopic, + }); + expect(response).toEqual(expectedResp); + }); + it('should omit unselected fetch options', async () => { server.use( rest.get(`${mockBaseUrl}/notifications`, (req, res, ctx) => { @@ -131,4 +150,36 @@ describe('NotificationsClient', () => { expect(response).toEqual(expectedResp); }); }); + + describe('getTopics', () => { + const expectedResp = [testTopic]; + + it('should fetch topics from correct endpoint', async () => { + server.use( + rest.get(`${mockBaseUrl}/topics`, (_, res, ctx) => + res(ctx.json(expectedResp)), + ), + ); + const response = await client.getTopics(); + expect(response).toEqual(expectedResp); + }); + + it('should fetch topics with options', async () => { + server.use( + rest.get(`${mockBaseUrl}/topics`, (req, res, ctx) => { + expect(req.url.search).toBe( + '?search=find+me&read=true&createdAfter=1970-01-01T00%3A00%3A00.005Z', + ); + return res(ctx.json(expectedResp)); + }), + ); + + const response = await client.getTopics({ + search: 'find me', + read: true, + createdAfter: new Date(5), + }); + expect(response).toEqual(expectedResp); + }); + }); }); diff --git a/plugins/notifications/src/api/NotificationsClient.ts b/plugins/notifications/src/api/NotificationsClient.ts index 5e763ecb8c..002553ad22 100644 --- a/plugins/notifications/src/api/NotificationsClient.ts +++ b/plugins/notifications/src/api/NotificationsClient.ts @@ -14,8 +14,11 @@ * limitations under the License. */ import { + GetNotificationsCommonOptions, GetNotificationsOptions, GetNotificationsResponse, + GetTopicsOptions, + GetTopicsResponse, NotificationsApi, UpdateNotificationsOptions, } from './NotificationsApi'; @@ -56,20 +59,11 @@ export class NotificationsClient implements NotificationsApi { `${options.sort},${options?.sortOrder ?? 'desc'}`, ); } - if (options?.search) { - queryString.append('search', options.search); - } - if (options?.read !== undefined) { - queryString.append('read', options.read ? 'true' : 'false'); - } - if (options?.saved !== undefined) { - queryString.append('saved', options.saved ? 'true' : 'false'); - } - if (options?.createdAfter !== undefined) { - queryString.append('createdAfter', options.createdAfter.toISOString()); - } - if (options?.minimumSeverity !== undefined) { - queryString.append('minimumSeverity', options.minimumSeverity); + + this.appendCommonQueryStrings(queryString, options); + + if (options?.topic !== undefined) { + queryString.append('topic', options.topic); } return await this.request( @@ -111,6 +105,34 @@ export class NotificationsClient implements NotificationsApi { }); } + async getTopics(options?: GetTopicsOptions): Promise { + const queryString = new URLSearchParams(); + this.appendCommonQueryStrings(queryString, options); + + return await this.request(`/topics?${queryString}`); + } + + private appendCommonQueryStrings( + queryString: URLSearchParams, + options?: GetNotificationsCommonOptions, + ) { + if (options?.search) { + queryString.append('search', options.search); + } + if (options?.read !== undefined) { + queryString.append('read', options.read ? 'true' : 'false'); + } + if (options?.saved !== undefined) { + queryString.append('saved', options.saved ? 'true' : 'false'); + } + if (options?.createdAfter !== undefined) { + queryString.append('createdAfter', options.createdAfter.toISOString()); + } + if (options?.minimumSeverity !== undefined) { + queryString.append('minimumSeverity', options.minimumSeverity); + } + } + private async request(path: string, init?: RequestInit): Promise { const baseUrl = await this.discoveryApi.getBaseUrl('notifications'); const res = await this.fetchApi.fetch(`${baseUrl}${path}`, init); diff --git a/plugins/notifications/src/components/NotificationsFilters/NotificationsFilters.tsx b/plugins/notifications/src/components/NotificationsFilters/NotificationsFilters.tsx index 0cb88bb3f8..300c56bdee 100644 --- a/plugins/notifications/src/components/NotificationsFilters/NotificationsFilters.tsx +++ b/plugins/notifications/src/components/NotificationsFilters/NotificationsFilters.tsx @@ -40,8 +40,13 @@ export type NotificationsFiltersProps = { onSavedChanged: (checked: boolean | undefined) => void; severity: NotificationSeverity; onSeverityChanged: (severity: NotificationSeverity) => void; + topic?: string; + onTopicChanged: (value: string | undefined) => void; + allTopics?: string[]; }; +const ALL = '___all___'; + export const CreatedAfterOptions: { [key: string]: { label: string; getDate: () => Date }; } = { @@ -127,6 +132,9 @@ export const NotificationsFilters = ({ onSavedChanged, severity, onSeverityChanged, + topic, + onTopicChanged, + allTopics, }: NotificationsFiltersProps) => { const sortByText = getSortByText(sorting); @@ -180,6 +188,15 @@ export const NotificationsFilters = ({ onSeverityChanged(value); }; + const handleOnTopicChanged = ( + event: React.ChangeEvent<{ name?: string; value: unknown }>, + ) => { + const value = event.target.value as string; + onTopicChanged(value === ALL ? undefined : value); + }; + + const sortedAllTopics = (allTopics || []).sort((a, b) => a.localeCompare(b)); + return ( <> @@ -265,6 +282,29 @@ export const NotificationsFilters = ({ + + + + Topic + + + + ); diff --git a/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx b/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx index 0d48a064e5..194033a697 100644 --- a/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx +++ b/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx @@ -33,7 +33,11 @@ import { SortBy, SortByOptions, } from '../NotificationsFilters'; -import { GetNotificationsOptions, GetNotificationsResponse } from '../../api'; +import { + GetNotificationsOptions, + GetNotificationsResponse, + GetTopicsResponse, +} from '../../api'; import { NotificationSeverity, NotificationStatus, @@ -76,9 +80,10 @@ export const NotificationsPage = (props?: NotificationsPageProps) => { SortByOptions.newest.sortBy, ); const [severity, setSeverity] = React.useState('low'); + const [topic, setTopic] = React.useState(); const { error, value, retry, loading } = useNotificationsApi< - [GetNotificationsResponse, NotificationStatus] + [GetNotificationsResponse, NotificationStatus, GetTopicsResponse] >( api => { const options: GetNotificationsOptions = { @@ -94,13 +99,20 @@ export const NotificationsPage = (props?: NotificationsPageProps) => { if (saved !== undefined) { options.saved = saved; } + if (topic !== undefined) { + options.topic = topic; + } const createdAfterDate = CreatedAfterOptions[createdAfter].getDate(); if (createdAfterDate.valueOf() > 0) { options.createdAfter = createdAfterDate; } - return Promise.all([api.getNotifications(options), api.getStatus()]); + return Promise.all([ + api.getNotifications(options), + api.getStatus(), + api.getTopics(options), + ]); }, [ containsText, @@ -111,6 +123,7 @@ export const NotificationsPage = (props?: NotificationsPageProps) => { sorting, saved, severity, + topic, ], ); @@ -143,6 +156,7 @@ export const NotificationsPage = (props?: NotificationsPageProps) => { const notifications = value?.[0]?.notifications; const totalCount = value?.[0]?.totalCount; const isUnread = !!value?.[1]?.unread; + const allTopics = value?.[2]?.topics; let tableTitle = `All notifications (${totalCount})`; if (saved) { @@ -177,6 +191,9 @@ export const NotificationsPage = (props?: NotificationsPageProps) => { onSavedChanged={setSaved} severity={severity} onSeverityChanged={setSeverity} + topic={topic} + onTopicChanged={setTopic} + allTopics={allTopics} /> From a68c0b0ec38232e56e3c6aa9298bc86052ef3814 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 16 Jan 2025 08:55:40 +0100 Subject: [PATCH 39/45] feat: ensure that refresh_state_references are cleaned Signed-off-by: blam --- .../src/database/DefaultProviderDatabase.ts | 1 - .../src/tests/integration.test.ts | 164 ++++++++++++++++-- 2 files changed, 149 insertions(+), 16 deletions(-) diff --git a/plugins/catalog-backend/src/database/DefaultProviderDatabase.ts b/plugins/catalog-backend/src/database/DefaultProviderDatabase.ts index 03f72e06b7..a078b578e4 100644 --- a/plugins/catalog-backend/src/database/DefaultProviderDatabase.ts +++ b/plugins/catalog-backend/src/database/DefaultProviderDatabase.ts @@ -165,7 +165,6 @@ export class DefaultProviderDatabase implements ProviderDatabase { await tx('refresh_state_references') .where('target_entity_ref', entityRef) - .andWhere({ source_key: options.sourceKey }) .delete(); if (ok) { diff --git a/plugins/catalog-backend/src/tests/integration.test.ts b/plugins/catalog-backend/src/tests/integration.test.ts index e85ebe48ce..5aa1a03f6e 100644 --- a/plugins/catalog-backend/src/tests/integration.test.ts +++ b/plugins/catalog-backend/src/tests/integration.test.ts @@ -31,7 +31,7 @@ import { } from '@backstage/plugin-catalog-node'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { createHash } from 'crypto'; -import { Knex } from 'knex'; +import knexFactory, { Knex } from 'knex'; import { EntitiesCatalog } from '../catalog/types'; import { DefaultCatalogDatabase } from '../database/DefaultCatalogDatabase'; import { DefaultProcessingDatabase } from '../database/DefaultProcessingDatabase'; @@ -55,6 +55,7 @@ import { mockServices } from '@backstage/backend-test-utils'; import { LoggerService } from '@backstage/backend-plugin-api'; import { DatabaseManager } from '@backstage/backend-common'; import { entitiesResponseToObjects } from '../service/response'; +import { DbRefreshStateReferencesRow } from '../database/tables'; const voidLogger = mockServices.logger.mock(); @@ -194,7 +195,7 @@ class TestHarness { readonly #catalog: EntitiesCatalog; readonly #engine: CatalogProcessingEngine; readonly #refresh: RefreshService; - readonly #provider: TestProvider; + readonly #providers: TestProvider[]; readonly #proxyProgressTracker: ProxyProgressTracker; static async create(options?: { @@ -202,6 +203,7 @@ class TestHarness { logger?: LoggerService; db?: Knex; permissions?: PermissionEvaluator; + providers?: TestProvider[]; processEntity?( entity: Entity, location: LocationSpec, @@ -300,9 +302,8 @@ class TestHarness { const refresh = new DefaultRefreshService({ database: catalogDatabase }); - const provider = new TestProvider(); - - await connectEntityProviders(providerDatabase, [provider]); + const providers = options?.providers ?? [new TestProvider()]; + await connectEntityProviders(providerDatabase, providers); return new TestHarness( catalog, @@ -317,7 +318,7 @@ class TestHarness { }, }, refresh, - provider, + providers, proxyProgressTracker, ); } @@ -326,13 +327,13 @@ class TestHarness { catalog: EntitiesCatalog, engine: CatalogProcessingEngine, refresh: RefreshService, - provider: TestProvider, + providers: TestProvider[], proxyProgressTracker: ProxyProgressTracker, ) { this.#catalog = catalog; this.#engine = engine; this.#refresh = refresh; - this.#provider = provider; + this.#providers = providers; this.#proxyProgressTracker = proxyProgressTracker; } @@ -353,13 +354,15 @@ class TestHarness { } async setInputEntities(entities: (Entity & { locationKey?: string })[]) { - return this.#provider.getConnection().applyMutation({ - type: 'full', - entities: entities.map(({ locationKey, ...entity }) => ({ - entity, - locationKey, - })), - }); + for (const provider of this.#providers) { + await provider.getConnection().applyMutation({ + type: 'full', + entities: entities.map(({ locationKey, ...entity }) => ({ + entity, + locationKey, + })), + }); + } } async getOutputEntities(): Promise> { @@ -838,4 +841,135 @@ describe('Catalog Backend Integration', () => { }, }); }); + + it('should replace any refresh_state_references that are dangling after claiming an entityRef with locationKey', async () => { + const db = knexFactory({ + client: 'better-sqlite3', + connection: ':memory:', + useNullAsDefault: true, + }); + + const firstProvider = new TestProvider(); + firstProvider.getProviderName = () => 'first'; + + const secondProvider = new TestProvider(); + secondProvider.getProviderName = () => 'second'; + + const harness = await TestHarness.create({ + providers: [firstProvider, secondProvider], + db, + }); + + await firstProvider.getConnection().applyMutation({ + type: 'full', + entities: [ + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'component-1', + annotations: { + 'backstage.io/managed-by-location': 'url:.', + 'backstage.io/managed-by-origin-location': 'url:.', + }, + }, + spec: { + type: 'service', + owner: 'no-location-key', + }, + }, + }, + ], + }); + + await expect(harness.process()).resolves.toEqual({}); + + await expect(harness.getOutputEntities()).resolves.toEqual({ + 'component:default/component-1': expect.objectContaining({ + spec: { + type: 'service', + owner: 'no-location-key', + }, + }), + }); + + await secondProvider.getConnection().applyMutation({ + type: 'full', + entities: [ + { + locationKey: 'takeover', + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'component-1', + annotations: { + 'backstage.io/managed-by-location': 'url:.', + 'backstage.io/managed-by-origin-location': 'url:.', + }, + }, + spec: { + type: 'service', + owner: 'location-key', + }, + }, + }, + ], + }); + + await expect(harness.process()).resolves.toEqual({}); + + await expect(harness.getOutputEntities()).resolves.toEqual({ + 'component:default/component-1': expect.objectContaining({ + spec: { + type: 'service', + owner: 'location-key', + }, + }), + }); + + await expect( + db('refresh_state_references') + .where({ target_entity_ref: 'component:default/component-1' }) + .select(), + ).resolves.toEqual([ + expect.objectContaining({ + source_key: 'second', + target_entity_ref: 'component:default/component-1', + }), + ]); + + await secondProvider.getConnection().applyMutation({ + type: 'full', + entities: [ + { + locationKey: 'takeover', + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'component-2', + annotations: { + 'backstage.io/managed-by-location': 'url:.', + 'backstage.io/managed-by-origin-location': 'url:.', + }, + }, + spec: { + type: 'service', + owner: 'location-key', + }, + }, + }, + ], + }); + + await expect(harness.process()).resolves.toEqual({}); + + await expect( + db('refresh_state_references') + .where({ target_entity_ref: 'component:default/component-1' }) + .select(), + ).resolves.toEqual([]); + }); }); From 22dfc634047af9da6e3568af7fd9e1c87ab323d4 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 16 Jan 2025 08:57:14 +0100 Subject: [PATCH 40/45] chore: add another assert to check output entities Signed-off-by: blam --- plugins/catalog-backend/src/tests/integration.test.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/plugins/catalog-backend/src/tests/integration.test.ts b/plugins/catalog-backend/src/tests/integration.test.ts index 5aa1a03f6e..3ef45c024d 100644 --- a/plugins/catalog-backend/src/tests/integration.test.ts +++ b/plugins/catalog-backend/src/tests/integration.test.ts @@ -971,5 +971,14 @@ describe('Catalog Backend Integration', () => { .where({ target_entity_ref: 'component:default/component-1' }) .select(), ).resolves.toEqual([]); + + await expect(harness.getOutputEntities()).resolves.toEqual({ + 'component:default/component-2': expect.objectContaining({ + spec: { + type: 'service', + owner: 'location-key', + }, + }), + }); }); }); From f178b129b4e8d4d79f1da43011376fbd69943acf Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 16 Jan 2025 08:58:12 +0100 Subject: [PATCH 41/45] chore: changeset Signed-off-by: blam --- .changeset/fair-mangos-sleep.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fair-mangos-sleep.md diff --git a/.changeset/fair-mangos-sleep.md b/.changeset/fair-mangos-sleep.md new file mode 100644 index 0000000000..e2b24d441d --- /dev/null +++ b/.changeset/fair-mangos-sleep.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Cleanup `refresh_state_references` for providers that are no longer in control of a `refresh_state` row for entity From ad39179ce73b9bea83cc5d58b1961c9734c384db Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 16 Jan 2025 09:09:59 +0100 Subject: [PATCH 42/45] chore: only remove if ok Signed-off-by: blam Signed-off-by: blam --- .../catalog-backend/src/database/DefaultProviderDatabase.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/catalog-backend/src/database/DefaultProviderDatabase.ts b/plugins/catalog-backend/src/database/DefaultProviderDatabase.ts index a078b578e4..9ec5d13591 100644 --- a/plugins/catalog-backend/src/database/DefaultProviderDatabase.ts +++ b/plugins/catalog-backend/src/database/DefaultProviderDatabase.ts @@ -165,9 +165,14 @@ export class DefaultProviderDatabase implements ProviderDatabase { await tx('refresh_state_references') .where('target_entity_ref', entityRef) + .andWhere({ source_key: options.sourceKey }) .delete(); if (ok) { + await tx('refresh_state_references') + .where('target_entity_ref', entityRef) + .delete(); + await tx( 'refresh_state_references', ).insert({ From 67bf279c8cf1416aa0e2fe731600c03ae1cdf1b9 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 16 Jan 2025 10:30:34 +0100 Subject: [PATCH 43/45] chore: refactor tests a little bit Signed-off-by: blam --- .../src/tests/integration.test.ts | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/plugins/catalog-backend/src/tests/integration.test.ts b/plugins/catalog-backend/src/tests/integration.test.ts index 3ef45c024d..bc4a68e5c3 100644 --- a/plugins/catalog-backend/src/tests/integration.test.ts +++ b/plugins/catalog-backend/src/tests/integration.test.ts @@ -195,7 +195,7 @@ class TestHarness { readonly #catalog: EntitiesCatalog; readonly #engine: CatalogProcessingEngine; readonly #refresh: RefreshService; - readonly #providers: TestProvider[]; + readonly #provider: TestProvider; readonly #proxyProgressTracker: ProxyProgressTracker; static async create(options?: { @@ -203,7 +203,7 @@ class TestHarness { logger?: LoggerService; db?: Knex; permissions?: PermissionEvaluator; - providers?: TestProvider[]; + additionalProviders?: EntityProvider[]; processEntity?( entity: Entity, location: LocationSpec, @@ -302,7 +302,13 @@ class TestHarness { const refresh = new DefaultRefreshService({ database: catalogDatabase }); - const providers = options?.providers ?? [new TestProvider()]; + const provider = new TestProvider(); + const providers: EntityProvider[] = [provider]; + + if (options?.additionalProviders) { + providers.push(...options.additionalProviders); + } + await connectEntityProviders(providerDatabase, providers); return new TestHarness( @@ -318,7 +324,7 @@ class TestHarness { }, }, refresh, - providers, + provider, proxyProgressTracker, ); } @@ -327,13 +333,13 @@ class TestHarness { catalog: EntitiesCatalog, engine: CatalogProcessingEngine, refresh: RefreshService, - providers: TestProvider[], + provider: TestProvider, proxyProgressTracker: ProxyProgressTracker, ) { this.#catalog = catalog; this.#engine = engine; this.#refresh = refresh; - this.#providers = providers; + this.#provider = provider; this.#proxyProgressTracker = proxyProgressTracker; } @@ -354,15 +360,13 @@ class TestHarness { } async setInputEntities(entities: (Entity & { locationKey?: string })[]) { - for (const provider of this.#providers) { - await provider.getConnection().applyMutation({ - type: 'full', - entities: entities.map(({ locationKey, ...entity }) => ({ - entity, - locationKey, - })), - }); - } + await this.#provider.getConnection().applyMutation({ + type: 'full', + entities: entities.map(({ locationKey, ...entity }) => ({ + entity, + locationKey, + })), + }); } async getOutputEntities(): Promise> { @@ -856,7 +860,7 @@ describe('Catalog Backend Integration', () => { secondProvider.getProviderName = () => 'second'; const harness = await TestHarness.create({ - providers: [firstProvider, secondProvider], + additionalProviders: [firstProvider, secondProvider], db, }); From 3c51a162370df38e7ecc9645616bf1b816452306 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 16 Jan 2025 13:38:05 +0100 Subject: [PATCH 44/45] chore: make a little more optimal Signed-off-by: blam --- .../src/database/DefaultProviderDatabase.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/plugins/catalog-backend/src/database/DefaultProviderDatabase.ts b/plugins/catalog-backend/src/database/DefaultProviderDatabase.ts index 9ec5d13591..60dd43bb59 100644 --- a/plugins/catalog-backend/src/database/DefaultProviderDatabase.ts +++ b/plugins/catalog-backend/src/database/DefaultProviderDatabase.ts @@ -162,12 +162,6 @@ export class DefaultProviderDatabase implements ProviderDatabase { logger: this.options.logger, }); } - - await tx('refresh_state_references') - .where('target_entity_ref', entityRef) - .andWhere({ source_key: options.sourceKey }) - .delete(); - if (ok) { await tx('refresh_state_references') .where('target_entity_ref', entityRef) @@ -180,6 +174,11 @@ export class DefaultProviderDatabase implements ProviderDatabase { target_entity_ref: entityRef, }); } else { + await tx('refresh_state_references') + .where('target_entity_ref', entityRef) + .andWhere({ source_key: options.sourceKey }) + .delete(); + const conflictingKey = await checkLocationKeyConflict({ tx, entityRef, From 11d0b7526afc737a977fc3a882e751e37350ddd9 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 16 Jan 2025 12:02:41 +0100 Subject: [PATCH 45/45] catalog-backend: fix catalog processing not replacing all existing references Signed-off-by: Patrik Oldsberg --- .changeset/fair-mangos-sleep.md | 2 +- .../src/database/DefaultProcessingDatabase.ts | 7 +- .../src/tests/integration.test.ts | 225 ++++++++++++++++-- 3 files changed, 208 insertions(+), 26 deletions(-) diff --git a/.changeset/fair-mangos-sleep.md b/.changeset/fair-mangos-sleep.md index e2b24d441d..8f0a6371c3 100644 --- a/.changeset/fair-mangos-sleep.md +++ b/.changeset/fair-mangos-sleep.md @@ -2,4 +2,4 @@ '@backstage/plugin-catalog-backend': patch --- -Cleanup `refresh_state_references` for providers that are no longer in control of a `refresh_state` row for entity +Cleanup `refresh_state_references` for entity processors and providers that are no longer in control of a `refresh_state` row for entity diff --git a/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts b/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts index ee836ff038..bb5c38de18 100644 --- a/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts +++ b/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts @@ -383,9 +383,12 @@ export class DefaultProcessingDatabase implements ProcessingDatabase { } } - // Replace all references for the originating entity or source and then create new ones + // Lastly, replace refresh state references for the originating entity and any successfully added entities await tx('refresh_state_references') - .andWhere({ source_entity_ref: options.sourceEntityRef }) + // Remove all existing references from the originating entity + .where({ source_entity_ref: options.sourceEntityRef }) + // And remove any existing references to entities that we're inserting new references for + .orWhereIn('target_entity_ref', stateReferences) .delete(); await tx.batchInsert( 'refresh_state_references', diff --git a/plugins/catalog-backend/src/tests/integration.test.ts b/plugins/catalog-backend/src/tests/integration.test.ts index bc4a68e5c3..0c8e475b39 100644 --- a/plugins/catalog-backend/src/tests/integration.test.ts +++ b/plugins/catalog-backend/src/tests/integration.test.ts @@ -31,7 +31,8 @@ import { } from '@backstage/plugin-catalog-node'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { createHash } from 'crypto'; -import knexFactory, { Knex } from 'knex'; +import { Knex } from 'knex'; +import merge from 'lodash/merge'; import { EntitiesCatalog } from '../catalog/types'; import { DefaultCatalogDatabase } from '../database/DefaultCatalogDatabase'; import { DefaultProcessingDatabase } from '../database/DefaultProcessingDatabase'; @@ -55,7 +56,7 @@ import { mockServices } from '@backstage/backend-test-utils'; import { LoggerService } from '@backstage/backend-plugin-api'; import { DatabaseManager } from '@backstage/backend-common'; import { entitiesResponseToObjects } from '../service/response'; -import { DbRefreshStateReferencesRow } from '../database/tables'; +import { deleteOrphanedEntities } from '../database/operations/util/deleteOrphanedEntities'; const voidLogger = mockServices.logger.mock(); @@ -197,6 +198,7 @@ class TestHarness { readonly #refresh: RefreshService; readonly #provider: TestProvider; readonly #proxyProgressTracker: ProxyProgressTracker; + readonly #db: Knex; static async create(options?: { disableRelationsCompatibility?: boolean; @@ -326,6 +328,7 @@ class TestHarness { refresh, provider, proxyProgressTracker, + db, ); } @@ -335,12 +338,14 @@ class TestHarness { refresh: RefreshService, provider: TestProvider, proxyProgressTracker: ProxyProgressTracker, + db: Knex, ) { this.#catalog = catalog; this.#engine = engine; this.#refresh = refresh; this.#provider = provider; this.#proxyProgressTracker = proxyProgressTracker; + this.#db = db; } async process(entityRefs?: Set) { @@ -382,6 +387,55 @@ class TestHarness { async refresh(options: RefreshOptions) { return this.#refresh.refresh(options); } + + async removeOrphanedEntities() { + await deleteOrphanedEntities({ + knex: this.#db, + strategy: { mode: 'immediate' }, + }); + } + + async getRefreshState(): Promise< + Record< + string, + { + id: string; + unprocessedEntity: Entity; + processedEntity: Entity; + locationKey: string | null; + } + > + > { + const result = await this.#db('refresh_state').select('*'); + return Object.fromEntries( + result.map(r => [ + r.entity_ref, + { + id: r.entity_id, + unprocessedEntity: JSON.parse(r.unprocessed_entity), + processedEntity: r.processed_entity + ? JSON.parse(r.processed_entity) + : undefined, + locationKey: r.location_key, + }, + ]), + ); + } + + async getRefreshStateReferences(): Promise< + Array<{ + sourceKey: string | null; + sourceEntityRef: string | null; + targetEntityRef: string; + }> + > { + const result = await this.#db('refresh_state_references').select('*'); + return result.map(r => ({ + sourceKey: r.source_key ?? undefined, + sourceEntityRef: r.source_entity_ref ?? undefined, + targetEntityRef: r.target_entity_ref, + })); + } } describe('Catalog Backend Integration', () => { @@ -847,12 +901,6 @@ describe('Catalog Backend Integration', () => { }); it('should replace any refresh_state_references that are dangling after claiming an entityRef with locationKey', async () => { - const db = knexFactory({ - client: 'better-sqlite3', - connection: ':memory:', - useNullAsDefault: true, - }); - const firstProvider = new TestProvider(); firstProvider.getProviderName = () => 'first'; @@ -861,7 +909,6 @@ describe('Catalog Backend Integration', () => { const harness = await TestHarness.create({ additionalProviders: [firstProvider, secondProvider], - db, }); await firstProvider.getConnection().applyMutation({ @@ -933,15 +980,11 @@ describe('Catalog Backend Integration', () => { }), }); - await expect( - db('refresh_state_references') - .where({ target_entity_ref: 'component:default/component-1' }) - .select(), - ).resolves.toEqual([ - expect.objectContaining({ - source_key: 'second', - target_entity_ref: 'component:default/component-1', - }), + await expect(harness.getRefreshStateReferences()).resolves.toEqual([ + { + sourceKey: 'second', + targetEntityRef: 'component:default/component-1', + }, ]); await secondProvider.getConnection().applyMutation({ @@ -970,11 +1013,12 @@ describe('Catalog Backend Integration', () => { await expect(harness.process()).resolves.toEqual({}); - await expect( - db('refresh_state_references') - .where({ target_entity_ref: 'component:default/component-1' }) - .select(), - ).resolves.toEqual([]); + await expect(harness.getRefreshStateReferences()).resolves.toEqual([ + { + sourceKey: 'second', + targetEntityRef: 'component:default/component-2', + }, + ]); await expect(harness.getOutputEntities()).resolves.toEqual({ 'component:default/component-2': expect.objectContaining({ @@ -985,4 +1029,139 @@ describe('Catalog Backend Integration', () => { }), }); }); + + function withOutputFields(entity: Entity) { + return { + ...entity, + metadata: { + ...entity.metadata, + etag: expect.any(String), + uid: expect.any(String), + }, + relations: [], + }; + } + + it('should fully replace existing entities when emitting override entities during processing', async () => { + const baseEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + annotations: { + 'backstage.io/managed-by-location': 'url:.', + 'backstage.io/managed-by-origin-location': 'url:.', + }, + }, + }; + const entityA = merge({ metadata: { name: 'a' } }, baseEntity); + const entityB = merge({ metadata: { name: 'b' } }, baseEntity); + const entityBOverride = merge({ metadata: { override: true } }, entityB); + + const processEntity = jest.fn( + async ( + entity: Entity, + _location: LocationSpec, + _emit: CatalogProcessorEmit, + ) => entity, + ); + const harness = await TestHarness.create({ processEntity }); + + processEntity.mockImplementation(async (entity, location, emit) => { + if (entity.metadata.name === entityA.metadata.name) { + emit(processingResult.entity(location, entityBOverride)); + } + return entity; + }); + + // A and B are added to the catalog, but the processor emits B' from A that overrides B + await harness.setInputEntities([entityA, entityB]); + await expect(harness.process()).resolves.toEqual({}); + + // Expect to find A and B' in the catalog + await expect(harness.getRefreshStateReferences()).resolves.toEqual([ + { + sourceKey: 'test', + targetEntityRef: 'component:default/a', + }, + { + sourceEntityRef: 'component:default/a', + targetEntityRef: 'component:default/b', + }, + ]); + await expect(harness.getRefreshState()).resolves.toEqual({ + 'component:default/a': expect.objectContaining({ + locationKey: null, + unprocessedEntity: entityA, + }), + 'component:default/b': expect.objectContaining({ + locationKey: 'url:.', + unprocessedEntity: entityBOverride, + }), + }); + await expect(harness.getOutputEntities()).resolves.toEqual({ + 'component:default/a': withOutputFields(entityA), + 'component:default/b': withOutputFields(entityBOverride), + }); + + // Stop emitting B' from A, then do a full sync with A and B + processEntity.mockImplementation(async entity => entity); + await harness.setInputEntities([entityA, entityB]); + + // At this point we should still have A and B' in the catalog + await expect(harness.getRefreshStateReferences()).resolves.toEqual([ + { + sourceKey: 'test', + targetEntityRef: 'component:default/a', + }, + { + sourceEntityRef: 'component:default/a', + targetEntityRef: 'component:default/b', + }, + ]); + await expect(harness.getRefreshState()).resolves.toEqual({ + 'component:default/a': expect.objectContaining({ + locationKey: null, + unprocessedEntity: entityA, + }), + 'component:default/b': expect.objectContaining({ + locationKey: 'url:.', + unprocessedEntity: entityBOverride, + }), + }); + await expect(harness.getOutputEntities()).resolves.toEqual({ + 'component:default/a': withOutputFields(entityA), + 'component:default/b': withOutputFields(entityBOverride), + }); + + // Once we process, B' should be orphaned + await expect(harness.process()).resolves.toEqual({}); + // This is expected to remove B' + await harness.removeOrphanedEntities(); + + // At this point only A is left in the catalog + await expect(harness.getRefreshStateReferences()).resolves.toEqual([ + { + sourceKey: 'test', + targetEntityRef: 'component:default/a', + }, + ]); + await expect(harness.getRefreshState()).resolves.toEqual({ + 'component:default/a': expect.objectContaining({ + locationKey: null, + unprocessedEntity: entityA, + }), + }); + await expect(harness.getOutputEntities()).resolves.toEqual({ + 'component:default/a': withOutputFields(entityA), + }); + + // Next time the provider runs and does a full sync we should now be able to add back B + await harness.setInputEntities([entityA, entityB]); + await expect(harness.process()).resolves.toEqual({}); + + await expect(harness.getOutputEntities()).resolves.toEqual({ + 'component:default/a': withOutputFields(entityA), + 'component:default/b': withOutputFields(entityB), + }); + }); });