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

feat(cli): connect Vuejs project with client's cloud org #43

Merged
merged 16 commits into from
Feb 25, 2021
Merged
Show file tree
Hide file tree
Changes from 11 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
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
{
"useConfigFiles": true,
"plugins": {
"@vue/cli-plugin-babel": {},
"@vue/cli-plugin-typescript": {
"classComponent": true,
"useTsWithBabel": true
"classComponent": true
},
"@vue/cli-plugin-router": {
"historyMode": true
},
"@vue/cli-plugin-eslint": {
"config": "standard",
"lintOn": ["save", "commit"]
"lintOn": ["commit"]
}
},
"cssPreprocessor": "node-sass",
"vueVersion": "2"
}
64 changes: 34 additions & 30 deletions packages/cli/src/commands/ui/create/vue.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import {Command, flags} from '@oclif/command';
import {dirname, resolve} from 'path';
import {resolve} from 'path';
import {
buildAnalyticsFailureHook,
buildAnalyticsSuccessHook,
} from '../../../hooks/analytics/analytics';
import {Config} from '../../../lib/config/config';
import AuthenticationRequired from '../../../lib/decorators/authenticationRequired';
import {Storage} from '../../../lib/oauth/storage';
import {platformUrl} from '../../../lib/platform/environment';
import {spawnProcess} from '../../../lib/utils/process';

export default class Vue extends Command {
Expand All @@ -19,7 +23,6 @@ export default class Vue extends Command {
'If not specified, the default TypeScript preset will be taken.',
'For more information about Vue CLI presets, please consult https://cli.vuejs.org/guide/plugins-and-presets.html#presets',
].join('\n'),
// default: TODO: add default preset once it is available in npm https://coveord.atlassian.net/browse/CDX-39
}),
};

Expand All @@ -32,6 +35,7 @@ export default class Vue extends Command {
{name: 'name', description: 'application name', required: true},
];

