Skip to content
143 changes: 120 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,45 +12,145 @@ Snapfu is the scaffolding command line tool for the Searchspring Snap SDK. This
npm install -g snapfu
```

## Login
## Usage

Login to access your Github organizations - the following command will open a browser window
to give snapfu access to your Github organizations and to be able to create repositories in subsequent steps.
```bash
snapfu <command> <args> [--options]
```

## Commands

### `init` - Create a new snap project
Creates a new snap project (optional directory)

```bash
snapfu init <directory>
```


### `badges` - Badge template management
Manage badge templates for your project

```bash
snapfu badges <command> <args> [--options]
```

**Subcommands:**
- `init` - Initialize badge template in current project
- `list [local | remote]` - Display list of badge templates (local or remote)
- `archive <name>` - Remove remote badge template
- `--secret-key <key>` - Secret key for authentication
- `sync [<name> | locations.json]` - Synchronize badge template and parameters with remote
- `--secret-key <key>` - Secret key for authentication

### `recs` - Recommendation template management
Manage recommendation templates for your project

```bash
snapfu recs <command> <args> [--options]
```

**Subcommands:**
- `init` - Initialize recommendation template in current project
- `list [local | remote]` - Display list of recommendation templates (local or remote)
- `archive <name> <branch>` - Remove remote recommendation template (optional branch)
- `--secret-key <key>` - Secret key for authentication
- `sync <name> <branch>` - Synchronize recommendation template and parameters with remote (optional branch)
- `--secret-key <key>` - Secret key for authentication

### `secrets` - Project secret management
Manage secrets in your snap project

```bash
snapfu secrets <command> <args> [--options]
```

**Subcommands:**
- `add` - Adds secrets to snap project
- `update` - Update secrets in snap project
- `verify` - Verify secrets in snap project

### `patch` - Apply patches to update project
Apply patches to update your project

```bash
snapfu patch <command> <args> [--options]
```

**Subcommands:**
- `apply` - Apply patch version (version or latest)
- `list` - List available versions for project
- `fetch` - Fetch latest versions of patches

### `login` - OAuth with GitHub
OAuths with GitHub to retrieve additional scaffolds and create repositories when using the init command

```bash
snapfu login
```

## Init
### `logout` - Remove login credentials
Removes login credentials

Create your website with the init command. Init will gather some information about the kind
of Snap project you wish to create. You will need your `siteId` and `secretKey` from the SMC before you run this command. This command will:
```bash
snapfu logout
```

### `org-access` - Review organization access
Review and change organization access for the tool

```bash
snapfu org-access
```

- download scaffolding files
- create and initialize a repository in the Github organization you selected
- populate a Github secret with the provided `secretKey`
### `whoami` - Show current user
Shows the current user

```bash
snapfu init my-awesome-website
snapfu whoami
```

<img src="https://raw.githubusercontent.com/searchspring/snapfu/main/cli.png">
### `about` - Show versioning
Shows versioning information

## Run it
```bash
snapfu about
```

Now you can run the project with your standard `npm` tooling.
### `help` - Display help text
Display help text (optional command)

```bash
cd my-awesome-website
npm install
npm run dev
snapfu help [<command>]
```

See the `package.json` for other npm commands.
## Getting Started

1. **Install snapfu globally:**
```bash
npm install -g snapfu
```

2. **Login (optional):**
```bash
snapfu login
```

3. **Create a new project:**
```bash
snapfu init my-awesome-website
```

4. **Run the project:**
```bash
cd my-awesome-website
npm install
npm run dev
```

## Deployment

This tool integrates with the Searchspring build and deploy process. In order to take advantage of this you must select searchspring-implementations as your organizaiton during init.
This tool integrates with the Searchspring build and deploy process. In order to take advantage of this you must have access to the `searchspring-implementations` Github organization and select it during init command. (Requires login & invitation to the organization upon request).

The tool uses Github actions to copy files to our AWS S3 backed CDN (Cloudfront).

Expand All @@ -69,17 +169,14 @@ https://snapui.searchspring.io/<siteId>/my-branch/bundle.js

## Deploying to other places

You can modify the file `deploy.yml` in your generated project under `my-awesome-website/.github/workflows/deploy.yml`
to complete different actions if you don't want to use the Searchspring build process or don't have access to it.
You can modify the file `deploy.yml` in your generated project under `my-awesome-website/.github/workflows/deploy.yml` to complete different actions if you don't want to use the Searchspring build process or don't have access to it.

### SCP

Deploy the built artifacts using `scp`. [https://github.com/marketplace/actions/scp-command-to-transfer-files](https://github.com/marketplace/actions/scp-command-to-transfer-files)

### Google Cloud

Deploy to GCP using `gcloud`. [https://github.com/marketplace/actions/setup-gcloud-environment](https://github.com/marketplace/actions/setup-gcloud-environment)

### SFTP

Deploy a built artifacts through SFTP. [https://github.com/marketplace/actions/sftp-deploy](https://github.com/marketplace/actions/sftp-deploy)

4 changes: 4 additions & 0 deletions src/badges.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ export async function listBadgeTemplates(options) {
const list = async (secretKey, siteId = '', name = '') => {
if (!secretKey) {
console.log(chalk.red('Unable to list remote badge template due to missing secretKey'));
console.log(chalk.grey(`\n\tsnapfu secrets add\n`));
return;
}
const remoteTemplates = await new ConfigApi(secretKey, options).getBadgeTemplates({ siteId });
Expand Down Expand Up @@ -318,6 +319,7 @@ export async function removeBadgeTemplate(options) {

if (!secretKey) {
console.log(chalk.red('Unable to archive remote badge template due to missing secretKey'));
console.log(chalk.grey(`\n\tsnapfu secrets add\n`));
return;
}
const { message } = await new ConfigApi(secretKey, options).archiveBadgeTemplate({ payload, siteId });
Expand Down Expand Up @@ -767,6 +769,7 @@ export async function syncBadgeTemplate(options) {
const sync = async (template, secretKey, siteId) => {
if (!secretKey) {
console.log(chalk.red('Unable to sync remote badge template due to missing secretKey'));
console.log(chalk.grey(`\n\tsnapfu secrets add\n`));
return;
}
// validate template against remote locations (locations get validated and synced first)
Expand Down Expand Up @@ -824,6 +827,7 @@ export async function syncBadgeTemplate(options) {
const syncLocations = async (secretKey, siteId) => {
if (!secretKey) {
console.log(chalk.red('Unable to sync remote badge locations due to missing secretKey'));
console.log(chalk.grey(`\n\tsnapfu secrets add\n`));
return;
}
console.log(` synchronizing locations`);
Expand Down
153 changes: 73 additions & 80 deletions src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,102 +61,95 @@ async function parseArgumentsIntoOptions(rawArgs) {
let multipleSites = [];

// drop out if not logged in for certain commands
const userCommands = ['init', 'badges', 'recs', 'recommendation', 'recommendations', 'secret', 'secrets', 'logout', 'whoami', 'org-access'];
const secretCommands = ['badges', 'recs', 'recommendation', 'recommendations', 'secret', 'secrets'];
const requiredLoginCommands = ['logout', 'whoami', 'org-access'];
const templatesRestrictedCommands = ['recs', 'recommendation', 'recommendations'];

const loggedIn = user && user.token;
const secretOptions = args['--secrets-ci'] || secretKey;

if (userCommands.includes(command) && !(loggedIn || secretOptions || args['--ci'])) {
console.log(chalk.yellow(`Login is required. Please login.`));
console.log(chalk.grey(`\n\tsnapfu login\n`));
if (requiredLoginCommands.includes(command) && !(loggedIn || secretOptions || args['--ci'])) {
console.log(chalk.yellow(`Login is required when using the '${command}' command.`));
exit(1);
} else if (context.project.distribution == 'SnapTemplates' && templatesRestrictedCommands.includes(command)) {
console.log(chalk.yellow(`The '${command}' command is not supported when using SnapTemplates.`));
exit(0);
} else if (secretCommands.includes(command) && (loggedIn || secretOptions || args['--ci'])) {
const getSecretKeyFromCLI = (siteId) => {
try {
const secrets = JSON.parse(args['--secrets-ci']);
const secretKey = secrets[`WEBSITE_SECRET_KEY_${siteId.toUpperCase()}`];
return secretKey;
} catch (e) {
return;
}
};
}

if (context.searchspring && typeof context.searchspring.siteId === 'object') {
// searchsoring.siteId contains multiple sites
const getSecretKeyFromCLI = (siteId) => {
try {
const secrets = JSON.parse(args['--secrets-ci']);
const secretKey = secrets[`WEBSITE_SECRET_KEY_${siteId.toUpperCase()}`];
return secretKey;
} catch (e) {
return;
}
};

const siteIds = Object.keys(context.searchspring.siteId);
if (!siteIds || !siteIds.length) {
console.log(chalk.red('searchspring.siteId object in package.json is empty'));
exit(1);
}
if (context.searchspring && typeof context.searchspring.siteId === 'object') {
// searchsoring.siteId contains multiple sites

multipleSites = siteIds
.map((siteId) => {
try {
const { name } = context.searchspring.siteId[siteId];
const secretKey = getSecretKeyFromCLI(siteId) || user.keys[siteId];

if (!secretKey) {
console.log(chalk.red(`Cannot find the secretKey for siteId '${siteId}'.`));
console.log(chalk.bold.white(`Please run the following command:`));
console.log(chalk.gray(`\tsnapfu secrets add\n`));
}
if (!secretKey && args['--secrets-ci']) {
console.log(
chalk.red(`Could not find Github secret 'WEBSITE_SECRET_KEY_${siteId.toUpperCase()}' in 'secrets' input
It can be added by running 'snapfu secrets add' in the project's directory locally,
or added manual in the project's repository secrets.
The value can be obtained in the Searchspring Management Console.
Then ensure that you are providing 'secrets' when running the action. ie:

