Skip to content

Commit

Permalink
feat(cli): CATALYST-246 create-catalyst login, create env, scaffold p…
Browse files Browse the repository at this point in the history
…roject
  • Loading branch information
matthewvolk committed Feb 1, 2024
1 parent 16f9b05 commit 9264edf
Show file tree
Hide file tree
Showing 21 changed files with 1,381 additions and 80 deletions.
42 changes: 42 additions & 0 deletions .github/workflows/cli.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: CLI

on:
push:
branches: [main]
pull_request:
types: [opened, synchronize]
merge_group:
types: [checks_requested]

jobs:
lint-typecheck:
name: Run Tests

runs-on: ubuntu-latest

env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
BIGCOMMERCE_STORE_HASH: ${{ secrets.BIGCOMMERCE_STORE_HASH }}
BIGCOMMERCE_CUSTOMER_IMPERSONATION_TOKEN: ${{ secrets.BIGCOMMERCE_CUSTOMER_IMPERSONATION_TOKEN }}

steps:
- name: Checkout code
uses: actions/checkout@main
with:
fetch-depth: 2

- uses: pnpm/action-setup@v2

- name: Use Node.js
uses: actions/setup-node@main
with:
node-version-file: ".nvmrc"
cache: "pnpm"

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Run Tests
run: pnpm test
working-directory: packages/create-catalyst
2 changes: 2 additions & 0 deletions packages/create-catalyst/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ const config = {
extends: ['@bigcommerce/catalyst/base', '@bigcommerce/catalyst/prettier'],
rules: {
'no-console': 'off',
'import/no-named-as-default': 'off',
'@typescript-eslint/naming-convention': 'off',
},
ignorePatterns: ['/dist/**'],
};
Expand Down
17 changes: 17 additions & 0 deletions packages/create-catalyst/.swcrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"$schema": "https://json.schemastore.org/swcrc",
"sourceMaps": true,
"jsc": {
"parser": {
"syntax": "typescript",
"decorators": true,
"dynamicImport": true
},
"transform": {
"legacyDecorator": true,
"decoratorMetadata": true
},
"baseUrl": "./"
},
"minify": false
}
54 changes: 0 additions & 54 deletions packages/create-catalyst/README.md
Original file line number Diff line number Diff line change
@@ -1,55 +1 @@
# packages/create-catalyst

> [!WARNING]
> The create-catalyst package is in development and not published to the NPM registry
Scaffolding for Catalyst storefront projects.

## Usage

**NPM:**

```sh
npm create @bigcommerce/catalyst@latest my-catalyst-store
```

**PNPM:**

```sh
pnpm create @bigcommerce/catalyst@latest my-catalyst-store
```

**Yarn:**

```sh
yarn create @bigcommerce/catalyst@latest my-catalyst-store
```

## Contributing

**Prerequisites:**