@AuthenticationRequired()
async run() {
const {args, flags} = this.parse(Vue);

Expand All @@ -45,9 +49,7 @@ export default class Vue extends Command {
}
}
await this.createProject(args.name, preset);
await this.installPlugin(args.name);
await this.invokePlugin(args.name);
this.startServer(args.name);
await this.config.runHook(
'analytics',
buildAnalyticsSuccessHook(this, flags)
Expand All @@ -63,48 +65,50 @@ export default class Vue extends Command {
throw err;
}

private installPlugin(applicationName: string) {
// TODO: DELETE THIS METHOD ONCE THE PLUGIN IS PUBLISHED AND PART OF THE PRESET
// CDX-39
// Once the coveo plugin is published to npm, simply include it in the preset typescript-preset.json
// This will prevent from running `2 npm install` commands (one by @vue/cli, one for the plugin)
const pathToPlugin = dirname(require.resolve('vue-cli-plugin-coveo'));
return spawnProcess(
'npm',
['install', '--save-dev', `file:${pathToPlugin}`],
{
cwd: applicationName,
}
);
}
private async invokePlugin(applicationName: string) {
const cfg = await this.configuration.get();
const storage = await this.storage.get();

private invokePlugin(applicationName: string) {
return this.runVueCliCommand(['invoke', 'vue-cli-plugin-coveo'], {
const cliArgs = [
'invoke',
'@coveo/vue-cli-plugin-typescript',
'--orgId',
cfg.organization,
'--apiKey',
storage.accessToken!,
'--platformUrl',
platformUrl({environment: cfg.environment}),
// TODO: CDX-91 Extract user email from oauth flow
'--user',
'[email protected]',
];

return this.runVueCliCommand(cliArgs, {
cwd: applicationName,
});
}

private startServer(applicationName: string) {
// TODO: DELETE THIS METHOD ONCE THE PLUGIN IS PUBLISHED AND PART OF THE PRESET
// The @vue/cli already logs instructions once the installation completes
this.log(`Successfully created project ${applicationName}.`);
this.log('Get started with the following commands:\n');

this.log('$ cd ${applicationName}');
this.log('$ yarn serve');
}

private createProject(name: string, preset: {}) {
return this.runVueCliCommand([
'create',
name,
'--inlinePreset',
JSON.stringify(preset),
'--skipGetStarted',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does --skipGetStarted and --bare options do ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--bare: Scaffold project without beginner instructions on the home page. There is no point in having them because we override the home page with the search page.

--skipGetStarted: Skip displaying "Get started" instructions. We don't want the create command to display the To get started, cd myproject && yarn serve message because the Coveo template was not invoked yet. The creation of a Coveo project is a 2 step process:

  1. vue create project
  2. vue invoke @coveo/typescript

Ideally, we want to display a nice get-started message after our template is invoked.

'--bare',
]);
}

private runVueCliCommand(args: string[], options = {}) {
const executable = require.resolve('@vue/cli/bin/vue.js');
return spawnProcess(executable, args, options);
}

private get configuration() {
return new Config(this.config.configDir, this.error);
}

private get storage() {
return new Storage();
}
}
15 changes: 9 additions & 6 deletions packages/vue-cli-plugin-typescript/generator/index.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
// eslint-disable-next-line no-unused-vars
module.exports = (api, options, rootOptions) => {
api.extendPackage({
scripts: {
postinstall: 'node ./scripts/setup-server.js',
start: 'concurrently --raw "npm run start-server" "npm run serve"',
'start-server': 'node ./scripts/start-server.js',
},
dependencies: {
'@coveo/headless': '^0.1.0',
'@coveo/search-token-server':
'file:/Users/ylakhdar/Repos/coveo-cli/packages/search-token-server/search-token-server-0.0.0.tgz',
buefy: '^0.9.4',
},
devDependencies: {
'node-sass': '^5.0.0',
'sass-loader': '^10.1.1',
concurrently: '^5.3.0',
},
});

api.render('./template', {
// TODO: inject the appropriate file type
...rootOptions,
...options,
});
};
29 changes: 29 additions & 0 deletions packages/vue-cli-plugin-typescript/generator/template/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# <%- rootOptions.projectName %>

## Project setup

```
npm install
```

### Compiles and hot-reloads for development

```
npm start
```

### Compiles and minifies for production

```
npm run build
```

### Lints and fixes files

```
npm run lint
```

### Customize configuration

See [Configuration Reference](https://cli.vuejs.org/config/).
9 changes: 9 additions & 0 deletions packages/vue-cli-plugin-typescript/generator/template/_env
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
VUE_APP_PLATFORM_URL=<%- options.platformUrl -%>

VUE_APP_ORGANIZATION_ID=<%- options.orgId -%>

VUE_APP_API_KEY=<%- options.apiKey -%>

VUE_APP_USER_EMAIL=<%- options.user -%>

VUE_APP_TOKEN_ENDPOINT="/token"
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# The Plaform URL to use.
# See https://docs.coveo.com/en/2976/coveo-solutions/deployment-regions-and-strategies
VUE_APP_PLATFORM_URL=https://platform.cloud.coveo.com

# The unique identifier of the organization in which to generate a search token.
# Example: VUE_APP_ORGANIZATION_ID=mycoveoorganizationg8tp8wu3.
# See https://docs.coveo.com/en/148/manage-an-organization/retrieve-the-organization-id
VUE_APP_ORGANIZATION_ID=<YOUR_ORGANIZATION_ID>

# An API key granting the impersonate privilege in your organization.
# The API key should have the impersonate privilege.
# See https://docs.coveo.com/en/1718/manage-an-organization/manage-api-keys#add-an-api-key
VUE_APP_API_KEY=<YOUR_API_KEY>

# The name of the security identity to impersonate.
# Example: VUE_APP_USER_EMAIL="[email protected]"
# See https://docs.coveo.com/en/56/#name-string-required.
VUE_APP_USER_EMAIL=<YOUR_USER_EMAIL>

# The path to the server returning Coveo search tokens.
# By default, it targets server on the same host.
VUE_APP_TOKEN_ENDPOINT="/token"
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const {spawnSync} = require('child_process');
const {copySync} = require('fs-extra');
const {sep, resolve} = require('path');

function installSearchTokenServerDependencies() {
const child = spawnSync('npm', ['install'], {
stdio: 'inherit',
cwd: resolve('server'),
});
if (child.status !== 0) {
process.exit(child.status);
}
}

function isNodeModule(path) {
return path.split(sep).indexOf('node_modules') !== -1;
}

function isEnvFile(path) {
return path.split(sep).indexOf('.env.example') !== -1;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about this function.

The condition for a file to be an envFile would be imo:

/.env$/.test(path);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'll rename it to be isExampleEnvFile.
The goal is to copy everything but the env.example file.

And that's because the .env is define at the root of the entire project (not at the server level).
I'm creating the .env file later in the process

}

function copySearchTokenServerToRoot() {
copySync(
resolve('node_modules', '@coveo', 'search-token-server'),
resolve('server'),
{
filter: (src, dest) => !isNodeModule(dest) && !isEnvFile(dest),
}
);
}

function main() {
copySearchTokenServerToRoot();
installSearchTokenServerDependencies();
}

main();
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const {spawnSync} = require('child_process');
const {resolve, join} = require('path');
const {config} = require('dotenv');
config();

function getEnvVariables() {
// TODO: CDX-99: assign port dynamically
// The Vue CLi has a --proxy flag.
const searchTokenServerPort = 4000;
var envVars = Object.assign(
{
SERVER_PORT: searchTokenServerPort,
API_KEY: process.env.VUE_APP_API_KEY,
ORGANIZATION_ID: process.env.VUE_APP_ORGANIZATION_ID,
PLATFORM_URL: process.env.VUE_APP_PLATFORM_URL,
USER_EMAIL: process.env.VUE_APP_USER_EMAIL,
},
process.env
);
return envVars;
}

function startServer() {
const serverPath = join(process.cwd(), 'server');
const child = spawnSync('npm', ['run', 'start'], {
stdio: 'inherit',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we OK with mixing up stdio of npm run serve and the server?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well we want to make it easy for the user.
The other option would be to run 2 npm scripts (one for the web app and one for the token server).

env: getEnvVariables(),
cwd: resolve(serverPath),
});
if (child.status !== 0) {
process.exit(child.status);
}
}

function main() {
startServer();
}

main();
Original file line number Diff line number Diff line change
@@ -1,32 +1,3 @@
<template>
<div id="app">
<div class="hero is-primary is-medium">
<Hero msg="Welcome to Your Coveo Vue.js Search Page" />
</div>
<SearchPage />
</div>
<router-view />
</template>

<script lang="ts">
import {Component, Vue} from 'vue-property-decorator';
import Hero from './components/Hero.vue';
import SearchPage from './components/SearchPage.vue';

@Component({
components: {
Hero,
SearchPage,
},
})
export default class App extends Vue {}
</script>

<style lang="scss">
#app {
font-family: $family-primary;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: $primary !important;
}
</style>

This file was deleted.

Loading