Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Got verdaccio working, borrowing heavily from the old repro command #18844

Merged
merged 3 commits into from
Aug 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ dist
junit.xml
/repros
/sandbox
.verdaccio-cache

# Yarn stuff
/**/.yarn/*
Expand Down
62 changes: 42 additions & 20 deletions scripts/sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import prompts from 'prompts';

import { getOptionsOrPrompt } from './utils/options';
import { executeCLIStep } from './utils/cli-step';
import { exec } from '../code/lib/cli/src/repro-generators/scripts';
import { installYarn2, configureYarn2ForVerdaccio, addPackageResolutions } from './utils/yarn';
import { exec } from './utils/exec';
import { getInterpretedFile } from '../code/lib/core-common';
import { ConfigFile, readConfig, writeConfig } from '../code/lib/csf-tools';
import { babelParse } from '../code/lib/csf-tools/src/babelParse';
Expand Down Expand Up @@ -134,7 +135,7 @@ async function addPackageScripts({
cwd: string;
scripts: Record<string, string>;
}) {
logger.info(`🔢 Adding package resolutions:`);
logger.info(`🔢 Adding package scripts:`);
const packageJsonPath = path.join(cwd, 'package.json');
const packageJson = await readJSON(packageJsonPath);
packageJson.scripts = {
Expand All @@ -159,7 +160,7 @@ const webpackFinalCode = `
...config.modules,
rules: [
{
test: [/\\/node_modules\\/@storybook\\/[^/]*\\/template\\/stories\\//],
test: [/\\/code\\/[^/]*\\/[^/]*\\/template\\/stories\\//],
loader: '${loaderPath}',
options: {
loader: 'tsx',
Expand All @@ -171,7 +172,7 @@ const webpackFinalCode = `
},
})`;

// paths are of the form 'node_modules/@storybook/react'
// paths are of the form 'renderers/react', 'addons/actions'
async function addStories(paths: string[], { mainConfig }: { mainConfig: ConfigFile }) {
const stories = mainConfig.getFieldValue(['stories']) as string[];
const extraStoryDirsAndExistence = await Promise.all(
Expand All @@ -180,9 +181,10 @@ async function addStories(paths: string[], { mainConfig }: { mainConfig: ConfigF
.map(async (p) => [p, await pathExists(path.resolve(codeDir, p))] as const)
);

const relativeCodeDir = path.join('..', '..', '..', 'code');
const extraStories = extraStoryDirsAndExistence
.filter(([, exists]) => exists)
.map(([p]) => path.join('..', p, '*.stories.@(js|jsx|ts|tsx)'));
.map(([p]) => path.join(relativeCodeDir, p, '*.stories.@(js|jsx|ts|tsx)'));
mainConfig.setFieldValue(['stories'], [...stories, ...extraStories]);

mainConfig.setFieldNode(
Expand Down Expand Up @@ -228,7 +230,8 @@ async function main() {
const storiesPath = await findFirstPath([path.join('src', 'stories'), 'stories'], { cwd });

// Link in the template/components/index.js from the renderer
const rendererPath = path.join('node_modules', templateConfig.expected.renderer);
const rendererName = templateConfig.expected.renderer.split('/')[1];
const rendererPath = path.join('renderers', rendererName);
await ensureSymlink(
path.join(codeDir, rendererPath, 'template', 'components'),
path.resolve(cwd, storiesPath, 'components')
Expand All @@ -252,34 +255,53 @@ async function main() {
}

for (const addon of [...defaultAddons, ...optionValues.addon]) {
storiesToAdd.push(path.join('node_modules', '@storybook', `addon-${addon}`));
storiesToAdd.push(path.join('addons', addon));
}
await addStories(storiesToAdd, { mainConfig });

await writeConfig(mainConfig);

await installYarn2({ cwd, dryRun });
if (link) {
await exec('yarn set version berry', { cwd }, { dryRun });
await exec('yarn config set enableGlobalCache true', { cwd }, { dryRun });
await exec('yarn config set nodeLinker node-modules', { cwd }, { dryRun });

await executeCLIStep(steps.link, {
argument: cwd,
cwd: codeDir,
dryRun,
optionValues: { local: true, start: false },
});
} else {
await exec('yarn local-registry --publish', { cwd: codeDir }, { dryRun });

await addPackageScripts({
cwd,
scripts: {
storybook:
'NODE_OPTIONS="--preserve-symlinks --preserve-symlinks-main" storybook dev -p 6006',
'build-storybook':
'NODE_OPTIONS="--preserve-symlinks --preserve-symlinks-main" storybook build',
},
});
// NOTE: this is a background task and will run forever (TODO: sort out logging/exiting)
exec('CI=true yarn local-registry --open', { cwd: codeDir }, { dryRun });
await exec('yarn wait-on http://localhost:6000', { cwd: codeDir }, { dryRun });

// We need to add package resolutions to ensure that we only ever install the latest version
// of any storybook packages as verdaccio is not able to both proxy to npm and publish over
// the top. In theory this could mask issues where different versions cause problems.
await addPackageResolutions({ cwd, dryRun });
await configureYarn2ForVerdaccio({ cwd, dryRun });

await exec(
'yarn install',
{ cwd },
{
dryRun,
startMessage: `⬇️ Installing local dependencies`,
errorMessage: `🚨 Installing local dependencies failed`,
}
);
}

await addPackageScripts({
cwd,
scripts: {
storybook:
'NODE_OPTIONS="--preserve-symlinks --preserve-symlinks-main" storybook dev -p 6006',
'build-storybook':
'NODE_OPTIONS="--preserve-symlinks --preserve-symlinks-main" storybook build',
},
});
}

const { start } = optionValues;
Expand Down
2 changes: 1 addition & 1 deletion scripts/utils/cli-step.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getCommand, OptionSpecifier, OptionValues } from './options';
import { exec } from '../../code/lib/cli/src/repro-generators/scripts';
import { exec } from './exec';

const cliExecutable = require.resolve('../../code/lib/cli/bin/index.js');

Expand Down
46 changes: 46 additions & 0 deletions scripts/utils/exec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import shell, { ExecOptions } from 'shelljs';
import chalk from 'chalk';

const logger = console;

export const exec = async (
command: string,
options: ExecOptions = {},
{
startMessage,
errorMessage,
dryRun,
}: { startMessage?: string; errorMessage?: string; dryRun?: boolean } = {}
) => {
if (startMessage) logger.info(startMessage);

if (dryRun) {
logger.info(`\n> ${command}\n`);
return undefined;
}

logger.debug(command);
return new Promise((resolve, reject) => {
const defaultOptions: ExecOptions = {
silent: false,
};
const child = shell.exec(command, {
...defaultOptions,
...options,
async: true,
silent: false,
});

child.stderr.pipe(process.stderr);

child.on('exit', (code) => {
if (code === 0) {
resolve(undefined);
} else {
logger.error(chalk.red(`An error occurred while executing: \`${command}\``));
logger.log(errorMessage);
reject(new Error(`command exited with code: ${code}: `));
}
});
});
};
63 changes: 63 additions & 0 deletions scripts/utils/yarn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { readJSON, writeJSON } from 'fs-extra';
import path from 'path';

import { exec } from './exec';
// TODO -- should we generate this file a second time outside of CLI?
import storybookVersions from '../../code/lib/cli/src/versions';

export type YarnOptions = {
cwd: string;
dryRun?: boolean;
};

const logger = console;

export const addPackageResolutions = async ({ cwd, dryRun }: YarnOptions) => {
logger.info(`🔢 Adding package resolutions:`);
if (dryRun) return;

const packageJsonPath = path.join(cwd, 'package.json');
const packageJson = await readJSON(packageJsonPath);
packageJson.resolutions = storybookVersions;
await writeJSON(packageJsonPath, packageJson, { spaces: 2 });
};

export const installYarn2 = async ({ cwd, dryRun }: YarnOptions) => {
const command = [
`yarn set version berry`,
// Use the global cache so we aren't re-caching dependencies each time we run sandbox
`yarn config set enableGlobalCache true`,
`yarn config set nodeLinker node-modules`,
];

await exec(
command.join(' && '),
{ cwd },
{ dryRun, startMessage: `🧶 Installing Yarn 2`, errorMessage: `🚨 Installing Yarn 2 failed` }
);
};

export const configureYarn2ForVerdaccio = async ({ cwd, dryRun }: YarnOptions) => {
const command = [
// We don't want to use the cache or we might get older copies of our built packages
// (with identical versions), as yarn (correctly I guess) assumes the same version hasn't changed
`yarn config set enableGlobalCache false`,
`yarn config set enableMirror false`,
// ⚠️ Need to set registry because Yarn 2 is not using the conf of Yarn 1 (URL is hardcoded in CircleCI config.yml)
`yarn config set npmScopes --json '{ "storybook": { "npmRegistryServer": "http://localhost:6000/" } }'`,
// Some required magic to be able to fetch deps from local registry
`yarn config set unsafeHttpWhitelist --json '["localhost"]'`,
// Disable fallback mode to make sure everything is required correctly
`yarn config set pnpFallbackMode none`,
// We need to be able to update lockfile when bootstrapping the examples
`yarn config set enableImmutableInstalls false`,
// Discard all YN0013 - FETCH_NOT_CACHED messages
`yarn config set logFilters --json '[ { "code": "YN0013", "level": "discard" } ]'`,
].join(' && ');

await exec(
command,
{ cwd },
{ startMessage: `🎛 Configuring Yarn 2`, errorMessage: `🚨 Configuring Yarn 2 failed`, dryRun }
);
};