diff --git a/tools/workspace-plugin/project.json b/tools/workspace-plugin/project.json index d24ca7734cf7b8..f7546a2792eb49 100644 --- a/tools/workspace-plugin/project.json +++ b/tools/workspace-plugin/project.json @@ -71,5 +71,5 @@ "command": "node ./tools/workspace-plugin/scripts/check-dep-graph.js" } }, - "tags": ["platform:any", "tools"] + "tags": ["platform:node", "tools"] } diff --git a/tools/workspace-plugin/src/generators/migrate-converged-pkg/index.spec.ts b/tools/workspace-plugin/src/generators/migrate-converged-pkg/index.spec.ts index a4a87f5f5b018a..63e9f7db1253a8 100644 --- a/tools/workspace-plugin/src/generators/migrate-converged-pkg/index.spec.ts +++ b/tools/workspace-plugin/src/generators/migrate-converged-pkg/index.spec.ts @@ -243,7 +243,7 @@ describe('migrate-converged-pkg generator', () => { const tsConfigTest = getTsConfig.test(); expect(tsConfigMain).toEqual({ - extends: '../../../../../tsconfig.base.json', + extends: '../../tsconfig.base.json', compilerOptions: { importHelpers: true, isolatedModules: true, @@ -439,7 +439,7 @@ describe('migrate-converged-pkg generator', () => { */ module.exports = { displayName: 'react-dummy', - preset: '../../../../../jest.preset.js', + preset: '../../jest.preset.js', transform: { '^.+\\\\\\\\.tsx?$': [ 'ts-jest', @@ -585,10 +585,10 @@ describe('migrate-converged-pkg generator', () => { ); expect(tree.read(`${projectStorybookConfigPath}/main.js`)?.toString('utf-8')).toMatchInlineSnapshot(` - "const rootMain = require('../../../../../../.storybook/main'); + "const rootMain = require('../../../.storybook/main'); module.exports = - /** @type {Omit} */ ({ + /** @type {Omit} */ ({ ...rootMain, stories: [ ...rootMain.stories, @@ -608,7 +608,7 @@ describe('migrate-converged-pkg generator', () => { `); expect(tree.read(`${projectStorybookConfigPath}/preview.js`)?.toString('utf-8')).toMatchInlineSnapshot(` - "import * as rootPreview from '../../../../../../.storybook/preview'; + "import * as rootPreview from '../../../.storybook/preview'; /** @type {typeof rootPreview.decorators} */ export const decorators = [...rootPreview.decorators]; @@ -863,6 +863,7 @@ describe('migrate-converged-pkg generator', () => { it(`should update package npm scripts`, async () => { const projectConfig = readProjectConfiguration(tree, options.name); + const pkgJsonPath = `${projectConfig.root}/package.json`; updateJson(tree, pkgJsonPath, json => { json.scripts.docs = 'api-extractor run --config=config/api-extractor.local.json --local'; @@ -901,31 +902,30 @@ describe('migrate-converged-pkg generator', () => { just: 'just-scripts', lint: 'just-scripts lint', test: 'jest --passWithNoTests', - 'test-ssr': 'test-ssr "./stories/**/*.stories.tsx"', 'type-check': 'tsc -b tsconfig.json', }); - }); - describe(`export-maps`, () => { - function setup(config: { name: string; addModuleField?: boolean }) { - const addModuleField = config.addModuleField ?? true; - const projectConfig = readProjectConfiguration(tree, config.name); - const pkgJsonPath = `${projectConfig.root}/package.json`; + // verify test-ssr addition + updateProjectConfiguration(tree, options.name, { ...projectConfig, tags: ['platform:web'] }); + tree.write(joinPathFragments(projectConfig.root, 'stories/Foo/Foo.stories.tsx'), 'export {}'); - if (addModuleField) { - updateJson(tree, pkgJsonPath, json => { - json.module = './lib/index.js'; - return json; - }); - } + await generator(tree, options); - const getPackageJson = () => readJson(tree, pkgJsonPath); + pkgJson = readJson(tree, `${projectConfig.root}/package.json`); - return { getPackageJson }; - } + expect(pkgJson.scripts).toEqual( + expect.objectContaining({ + 'test-ssr': 'test-ssr "./stories/**/*.stories.tsx"', + }), + ); + }); + describe(`export-maps`, () => { it(`should update exports map`, async () => { - const { getPackageJson } = setup({ name: options.name }); + const { getPackageJson } = updatePackageJson(tree, { + projectName: options.name, + jsonUpdates: { module: './lib/index.js', style: './css/index.css' }, + }); let pkgJson = getPackageJson(); @@ -936,25 +936,28 @@ describe('migrate-converged-pkg generator', () => { pkgJson = getPackageJson(); expect(pkgJson.exports).toMatchInlineSnapshot(` - Object { - ".": Object { - "import": "./lib/index.js", - "node": "./lib-commonjs/index.js", - "require": "./lib-commonjs/index.js", - "types": "./dist/index.d.ts", - }, - "./package.json": "./package.json", - } - `); + Object { + ".": Object { + "import": "./lib/index.js", + "node": "./lib-commonjs/index.js", + "require": "./lib-commonjs/index.js", + "style": "./css/index.css", + "types": "./dist/index.d.ts", + }, + "./package.json": "./package.json", + } + `); }); it(`should update exports map based on main,module fields`, async () => { - const { getPackageJson } = setup({ name: options.name, addModuleField: false }); + const { getPackageJson } = updatePackageJson(tree, { + projectName: options.name, + }); await generator(tree, options); const pkgJson = getPackageJson(); - expect(pkgJson.exports['.'].module).toBe(undefined); + expect((pkgJson.exports?.['.'] as { import?: string }).import).toBe(undefined); }); }); @@ -989,27 +992,63 @@ describe('migrate-converged-pkg generator', () => { await generator(tree, options); expect(tree.exists(npmIgnoreConfigPath)).toBe(false); - let pkgJson = readJson(tree, `${projectConfig.root}/package.json`); + const pkgJson = readJson(tree, `${projectConfig.root}/package.json`); expect(pkgJson.files).toMatchInlineSnapshot(` Array [ - "lib", - "lib-commonjs", + "*.md", "dist/*.d.ts", + "lib-commonjs", ] `); + }); + it(`should set package.json#files bases on project setup`, async () => { + const projectConfig = readProjectConfiguration(tree, options.name); updateProjectConfiguration(tree, projectConfig.name!, { ...projectConfig, tags: ['ships-amd'] }); - await generator(tree, options); + let getPackageJson = updatePackageJson(tree, { + projectName: options.name, + jsonUpdates: { module: './lib/index.js', style: './sass/index.scss', bin: './bin/cli.js', storybook: {} }, + }).getPackageJson; - pkgJson = readJson(tree, `${projectConfig.root}/package.json`); + await generator(tree, options); - expect(pkgJson.files).toMatchInlineSnapshot(` + expect(getPackageJson().files).toMatchInlineSnapshot(` Array [ + "*.md", + "bin", + "dist/*.d.ts", "lib", - "lib-commonjs", "lib-amd", + "lib-commonjs", + "preset.js", + "sass", + ] + `); + + getPackageJson = updatePackageJson(tree, { + projectName: options.name, + jsonUpdates: { + module: './lib/index.js', + types: 'one/two/index.d.ts', + bin: { one: './bin/one.js', two: './bin-two/one.js' }, + }, + }).getPackageJson; + + await generator(tree, options); + + expect(getPackageJson().files).toMatchInlineSnapshot(` + Array [ + "*.md", + "bin", + "bin-two", "dist/*.d.ts", + "lib", + "lib-amd", + "lib-commonjs", + "one/two/*.d.ts", + "preset.js", + "sass", ] `); }); @@ -1268,6 +1307,27 @@ describe('migrate-converged-pkg generator', () => { } " `); + + // make sure it is added only once + + await generator(tree, options); + + expect(stripIndents`${tree.read(conformanceSetupPath, 'utf-8')}`).toEqual( + expect.stringContaining(stripIndents` + const defaultOptions: Partial> = { + tsConfig: { configName: 'tsconfig.spec.json' }, + componentPath: require.main?.filename.replace('.test', ''), + extraTests: griffelTests as TestObject, + testOptions: { + 'make-styles-overrides-win': { + callCount: 2, + }, + // TODO: https://github.com/microsoft/fluentui/issues/19618 + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any, + }; + `), + ); }); }); @@ -1654,3 +1714,17 @@ function assertAndReadNxJson(tree: Tree) { return nxJson; } + +function updatePackageJson(tree: Tree, config: { projectName: string; jsonUpdates?: Partial }) { + const projectConfig = readProjectConfiguration(tree, config.projectName); + const pkgJsonPath = `${projectConfig.root}/package.json`; + + updateJson(tree, pkgJsonPath, json => { + Object.assign(json, config.jsonUpdates); + return json; + }); + + const getPackageJson = () => readJson(tree, pkgJsonPath); + + return { getPackageJson }; +} diff --git a/tools/workspace-plugin/src/generators/migrate-converged-pkg/index.ts b/tools/workspace-plugin/src/generators/migrate-converged-pkg/index.ts index 086586e5217a37..1306afd92c5262 100644 --- a/tools/workspace-plugin/src/generators/migrate-converged-pkg/index.ts +++ b/tools/workspace-plugin/src/generators/migrate-converged-pkg/index.ts @@ -17,7 +17,6 @@ import { readNxJson, } from '@nx/devkit'; import path from 'path'; -import os from 'os'; import ts from 'typescript'; import { getTemplate, uniqueArray } from './lib/utils'; @@ -186,7 +185,7 @@ const templates = { return { main: () => { const tsConfig = { - extends: '../../../../../tsconfig.base.json', + extends: offsetFromRoot(options.projectConfig.root) + 'tsconfig.base.json', compilerOptions: { target: 'ES2019', // by default we gonna use tsc for type checking only @@ -294,6 +293,7 @@ const templates = { pkgName: string; addSnapshotSerializers: boolean; testSetupFilePath: string; + projectConfig: ProjectConfiguration; }) => stripIndents` // @ts-check @@ -302,7 +302,7 @@ const templates = { */ module.exports = { displayName: '${options.pkgName}', - preset: '../../../../../jest.preset.js', + preset: '${offsetFromRoot(options.projectConfig.root)}jest.preset.js', transform: { '^.+\\.tsx?$': [ 'ts-jest', @@ -317,11 +317,13 @@ const templates = { ${options.addSnapshotSerializers ? `snapshotSerializers: ['@griffel/jest-serializer'],` : ''} }; `, - storybook: { - main: stripIndents` - const rootMain = require('../../../../../../.storybook/main'); + storybook: (options: NormalizedSchema) => { + const rootOffsetPath = offsetFromRoot(options.paths.storybook.rootFolder); + return { + main: stripIndents` + const rootMain = require('${rootOffsetPath}.storybook/main'); - module.exports = /** @type {Omit} */ ({ + module.exports = /** @type {Omit} */ ({ ...rootMain, stories: [...rootMain.stories, '../stories/**/*.stories.mdx', '../stories/**/index.stories.@(ts|tsx)'], addons: [...rootMain.addons], @@ -334,8 +336,8 @@ const templates = { }, }); `, - preview: stripIndents` - import * as rootPreview from '../../../../../../.storybook/preview'; + preview: stripIndents` + import * as rootPreview from '${rootOffsetPath}.storybook/preview'; /** @type {typeof rootPreview.decorators} */ export const decorators = [...rootPreview.decorators]; @@ -343,15 +345,16 @@ const templates = { /** @type {typeof rootPreview.parameters} */ export const parameters = { ...rootPreview.parameters }; `, - tsconfig: { - extends: '../tsconfig.json', - compilerOptions: { - outDir: '', - allowJs: true, - checkJs: true, + tsconfig: { + extends: '../tsconfig.json', + compilerOptions: { + outDir: '', + allowJs: true, + checkJs: true, + }, + include: ['../stories/**/*.stories.ts', '../stories/**/*.stories.tsx', '*.js'], }, - include: ['../stories/**/*.stories.ts', '../stories/**/*.stories.tsx', '*.js'], - }, + }; }, cypress: { tsconfig: { @@ -364,47 +367,7 @@ const templates = { include: ['**/*.cy.ts', '**/*.cy.tsx'], }, }, - npmIgnoreConfig: - stripIndents` - .storybook/ - .vscode/ - bundle-size/ - config/ - coverage/ - docs/ - etc/ - node_modules/ - src/ - stories/ - dist/types/ - temp/ - __fixtures__ - __mocks__ - __tests__ - - *.api.json - *.log - *.spec.* - *.cy.* - *.test.* - *.yml - - # config files - *config.* - *rc.* - .editorconfig - .eslint* - .git* - .prettierignore - .swcrc - project.json - - # exclude gitignore patterns explicitly - !lib - !lib-commonjs - !lib-amd - !dist/*.d.ts - ` + os.EOL, + npmIgnoreConfig: ``, swcConfig: () => { return { $schema: 'https://json.schemastore.org/swcrc', @@ -693,12 +656,17 @@ function updatePackageJson(tree: Tree, options: NormalizedSchemaWithTsConfigs) { return tree; function setupScripts(json: PackageJson) { - const scripts = { + const packageType = getPackageType(tree, options); + const hasStories = shouldSetupStorybook(tree, options) === 'init'; + const scripts: Record = { test: 'jest --passWithNoTests', - 'test-ssr': 'test-ssr "./stories/**/*.stories.tsx"', 'type-check': 'tsc -b tsconfig.json', }; + if (packageType === 'web' && hasStories) { + scripts['test-ssr'] = 'test-ssr "./stories/**/*.stories.tsx"'; + } + json.scripts = json.scripts || {}; delete json.scripts['update-snapshots']; delete json.scripts['start-test']; @@ -708,7 +676,7 @@ function updatePackageJson(tree: Tree, options: NormalizedSchemaWithTsConfigs) { Object.assign(json.scripts, scripts); - if (getPackageType(tree, options) === 'node') { + if (packageType === 'node') { delete json.scripts.start; delete json.scripts.storybook; } @@ -720,6 +688,7 @@ function updatePackageJson(tree: Tree, options: NormalizedSchemaWithTsConfigs) { json.exports = { '.': { types: json.typings, + ...(json.style ? { style: normalizePackageEntryPointPaths(json.style) } : null), ...(json.main ? { node: normalizePackageEntryPointPaths(json.main) } : null), ...(json.module ? { import: normalizePackageEntryPointPaths(json.module) } : null), ...(json.main ? { require: normalizePackageEntryPointPaths(json.main) } : null), @@ -742,17 +711,70 @@ function updatePackageJson(tree: Tree, options: NormalizedSchemaWithTsConfigs) { } function setupNpmPublishFiles(json: PackageJson) { + const rootMarkdownPath = '*.md'; + const mainPath = json.main ? path.dirname(json.main) : null; + const modulePath = json.module ? path.dirname(json.module) : null; + const binPath = getBinFolders(json); + const stylesPath = json.style ? path.dirname(json.style) : null; + const typesPath = getTypes(json); + const storybookPath = getStorybookPath(json); + const amdPath = options.projectConfig.tags?.includes('ships-amd') ? 'lib-amd' : null; + const filesDefinition = [ + rootMarkdownPath, + mainPath, + modulePath, + ...(binPath ?? []), + stylesPath, + typesPath, + storybookPath, + amdPath, + ] + .filter(Boolean) + .map(value => { + return value?.replace('./', ''); + }) as string[]; + json.files = json.files ?? []; - json.files = [ - 'lib', - 'lib-commonjs', - options.projectConfig.tags?.includes('ships-amd') ? 'lib-amd' : '', - 'dist/*.d.ts', - ].filter(Boolean); + json.files = uniqueArray([...json.files, ...filesDefinition]).sort(); tree.delete(options.paths.npmConfig); return json; + + // helpers + + function getStorybookPath(pkgJson: PackageJson) { + return pkgJson.storybook || tree.exists(joinPathFragments(options.projectConfig.root, 'preset.js')) + ? 'preset.js' + : null; + } + + function getTypes(pkgJson: PackageJson) { + const typesDeclarationPath = pkgJson.types || pkgJson.typings; + return typesDeclarationPath ? globifyPath(path.dirname(typesDeclarationPath), 'd.ts') : null; + } + function getBinFolders(pkgJson: PackageJson) { + const bin = pkgJson.bin; + if (!bin) { + return null; + } + + if (typeof bin === 'string') { + return [path.dirname(bin)]; + } + + const binRootPaths = new Set(); + + for (const binDefinitionPath of Object.values(bin)) { + binRootPaths.add(path.dirname(binDefinitionPath)); + } + + return Array.from(binRootPaths); + } + + function globifyPath(value: string, extension: string) { + return `${value}/*.${extension}`; + } } } @@ -786,9 +808,10 @@ function setupStorybook(tree: Tree, options: NormalizedSchema) { const js = isJs(tree, options); if (sbAction === 'init') { - tree.write(options.paths.storybook.tsconfig, serializeJson(templates.storybook.tsconfig)); - tree.write(options.paths.storybook.main, templates.storybook.main); - tree.write(options.paths.storybook.preview, templates.storybook.preview); + const sbTemplates = templates.storybook(options); + tree.write(options.paths.storybook.tsconfig, serializeJson(sbTemplates.tsconfig)); + tree.write(options.paths.storybook.main, sbTemplates.main); + tree.write(options.paths.storybook.preview, sbTemplates.preview); const libTsConfig: TsConfig = readJson(tree, options.paths.tsconfig.lib); @@ -954,6 +977,7 @@ function updateLocalJestConfig(tree: Tree, options: NormalizedSchema) { Object.keys(packageJson.dependencies).some(pkgDepName => packagesThatTriggerAddingSnapshots.includes(pkgDepName)), testSetupFilePath: `./${path.basename(options.paths.configRoot)}/tests.js`, platform: packageType, + projectConfig: options.projectConfig, } as const; tree.write(options.paths.jestConfig, templates.jest(config)); @@ -998,6 +1022,14 @@ function updateConformanceSetup(tree: Tree, options: NormalizedSchema) { if (defaultOptionsVar === 'defaultOptions') { const initializer = node.declarationList.declarations[0].initializer; if (initializer && ts.isObjectLiteralExpression(initializer)) { + const isTsConfigAlreadySet = initializer.properties.some(prop => { + return prop.name && ts.isIdentifier(prop.name) && prop.name.escapedText === 'tsConfig'; + }); + + if (isTsConfigAlreadySet) { + return; + } + const firstProp = initializer.properties[0]; start = firstProp.pos; return; diff --git a/tools/workspace-plugin/src/generators/react-library/files/package.json__tmpl__ b/tools/workspace-plugin/src/generators/react-library/files/package.json__tmpl__ index 7f8f2b41dfd0c9..5cede71a9f613c 100644 --- a/tools/workspace-plugin/src/generators/react-library/files/package.json__tmpl__ +++ b/tools/workspace-plugin/src/generators/react-library/files/package.json__tmpl__ @@ -7,7 +7,12 @@ "module": "lib/index.js", "typings": "./dist/index.d.ts", "sideEffects": false, - "files": ["lib", "lib-commonjs", "dist/*.d.ts"], + "files": [ + "*.md", + "dist/*.d.ts", + "lib", + "lib-commonjs" + ], "repository": { "type": "git", "url": "https://github.com/microsoft/fluentui" diff --git a/tools/workspace-plugin/src/generators/react-library/index.spec.ts b/tools/workspace-plugin/src/generators/react-library/index.spec.ts index 58d391e4f5393a..2c1cab0592ed3a 100644 --- a/tools/workspace-plugin/src/generators/react-library/index.spec.ts +++ b/tools/workspace-plugin/src/generators/react-library/index.spec.ts @@ -101,6 +101,7 @@ describe('react-library generator', () => { name: '@proj/react-one-preview', private: true, version: '0.0.0', + files: ['*.md', 'dist/*.d.ts', 'lib', 'lib-commonjs'], dependencies: { '@fluentui/react-jsx-runtime': '^9.0.0', '@fluentui/react-theme': '^9.0.0', diff --git a/tools/workspace-plugin/src/types.ts b/tools/workspace-plugin/src/types.ts index 155963fa68c7e2..1b0e524daf29fb 100644 --- a/tools/workspace-plugin/src/types.ts +++ b/tools/workspace-plugin/src/types.ts @@ -28,6 +28,20 @@ export interface PackageJson { name: string; main: string; module?: string; + /** + * Vite and Webpack(sass-loader) consume this field + * @see https://github.com/microsoft/fluentui/pull/27274 + */ + style?: string; + /** + * https://storybook.js.org/docs/6.5/react/addons/integration-catalog + */ + storybook?: Partial<{ + displayName: string; + icon: string; + unsupportedFrameworks: string[]; + supportedFrameworks: string[]; + }>; version: string; scripts?: Record; dependencies?: Record;