Refactor and fixes
This commit is contained in:
parent
0709670550
commit
b08617d77e
18
README.md
18
README.md
|
@ -12,7 +12,7 @@ Any help is welcomed! Feel free to contribute.
|
|||
|
||||
* Node.js <= 12.0
|
||||
* Git
|
||||
* Docker (Only to setup a local server)
|
||||
* Docker (Local server)
|
||||
|
||||
## Installation
|
||||
|
||||
|
@ -35,19 +35,19 @@ $ npx myvc [command]
|
|||
Execute *myvc* with the desired command.
|
||||
|
||||
```text
|
||||
$ myvc [-w|--workspace] [-e|--env] [-h|--help] command
|
||||
$ myvc [-w|--workspace] [-r|--remote] [-d|--debug] [-h|--help] command
|
||||
```
|
||||
|
||||
The default workspace directory is the current working directory and unless
|
||||
otherwise indicated, the default environment is *local*.
|
||||
otherwise indicated, the default remote is *local*.
|
||||
|
||||
Commands for database versioning:
|
||||
Database versioning commands:
|
||||
|
||||
* **init**: Initialize an empty workspace.
|
||||
* **pull**: Export database routines into workspace.
|
||||
* **push**: Apply changes into database.
|
||||
|
||||
Commands for local server management:
|
||||
Local server management commands:
|
||||
|
||||
* **dump**: Export database structure and fixtures from *production*.
|
||||
* **run**: Build and starts local database server container.
|
||||
|
@ -67,14 +67,14 @@ Now you can configure MyVC using *myvc.config.yml* file, located at the root of
|
|||
your workspace. This file should include the project codename and schemas/tables
|
||||
wich are exported when you use *pull* or *dump* commands.
|
||||
|
||||
### Environments
|
||||
### Remotes
|
||||
|
||||
Create database connection configuration for each environment at *remotes*
|
||||
folder using standard MySQL *ini* configuration files. The predefined
|
||||
environment names are *production* and *test*.
|
||||
folder using standard MySQL *ini* configuration files. The convention remote
|
||||
names are *production* and *test*.
|
||||
|
||||
```text
|
||||
remotes/[environment].ini
|
||||
remotes/[remote].ini
|
||||
```
|
||||
|
||||
### Dumps
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const MyVC = require('./myvc');
|
||||
new MyVC().run();
|
253
index.js
253
index.js
|
@ -1,253 +0,0 @@
|
|||
require('require-yaml');
|
||||
require('colors');
|
||||
const getopts = require('getopts');
|
||||
const packageJson = require('./package.json');
|
||||
const fs = require('fs-extra');
|
||||
const ini = require('ini');
|
||||
const path = require('path');
|
||||
const mysql = require('mysql2/promise');
|
||||
const nodegit = require('nodegit');
|
||||
|
||||
class MyVC {
|
||||
async run(command) {
|
||||
console.log(
|
||||
'MyVC (MySQL Version Control)'.green,
|
||||
`v${packageJson.version}`.magenta
|
||||
);
|
||||
|
||||
const opts = {};
|
||||
const argv = process.argv.slice(2);
|
||||
const cliOpts = getopts(argv, {
|
||||
alias: {
|
||||
env: 'e',
|
||||
workspace: 'w',
|
||||
socket: 's',
|
||||
debug: 'd',
|
||||
version: 'v',
|
||||
help: 'h'
|
||||
},
|
||||
default: {
|
||||
workspace: process.cwd()
|
||||
}
|
||||
})
|
||||
|
||||
if (cliOpts.version)
|
||||
process.exit(0);
|
||||
|
||||
try {
|
||||
if (!command) {
|
||||
const commandName = cliOpts._[0];
|
||||
if (!commandName) {
|
||||
console.log(
|
||||
'Usage:'.gray,
|
||||
'[npx] myvc'
|
||||
+ '[-w|--workspace]'
|
||||
+ '[-e|--env]'
|
||||
+ '[-d|--debug]'
|
||||
+ '[-h|--help]'
|
||||
+ '[-v|--version]'
|
||||
+ 'command'.blue
|
||||
);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const commands = [
|
||||
'init',
|
||||
'pull',
|
||||
'push',
|
||||
'dump',
|
||||
'start',
|
||||
'run'
|
||||
];
|
||||
|
||||
if (commands.indexOf(commandName) == -1)
|
||||
throw new Error (`Unknown command '${commandName}'`);
|
||||
|
||||
const Klass = require(`./myvc-${commandName}`);
|
||||
command = new Klass();
|
||||
}
|
||||
|
||||
const commandOpts = getopts(argv, command.myOpts);
|
||||
Object.assign(cliOpts, commandOpts);
|
||||
|
||||
for (const opt in cliOpts) {
|
||||
if (opt.length > 1 || opt == '_')
|
||||
opts[opt] = cliOpts[opt];
|
||||
}
|
||||
|
||||
parameter('Workspace:', opts.workspace);
|
||||
parameter('Environment:', opts.env);
|
||||
|
||||
await this.load(opts);
|
||||
command.opts = opts;
|
||||
await command.run(this, opts);
|
||||
await this.unload();
|
||||
} catch (err) {
|
||||
if (err.name == 'Error' && !opts.debug)
|
||||
console.error('Error:'.gray, err.message.red);
|
||||
else
|
||||
throw err;
|
||||
}
|
||||
|
||||
function parameter(parameter, value) {
|
||||
console.log(parameter.gray, (value || 'null').blue);
|
||||
}
|
||||
|
||||
process.exit();
|
||||
}
|
||||
|
||||
async load(opts) {
|
||||
// Configuration file
|
||||
|
||||
const config = require(`${__dirname}/myvc.default.yml`);
|
||||
|
||||
const configFile = 'myvc.config.yml';
|
||||
const configPath = path.join(opts.workspace, configFile);
|
||||
if (await fs.pathExists(configPath))
|
||||
Object.assign(config, require(configPath));
|
||||
|
||||
Object.assign(opts, config);
|
||||
opts.configFile = configFile;
|
||||
|
||||
// Database configuration
|
||||
|
||||
let iniFile = 'db.ini';
|
||||
let iniDir = __dirname;
|
||||
if (opts.env) {
|
||||
iniFile = `remotes/${opts.env}.ini`;
|
||||
iniDir = opts.workspace;
|
||||
}
|
||||
const iniPath = path.join(iniDir, iniFile);
|
||||
|
||||
if (!await fs.pathExists(iniPath))
|
||||
throw new Error(`Database config file not found: ${iniFile}`);
|
||||
|
||||
const iniConfig = ini.parse(await fs.readFile(iniPath, 'utf8')).client;
|
||||
const dbConfig = {
|
||||
host: iniConfig.host,
|
||||
port: iniConfig.port,
|
||||
user: iniConfig.user,
|
||||
password: iniConfig.password,
|
||||
database: opts.versionSchema,
|
||||
authPlugins: {
|
||||
mysql_clear_password() {
|
||||
return () => iniConfig.password + '\0';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (iniConfig.ssl_ca) {
|
||||
dbConfig.ssl = {
|
||||
ca: await fs.readFile(`${opts.workspace}/${iniConfig.ssl_ca}`),
|
||||
rejectUnauthorized: iniConfig.ssl_verify_server_cert != undefined
|
||||
}
|
||||
}
|
||||
if (opts.socket)
|
||||
dbConfig.socketPath = '/var/run/mysqld/mysqld.sock';
|
||||
|
||||
Object.assign(opts, {
|
||||
iniFile,
|
||||
dbConfig
|
||||
});
|
||||
this.opts = opts;
|
||||
}
|
||||
|
||||
async dbConnect() {
|
||||
if (!this.conn)
|
||||
this.conn = await this.createConnection();
|
||||
return this.conn;
|
||||
}
|
||||
|
||||
async createConnection() {
|
||||
return await mysql.createConnection(this.opts.dbConfig);
|
||||
}
|
||||
|
||||
async unload() {
|
||||
if (this.conn)
|
||||
await this.conn.end();
|
||||
}
|
||||
|
||||
async fetchDbVersion() {
|
||||
const {opts} = this;
|
||||
|
||||
const [[res]] = await this.conn.query(
|
||||
`SELECT COUNT(*) > 0 tableExists
|
||||
FROM information_schema.tables
|
||||
WHERE TABLE_SCHEMA = ?
|
||||
AND TABLE_NAME = 'version'`,
|
||||
[opts.versionSchema]
|
||||
);
|
||||
|
||||
if (!res.tableExists) {
|
||||
const structure = await fs.readFile(`${__dirname}/structure.sql`, 'utf8');
|
||||
await this.conn.query(structure);
|
||||
return null;
|
||||
}
|
||||
|
||||
const [[version]] = await this.conn.query(
|
||||
`SELECT number, gitCommit
|
||||
FROM version WHERE code = ?`,
|
||||
[opts.code]
|
||||
);
|
||||
return version;
|
||||
}
|
||||
|
||||
async changedRoutines(commit) {
|
||||
const repo = await nodegit.Repository.open(this.opts.workspace);
|
||||
|
||||
const from = await repo.getCommit(commit);
|
||||
const fromTree = await from.getTree();
|
||||
|
||||
const to = await repo.getHeadCommit();
|
||||
const toTree = await to.getTree();
|
||||
|
||||
const diff = await toTree.diff(fromTree);
|
||||
const patches = await diff.patches();
|
||||
|
||||
const changes = [];
|
||||
for (const patch of patches) {
|
||||
const path = patch.newFile().path();
|
||||
const match = path.match(/^routines\/(.+)\.sql$/);
|
||||
if (!match) continue;
|
||||
|
||||
changes.push({
|
||||
mark: patch.isDeleted() ? '-' : '+',
|
||||
path: match[1]
|
||||
});
|
||||
}
|
||||
|
||||
return changes.sort(
|
||||
(a, b) => b.mark == '-' && b.mark != a.mark ? 1 : -1
|
||||
);
|
||||
}
|
||||
|
||||
async cachedChanges() {
|
||||
const changes = [];
|
||||
const dumpDir = `${this.opts.workspace}/dump`;
|
||||
const dumpChanges = `${dumpDir}/.changes`;
|
||||
|
||||
if (!await fs.pathExists(dumpChanges))
|
||||
return null;
|
||||
|
||||
const readline = require('readline');
|
||||
const rl = readline.createInterface({
|
||||
input: fs.createReadStream(dumpChanges),
|
||||
//output: process.stdout,
|
||||
console: false
|
||||
});
|
||||
|
||||
for await (const line of rl) {
|
||||
changes.push({
|
||||
mark: line.charAt(0),
|
||||
path: line.substr(1)
|
||||
});
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MyVC;
|
||||
|
||||
if (require.main === module)
|
||||
new MyVC().run();
|
11
myvc-dump.js
11
myvc-dump.js
|
@ -1,5 +1,5 @@
|
|||
|
||||
const MyVC = require('./index');
|
||||
const MyVC = require('./myvc');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const docker = require('./docker');
|
||||
|
@ -11,10 +11,10 @@ class Dump {
|
|||
get myOpts() {
|
||||
return {
|
||||
alias: {
|
||||
env: 'e'
|
||||
remote: 'r'
|
||||
},
|
||||
default: {
|
||||
env: 'production'
|
||||
remote: 'production'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ class Dump {
|
|||
|
||||
await docker.build(__dirname, {
|
||||
tag: 'myvc/client',
|
||||
file: path.join(__dirname, 'Dockerfile.client')
|
||||
file: path.join(__dirname, 'server', 'Dockerfile')
|
||||
}, opts.debug);
|
||||
|
||||
let dumpArgs = [
|
||||
|
@ -84,7 +84,8 @@ class Dump {
|
|||
async dockerRun(command, args, execOptions) {
|
||||
const commandArgs = [command].concat(args);
|
||||
await docker.run('myvc/client', commandArgs, {
|
||||
volume: `${this.opts.workspace}:/workspace`
|
||||
volume: `${this.opts.workspace}:/workspace`,
|
||||
rm: true
|
||||
}, execOptions);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
const MyVC = require('./index');
|
||||
const MyVC = require('./myvc');
|
||||
const fs = require('fs-extra');
|
||||
|
||||
class Init {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
const MyVC = require('./index');
|
||||
const MyVC = require('./myvc');
|
||||
const fs = require('fs-extra');
|
||||
const ejs = require('ejs');
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
const MyVC = require('./index');
|
||||
const MyVC = require('./myvc');
|
||||
const fs = require('fs-extra');
|
||||
const nodegit = require('nodegit');
|
||||
|
||||
|
@ -58,7 +58,7 @@ class Push {
|
|||
version = userVersion;
|
||||
}
|
||||
|
||||
if (opts.env == 'production') {
|
||||
if (opts.remote == 'production') {
|
||||
console.log(
|
||||
'\n ( ( ) ( ( ) ) '
|
||||
+ '\n )\\ ))\\ ) ( /( )\\ ) ( ))\\ ) ( /( ( /( '
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
|
||||
const MyVC = require('./index');
|
||||
const MyVC = require('./myvc');
|
||||
const docker = require('./docker');
|
||||
const Container = require('./docker').Container;
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const Server = require('./server/server');
|
||||
|
@ -37,7 +38,7 @@ class Run {
|
|||
const changes = await myvc.changedRoutines(version.gitCommit);
|
||||
|
||||
let isEqual = false;
|
||||
if (cache && changes && cache.length == changes.lenth)
|
||||
if (cache && changes && cache.length == changes.length)
|
||||
for (let i = 0; i < changes.length; i++) {
|
||||
isEqual = cache[i].path == changes[i].path
|
||||
&& cache[i].mark == changes[i].mark;
|
||||
|
@ -45,6 +46,7 @@ class Run {
|
|||
}
|
||||
|
||||
if (!isEqual) {
|
||||
console.log('not equal');
|
||||
const fd = await fs.open(`${dumpDir}/.changes`, 'w+');
|
||||
for (const change of changes)
|
||||
fs.write(fd, change.mark + change.path + '\n');
|
||||
|
@ -85,7 +87,8 @@ class Run {
|
|||
publish: `3306:${dbConfig.port}`
|
||||
};
|
||||
try {
|
||||
await this.rm();
|
||||
const server = new Server(new Container(opts.code));
|
||||
await server.rm();
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
const MyVC = require('./index');
|
||||
const MyVC = require('./myvc');
|
||||
const Container = require('./docker').Container;
|
||||
const Server = require('./server/server');
|
||||
const Run = require('./myvc-run');
|
||||
|
|
255
myvc.js
255
myvc.js
|
@ -1,4 +1,255 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const MyVC = require('./');
|
||||
new MyVC().run();
|
||||
require('require-yaml');
|
||||
require('colors');
|
||||
const getopts = require('getopts');
|
||||
const packageJson = require('./package.json');
|
||||
const fs = require('fs-extra');
|
||||
const ini = require('ini');
|
||||
const path = require('path');
|
||||
const mysql = require('mysql2/promise');
|
||||
const nodegit = require('nodegit');
|
||||
|
||||
class MyVC {
|
||||
async run(command) {
|
||||
console.log(
|
||||
'MyVC (MySQL Version Control)'.green,
|
||||
`v${packageJson.version}`.magenta
|
||||
);
|
||||
|
||||
const opts = {};
|
||||
const argv = process.argv.slice(2);
|
||||
const cliOpts = getopts(argv, {
|
||||
alias: {
|
||||
remote: 'r',
|
||||
workspace: 'w',
|
||||
socket: 's',
|
||||
debug: 'd',
|
||||
version: 'v',
|
||||
help: 'h'
|
||||
},
|
||||
default: {
|
||||
workspace: process.cwd()
|
||||
}
|
||||
})
|
||||
|
||||
if (cliOpts.version)
|
||||
process.exit(0);
|
||||
|
||||
try {
|
||||
if (!command) {
|
||||
const commandName = cliOpts._[0];
|
||||
if (!commandName) {
|
||||
console.log(
|
||||
'Usage:'.gray,
|
||||
'[npx] myvc'
|
||||
+ '[-w|--workspace]'
|
||||
+ '[-r|--remote]'
|
||||
+ '[-d|--debug]'
|
||||
+ '[-h|--help]'
|
||||
+ '[-v|--version]'
|
||||
+ 'command'.blue
|
||||
);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const commands = [
|
||||
'init',
|
||||
'pull',
|
||||
'push',
|
||||
'dump',
|
||||
'start',
|
||||
'run'
|
||||
];
|
||||
|
||||
if (commands.indexOf(commandName) == -1)
|
||||
throw new Error (`Unknown command '${commandName}'`);
|
||||
|
||||
const Klass = require(`./myvc-${commandName}`);
|
||||
command = new Klass();
|
||||
}
|
||||
|
||||
const commandOpts = getopts(argv, command.myOpts);
|
||||
Object.assign(cliOpts, commandOpts);
|
||||
|
||||
for (const opt in cliOpts) {
|
||||
if (opt.length > 1 || opt == '_')
|
||||
opts[opt] = cliOpts[opt];
|
||||
}
|
||||
|
||||
parameter('Workspace:', opts.workspace);
|
||||
parameter('Remote:', opts.remote || 'local');
|
||||
|
||||
await this.load(opts);
|
||||
command.opts = opts;
|
||||
await command.run(this, opts);
|
||||
await this.unload();
|
||||
} catch (err) {
|
||||
if (err.name == 'Error' && !opts.debug)
|
||||
console.error('Error:'.gray, err.message.red);
|
||||
else
|
||||
throw err;
|
||||
}
|
||||
|
||||
function parameter(parameter, value) {
|
||||
console.log(parameter.gray, (value || 'null').blue);
|
||||
}
|
||||
|
||||
process.exit();
|
||||
}
|
||||
|
||||
async load(opts) {
|
||||
// Configuration file
|
||||
|
||||
const config = require(`${__dirname}/myvc.default.yml`);
|
||||
|
||||
const configFile = 'myvc.config.yml';
|
||||
const configPath = path.join(opts.workspace, configFile);
|
||||
if (await fs.pathExists(configPath))
|
||||
Object.assign(config, require(configPath));
|
||||
|
||||
Object.assign(opts, config);
|
||||
opts.configFile = configFile;
|
||||
|
||||
// Database configuration
|
||||
|
||||
let iniFile = 'db.ini';
|
||||
let iniDir = __dirname;
|
||||
if (opts.remote) {
|
||||
iniFile = `remotes/${opts.remote}.ini`;
|
||||
iniDir = opts.workspace;
|
||||
}
|
||||
const iniPath = path.join(iniDir, iniFile);
|
||||
|
||||
if (!await fs.pathExists(iniPath))
|
||||
throw new Error(`Database config file not found: ${iniFile}`);
|
||||
|
||||
const iniConfig = ini.parse(await fs.readFile(iniPath, 'utf8')).client;
|
||||
const dbConfig = {
|
||||
host: iniConfig.host,
|
||||
port: iniConfig.port,
|
||||
user: iniConfig.user,
|
||||
password: iniConfig.password,
|
||||
database: opts.versionSchema,
|
||||
authPlugins: {
|
||||
mysql_clear_password() {
|
||||
return () => iniConfig.password + '\0';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (iniConfig.ssl_ca) {
|
||||
dbConfig.ssl = {
|
||||
ca: await fs.readFile(`${opts.workspace}/${iniConfig.ssl_ca}`),
|
||||
rejectUnauthorized: iniConfig.ssl_verify_server_cert != undefined
|
||||
}
|
||||
}
|
||||
if (opts.socket)
|
||||
dbConfig.socketPath = '/var/run/mysqld/mysqld.sock';
|
||||
|
||||
Object.assign(opts, {
|
||||
iniFile,
|
||||
dbConfig
|
||||
});
|
||||
this.opts = opts;
|
||||
}
|
||||
|
||||
async dbConnect() {
|
||||
if (!this.conn)
|
||||
this.conn = await this.createConnection();
|
||||
return this.conn;
|
||||
}
|
||||
|
||||
async createConnection() {
|
||||
return await mysql.createConnection(this.opts.dbConfig);
|
||||
}
|
||||
|
||||
async unload() {
|
||||
if (this.conn)
|
||||
await this.conn.end();
|
||||
}
|
||||
|
||||
async fetchDbVersion() {
|
||||
const {opts} = this;
|
||||
|
||||
const [[res]] = await this.conn.query(
|
||||
`SELECT COUNT(*) > 0 tableExists
|
||||
FROM information_schema.tables
|
||||
WHERE TABLE_SCHEMA = ?
|
||||
AND TABLE_NAME = 'version'`,
|
||||
[opts.versionSchema]
|
||||
);
|
||||
|
||||
if (!res.tableExists) {
|
||||
const structure = await fs.readFile(`${__dirname}/structure.sql`, 'utf8');
|
||||
await this.conn.query(structure);
|
||||
return null;
|
||||
}
|
||||
|
||||
const [[version]] = await this.conn.query(
|
||||
`SELECT number, gitCommit
|
||||
FROM version WHERE code = ?`,
|
||||
[opts.code]
|
||||
);
|
||||
return version;
|
||||
}
|
||||
|
||||
async changedRoutines(commit) {
|
||||
const repo = await nodegit.Repository.open(this.opts.workspace);
|
||||
|
||||
const from = await repo.getCommit(commit);
|
||||
const fromTree = await from.getTree();
|
||||
|
||||
const to = await repo.getHeadCommit();
|
||||
const toTree = await to.getTree();
|
||||
|
||||
const diff = await toTree.diff(fromTree);
|
||||
const patches = await diff.patches();
|
||||
|
||||
const changes = [];
|
||||
for (const patch of patches) {
|
||||
const path = patch.newFile().path();
|
||||
const match = path.match(/^routines\/(.+)\.sql$/);
|
||||
if (!match) continue;
|
||||
|
||||
changes.push({
|
||||
mark: patch.isDeleted() ? '-' : '+',
|
||||
path: match[1]
|
||||
});
|
||||
}
|
||||
|
||||
return changes.sort(
|
||||
(a, b) => b.mark == '-' && b.mark != a.mark ? 1 : -1
|
||||
);
|
||||
}
|
||||
|
||||
async cachedChanges() {
|
||||
const changes = [];
|
||||
const dumpDir = `${this.opts.workspace}/dump`;
|
||||
const dumpChanges = `${dumpDir}/.changes`;
|
||||
|
||||
if (!await fs.pathExists(dumpChanges))
|
||||
return null;
|
||||
|
||||
const readline = require('readline');
|
||||
const rl = readline.createInterface({
|
||||
input: fs.createReadStream(dumpChanges),
|
||||
//output: process.stdout,
|
||||
console: false
|
||||
});
|
||||
|
||||
for await (const line of rl) {
|
||||
changes.push({
|
||||
mark: line.charAt(0),
|
||||
path: line.substr(1)
|
||||
});
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MyVC;
|
||||
|
||||
if (require.main === module)
|
||||
new MyVC().run();
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"name": "myvc",
|
||||
"version": "1.1.3",
|
||||
"version": "1.1.4",
|
||||
"author": "Verdnatura Levante SL",
|
||||
"description": "MySQL Version Control",
|
||||
"license": "GPL-3.0",
|
||||
"bin": "myvc.js",
|
||||
"bin": "cli.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/verdnatura/myvc.git"
|
||||
|
|
|
@ -32,7 +32,6 @@ RUN npm install --only=prod
|
|||
|
||||
COPY \
|
||||
structure.sql \
|
||||
index.js \
|
||||
myvc.js \
|
||||
myvc-push.js \
|
||||
myvc.default.yml \
|
||||
|
|
Loading…
Reference in New Issue