jobs:
Publish:
runs-on: ubuntu-latest
name: Snap Action
steps:
- name: Checkout action
uses: actions/checkout@v2
with:
repository: searchspring/snap-action
- name: Run @searchspring/snap-action
uses: ./
with:
secrets: \${{ toJSON(secrets) }}
...
`)
);
}

return {
siteId,
name,
secretKey,
};
} catch (e) {
console.log(chalk.red('The searchspring.siteId object in package.json is invalid. Expected format:'));
const siteIds = Object.keys(context.searchspring.siteId);
if (!siteIds || !siteIds.length) {
console.log(chalk.red('searchspring.siteId object in package.json is empty'));
exit(1);
}

multipleSites = siteIds
.map((siteId) => {
try {
const { name } = context.searchspring.siteId[siteId];
const secretKey = getSecretKeyFromCLI(siteId) || user.keys[siteId];

if (!secretKey && args['--secrets-ci']) {
console.log(
chalk.red(`
"searchspring": {
"siteId": {
"xxxxx1": {
"name": "site1.com.au"
},
"xxxxx2": {
"name": "site2.hk"
}
},
}`)
chalk.red(`Could not find Github secret 'WEBSITE_SECRET_KEY_${siteId.toUpperCase()}' in 'secrets' input
It can be added by running 'snapfu secrets add' in the project's directory locally,
or added manual in the project's repository secrets.
The value can be obtained in the Searchspring Management Console.
Then ensure that you are providing 'secrets' when running the action. ie:

jobs:
Publish:
runs-on: ubuntu-latest
name: Snap Action
steps:
- name: Checkout action
uses: actions/checkout@v2
with:
repository: searchspring/snap-action
- name: Run @searchspring/snap-action
uses: ./
with:
secrets: \${{ toJSON(secrets) }}
...
`)
);
exit(1);
}
})
.filter((site) => site.secretKey);

return {
siteId,
name,
secretKey,
};
} catch (e) {
console.log(chalk.red('The searchspring.siteId object in package.json is invalid. Expected format:'));
console.log(
chalk.red(`
"searchspring": {
"siteId": {
"xxxxx1": {
"name": "site1.com.au"
},
"xxxxx2": {
"name": "site2.hk"
}
},
}`)
);
exit(1);
}
})
.filter((site) => site.secretKey);
}

let packageJSON = {};
Expand Down
Loading