feat(storybook): add convert-to-inferred generator (#26595)
- feat(storybook): add convert-to-inferred generator - feat(storybook): add post target transformers - feat(storybook): convert-to-inferred handles kebab case flags <!-- Please make sure you have read the submission guidelines before posting an PR --> <!-- https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr --> <!-- Please make sure that your commit message follows our format --> <!-- Example: `fix(nx): must begin with lowercase` --> <!-- If this is a particularly complex change or feature addition, you can request a dedicated Nx release for this pull request branch. Mention someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they will confirm if the PR warrants its own release for testing purposes, and generate it for you if appropriate. --> ## Current Behavior <!-- This is the behavior we have today --> ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #
This commit is contained in:
parent
b1dbf47aa2
commit
18fdd9425b
@ -9485,6 +9485,14 @@
|
|||||||
"isExternal": false,
|
"isExternal": false,
|
||||||
"disableCollapsible": false
|
"disableCollapsible": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "convert-to-inferred",
|
||||||
|
"path": "/nx-api/storybook/generators/convert-to-inferred",
|
||||||
|
"name": "convert-to-inferred",
|
||||||
|
"children": [],
|
||||||
|
"isExternal": false,
|
||||||
|
"disableCollapsible": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "migrate-7",
|
"id": "migrate-7",
|
||||||
"path": "/nx-api/storybook/generators/migrate-7",
|
"path": "/nx-api/storybook/generators/migrate-7",
|
||||||
|
|||||||
@ -2867,6 +2867,15 @@
|
|||||||
"path": "/nx-api/storybook/generators/cypress-project",
|
"path": "/nx-api/storybook/generators/cypress-project",
|
||||||
"type": "generator"
|
"type": "generator"
|
||||||
},
|
},
|
||||||
|
"/nx-api/storybook/generators/convert-to-inferred": {
|
||||||
|
"description": "Convert existing Storybook project(s) using `@nx/storybook:*` executors to use `@nx/storybook/plugin`. Defaults to migrating all projects. Pass '--project' to migrate only one target.",
|
||||||
|
"file": "generated/packages/storybook/generators/convert-to-inferred.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "convert-to-inferred",
|
||||||
|
"originalFilePath": "/packages/storybook/src/generators/convert-to-inferred/schema.json",
|
||||||
|
"path": "/nx-api/storybook/generators/convert-to-inferred",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
"/nx-api/storybook/generators/migrate-7": {
|
"/nx-api/storybook/generators/migrate-7": {
|
||||||
"description": "Migrate to Storybook version 7.",
|
"description": "Migrate to Storybook version 7.",
|
||||||
"file": "generated/packages/storybook/generators/migrate-7.json",
|
"file": "generated/packages/storybook/generators/migrate-7.json",
|
||||||
|
|||||||
@ -2837,6 +2837,15 @@
|
|||||||
"path": "storybook/generators/cypress-project",
|
"path": "storybook/generators/cypress-project",
|
||||||
"type": "generator"
|
"type": "generator"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Convert existing Storybook project(s) using `@nx/storybook:*` executors to use `@nx/storybook/plugin`. Defaults to migrating all projects. Pass '--project' to migrate only one target.",
|
||||||
|
"file": "generated/packages/storybook/generators/convert-to-inferred.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "convert-to-inferred",
|
||||||
|
"originalFilePath": "/packages/storybook/src/generators/convert-to-inferred/schema.json",
|
||||||
|
"path": "storybook/generators/convert-to-inferred",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Migrate to Storybook version 7.",
|
"description": "Migrate to Storybook version 7.",
|
||||||
"file": "generated/packages/storybook/generators/migrate-7.json",
|
"file": "generated/packages/storybook/generators/migrate-7.json",
|
||||||
|
|||||||
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "convert-to-inferred",
|
||||||
|
"factory": "./src/generators/convert-to-inferred/convert-to-inferred",
|
||||||
|
"schema": {
|
||||||
|
"$schema": "https://json-schema.org/schema",
|
||||||
|
"$id": "NxStorybookConvertToInferred",
|
||||||
|
"description": "Convert existing Storybook project(s) using `@nx/storybook:*` executors to use `@nx/storybook/plugin`. Defaults to migrating all projects. Pass '--project' to migrate only one target.",
|
||||||
|
"title": "Convert Storybook project from executor to plugin",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"project": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The project to convert from using the `@nx/storybook:*` executors to use `@nx/storybook/plugin`.",
|
||||||
|
"x-priority": "important"
|
||||||
|
},
|
||||||
|
"skipFormat": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether to format files at the end of the migration.",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"presets": []
|
||||||
|
},
|
||||||
|
"description": "Convert existing Storybook project(s) using `@nx/storybook:*` executors to use `@nx/storybook/plugin`. Defaults to migrating all projects. Pass '--project' to migrate only one target.",
|
||||||
|
"implementation": "/packages/storybook/src/generators/convert-to-inferred/convert-to-inferred.ts",
|
||||||
|
"aliases": [],
|
||||||
|
"hidden": false,
|
||||||
|
"path": "/packages/storybook/src/generators/convert-to-inferred/schema.json",
|
||||||
|
"type": "generator"
|
||||||
|
}
|
||||||
@ -663,6 +663,7 @@
|
|||||||
- [init](/nx-api/storybook/generators/init)
|
- [init](/nx-api/storybook/generators/init)
|
||||||
- [configuration](/nx-api/storybook/generators/configuration)
|
- [configuration](/nx-api/storybook/generators/configuration)
|
||||||
- [cypress-project](/nx-api/storybook/generators/cypress-project)
|
- [cypress-project](/nx-api/storybook/generators/cypress-project)
|
||||||
|
- [convert-to-inferred](/nx-api/storybook/generators/convert-to-inferred)
|
||||||
- [migrate-7](/nx-api/storybook/generators/migrate-7)
|
- [migrate-7](/nx-api/storybook/generators/migrate-7)
|
||||||
- [tao](/nx-api/tao)
|
- [tao](/nx-api/tao)
|
||||||
- [vite](/nx-api/vite)
|
- [vite](/nx-api/vite)
|
||||||
|
|||||||
@ -22,6 +22,11 @@
|
|||||||
"x-deprecated": "Deprecated: Use 'interactionTests' instead when running '@nx/storybook:configuration'. This generator will be removed in v21.",
|
"x-deprecated": "Deprecated: Use 'interactionTests' instead when running '@nx/storybook:configuration'. This generator will be removed in v21.",
|
||||||
"hidden": false
|
"hidden": false
|
||||||
},
|
},
|
||||||
|
"convert-to-inferred": {
|
||||||
|
"factory": "./src/generators/convert-to-inferred/convert-to-inferred",
|
||||||
|
"schema": "./src/generators/convert-to-inferred/schema.json",
|
||||||
|
"description": "Convert existing Storybook project(s) using `@nx/storybook:*` executors to use `@nx/storybook/plugin`. Defaults to migrating all projects. Pass '--project' to migrate only one target."
|
||||||
|
},
|
||||||
"migrate-7": {
|
"migrate-7": {
|
||||||
"factory": "./src/generators/migrate-7/migrate-7",
|
"factory": "./src/generators/migrate-7/migrate-7",
|
||||||
"schema": "./src/generators/migrate-7/schema.json",
|
"schema": "./src/generators/migrate-7/schema.json",
|
||||||
|
|||||||
@ -0,0 +1,631 @@
|
|||||||
|
import {
|
||||||
|
type ProjectGraph,
|
||||||
|
type Tree,
|
||||||
|
type ProjectConfiguration,
|
||||||
|
joinPathFragments,
|
||||||
|
writeJson,
|
||||||
|
addProjectConfiguration,
|
||||||
|
readProjectConfiguration,
|
||||||
|
readNxJson,
|
||||||
|
type ExpandedPluginConfiguration,
|
||||||
|
updateNxJson,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
|
import { TempFs } from '@nx/devkit/internal-testing-utils';
|
||||||
|
import { getRelativeProjectJsonSchemaPath } from 'nx/src/generators/utils/project-configuration';
|
||||||
|
import { join } from 'path';
|
||||||
|
import { convertToInferred } from './convert-to-inferred';
|
||||||
|
|
||||||
|
let fs: TempFs;
|
||||||
|
let projectGraph: ProjectGraph;
|
||||||
|
|
||||||
|
jest.mock('@nx/devkit', () => ({
|
||||||
|
...jest.requireActual('@nx/devkit'),
|
||||||
|
createProjectGraphAsync: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation(() => Promise.resolve(projectGraph)),
|
||||||
|
updateProjectConfiguration: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation((tree, projectName, projectConfiguration) => {
|
||||||
|
function handleEmptyTargets(
|
||||||
|
projectName: string,
|
||||||
|
projectConfiguration: ProjectConfiguration
|
||||||
|
): void {
|
||||||
|
if (
|
||||||
|
projectConfiguration.targets &&
|
||||||
|
!Object.keys(projectConfiguration.targets).length
|
||||||
|
) {
|
||||||
|
// Re-order `targets` to appear after the `// target` comment.
|
||||||
|
delete projectConfiguration.targets;
|
||||||
|
projectConfiguration[
|
||||||
|
'// targets'
|
||||||
|
] = `to see all targets run: nx show project ${projectName} --web`;
|
||||||
|
projectConfiguration.targets = {};
|
||||||
|
} else {
|
||||||
|
delete projectConfiguration['// targets'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const projectConfigFile = joinPathFragments(
|
||||||
|
projectConfiguration.root,
|
||||||
|
'project.json'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!tree.exists(projectConfigFile)) {
|
||||||
|
throw new Error(
|
||||||
|
`Cannot update Project ${projectName} at ${projectConfiguration.root}. It either doesn't exist yet, or may not use project.json for configuration. Use \`addProjectConfiguration()\` instead if you want to create a new project.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
handleEmptyTargets(projectName, projectConfiguration);
|
||||||
|
writeJson(tree, projectConfigFile, {
|
||||||
|
name: projectConfiguration.name ?? projectName,
|
||||||
|
$schema: getRelativeProjectJsonSchemaPath(tree, projectConfiguration),
|
||||||
|
...projectConfiguration,
|
||||||
|
root: undefined,
|
||||||
|
});
|
||||||
|
projectGraph.nodes[projectName].data = projectConfiguration;
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
function addProject(tree: Tree, name: string, project: ProjectConfiguration) {
|
||||||
|
addProjectConfiguration(tree, name, project);
|
||||||
|
projectGraph.nodes[name] = {
|
||||||
|
name,
|
||||||
|
type: project.projectType === 'application' ? 'app' : 'lib',
|
||||||
|
data: {
|
||||||
|
projectType: project.projectType,
|
||||||
|
root: project.root,
|
||||||
|
targets: project.targets,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TestProjectOptions {
|
||||||
|
appName: string;
|
||||||
|
appRoot: string;
|
||||||
|
configDir: string;
|
||||||
|
buildTargetName: string;
|
||||||
|
serveTargetName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultTestProjectOptions: TestProjectOptions = {
|
||||||
|
appName: 'app1',
|
||||||
|
appRoot: 'apps/app1',
|
||||||
|
configDir: '.storybook',
|
||||||
|
buildTargetName: 'build-storybook',
|
||||||
|
serveTargetName: 'storybook',
|
||||||
|
};
|
||||||
|
|
||||||
|
function writeStorybookConfig(
|
||||||
|
tree: Tree,
|
||||||
|
projectRoot: string,
|
||||||
|
useVite: boolean = false
|
||||||
|
) {
|
||||||
|
const storybookConfig = {
|
||||||
|
stories: ['../src/app/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
|
||||||
|
addons: ['@storybook/addon-essentials', '@storybook/addon-interactions'],
|
||||||
|
framework: {
|
||||||
|
name: useVite ? '@storybook/react-vite' : '@storybook/react-webpack5',
|
||||||
|
options: useVite
|
||||||
|
? {
|
||||||
|
builder: {
|
||||||
|
viteConfigPath: `${projectRoot}/vite.config.ts`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const storybookConfigContents = `const config = ${JSON.stringify(
|
||||||
|
storybookConfig
|
||||||
|
)};
|
||||||
|
export default config;`;
|
||||||
|
|
||||||
|
if (useVite) {
|
||||||
|
tree.write(`${projectRoot}/vite.config.ts`, `module.exports = {}`);
|
||||||
|
fs.createFileSync(`${projectRoot}/vite.config.ts`, `module.exports = {}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
tree.write(`${projectRoot}/.storybook/main.ts`, storybookConfigContents);
|
||||||
|
fs.createFileSync(
|
||||||
|
`${projectRoot}/.storybook/main.ts`,
|
||||||
|
storybookConfigContents
|
||||||
|
);
|
||||||
|
jest.doMock(
|
||||||
|
join(fs.tempDir, projectRoot, '.storybook', 'main.ts'),
|
||||||
|
() => storybookConfig,
|
||||||
|
{ virtual: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTestProject(
|
||||||
|
tree: Tree,
|
||||||
|
opts: Partial<TestProjectOptions> = defaultTestProjectOptions,
|
||||||
|
extraTargetOptions: any = {},
|
||||||
|
extraConfigurations: any = {},
|
||||||
|
useVite = false
|
||||||
|
) {
|
||||||
|
let projectOpts = { ...defaultTestProjectOptions, ...opts };
|
||||||
|
const project: ProjectConfiguration = {
|
||||||
|
name: projectOpts.appName,
|
||||||
|
root: projectOpts.appRoot,
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {
|
||||||
|
[projectOpts.buildTargetName]: {
|
||||||
|
executor: '@nx/storybook:build',
|
||||||
|
outputs: ['{options.outputDir}'],
|
||||||
|
options: {
|
||||||
|
configDir: `${projectOpts.appRoot}/${projectOpts.configDir}`,
|
||||||
|
outputDir: `dist/storybook/${projectOpts.appRoot}`,
|
||||||
|
...extraTargetOptions,
|
||||||
|
},
|
||||||
|
configurations: {
|
||||||
|
...extraConfigurations,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[projectOpts.serveTargetName]: {
|
||||||
|
executor: '@nx/storybook:storybook',
|
||||||
|
options: {
|
||||||
|
port: 4400,
|
||||||
|
configDir: `${projectOpts.appRoot}/${projectOpts.configDir}`,
|
||||||
|
...extraTargetOptions,
|
||||||
|
},
|
||||||
|
configurations: {
|
||||||
|
ci: {
|
||||||
|
quiet: true,
|
||||||
|
},
|
||||||
|
...extraConfigurations,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
writeStorybookConfig(tree, project.root, useVite);
|
||||||
|
|
||||||
|
addProject(tree, project.name, project);
|
||||||
|
fs.createFileSync(
|
||||||
|
`${projectOpts.appRoot}/project.json`,
|
||||||
|
JSON.stringify(project)
|
||||||
|
);
|
||||||
|
return project;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Storybook - Convert To Inferred', () => {
|
||||||
|
let tree: Tree;
|
||||||
|
beforeEach(() => {
|
||||||
|
fs = new TempFs('storybook');
|
||||||
|
tree = createTreeWithEmptyWorkspace();
|
||||||
|
tree.root = fs.tempDir;
|
||||||
|
|
||||||
|
projectGraph = {
|
||||||
|
nodes: {},
|
||||||
|
dependencies: {},
|
||||||
|
externalNodes: {},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fs.cleanup();
|
||||||
|
jest.resetModules();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('--project', () => {
|
||||||
|
it('should correctly migrate a single project', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const project = createTestProject(tree);
|
||||||
|
const project2 = createTestProject(tree, {
|
||||||
|
appRoot: 'apps/project2',
|
||||||
|
appName: 'project2',
|
||||||
|
});
|
||||||
|
|
||||||
|
const project2Targets = project2.targets;
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await convertToInferred(tree, {
|
||||||
|
project: project.name,
|
||||||
|
skipFormat: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
const updatedProject = readProjectConfiguration(tree, project.name);
|
||||||
|
expect(updatedProject.targets).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"build-storybook": {
|
||||||
|
"options": {
|
||||||
|
"config-dir": ".storybook",
|
||||||
|
"output-dir": "../../dist/storybook/apps/app1",
|
||||||
|
},
|
||||||
|
"outputs": [
|
||||||
|
"{projectRoot}/{options.outputDir}",
|
||||||
|
"{workspaceRoot}/{projectRoot}/storybook-static",
|
||||||
|
"{options.output-dir}",
|
||||||
|
"{options.outputDir}",
|
||||||
|
"{options.o}",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"storybook": {
|
||||||
|
"configurations": {
|
||||||
|
"ci": {
|
||||||
|
"args": [
|
||||||
|
"--quiet",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"config-dir": ".storybook",
|
||||||
|
"port": 4400,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const updatedProject2 = readProjectConfiguration(tree, project2.name);
|
||||||
|
expect(updatedProject2.targets).toStrictEqual(project2Targets);
|
||||||
|
|
||||||
|
const nxJsonPlugins = readNxJson(tree).plugins;
|
||||||
|
const storybookPlugin = nxJsonPlugins.find(
|
||||||
|
(plugin): plugin is ExpandedPluginConfiguration =>
|
||||||
|
typeof plugin !== 'string' &&
|
||||||
|
plugin.plugin === '@nx/storybook/plugin' &&
|
||||||
|
plugin.include?.length === 1
|
||||||
|
);
|
||||||
|
expect(storybookPlugin).toBeTruthy();
|
||||||
|
expect(storybookPlugin.include).toEqual([`${project.root}/**/*`]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add a new plugin registration when the target name differs', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const project = createTestProject(tree);
|
||||||
|
const project2 = createTestProject(tree, {
|
||||||
|
appRoot: 'apps/project2',
|
||||||
|
appName: 'project2',
|
||||||
|
});
|
||||||
|
|
||||||
|
const project2Targets = project2.targets;
|
||||||
|
|
||||||
|
const nxJson = readNxJson(tree);
|
||||||
|
nxJson.plugins ??= [];
|
||||||
|
nxJson.plugins.push({
|
||||||
|
plugin: '@nx/storybook/plugin',
|
||||||
|
options: {
|
||||||
|
buildTargetName: 'storybook-build',
|
||||||
|
serveTargetName: defaultTestProjectOptions.serveTargetName,
|
||||||
|
staticStorybookTargetName: 'static-storybook',
|
||||||
|
testStorybookTargetName: 'test-storybook',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
updateNxJson(tree, nxJson);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await convertToInferred(tree, {
|
||||||
|
project: project.name,
|
||||||
|
skipFormat: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
const updatedProject = readProjectConfiguration(tree, project.name);
|
||||||
|
expect(updatedProject.targets).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"build-storybook": {
|
||||||
|
"options": {
|
||||||
|
"config-dir": ".storybook",
|
||||||
|
"output-dir": "../../dist/storybook/apps/app1",
|
||||||
|
},
|
||||||
|
"outputs": [
|
||||||
|
"{projectRoot}/{options.outputDir}",
|
||||||
|
"{workspaceRoot}/{projectRoot}/storybook-static",
|
||||||
|
"{options.output-dir}",
|
||||||
|
"{options.outputDir}",
|
||||||
|
"{options.o}",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"storybook": {
|
||||||
|
"configurations": {
|
||||||
|
"ci": {
|
||||||
|
"args": [
|
||||||
|
"--quiet",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"config-dir": ".storybook",
|
||||||
|
"port": 4400,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const updatedProject2 = readProjectConfiguration(tree, project2.name);
|
||||||
|
expect(updatedProject2.targets).toStrictEqual(project2Targets);
|
||||||
|
|
||||||
|
const nxJsonPlugins = readNxJson(tree).plugins;
|
||||||
|
const storybookPluginRegistrations = nxJsonPlugins.filter(
|
||||||
|
(plugin): plugin is ExpandedPluginConfiguration =>
|
||||||
|
typeof plugin !== 'string' && plugin.plugin === '@nx/storybook/plugin'
|
||||||
|
);
|
||||||
|
expect(storybookPluginRegistrations.length).toBe(2);
|
||||||
|
expect(storybookPluginRegistrations[1].include).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
"apps/app1/**/*",
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should merge target defaults', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const project = createTestProject(tree);
|
||||||
|
const project2 = createTestProject(tree, {
|
||||||
|
appRoot: 'apps/project2',
|
||||||
|
appName: 'project2',
|
||||||
|
});
|
||||||
|
|
||||||
|
const project2Targets = project2.targets;
|
||||||
|
|
||||||
|
const nxJson = readNxJson(tree);
|
||||||
|
nxJson.targetDefaults ??= {};
|
||||||
|
nxJson.targetDefaults['@nx/storybook:build'] = {
|
||||||
|
options: {
|
||||||
|
webpackStatsJson: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
updateNxJson(tree, nxJson);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await convertToInferred(tree, {
|
||||||
|
project: project.name,
|
||||||
|
skipFormat: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
const updatedProject = readProjectConfiguration(tree, project.name);
|
||||||
|
expect(updatedProject.targets).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"build-storybook": {
|
||||||
|
"options": {
|
||||||
|
"config-dir": ".storybook",
|
||||||
|
"output-dir": "../../dist/storybook/apps/app1",
|
||||||
|
"webpack-stats-json": true,
|
||||||
|
},
|
||||||
|
"outputs": [
|
||||||
|
"{projectRoot}/{options.outputDir}",
|
||||||
|
"{workspaceRoot}/{projectRoot}/storybook-static",
|
||||||
|
"{options.output-dir}",
|
||||||
|
"{options.outputDir}",
|
||||||
|
"{options.o}",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"storybook": {
|
||||||
|
"configurations": {
|
||||||
|
"ci": {
|
||||||
|
"args": [
|
||||||
|
"--quiet",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"config-dir": ".storybook",
|
||||||
|
"port": 4400,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const updatedProject2 = readProjectConfiguration(tree, project2.name);
|
||||||
|
expect(updatedProject2.targets).toStrictEqual(project2Targets);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should manage configurations correctly', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const project = createTestProject(tree, undefined, undefined, {
|
||||||
|
dev: {
|
||||||
|
docsMode: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const project2 = createTestProject(tree, {
|
||||||
|
appRoot: 'apps/project2',
|
||||||
|
appName: 'project2',
|
||||||
|
});
|
||||||
|
|
||||||
|
const project2Targets = project2.targets;
|
||||||
|
// ACT
|
||||||
|
await convertToInferred(tree, {
|
||||||
|
project: project.name,
|
||||||
|
skipFormat: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
const updatedProject = readProjectConfiguration(tree, project.name);
|
||||||
|
expect(updatedProject.targets).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"build-storybook": {
|
||||||
|
"configurations": {
|
||||||
|
"dev": {},
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"config-dir": ".storybook",
|
||||||
|
"output-dir": "../../dist/storybook/apps/app1",
|
||||||
|
},
|
||||||
|
"outputs": [
|
||||||
|
"{projectRoot}/{options.outputDir}",
|
||||||
|
"{workspaceRoot}/{projectRoot}/storybook-static",
|
||||||
|
"{options.output-dir}",
|
||||||
|
"{options.outputDir}",
|
||||||
|
"{options.o}",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"storybook": {
|
||||||
|
"configurations": {
|
||||||
|
"ci": {
|
||||||
|
"args": [
|
||||||
|
"--quiet",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"dev": {
|
||||||
|
"docs": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"config-dir": ".storybook",
|
||||||
|
"port": 4400,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const updatedProject2 = readProjectConfiguration(tree, project2.name);
|
||||||
|
expect(updatedProject2.targets).toStrictEqual(project2Targets);
|
||||||
|
|
||||||
|
const storybookConfigContents = tree.read(
|
||||||
|
`${project.root}/.storybook/main.ts`,
|
||||||
|
'utf-8'
|
||||||
|
);
|
||||||
|
expect(storybookConfigContents).toMatchInlineSnapshot(`
|
||||||
|
"
|
||||||
|
|
||||||
|
// These options were migrated by @nx/storybook:convert-to-inferred from the project.json file.
|
||||||
|
const configValues = {"default":{},"dev":{"docsMode":true}};
|
||||||
|
|
||||||
|
// Determine the correct configValue to use based on the configuration
|
||||||
|
const nxConfiguration = process.env.NX_TASK_TARGET_CONFIGURATION ?? 'default';
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
...configValues.default,
|
||||||
|
...(configValues[nxConfiguration] ?? {})
|
||||||
|
}
|
||||||
|
const config = {docs: { docsMode: options.docsMode },"stories":["../src/app/**/*.stories.@(js|jsx|ts|tsx|mdx)"],"addons":["@storybook/addon-essentials","@storybook/addon-interactions"],"framework":{"name":"@storybook/react-webpack5","options":{}}};
|
||||||
|
export default config;"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update vite config file', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const project = createTestProject(
|
||||||
|
tree,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
const project2 = createTestProject(tree, {
|
||||||
|
appRoot: 'apps/project2',
|
||||||
|
appName: 'project2',
|
||||||
|
});
|
||||||
|
|
||||||
|
const project2Targets = project2.targets;
|
||||||
|
// ACT
|
||||||
|
await convertToInferred(tree, {
|
||||||
|
project: project.name,
|
||||||
|
skipFormat: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
const storybookConfigContents = tree.read(
|
||||||
|
`${project.root}/.storybook/main.ts`,
|
||||||
|
'utf-8'
|
||||||
|
);
|
||||||
|
expect(storybookConfigContents).toMatchInlineSnapshot(`
|
||||||
|
"
|
||||||
|
|
||||||
|
// These options were migrated by @nx/storybook:convert-to-inferred from the project.json file.
|
||||||
|
const configValues = {"default":{}};
|
||||||
|
|
||||||
|
// Determine the correct configValue to use based on the configuration
|
||||||
|
const nxConfiguration = process.env.NX_TASK_TARGET_CONFIGURATION ?? 'default';
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
...configValues.default,
|
||||||
|
...(configValues[nxConfiguration] ?? {})
|
||||||
|
}
|
||||||
|
const config = {"stories":["../src/app/**/*.stories.@(js|jsx|ts|tsx|mdx)"],"addons":["@storybook/addon-essentials","@storybook/addon-interactions"],"framework":{"name":"@storybook/react-vite","options":{"builder":{"viteConfigPath":"./vite.config.ts"}}}};
|
||||||
|
export default config;"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('all projects', () => {
|
||||||
|
it('should correctly migrate all projects', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const project = createTestProject(tree);
|
||||||
|
const project2 = createTestProject(tree, {
|
||||||
|
appRoot: 'apps/project2',
|
||||||
|
appName: 'project2',
|
||||||
|
});
|
||||||
|
// ACT
|
||||||
|
await convertToInferred(tree, {
|
||||||
|
skipFormat: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
const updatedProject = readProjectConfiguration(tree, project.name);
|
||||||
|
expect(updatedProject.targets).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"build-storybook": {
|
||||||
|
"options": {
|
||||||
|
"config-dir": ".storybook",
|
||||||
|
"output-dir": "../../dist/storybook/apps/app1",
|
||||||
|
},
|
||||||
|
"outputs": [
|
||||||
|
"{projectRoot}/{options.outputDir}",
|
||||||
|
"{workspaceRoot}/{projectRoot}/storybook-static",
|
||||||
|
"{options.output-dir}",
|
||||||
|
"{options.outputDir}",
|
||||||
|
"{options.o}",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"storybook": {
|
||||||
|
"configurations": {
|
||||||
|
"ci": {
|
||||||
|
"args": [
|
||||||
|
"--quiet",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"config-dir": ".storybook",
|
||||||
|
"port": 4400,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const updatedProject2 = readProjectConfiguration(tree, project2.name);
|
||||||
|
expect(updatedProject2.targets).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"build-storybook": {
|
||||||
|
"options": {
|
||||||
|
"config-dir": ".storybook",
|
||||||
|
"output-dir": "../../dist/storybook/apps/project2",
|
||||||
|
},
|
||||||
|
"outputs": [
|
||||||
|
"{projectRoot}/{options.outputDir}",
|
||||||
|
"{workspaceRoot}/{projectRoot}/storybook-static",
|
||||||
|
"{options.output-dir}",
|
||||||
|
"{options.outputDir}",
|
||||||
|
"{options.o}",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"storybook": {
|
||||||
|
"configurations": {
|
||||||
|
"ci": {
|
||||||
|
"args": [
|
||||||
|
"--quiet",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"config-dir": ".storybook",
|
||||||
|
"port": 4400,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const nxJsonPlugins = readNxJson(tree).plugins;
|
||||||
|
const storybookPlugin = nxJsonPlugins.find(
|
||||||
|
(plugin): plugin is ExpandedPluginConfiguration =>
|
||||||
|
typeof plugin !== 'string' && plugin.plugin === '@nx/storybook/plugin'
|
||||||
|
);
|
||||||
|
expect(storybookPlugin).toBeTruthy();
|
||||||
|
expect(storybookPlugin.include).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,76 @@
|
|||||||
|
import {
|
||||||
|
addDependenciesToPackageJson,
|
||||||
|
createProjectGraphAsync,
|
||||||
|
formatFiles,
|
||||||
|
runTasksInSerial,
|
||||||
|
type Tree,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
|
||||||
|
import { migrateExecutorToPluginV1 } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator';
|
||||||
|
import { buildPostTargetTransformer } from './lib/build-post-target-transformer';
|
||||||
|
import { servePostTargetTransformer } from './lib/serve-post-target-transformer';
|
||||||
|
import { createNodes } from '../../plugins/plugin';
|
||||||
|
import { storybookVersion } from '../../utils/versions';
|
||||||
|
|
||||||
|
interface Schema {
|
||||||
|
project?: string;
|
||||||
|
skipFormat?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function convertToInferred(tree: Tree, options: Schema) {
|
||||||
|
const projectGraph = await createProjectGraphAsync();
|
||||||
|
const migrationLogs = new AggregatedLog();
|
||||||
|
const migratedBuildProjects = await migrateExecutorToPluginV1(
|
||||||
|
tree,
|
||||||
|
projectGraph,
|
||||||
|
'@nx/storybook:build',
|
||||||
|
'@nx/storybook/plugin',
|
||||||
|
(targetName) => ({
|
||||||
|
buildStorybookTargetName: targetName,
|
||||||
|
serveStorybookTargetName: 'storybook',
|
||||||
|
staticStorybookTargetName: 'static-storybook',
|
||||||
|
testStorybookTargetName: 'test-storybook',
|
||||||
|
}),
|
||||||
|
buildPostTargetTransformer(migrationLogs),
|
||||||
|
createNodes,
|
||||||
|
options.project
|
||||||
|
);
|
||||||
|
|
||||||
|
const migratedServeProjects = await migrateExecutorToPluginV1(
|
||||||
|
tree,
|
||||||
|
projectGraph,
|
||||||
|
'@nx/storybook:storybook',
|
||||||
|
'@nx/storybook/plugin',
|
||||||
|
(targetName) => ({
|
||||||
|
buildStorybookTargetName: 'build-storybook',
|
||||||
|
serveStorybookTargetName: targetName,
|
||||||
|
staticStorybookTargetName: 'static-storybook',
|
||||||
|
testStorybookTargetName: 'test-storybook',
|
||||||
|
}),
|
||||||
|
servePostTargetTransformer(migrationLogs),
|
||||||
|
createNodes,
|
||||||
|
options.project
|
||||||
|
);
|
||||||
|
|
||||||
|
const migratedProjects =
|
||||||
|
migratedBuildProjects.size + migratedServeProjects.size;
|
||||||
|
if (migratedProjects === 0) {
|
||||||
|
throw new Error('Could not find any targets to migrate.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.skipFormat) {
|
||||||
|
await formatFiles(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
const installTask = addDependenciesToPackageJson(
|
||||||
|
tree,
|
||||||
|
{},
|
||||||
|
{ storybook: storybookVersion }
|
||||||
|
);
|
||||||
|
|
||||||
|
return runTasksInSerial(installTask, () => {
|
||||||
|
migrationLogs.flushLogs();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default convertToInferred;
|
||||||
@ -0,0 +1,632 @@
|
|||||||
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
|
import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
|
||||||
|
import { buildPostTargetTransformer } from './build-post-target-transformer';
|
||||||
|
|
||||||
|
describe('buildPostTargetTransformer', () => {
|
||||||
|
describe('--react-vite', () => {
|
||||||
|
it('should migrate docsMode and staticDir to storybook config correctly', () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace();
|
||||||
|
|
||||||
|
const targetConfiguration = {
|
||||||
|
outputs: ['{options.outputDir}'],
|
||||||
|
options: {
|
||||||
|
outputDir: 'dist/storybook/myapp',
|
||||||
|
configDir: 'apps/myapp/.storybook',
|
||||||
|
docsMode: true,
|
||||||
|
staticDir: ['assets'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const inferredTargetConfiguration = {
|
||||||
|
outputs: ['{projectRoot}/{options.outputDir}'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const migrationLogs = new AggregatedLog();
|
||||||
|
|
||||||
|
tree.write(
|
||||||
|
'apps/myapp/.storybook/main.ts',
|
||||||
|
storybookConfigFileV17_ReactVite
|
||||||
|
);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
const target = buildPostTargetTransformer(migrationLogs)(
|
||||||
|
targetConfiguration,
|
||||||
|
tree,
|
||||||
|
{ projectName: 'myapp', root: 'apps/myapp' },
|
||||||
|
inferredTargetConfiguration
|
||||||
|
);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
const configFile = tree.read('apps/myapp/.storybook/main.ts', 'utf-8');
|
||||||
|
expect(configFile).toMatchInlineSnapshot(`
|
||||||
|
"import type { StorybookConfig } from '@storybook/react-vite';
|
||||||
|
|
||||||
|
// These options were migrated by @nx/storybook:convert-to-inferred from the project.json file.
|
||||||
|
const configValues = {"default":{"docsMode":true,"staticDir":["assets"]}};
|
||||||
|
|
||||||
|
// Determine the correct configValue to use based on the configuration
|
||||||
|
const nxConfiguration = process.env.NX_TASK_TARGET_CONFIGURATION ?? 'default';
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
...configValues.default,
|
||||||
|
...(configValues[nxConfiguration] ?? {})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const config: StorybookConfig = {staticDirs: options.staticDir,docs: { docsMode: options.docsMode },
|
||||||
|
stories: ['../src/app/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
|
||||||
|
addons: ['@storybook/addon-essentials', '@storybook/addon-interactions'],
|
||||||
|
framework: {
|
||||||
|
name: '@storybook/react-vite',
|
||||||
|
options: {
|
||||||
|
builder: {
|
||||||
|
viteConfigPath: './vite.config.ts',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;"
|
||||||
|
`);
|
||||||
|
expect(target).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"options": {
|
||||||
|
"config-dir": ".storybook",
|
||||||
|
"output-dir": "../../dist/storybook/myapp",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle configurations correctly and migrate docsMode and staticDir to storybook config correctly', () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace();
|
||||||
|
|
||||||
|
const targetConfiguration = {
|
||||||
|
outputs: ['{options.outputDir}'],
|
||||||
|
options: {
|
||||||
|
outputDir: 'dist/storybook/myapp',
|
||||||
|
configDir: 'apps/myapp/.storybook',
|
||||||
|
docsMode: true,
|
||||||
|
staticDir: ['assets'],
|
||||||
|
},
|
||||||
|
configurations: {
|
||||||
|
dev: {
|
||||||
|
outputDir: 'dist/storybook/myapp/dev',
|
||||||
|
configDir: 'apps/myapp/dev/.storybook',
|
||||||
|
docsMode: false,
|
||||||
|
staticDir: ['dev/assets'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const inferredTargetConfiguration = {
|
||||||
|
outputs: ['{projectRoot}/{options.outputDir}'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const migrationLogs = new AggregatedLog();
|
||||||
|
|
||||||
|
tree.write(
|
||||||
|
'apps/myapp/.storybook/main.ts',
|
||||||
|
storybookConfigFileV17_ReactVite
|
||||||
|
);
|
||||||
|
tree.write(
|
||||||
|
'apps/myapp/dev/.storybook/main.ts',
|
||||||
|
storybookConfigFileV17_ReactVite
|
||||||
|
);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
const target = buildPostTargetTransformer(migrationLogs)(
|
||||||
|
targetConfiguration,
|
||||||
|
tree,
|
||||||
|
{ projectName: 'myapp', root: 'apps/myapp' },
|
||||||
|
inferredTargetConfiguration
|
||||||
|
);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
const configFile = tree.read('apps/myapp/.storybook/main.ts', 'utf-8');
|
||||||
|
expect(configFile).toMatchInlineSnapshot(`
|
||||||
|
"import type { StorybookConfig } from '@storybook/react-vite';
|
||||||
|
|
||||||
|
// These options were migrated by @nx/storybook:convert-to-inferred from the project.json file.
|
||||||
|
const configValues = {"default":{"docsMode":true,"staticDir":["assets"]},"dev":{"docsMode":false,"staticDir":["dev/assets"]}};
|
||||||
|
|
||||||
|
// Determine the correct configValue to use based on the configuration
|
||||||
|
const nxConfiguration = process.env.NX_TASK_TARGET_CONFIGURATION ?? 'default';
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
...configValues.default,
|
||||||
|
...(configValues[nxConfiguration] ?? {})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const config: StorybookConfig = {staticDirs: options.staticDir,docs: { docsMode: options.docsMode },
|
||||||
|
stories: ['../src/app/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
|
||||||
|
addons: ['@storybook/addon-essentials', '@storybook/addon-interactions'],
|
||||||
|
framework: {
|
||||||
|
name: '@storybook/react-vite',
|
||||||
|
options: {
|
||||||
|
builder: {
|
||||||
|
viteConfigPath: './vite.config.ts',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;"
|
||||||
|
`);
|
||||||
|
expect(target).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"configurations": {
|
||||||
|
"dev": {
|
||||||
|
"config-dir": "./dev/.storybook",
|
||||||
|
"output-dir": "../../dist/storybook/myapp/dev",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"config-dir": ".storybook",
|
||||||
|
"output-dir": "../../dist/storybook/myapp",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
const devConfigFile = tree.read(
|
||||||
|
'apps/myapp/dev/.storybook/main.ts',
|
||||||
|
'utf-8'
|
||||||
|
);
|
||||||
|
expect(devConfigFile).toMatchInlineSnapshot(`
|
||||||
|
"import type { StorybookConfig } from '@storybook/react-vite';
|
||||||
|
|
||||||
|
const config: StorybookConfig = {staticDirs: options.staticDir,docs: { docsMode: options.docsMode },
|
||||||
|
stories: ['../src/app/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
|
||||||
|
addons: ['@storybook/addon-essentials', '@storybook/addon-interactions'],
|
||||||
|
framework: {
|
||||||
|
name: '@storybook/react-vite',
|
||||||
|
options: {
|
||||||
|
builder: {
|
||||||
|
viteConfigPath: 'apps/myapp/vite.config.ts',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('--vue-vite', () => {
|
||||||
|
it('should migrate docsMode and staticDir to storybook config correctly', () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace();
|
||||||
|
|
||||||
|
const targetConfiguration = {
|
||||||
|
outputs: ['{options.outputDir}'],
|
||||||
|
options: {
|
||||||
|
outputDir: 'dist/storybook/myapp',
|
||||||
|
configDir: 'apps/myapp/.storybook',
|
||||||
|
docsMode: true,
|
||||||
|
staticDir: ['assets'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const inferredTargetConfiguration = {
|
||||||
|
outputs: ['{projectRoot}/{options.outputDir}'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const migrationLogs = new AggregatedLog();
|
||||||
|
|
||||||
|
tree.write(
|
||||||
|
'apps/myapp/.storybook/main.ts',
|
||||||
|
storybookConfigFileV17_VueVite
|
||||||
|
);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
const target = buildPostTargetTransformer(migrationLogs)(
|
||||||
|
targetConfiguration,
|
||||||
|
tree,
|
||||||
|
{ projectName: 'myapp', root: 'apps/myapp' },
|
||||||
|
inferredTargetConfiguration
|
||||||
|
);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
const configFile = tree.read('apps/myapp/.storybook/main.ts', 'utf-8');
|
||||||
|
expect(configFile).toMatchInlineSnapshot(`
|
||||||
|
"import type { StorybookConfig } from '@storybook/vue3-vite';
|
||||||
|
|
||||||
|
// These options were migrated by @nx/storybook:convert-to-inferred from the project.json file.
|
||||||
|
const configValues = {"default":{"docsMode":true,"staticDir":["assets"]}};
|
||||||
|
|
||||||
|
// Determine the correct configValue to use based on the configuration
|
||||||
|
const nxConfiguration = process.env.NX_TASK_TARGET_CONFIGURATION ?? 'default';
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
...configValues.default,
|
||||||
|
...(configValues[nxConfiguration] ?? {})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const config: StorybookConfig = {staticDirs: options.staticDir,docs: { docsMode: options.docsMode },
|
||||||
|
stories: ['../src/app/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
|
||||||
|
addons: ['@storybook/addon-essentials', '@storybook/addon-interactions'],
|
||||||
|
framework: {
|
||||||
|
name: '@storybook/vue3-vite',
|
||||||
|
options: {
|
||||||
|
builder: {
|
||||||
|
viteConfigPath: './vite.config.ts',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;"
|
||||||
|
`);
|
||||||
|
expect(target).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"options": {
|
||||||
|
"config-dir": ".storybook",
|
||||||
|
"output-dir": "../../dist/storybook/myapp",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle configurations correctly and migrate docsMode and staticDir to storybook config correctly', () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace();
|
||||||
|
|
||||||
|
const targetConfiguration = {
|
||||||
|
outputs: ['{options.outputDir}'],
|
||||||
|
options: {
|
||||||
|
outputDir: 'dist/storybook/myapp',
|
||||||
|
configDir: 'apps/myapp/.storybook',
|
||||||
|
docsMode: true,
|
||||||
|
staticDir: ['assets'],
|
||||||
|
},
|
||||||
|
configurations: {
|
||||||
|
dev: {
|
||||||
|
outputDir: 'dist/storybook/myapp/dev',
|
||||||
|
configDir: 'apps/myapp/dev/.storybook',
|
||||||
|
docsMode: false,
|
||||||
|
staticDir: ['dev/assets'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const inferredTargetConfiguration = {
|
||||||
|
outputs: ['{projectRoot}/{options.outputDir}'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const migrationLogs = new AggregatedLog();
|
||||||
|
|
||||||
|
tree.write(
|
||||||
|
'apps/myapp/.storybook/main.ts',
|
||||||
|
storybookConfigFileV17_VueVite
|
||||||
|
);
|
||||||
|
tree.write(
|
||||||
|
'apps/myapp/dev/.storybook/main.ts',
|
||||||
|
storybookConfigFileV17_VueVite
|
||||||
|
);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
const target = buildPostTargetTransformer(migrationLogs)(
|
||||||
|
targetConfiguration,
|
||||||
|
tree,
|
||||||
|
{ projectName: 'myapp', root: 'apps/myapp' },
|
||||||
|
inferredTargetConfiguration
|
||||||
|
);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
const configFile = tree.read('apps/myapp/.storybook/main.ts', 'utf-8');
|
||||||
|
expect(configFile).toMatchInlineSnapshot(`
|
||||||
|
"import type { StorybookConfig } from '@storybook/vue3-vite';
|
||||||
|
|
||||||
|
// These options were migrated by @nx/storybook:convert-to-inferred from the project.json file.
|
||||||
|
const configValues = {"default":{"docsMode":true,"staticDir":["assets"]},"dev":{"docsMode":false,"staticDir":["dev/assets"]}};
|
||||||
|
|
||||||
|
// Determine the correct configValue to use based on the configuration
|
||||||
|
const nxConfiguration = process.env.NX_TASK_TARGET_CONFIGURATION ?? 'default';
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
...configValues.default,
|
||||||
|
...(configValues[nxConfiguration] ?? {})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const config: StorybookConfig = {staticDirs: options.staticDir,docs: { docsMode: options.docsMode },
|
||||||
|
stories: ['../src/app/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
|
||||||
|
addons: ['@storybook/addon-essentials', '@storybook/addon-interactions'],
|
||||||
|
framework: {
|
||||||
|
name: '@storybook/vue3-vite',
|
||||||
|
options: {
|
||||||
|
builder: {
|
||||||
|
viteConfigPath: './vite.config.ts',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;"
|
||||||
|
`);
|
||||||
|
expect(target).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"configurations": {
|
||||||
|
"dev": {
|
||||||
|
"config-dir": "./dev/.storybook",
|
||||||
|
"output-dir": "../../dist/storybook/myapp/dev",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"config-dir": ".storybook",
|
||||||
|
"output-dir": "../../dist/storybook/myapp",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
const devConfigFile = tree.read(
|
||||||
|
'apps/myapp/dev/.storybook/main.ts',
|
||||||
|
'utf-8'
|
||||||
|
);
|
||||||
|
expect(devConfigFile).toMatchInlineSnapshot(`
|
||||||
|
"import type { StorybookConfig } from '@storybook/vue3-vite';
|
||||||
|
|
||||||
|
const config: StorybookConfig = {staticDirs: options.staticDir,docs: { docsMode: options.docsMode },
|
||||||
|
stories: ['../src/app/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
|
||||||
|
addons: ['@storybook/addon-essentials', '@storybook/addon-interactions'],
|
||||||
|
framework: {
|
||||||
|
name: '@storybook/vue3-vite',
|
||||||
|
options: {
|
||||||
|
builder: {
|
||||||
|
viteConfigPath: 'apps/myapp/vite.config.ts',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('--react-webpack', () => {
|
||||||
|
it('should migrate docsMode and staticDir to storybook config correctly', () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace();
|
||||||
|
|
||||||
|
const targetConfiguration = {
|
||||||
|
outputs: ['{options.outputDir}'],
|
||||||
|
options: {
|
||||||
|
outputDir: 'dist/storybook/myapp',
|
||||||
|
configDir: 'apps/myapp/.storybook',
|
||||||
|
docsMode: true,
|
||||||
|
staticDir: ['assets'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const inferredTargetConfiguration = {
|
||||||
|
outputs: ['{projectRoot}/{options.outputDir}'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const migrationLogs = new AggregatedLog();
|
||||||
|
|
||||||
|
tree.write(
|
||||||
|
'apps/myapp/.storybook/main.ts',
|
||||||
|
storybookConfigFileV17_ReactWebpack
|
||||||
|
);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
const target = buildPostTargetTransformer(migrationLogs)(
|
||||||
|
targetConfiguration,
|
||||||
|
tree,
|
||||||
|
{ projectName: 'myapp', root: 'apps/myapp' },
|
||||||
|
inferredTargetConfiguration
|
||||||
|
);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
const configFile = tree.read('apps/myapp/.storybook/main.ts', 'utf-8');
|
||||||
|
expect(configFile).toMatchInlineSnapshot(`
|
||||||
|
"import type { StorybookConfig } from '@storybook/react-webpack5';
|
||||||
|
|
||||||
|
// These options were migrated by @nx/storybook:convert-to-inferred from the project.json file.
|
||||||
|
const configValues = {"default":{"docsMode":true,"staticDir":["assets"]}};
|
||||||
|
|
||||||
|
// Determine the correct configValue to use based on the configuration
|
||||||
|
const nxConfiguration = process.env.NX_TASK_TARGET_CONFIGURATION ?? 'default';
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
...configValues.default,
|
||||||
|
...(configValues[nxConfiguration] ?? {})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const config: StorybookConfig = {staticDirs: options.staticDir,docs: { docsMode: options.docsMode },
|
||||||
|
stories: ['../src/app/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
|
||||||
|
addons: [
|
||||||
|
'@storybook/addon-essentials',
|
||||||
|
'@storybook/addon-interactions',
|
||||||
|
'@nx/react/plugins/storybook',
|
||||||
|
],
|
||||||
|
framework: {
|
||||||
|
name: '@storybook/react-webpack5',
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;"
|
||||||
|
`);
|
||||||
|
expect(target).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"options": {
|
||||||
|
"config-dir": ".storybook",
|
||||||
|
"output-dir": "../../dist/storybook/myapp",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle configurations correctly and migrate docsMode and staticDir to storybook config correctly', () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace();
|
||||||
|
|
||||||
|
const targetConfiguration = {
|
||||||
|
outputs: ['{options.outputDir}'],
|
||||||
|
options: {
|
||||||
|
outputDir: 'dist/storybook/myapp',
|
||||||
|
configDir: 'apps/myapp/.storybook',
|
||||||
|
docsMode: true,
|
||||||
|
staticDir: ['assets'],
|
||||||
|
},
|
||||||
|
configurations: {
|
||||||
|
dev: {
|
||||||
|
outputDir: 'dist/storybook/myapp/dev',
|
||||||
|
configDir: 'apps/myapp/dev/.storybook',
|
||||||
|
docsMode: false,
|
||||||
|
staticDir: ['dev/assets'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const inferredTargetConfiguration = {
|
||||||
|
outputs: ['{projectRoot}/{options.outputDir}'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const migrationLogs = new AggregatedLog();
|
||||||
|
|
||||||
|
tree.write(
|
||||||
|
'apps/myapp/.storybook/main.ts',
|
||||||
|
storybookConfigFileV17_ReactWebpack
|
||||||
|
);
|
||||||
|
tree.write(
|
||||||
|
'apps/myapp/dev/.storybook/main.ts',
|
||||||
|
storybookConfigFileV17_ReactWebpack
|
||||||
|
);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
const target = buildPostTargetTransformer(migrationLogs)(
|
||||||
|
targetConfiguration,
|
||||||
|
tree,
|
||||||
|
{ projectName: 'myapp', root: 'apps/myapp' },
|
||||||
|
inferredTargetConfiguration
|
||||||
|
);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
const configFile = tree.read('apps/myapp/.storybook/main.ts', 'utf-8');
|
||||||
|
expect(configFile).toMatchInlineSnapshot(`
|
||||||
|
"import type { StorybookConfig } from '@storybook/react-webpack5';
|
||||||
|
|
||||||
|
// These options were migrated by @nx/storybook:convert-to-inferred from the project.json file.
|
||||||
|
const configValues = {"default":{"docsMode":true,"staticDir":["assets"]},"dev":{"docsMode":false,"staticDir":["dev/assets"]}};
|
||||||
|
|
||||||
|
// Determine the correct configValue to use based on the configuration
|
||||||
|
const nxConfiguration = process.env.NX_TASK_TARGET_CONFIGURATION ?? 'default';
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
...configValues.default,
|
||||||
|
...(configValues[nxConfiguration] ?? {})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const config: StorybookConfig = {staticDirs: options.staticDir,docs: { docsMode: options.docsMode },
|
||||||
|
stories: ['../src/app/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
|
||||||
|
addons: [
|
||||||
|
'@storybook/addon-essentials',
|
||||||
|
'@storybook/addon-interactions',
|
||||||
|
'@nx/react/plugins/storybook',
|
||||||
|
],
|
||||||
|
framework: {
|
||||||
|
name: '@storybook/react-webpack5',
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;"
|
||||||
|
`);
|
||||||
|
expect(target).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"configurations": {
|
||||||
|
"dev": {
|
||||||
|
"config-dir": "./dev/.storybook",
|
||||||
|
"output-dir": "../../dist/storybook/myapp/dev",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"config-dir": ".storybook",
|
||||||
|
"output-dir": "../../dist/storybook/myapp",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
const devConfigFile = tree.read(
|
||||||
|
'apps/myapp/dev/.storybook/main.ts',
|
||||||
|
'utf-8'
|
||||||
|
);
|
||||||
|
expect(devConfigFile).toMatchInlineSnapshot(`
|
||||||
|
"import type { StorybookConfig } from '@storybook/react-webpack5';
|
||||||
|
|
||||||
|
const config: StorybookConfig = {staticDirs: options.staticDir,docs: { docsMode: options.docsMode },
|
||||||
|
stories: ['../src/app/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
|
||||||
|
addons: [
|
||||||
|
'@storybook/addon-essentials',
|
||||||
|
'@storybook/addon-interactions',
|
||||||
|
'@nx/react/plugins/storybook',
|
||||||
|
],
|
||||||
|
framework: {
|
||||||
|
name: '@storybook/react-webpack5',
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const storybookConfigFileV17_ReactVite = `import type { StorybookConfig } from '@storybook/react-vite';
|
||||||
|
|
||||||
|
const config: StorybookConfig = {
|
||||||
|
stories: ['../src/app/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
|
||||||
|
addons: ['@storybook/addon-essentials', '@storybook/addon-interactions'],
|
||||||
|
framework: {
|
||||||
|
name: '@storybook/react-vite',
|
||||||
|
options: {
|
||||||
|
builder: {
|
||||||
|
viteConfigPath: 'apps/myapp/vite.config.ts',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;`;
|
||||||
|
|
||||||
|
const storybookConfigFileV17_ReactWebpack = `import type { StorybookConfig } from '@storybook/react-webpack5';
|
||||||
|
|
||||||
|
const config: StorybookConfig = {
|
||||||
|
stories: ['../src/app/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
|
||||||
|
addons: [
|
||||||
|
'@storybook/addon-essentials',
|
||||||
|
'@storybook/addon-interactions',
|
||||||
|
'@nx/react/plugins/storybook',
|
||||||
|
],
|
||||||
|
framework: {
|
||||||
|
name: '@storybook/react-webpack5',
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;`;
|
||||||
|
|
||||||
|
const storybookConfigFileV17_VueVite = `import type { StorybookConfig } from '@storybook/vue3-vite';
|
||||||
|
|
||||||
|
const config: StorybookConfig = {
|
||||||
|
stories: ['../src/app/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
|
||||||
|
addons: ['@storybook/addon-essentials', '@storybook/addon-interactions'],
|
||||||
|
framework: {
|
||||||
|
name: '@storybook/vue3-vite',
|
||||||
|
options: {
|
||||||
|
builder: {
|
||||||
|
viteConfigPath: 'apps/myapp/vite.config.ts',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;`;
|
||||||
@ -0,0 +1,313 @@
|
|||||||
|
import { joinPathFragments, TargetConfiguration, Tree } from '@nx/devkit';
|
||||||
|
import {
|
||||||
|
processTargetOutputs,
|
||||||
|
toProjectRelativePath,
|
||||||
|
} from '@nx/devkit/src/generators/plugin-migrations/plugin-migration-utils';
|
||||||
|
import { tsquery } from '@phenomnomnominal/tsquery';
|
||||||
|
import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
|
||||||
|
import {
|
||||||
|
addConfigValuesToConfigFile,
|
||||||
|
ensureViteConfigPathIsRelative,
|
||||||
|
getConfigFilePath,
|
||||||
|
STORYBOOK_PROP_MAPPINGS,
|
||||||
|
} from './utils';
|
||||||
|
import { getInstalledPackageVersionInfo } from './utils';
|
||||||
|
|
||||||
|
type StorybookConfigValues = { docsMode?: boolean; staticDir?: string };
|
||||||
|
|
||||||
|
export function buildPostTargetTransformer(migrationLogs: AggregatedLog) {
|
||||||
|
return (
|
||||||
|
target: TargetConfiguration,
|
||||||
|
tree: Tree,
|
||||||
|
projectDetails: { projectName: string; root: string },
|
||||||
|
inferredTargetConfiguration: TargetConfiguration
|
||||||
|
) => {
|
||||||
|
let defaultConfigDir = joinPathFragments(projectDetails.root, '.storybook');
|
||||||
|
|
||||||
|
const configValues: Record<string, StorybookConfigValues> = {
|
||||||
|
default: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (target.options) {
|
||||||
|
if (target.options.configDir) {
|
||||||
|
defaultConfigDir = target.options.configDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePropertiesFromTargetOptions(
|
||||||
|
tree,
|
||||||
|
target.options,
|
||||||
|
defaultConfigDir,
|
||||||
|
projectDetails.projectName,
|
||||||
|
projectDetails.root,
|
||||||
|
configValues['default'],
|
||||||
|
migrationLogs
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target.configurations) {
|
||||||
|
for (const configurationName in target.configurations) {
|
||||||
|
const configuration = target.configurations[configurationName];
|
||||||
|
configValues[configurationName] = {};
|
||||||
|
handlePropertiesFromTargetOptions(
|
||||||
|
tree,
|
||||||
|
configuration,
|
||||||
|
defaultConfigDir,
|
||||||
|
projectDetails.projectName,
|
||||||
|
projectDetails.root,
|
||||||
|
configValues[configurationName],
|
||||||
|
migrationLogs
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const configurationName in target.configurations) {
|
||||||
|
const configuration = target.configurations[configurationName];
|
||||||
|
if (
|
||||||
|
configuration.configDir &&
|
||||||
|
configuration.configDir !==
|
||||||
|
toProjectRelativePath(defaultConfigDir, projectDetails.root)
|
||||||
|
) {
|
||||||
|
const configFilePath = getConfigFilePath(
|
||||||
|
tree,
|
||||||
|
joinPathFragments(projectDetails.root, configuration.configDir)
|
||||||
|
);
|
||||||
|
addConfigValuesToConfigFile(tree, configFilePath, configValues);
|
||||||
|
ensureViteConfigPathIsRelative(
|
||||||
|
tree,
|
||||||
|
configFilePath,
|
||||||
|
projectDetails.projectName,
|
||||||
|
projectDetails.root,
|
||||||
|
'@nx/storybook:build',
|
||||||
|
migrationLogs
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
configurationName === 'ci' &&
|
||||||
|
Object.keys(configuration).length === 0
|
||||||
|
) {
|
||||||
|
delete target.configurations[configurationName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(target.configurations).length === 0) {
|
||||||
|
if ('defaultConfiguration' in target) {
|
||||||
|
delete target.defaultConfiguration;
|
||||||
|
}
|
||||||
|
delete target.configurations;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
'defaultConfiguration' in target &&
|
||||||
|
!target.configurations[target.defaultConfiguration]
|
||||||
|
) {
|
||||||
|
delete target.defaultConfiguration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target.outputs) {
|
||||||
|
processTargetOutputs(
|
||||||
|
target,
|
||||||
|
[{ newName: 'outputDir', oldName: 'outputDir' }],
|
||||||
|
inferredTargetConfiguration,
|
||||||
|
{
|
||||||
|
projectName: projectDetails.projectName,
|
||||||
|
projectRoot: projectDetails.root,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
addConfigValuesToConfigFile(
|
||||||
|
tree,
|
||||||
|
getConfigFilePath(tree, defaultConfigDir),
|
||||||
|
configValues
|
||||||
|
);
|
||||||
|
ensureViteConfigPathIsRelative(
|
||||||
|
tree,
|
||||||
|
getConfigFilePath(tree, defaultConfigDir),
|
||||||
|
projectDetails.projectName,
|
||||||
|
projectDetails.root,
|
||||||
|
'@nx/storybook:build',
|
||||||
|
migrationLogs
|
||||||
|
);
|
||||||
|
|
||||||
|
return target;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePropertiesFromTargetOptions(
|
||||||
|
tree: Tree,
|
||||||
|
options: any,
|
||||||
|
defaultConfigDir: string,
|
||||||
|
projectName: string,
|
||||||
|
projectRoot: string,
|
||||||
|
configValues: StorybookConfigValues,
|
||||||
|
migrationLogs: AggregatedLog
|
||||||
|
) {
|
||||||
|
let configDir = defaultConfigDir;
|
||||||
|
if ('configDir' in options) {
|
||||||
|
if (options.configDir !== defaultConfigDir) {
|
||||||
|
configDir = options.configDir as string;
|
||||||
|
}
|
||||||
|
options.configDir = toProjectRelativePath(options.configDir, projectRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.outputDir) {
|
||||||
|
options.outputDir = toProjectRelativePath(options.outputDir, projectRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('styles' in options) {
|
||||||
|
delete options.styles;
|
||||||
|
}
|
||||||
|
if ('stylePreprocessorOptions' in options) {
|
||||||
|
delete options.stylePreprocessorOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('docsMode' in options) {
|
||||||
|
configValues.docsMode = options.docsMode;
|
||||||
|
moveDocsModeToConfigFile(
|
||||||
|
tree,
|
||||||
|
configDir,
|
||||||
|
projectName,
|
||||||
|
migrationLogs,
|
||||||
|
configDir === defaultConfigDir
|
||||||
|
);
|
||||||
|
delete options.docsMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('staticDir' in options) {
|
||||||
|
configValues.staticDir = options.staticDir;
|
||||||
|
moveStaticDirToConfigFile(
|
||||||
|
tree,
|
||||||
|
configDir,
|
||||||
|
projectName,
|
||||||
|
migrationLogs,
|
||||||
|
configDir === defaultConfigDir
|
||||||
|
);
|
||||||
|
delete options.staticDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
const storybookPropMappings =
|
||||||
|
getInstalledPackageVersionInfo(tree, 'storybook')?.major === 8
|
||||||
|
? STORYBOOK_PROP_MAPPINGS.v8
|
||||||
|
: STORYBOOK_PROP_MAPPINGS.v7;
|
||||||
|
for (const [prevKey, newKey] of Object.entries(storybookPropMappings)) {
|
||||||
|
if (prevKey in options) {
|
||||||
|
let prevValue = options[prevKey];
|
||||||
|
delete options[prevKey];
|
||||||
|
options[newKey] = prevValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveDocsModeToConfigFile(
|
||||||
|
tree: Tree,
|
||||||
|
configDir: string,
|
||||||
|
projectName: string,
|
||||||
|
migrationLogs: AggregatedLog,
|
||||||
|
useConfigValues = true
|
||||||
|
) {
|
||||||
|
const configFilePath = getConfigFilePath(tree, configDir);
|
||||||
|
const configFileContents = tree.read(configFilePath, 'utf-8');
|
||||||
|
|
||||||
|
const ast = tsquery.ast(configFileContents);
|
||||||
|
const CONFIG_OBJECT_SELECTOR =
|
||||||
|
'VariableDeclaration:has(Identifier[name=config]) ObjectLiteralExpression';
|
||||||
|
const DOCS_MODE_SELECTOR =
|
||||||
|
'PropertyAssignment:has(Identifier[name=docs]) PropertyAssignment:has(Identifier[name=docsMode])';
|
||||||
|
const DOCS_SELECTOR = 'PropertyAssignment:has(Identifier[name=docs])';
|
||||||
|
|
||||||
|
const configNodes = tsquery(ast, CONFIG_OBJECT_SELECTOR, {
|
||||||
|
visitAllChildren: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (configNodes.length === 0) {
|
||||||
|
// Invalid config file
|
||||||
|
migrationLogs.addLog({
|
||||||
|
project: projectName,
|
||||||
|
executorName: '@nx/storybook:build',
|
||||||
|
log: 'Could not find a valid Storybook Config to migrate `docsMode`. Update your `main.ts` file to add `docsMode`.',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const configNode = configNodes[0];
|
||||||
|
const hasDocsMode =
|
||||||
|
tsquery(configNode, DOCS_MODE_SELECTOR, { visitAllChildren: true }).length >
|
||||||
|
0;
|
||||||
|
if (hasDocsMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let startPosition = configNode.getStart() + 1;
|
||||||
|
let needsDocObject = true;
|
||||||
|
|
||||||
|
const docsNodes = tsquery(configNode, DOCS_SELECTOR, {
|
||||||
|
visitAllChildren: true,
|
||||||
|
});
|
||||||
|
if (docsNodes.length > 0) {
|
||||||
|
needsDocObject = false;
|
||||||
|
startPosition = docsNodes[0].getStart() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const docsModeInsert = `options.docsMode`;
|
||||||
|
const nodeToInsert = needsDocObject
|
||||||
|
? `docs: { docsMode: ${docsModeInsert} },`
|
||||||
|
: `docsMode: ${docsModeInsert},`;
|
||||||
|
tree.write(
|
||||||
|
configFilePath,
|
||||||
|
`${configFileContents.slice(
|
||||||
|
0,
|
||||||
|
startPosition
|
||||||
|
)}${nodeToInsert}${configFileContents.slice(startPosition)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveStaticDirToConfigFile(
|
||||||
|
tree: Tree,
|
||||||
|
configDir: string,
|
||||||
|
projectName: string,
|
||||||
|
migrationLogs: AggregatedLog,
|
||||||
|
useConfigValues = true
|
||||||
|
) {
|
||||||
|
const configFilePath = getConfigFilePath(tree, configDir);
|
||||||
|
const configFileContents = tree.read(configFilePath, 'utf-8');
|
||||||
|
|
||||||
|
const ast = tsquery.ast(configFileContents);
|
||||||
|
const CONFIG_OBJECT_SELECTOR =
|
||||||
|
'VariableDeclaration:has(Identifier[name=config]) ObjectLiteralExpression';
|
||||||
|
const STATIC_DIRS_SELECTOR =
|
||||||
|
'PropertyAssignment:has(Identifier[name=staticDirs])';
|
||||||
|
|
||||||
|
const configNodes = tsquery(ast, CONFIG_OBJECT_SELECTOR, {
|
||||||
|
visitAllChildren: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (configNodes.length === 0) {
|
||||||
|
// Invalid config file
|
||||||
|
migrationLogs.addLog({
|
||||||
|
project: projectName,
|
||||||
|
executorName: '@nx/storybook:build',
|
||||||
|
log: 'Could not find a valid Storybook Config to migrate `staticDir`. Update your `main.ts` file to add `staticDirs`.',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const configNode = configNodes[0];
|
||||||
|
const hasStaticDir =
|
||||||
|
tsquery(configNode, STATIC_DIRS_SELECTOR, { visitAllChildren: true })
|
||||||
|
.length > 0;
|
||||||
|
if (hasStaticDir) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let startPosition = configNode.getStart() + 1;
|
||||||
|
|
||||||
|
const nodeToInsert = `staticDirs: options.staticDir,`;
|
||||||
|
tree.write(
|
||||||
|
configFilePath,
|
||||||
|
`${configFileContents.slice(
|
||||||
|
0,
|
||||||
|
startPosition
|
||||||
|
)}${nodeToInsert}${configFileContents.slice(startPosition)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,156 @@
|
|||||||
|
import { joinPathFragments, TargetConfiguration, Tree } from '@nx/devkit';
|
||||||
|
import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
|
||||||
|
import { toProjectRelativePath } from '@nx/devkit/src/generators/plugin-migrations/plugin-migration-utils';
|
||||||
|
import {
|
||||||
|
ensureViteConfigPathIsRelative,
|
||||||
|
getConfigFilePath,
|
||||||
|
getInstalledPackageVersionInfo,
|
||||||
|
STORYBOOK_PROP_MAPPINGS,
|
||||||
|
} from './utils';
|
||||||
|
export function servePostTargetTransformer(migrationLogs: AggregatedLog) {
|
||||||
|
return (
|
||||||
|
target: TargetConfiguration,
|
||||||
|
tree: Tree,
|
||||||
|
projectDetails: { projectName: string; root: string },
|
||||||
|
inferredTargetConfiguration: TargetConfiguration
|
||||||
|
) => {
|
||||||
|
let defaultConfigDir = getConfigFilePath(
|
||||||
|
tree,
|
||||||
|
joinPathFragments(projectDetails.root, '.storybook')
|
||||||
|
);
|
||||||
|
|
||||||
|
if (target.options) {
|
||||||
|
if (target.options.configDir) {
|
||||||
|
defaultConfigDir = target.options.configDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePropertiesFromTargetOptions(
|
||||||
|
tree,
|
||||||
|
target.options,
|
||||||
|
projectDetails.projectName,
|
||||||
|
projectDetails.root,
|
||||||
|
migrationLogs
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target.configurations) {
|
||||||
|
for (const configurationName in target.configurations) {
|
||||||
|
const configuration = target.configurations[configurationName];
|
||||||
|
if (
|
||||||
|
configuration.configDir &&
|
||||||
|
configuration.configDir !== defaultConfigDir
|
||||||
|
) {
|
||||||
|
ensureViteConfigPathIsRelative(
|
||||||
|
tree,
|
||||||
|
getConfigFilePath(tree, configuration.configDir),
|
||||||
|
projectDetails.projectName,
|
||||||
|
projectDetails.root,
|
||||||
|
'@nx/storybook:storybook',
|
||||||
|
migrationLogs
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePropertiesFromTargetOptions(
|
||||||
|
tree,
|
||||||
|
configuration,
|
||||||
|
projectDetails.projectName,
|
||||||
|
projectDetails.root,
|
||||||
|
migrationLogs
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(target.configurations).length === 0) {
|
||||||
|
if ('defaultConfiguration' in target) {
|
||||||
|
delete target.defaultConfiguration;
|
||||||
|
}
|
||||||
|
delete target.configurations;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
'defaultConfiguration' in target &&
|
||||||
|
!target.configurations[target.defaultConfiguration]
|
||||||
|
) {
|
||||||
|
delete target.defaultConfiguration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureViteConfigPathIsRelative(
|
||||||
|
tree,
|
||||||
|
getConfigFilePath(tree, defaultConfigDir),
|
||||||
|
projectDetails.projectName,
|
||||||
|
projectDetails.root,
|
||||||
|
'@nx/storybook:storybook',
|
||||||
|
migrationLogs
|
||||||
|
);
|
||||||
|
|
||||||
|
return target;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePropertiesFromTargetOptions(
|
||||||
|
tree: Tree,
|
||||||
|
options: any,
|
||||||
|
projectName: string,
|
||||||
|
projectRoot: string,
|
||||||
|
migrationLogs: AggregatedLog
|
||||||
|
) {
|
||||||
|
if ('configDir' in options) {
|
||||||
|
options.configDir = toProjectRelativePath(options.configDir, projectRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.outputDir) {
|
||||||
|
options.outputDir = toProjectRelativePath(options.outputDir, projectRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('uiFramework' in options) {
|
||||||
|
delete options.uiFramework;
|
||||||
|
}
|
||||||
|
if ('staticDir' in options) {
|
||||||
|
migrationLogs.addLog({
|
||||||
|
project: projectName,
|
||||||
|
executorName: '@nx/storybook:storybook',
|
||||||
|
log: 'Could not migrate `staticDir`. Update your `main.ts` file to add `staticDirs`.',
|
||||||
|
});
|
||||||
|
delete options.staticDir;
|
||||||
|
}
|
||||||
|
if ('open' in options) {
|
||||||
|
if (!options.open) {
|
||||||
|
options['args'] ??= [];
|
||||||
|
options['args'].push('--no-open');
|
||||||
|
}
|
||||||
|
delete options.open;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('no-open' in options) {
|
||||||
|
if (options['no-open']) {
|
||||||
|
options['args'] ??= [];
|
||||||
|
options['args'].push('--no-open');
|
||||||
|
}
|
||||||
|
delete options['no-open'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('quiet' in options) {
|
||||||
|
if (options['quiet']) {
|
||||||
|
options['args'] ??= [];
|
||||||
|
options['args'].push('--quiet');
|
||||||
|
}
|
||||||
|
delete options.quiet;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('docsMode' in options) {
|
||||||
|
options.docs = options.docsMode;
|
||||||
|
delete options.docsMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const storybookPropMappings =
|
||||||
|
getInstalledPackageVersionInfo(tree, 'storybook')?.major === 8
|
||||||
|
? STORYBOOK_PROP_MAPPINGS.v8
|
||||||
|
: STORYBOOK_PROP_MAPPINGS.v7;
|
||||||
|
for (const [prevKey, newKey] of Object.entries(storybookPropMappings)) {
|
||||||
|
if (prevKey in options) {
|
||||||
|
let prevValue = options[prevKey];
|
||||||
|
delete options[prevKey];
|
||||||
|
options[newKey] = prevValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,200 @@
|
|||||||
|
import { tsquery } from '@phenomnomnominal/tsquery';
|
||||||
|
import { readJson, joinPathFragments, type Tree } from '@nx/devkit';
|
||||||
|
import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
|
||||||
|
import { toProjectRelativePath } from '@nx/devkit/src/generators/plugin-migrations/plugin-migration-utils';
|
||||||
|
import { dirname } from 'path/posix';
|
||||||
|
import { coerce, major } from 'semver';
|
||||||
|
|
||||||
|
export function getConfigFilePath(tree: Tree, configDir: string) {
|
||||||
|
return [
|
||||||
|
joinPathFragments(configDir, `main.ts`),
|
||||||
|
joinPathFragments(configDir, `main.cts`),
|
||||||
|
joinPathFragments(configDir, `main.mts`),
|
||||||
|
joinPathFragments(configDir, `main.js`),
|
||||||
|
joinPathFragments(configDir, `main.cjs`),
|
||||||
|
joinPathFragments(configDir, `main.mjs`),
|
||||||
|
].find((f) => tree.exists(f));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addConfigValuesToConfigFile(
|
||||||
|
tree: Tree,
|
||||||
|
configFile: string,
|
||||||
|
configValues: Record<string, Record<string, unknown>>
|
||||||
|
) {
|
||||||
|
const IMPORT_PROPERTY_SELECTOR = 'ImportDeclaration';
|
||||||
|
const configFileContents = tree.read(configFile, 'utf-8');
|
||||||
|
|
||||||
|
const ast = tsquery.ast(configFileContents);
|
||||||
|
// AST TO GET SECTION TO APPEND TO
|
||||||
|
const importNodes = tsquery(ast, IMPORT_PROPERTY_SELECTOR, {
|
||||||
|
visitAllChildren: true,
|
||||||
|
});
|
||||||
|
let startPosition = 0;
|
||||||
|
if (importNodes.length !== 0) {
|
||||||
|
const lastImportNode = importNodes[importNodes.length - 1];
|
||||||
|
startPosition = lastImportNode.getEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
const configValuesString = `
|
||||||
|
// These options were migrated by @nx/storybook:convert-to-inferred from the project.json file.
|
||||||
|
const configValues = ${JSON.stringify(configValues)};
|
||||||
|
|
||||||
|
// Determine the correct configValue to use based on the configuration
|
||||||
|
const nxConfiguration = process.env.NX_TASK_TARGET_CONFIGURATION ?? 'default';
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
...configValues.default,
|
||||||
|
...(configValues[nxConfiguration] ?? {})
|
||||||
|
}`;
|
||||||
|
|
||||||
|
tree.write(
|
||||||
|
configFile,
|
||||||
|
`${configFileContents.slice(0, startPosition)}
|
||||||
|
${configValuesString}
|
||||||
|
${configFileContents.slice(startPosition)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const STORYBOOK_PROP_MAPPINGS = {
|
||||||
|
v7: {
|
||||||
|
port: 'port',
|
||||||
|
previewUrl: 'preview-url',
|
||||||
|
host: 'host',
|
||||||
|
docs: 'docs',
|
||||||
|
configDir: 'config-dir',
|
||||||
|
logLevel: 'loglevel',
|
||||||
|
quiet: 'quiet',
|
||||||
|
webpackStatsJson: 'webpack-stats-json',
|
||||||
|
debugWebpack: 'debug-webpack',
|
||||||
|
disableTelemetry: 'disable-telemetry',
|
||||||
|
https: 'https',
|
||||||
|
sslCa: 'ssl-ca',
|
||||||
|
sslCert: 'ssl-cert',
|
||||||
|
sslKey: 'ssl-key',
|
||||||
|
smokeTest: 'smoke-test',
|
||||||
|
noOpen: 'no-open',
|
||||||
|
outputDir: 'output-dir',
|
||||||
|
},
|
||||||
|
v8: {
|
||||||
|
port: 'port',
|
||||||
|
previewUrl: 'preview-url',
|
||||||
|
host: 'host',
|
||||||
|
docs: 'docs',
|
||||||
|
configDir: 'config-dir',
|
||||||
|
logLevel: 'loglevel',
|
||||||
|
quiet: 'quiet',
|
||||||
|
webpackStatsJson: 'stats-json',
|
||||||
|
debugWebpack: 'debug-webpack',
|
||||||
|
disableTelemetry: 'disable-telemetry',
|
||||||
|
https: 'https',
|
||||||
|
sslCa: 'ssl-ca',
|
||||||
|
sslCert: 'ssl-cert',
|
||||||
|
sslKey: 'ssl-key',
|
||||||
|
smokeTest: 'smoke-test',
|
||||||
|
noOpen: 'no-open',
|
||||||
|
outputDir: 'output-dir',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ensureViteConfigPathIsRelative(
|
||||||
|
tree: Tree,
|
||||||
|
configPath: string,
|
||||||
|
projectName: string,
|
||||||
|
projectRoot: string,
|
||||||
|
executorName: string,
|
||||||
|
migrationLogs: AggregatedLog
|
||||||
|
) {
|
||||||
|
const configFileContents = tree.read(configPath, 'utf-8');
|
||||||
|
|
||||||
|
if (configFileContents.includes('viteFinal:')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ast = tsquery.ast(configFileContents);
|
||||||
|
const REACT_FRAMEWORK_SELECTOR_IDENTIFIERS =
|
||||||
|
'PropertyAssignment:has(Identifier[name=framework]) PropertyAssignment:has(Identifier[name=name]) StringLiteral[value=@storybook/react-vite]';
|
||||||
|
const REACT_FRAMEWORK_SELECTOR_STRING_LITERALS =
|
||||||
|
'PropertyAssignment:has(StringLiteral[value=framework]) PropertyAssignment:has(StringLiteral[value=name]) StringLiteral[value=@storybook/react-vite]';
|
||||||
|
|
||||||
|
const VUE_FRAMEWORK_SELECTOR_IDENTIFIERS =
|
||||||
|
'PropertyAssignment:has(Identifier[name=framework]) PropertyAssignment:has(Identifier[name=name]) StringLiteral[value=@storybook/vue3-vite]';
|
||||||
|
const VUE_FRAMEWORK_SELECTOR_STRING_LITERALS =
|
||||||
|
'PropertyAssignment:has(StringLiteral[value=framework]) PropertyAssignment:has(StringLiteral[value=name]) StringLiteral[value=@storybook/vue3-vite]';
|
||||||
|
const isUsingVite =
|
||||||
|
tsquery(ast, REACT_FRAMEWORK_SELECTOR_IDENTIFIERS, {
|
||||||
|
visitAllChildren: true,
|
||||||
|
}).length > 0 ||
|
||||||
|
tsquery(ast, REACT_FRAMEWORK_SELECTOR_STRING_LITERALS, {
|
||||||
|
visitAllChildren: true,
|
||||||
|
}).length > 0 ||
|
||||||
|
tsquery(ast, VUE_FRAMEWORK_SELECTOR_STRING_LITERALS, {
|
||||||
|
visitAllChildren: true,
|
||||||
|
}).length > 0 ||
|
||||||
|
tsquery(ast, VUE_FRAMEWORK_SELECTOR_IDENTIFIERS, { visitAllChildren: true })
|
||||||
|
.length > 0;
|
||||||
|
if (!isUsingVite) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VITE_CONFIG_PATH_SELECTOR =
|
||||||
|
'PropertyAssignment:has(Identifier[name=framework]) PropertyAssignment PropertyAssignment PropertyAssignment:has(Identifier[name=viteConfigPath]) > StringLiteral';
|
||||||
|
let viteConfigPathNodes = tsquery(ast, VITE_CONFIG_PATH_SELECTOR, {
|
||||||
|
visitAllChildren: true,
|
||||||
|
});
|
||||||
|
if (viteConfigPathNodes.length === 0) {
|
||||||
|
const VITE_CONFIG_PATH_SELECTOR_STRING_LITERALS =
|
||||||
|
'PropertyAssignment:has(StringLiteral[value=framework]) PropertyAssignment PropertyAssignment PropertyAssignment:has(StringLiteral[value=viteConfigPath]) > StringLiteral:not(StringLiteral[value=viteConfigPath])';
|
||||||
|
viteConfigPathNodes = tsquery(
|
||||||
|
ast,
|
||||||
|
VITE_CONFIG_PATH_SELECTOR_STRING_LITERALS,
|
||||||
|
{
|
||||||
|
visitAllChildren: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (viteConfigPathNodes.length === 0) {
|
||||||
|
migrationLogs.addLog({
|
||||||
|
project: projectName,
|
||||||
|
executorName,
|
||||||
|
log: 'Unable to find `viteConfigPath` in Storybook Config. Please ensure the `viteConfigPath` is relative to the project root.',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const viteConfigPathNode = viteConfigPathNodes[0];
|
||||||
|
const pathToViteConfig = viteConfigPathNode.getText().replace(/('|")/g, '');
|
||||||
|
if (pathToViteConfig.match(/^(\.\.\/|\.\/)/)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const relativePathToViteConfig = toProjectRelativePath(
|
||||||
|
pathToViteConfig,
|
||||||
|
projectRoot
|
||||||
|
);
|
||||||
|
|
||||||
|
tree.write(
|
||||||
|
configPath,
|
||||||
|
`${configFileContents.slice(
|
||||||
|
0,
|
||||||
|
viteConfigPathNode.getStart() + 1
|
||||||
|
)}${relativePathToViteConfig}${configFileContents.slice(
|
||||||
|
viteConfigPathNode.getEnd() - 1
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getInstalledPackageVersion(
|
||||||
|
tree: Tree,
|
||||||
|
pkgName: string
|
||||||
|
): string | null {
|
||||||
|
const { dependencies, devDependencies } = readJson(tree, 'package.json');
|
||||||
|
const version = dependencies?.[pkgName] ?? devDependencies?.[pkgName];
|
||||||
|
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getInstalledPackageVersionInfo(tree: Tree, pkgName: string) {
|
||||||
|
const version = getInstalledPackageVersion(tree, pkgName);
|
||||||
|
|
||||||
|
return version ? { major: major(coerce(version)), version } : null;
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/schema",
|
||||||
|
"$id": "NxStorybookConvertToInferred",
|
||||||
|
"description": "Convert existing Storybook project(s) using `@nx/storybook:*` executors to use `@nx/storybook/plugin`. Defaults to migrating all projects. Pass '--project' to migrate only one target.",
|
||||||
|
"title": "Convert Storybook project from executor to plugin",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"project": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The project to convert from using the `@nx/storybook:*` executors to use `@nx/storybook/plugin`.",
|
||||||
|
"x-priority": "important"
|
||||||
|
},
|
||||||
|
"skipFormat": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether to format files at the end of the migration.",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -38,13 +38,13 @@ export function buildPostTargetTransformer(
|
|||||||
if (target.configurations) {
|
if (target.configurations) {
|
||||||
for (const configurationName in target.configurations) {
|
for (const configurationName in target.configurations) {
|
||||||
const configuration = target.configurations[configurationName];
|
const configuration = target.configurations[configurationName];
|
||||||
configValues[configuration] = {};
|
configValues[configurationName] = {};
|
||||||
removePropertiesFromTargetOptions(
|
removePropertiesFromTargetOptions(
|
||||||
tree,
|
tree,
|
||||||
configuration,
|
configuration,
|
||||||
viteConfigPath,
|
viteConfigPath,
|
||||||
projectDetails.root,
|
projectDetails.root,
|
||||||
configValues[configuration]
|
configValues[configurationName]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (Object.keys(configuration).length === 0) {
|
if (Object.keys(configuration).length === 0) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user