- Node `>=18.16`
- [Verdaccio](https://verdaccio.org/) `>=5`

**Developing Locally:**

While developing `create-catalyst` locally, it's essential to test your changes before publishing them to NPM. To achieve this, we utilize Verdaccio, a lightweight private npm proxy registry that you can run in your local environment. By publishing `create-catalyst` to Verdaccio during local development, we can point `[yarn|npm|pnpm] create @bigcommerce/catalyst` at the Verdaccio registry URL and observe how our changes will behave once they are published to NPM.

1. Install Verdaccio: https://verdaccio.org/docs/installation
2. Run Verdaccio: `verdaccio --listen 4873`
3. Add an NPM user with `@bigcommerce` scope to Verdaccio: `npm adduser --scope=@bigcommerce --registry=http://localhost:4873`

> ⚠️ **IMPORTANT:** NPM registry data is immutable, meaning once published, a package cannot change. Be careful to ensure that you do not run commands such as `publish` against the default NPM registry if your work is not ready to be published. Always explicitly pass `--registry=http://localhost:<VERDACCIO_PORT>` with commands that modify the registry (such as `publish`) to ensure you only publish to Verdaccio when working locally.
4. If necessary, run `pnpm build` and `pnpm publish --registry=http://localhost:4873` in each Catalyst-scoped package required by relevant examples listed in `apps/` (e.g., Catalyst `core` examples require `@bigcommerce/reactant`, `@bigcommerce/eslint-config-catalyst`, `@bigcommerce/catalyst-configs`, `@bigcommerce/catalyst-client`)
5. Run `pnpm build` and `pnpm publish --registry=http://localhost:4873` in the `@bigcommerce/create-catalyst` package
6. Confirm published packages are listed in Verdaccio: http://localhost:4873

In order to point `npm create`, `pnpm create`, and/or `yarn create` to the Verdaccio registry, run one or more of the following commands against the package manager's global configuration:

- **NPM:** `npm config set @bigcommerce:registry http://localhost:4873`
- **PNPM:** `pnpm config set @bigcommerce:registry http://localhost:4873`
- **Yarn:** `yarn config set npmScopes.bigcommerce.npmRegistryServer "http://localhost:4873" -H` and then `yarn config set unsafeHttpWhitelist "localhost" -H`

7. Finally, navigate to the directory in which you'd like to create a new Catalyst storefront, and run `[yarn|npm|pnpm] create @bigcommerce/catalyst name-of-your-catalyst-storefront`
12 changes: 12 additions & 0 deletions packages/create-catalyst/jest.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module.exports = {
extensionsToTreatAsEsm: ['.ts'],
transform: {
'^.+\\.(t|j)s?$': '@swc/jest',
},
transformIgnorePatterns: [],
// moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1',
},
testEnvironment: 'node',
};
29 changes: 27 additions & 2 deletions packages/create-catalyst/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,43 @@
"dist"
],
"scripts": {
"dev": "tsup --watch",
"typecheck": "tsc --noEmit",
"lint": "eslint . --max-warnings 0",
"test": "jest"
"test": "jest",
"build": "tsup"
},
"engines": {
"node": ">=18.16"
},
"dependencies": {
"chalk": "^5.3.0",
"commander": "^11.1.0",
"fs-extra": "^11.2.0",
"giget": "^1.2.1",
"lodash.kebabcase": "^4.1.1",
"lodash.merge": "^4.6.2",
"lodash.set": "^4.3.2",
"lodash.unset": "^4.5.2",
"nypm": "^0.3.6",
"ora": "^8.0.1",
"prompts": "^2.4.2",
"zod": "^3.22.4",
"zod-validation-error": "^3.0.0"
},
"devDependencies": {
"@bigcommerce/catalyst-configs": "workspace:^",
"@bigcommerce/eslint-config": "^2.7.0",
"@bigcommerce/eslint-config-catalyst": "workspace:^",
"@swc/core": "^1.3.107",
"@swc/jest": "^0.2.34",
"@types/fs-extra": "^11.0.4",
"@types/jest": "^29.5.11",
"@types/lodash.kebabcase": "^4.1.9",
"@types/lodash.merge": "^4.6.9",
"@types/lodash.set": "^4.3.9",
"@types/lodash.unset": "^4.5.9",
"@types/node": "^18.17.12",
"@types/prompts": "^2.4.9",
"eslint": "^8.55.0",
"jest": "^29.7.0",
"prettier": "^3.1.1",
Expand Down
161 changes: 161 additions & 0 deletions packages/create-catalyst/src/commands/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import chalk from 'chalk';
import { exec as execCallback } from 'child_process';
import { copySync, outputFileSync, readJsonSync, removeSync, writeJsonSync } from 'fs-extra/esm';
import merge from 'lodash.merge';
import set from 'lodash.set';
import unset from 'lodash.unset';
import { installDependencies } from 'nypm';
import { join } from 'path';
import { promisify } from 'util';
import * as z from 'zod';

import { cloneTemplate } from '../utils/clone.js';
import { getPackageManager } from '../utils/pm.js';
import { promptWithValidation } from '../utils/prompt.js';
import { PkgJsonSchema, TsConfigSchema } from '../utils/schemas.js';
import { spinner } from '../utils/spinner.js';
import { validate } from '../utils/validate.js';

import { env } from './env.js';
import { login } from './login.js';

const exec = promisify(execCallback);

export const create = async () => {
const GITHUB_TOKEN = validate(
process.env.GITHUB_TOKEN,
z
.string({ required_error: 'GITHUB_TOKEN is a required environment variable' })
.trim()
.startsWith('ghp'),
);

const REF = validate(process.env.REF, z.string().trim().optional().default('main'));

console.log(chalk.cyanBright('\n◢ @bigcommerce/create-catalyst v0.1.0\n'));

const { projectName } = await promptWithValidation(
{
type: 'text',
name: 'projectName',
message: 'What is the name of your project?',
},
z.object({ projectName: z.string().min(1) }),
);

const projectDir = join(process.cwd(), projectName);

const credentials = await login();

if (!credentials) {
console.log(chalk.yellow('\n@todo use sample data\n'));
process.exit(0);
}

const environment = await env({ ...credentials });

console.log(`\nCreating '${projectName}' at '${projectDir}'\n`);

await spinner(
cloneTemplate(`github:bigcommerce/catalyst/apps/core#${REF}`, {
dest: projectDir,
auth: GITHUB_TOKEN,
}),
{
text: 'Cloning Catalyst template...',
successText: 'Catalyst template cloned successfully',
},
);

const componentsTmpDir = join(projectDir, 'tmp', 'ui');

await spinner(
cloneTemplate(`github:bigcommerce/catalyst/packages/reactant#${REF}`, {
dest: componentsTmpDir,
auth: GITHUB_TOKEN,
}),
{
text: 'Cloning Catalyst components...',
successText: 'Catalyst components cloned successfully',
},
);

outputFileSync(join(projectDir, '.env.local'), environment);

copySync(join(componentsTmpDir, 'src/components'), join(projectDir, 'components', 'ui'));

const paths = {
catalyst: {
tailwind: join(projectDir, 'tailwind.config.js'),
package: join(projectDir, 'package.json'),
tsconfig: join(projectDir, 'tsconfig.json'),
},
components: {
tailwind: join(componentsTmpDir, 'tailwind.config.js'),
package: join(componentsTmpDir, 'package.json'),
tsconfig: join(componentsTmpDir, 'tsconfig.json'),
},
};

const catalystPkgJson = validate(readJsonSync(paths.catalyst.package), PkgJsonSchema);
const componentsPkgJson = validate(readJsonSync(paths.components.package), PkgJsonSchema);

const packageJson = merge({}, catalystPkgJson, {
name: projectName,
description: '',
version: '0.1.0',
dependencies: componentsPkgJson.dependencies,
devDependencies: componentsPkgJson.devDependencies,
});

unset(packageJson, 'dependencies.@bigcommerce/reactant'); // keep
unset(packageJson, 'devDependencies.react'); // move to dep/devDep
unset(packageJson, 'devDependencies.react-dom'); // move to dep/devDep

set(packageJson, 'dependencies.@bigcommerce/catalyst-client', '^0.1.0'); // modify pkg.json
set(packageJson, 'devDependencies.@bigcommerce/catalyst-configs', '^0.1.0'); // modify pkg.json
set(packageJson, 'devDependencies.@bigcommerce/eslint-config-catalyst', '^0.1.0'); // modify pkg.json

writeJsonSync(join(projectDir, 'package.json'), packageJson, { spaces: 2 });

const catalystTsConfig = validate(readJsonSync(paths.catalyst.tsconfig), TsConfigSchema);
const componentsTsConfig = validate(readJsonSync(paths.components.tsconfig), TsConfigSchema);

const tsConfig = merge({}, componentsTsConfig, catalystTsConfig);

unset(tsConfig, 'compilerOptions.declaration');
unset(tsConfig, 'compilerOptions.declarationMap');

set(tsConfig, 'compilerOptions.paths.@bigcommerce/reactant/*', ['./components/ui/*']);

writeJsonSync(join(projectDir, 'tsconfig.json'), tsConfig, { spaces: 2 });

copySync(paths.components.tailwind, paths.catalyst.tailwind);

removeSync(join(projectDir, 'tmp'));

// @todo yarn doesn't work?
const pm = getPackageManager();

console.log(`\nUsing ${chalk.bold(pm)}\n`);

await spinner(installDependencies({ cwd: projectDir, silent: true, packageManager: pm }), {
text: `Installing dependencies. This could take a minute...`,
successText: `Dependencies installed successfully`,
});

await spinner(exec(`${pm} run codegen`, { cwd: projectDir }), {
text: 'Generating GraphQL types...',
successText: 'GraphQL types generated successfully',
});

await spinner(exec(`${pm} run lint --fix`, { cwd: projectDir }), {
text: 'Linting to validate generated types...',
successText: 'GraphQL types validated successfully',
});

console.log(`\n${chalk.green('Success!')} Created '${projectName}' at '${projectDir}'\n`);

console.log('Next steps:');
console.log(chalk.yellow(`\ncd ${projectName} && ${pm} run dev\n`));
};
Loading

0 comments on commit 9264edf

Please sign in to comment.