├── src ├── cli │ ├── test │ │ ├── testWorkspace │ │ │ ├── .gitignore │ │ │ ├── src │ │ │ │ └── HR │ │ │ │ │ └── FUNCTIONS │ │ │ │ │ ├── FUNC_TEST.sql │ │ │ │ │ └── FUNC_TEST1.sql │ │ │ └── oradewrc.json │ │ ├── resources │ │ │ ├── oradewrc.TEST.json │ │ │ ├── dbconfig.json │ │ │ ├── dbconfig.default.json │ │ │ ├── oradewrc.json │ │ │ └── oradewrc.default.json │ │ ├── base.test.ts │ │ ├── utility.test.ts │ │ ├── db.test.ts │ │ └── dbobject.test.ts │ ├── templates │ │ ├── .gitignore │ │ ├── scripts │ │ │ ├── initial_DDL.sql │ │ │ ├── initial_DML.sql │ │ │ └── final.sql │ │ ├── BOL.md │ │ └── test │ │ │ └── PCK_1.test.sql │ ├── oradew.js │ ├── gulpfile.ts │ ├── tasks │ │ ├── generate.ts │ │ ├── create.ts │ │ ├── run.ts │ │ ├── import.ts │ │ ├── init.ts │ │ ├── compile.ts │ │ └── package.ts │ ├── common │ │ ├── nedb.ts │ │ ├── globs.ts │ │ ├── git.ts │ │ ├── utility.ts │ │ ├── config.ts │ │ ├── dbobject.ts │ │ └── base.ts │ ├── schemas │ │ ├── oradewrc-generate-schema.json │ │ ├── dbconfig-schema.json │ │ └── oradewrc-schema.json │ ├── process.ts │ └── cli.ts └── extension │ ├── images │ ├── run_icon.png │ ├── compile_icon.png │ ├── oradew_icon_128.png │ └── compile_icon_alt.png │ ├── test │ └── runTest.ts │ ├── common │ ├── telemetry.ts │ ├── activation.ts │ ├── configuration-manager.ts │ └── selection.ts │ ├── controllers │ ├── file-controller.ts │ ├── generator-controller.ts │ ├── environment-controller.ts │ └── user-controller.ts │ ├── task-provider.ts │ └── extension.ts ├── .prettierrc ├── demo.gif ├── .gitignore ├── .gitmodules ├── .vscode ├── extensions.json ├── settings.json ├── tasks.json └── launch.json ├── .github └── FUNDING.yml ├── .vscodeignore ├── precompile.js ├── tslint.json ├── LICENSE ├── webpack.config.js ├── tsconfig.json ├── CHANGELOG.md ├── README.md └── package.json /src/cli/test/testWorkspace/.gitignore: -------------------------------------------------------------------------------- 1 | dbconfig.json -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "quoteProps": "preserve" 4 | } -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickeypearce/oradew-vscode/HEAD/demo.gif -------------------------------------------------------------------------------- /src/cli/templates/.gitignore: -------------------------------------------------------------------------------- 1 | # oradew files 2 | dbconfig.json 3 | *.log 4 | *.db -------------------------------------------------------------------------------- /src/cli/test/resources/oradewrc.TEST.json: -------------------------------------------------------------------------------- 1 | { 2 | "customField": "no this." 3 | } 4 | -------------------------------------------------------------------------------- /src/cli/test/testWorkspace/src/HR/FUNCTIONS/FUNC_TEST.sql: -------------------------------------------------------------------------------- 1 | 2 | begin 3 | select 4 | end; 5 | / -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .vscode-test/ 4 | *.vsix 5 | *.db 6 | deploy/ 7 | *.log 8 | dist -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "wiki"] 2 | path = wiki 3 | url = https://github.com/mickeypearce/oradew-vscode.wiki.git 4 | -------------------------------------------------------------------------------- /src/cli/oradew.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const { program } = require("./cli"); 3 | program.parse(process.argv); 4 | -------------------------------------------------------------------------------- /src/extension/images/run_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickeypearce/oradew-vscode/HEAD/src/extension/images/run_icon.png -------------------------------------------------------------------------------- /src/cli/templates/scripts/initial_DDL.sql: -------------------------------------------------------------------------------- 1 | -- DDL Statements 2 | -- create table table_name (empno number(5), tax number(10,2)); 3 | -------------------------------------------------------------------------------- /src/cli/templates/scripts/initial_DML.sql: -------------------------------------------------------------------------------- 1 | -- DML Statements 2 | -- insert into table_name (list_of_columns) 3 | -- values (list_of_values); -------------------------------------------------------------------------------- /src/extension/images/compile_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickeypearce/oradew-vscode/HEAD/src/extension/images/compile_icon.png -------------------------------------------------------------------------------- /src/extension/images/oradew_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickeypearce/oradew-vscode/HEAD/src/extension/images/oradew_icon_128.png -------------------------------------------------------------------------------- /src/extension/images/compile_icon_alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mickeypearce/oradew-vscode/HEAD/src/extension/images/compile_icon_alt.png -------------------------------------------------------------------------------- /src/cli/test/testWorkspace/src/HR/FUNCTIONS/FUNC_TEST1.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE FUNCTION "FUNC_TEST1" 2 | RETURN varchar2 AS 3 | BEGIN 4 | RETURN 'test'; 5 | END FUNC_TEST1; -------------------------------------------------------------------------------- /src/cli/templates/BOL.md: -------------------------------------------------------------------------------- 1 | # Bill Of Lading 2 | 3 | ## ${config["version.number"]} - ${config["version.description"]} (${config["version.releaseDate"]}) 4 | 5 | ${data.content} 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "eg2.tslint" 6 | ] 7 | } -------------------------------------------------------------------------------- /src/cli/templates/test/PCK_1.test.sql: -------------------------------------------------------------------------------- 1 | begin 2 | -- UNIT Tests 3 | 4 | /** FUNCTION PCK_1.f_testable */ 5 | -- ut_assert.equal( 6 | -- actual => PCK_1.f_testable(aparam1 => ''), 7 | -- expected => '0', 8 | -- message => 'Problem with empty string param.' 9 | -- ); 10 | 11 | NULL; 12 | end; -------------------------------------------------------------------------------- /src/cli/templates/scripts/final.sql: -------------------------------------------------------------------------------- 1 | -- Increment version 2 | -- begin 3 | -- p_bumpVersion(aVersionNumber => '${config["version.number"]}', aVersionDescription => '${config["version.description"]}'); 4 | -- end; 5 | -- / 6 | -- Recompile invalid current schema objects 7 | -- exec dbms_utility.compile_schema(schema => user, compile_all => false); 8 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | custom: 3 | [ 4 | "https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=97NH366RK4UQA&item_name=Oradew¤cy_code=EUR&source=url", 5 | "https://www.blockchain.com/btc/address/1GM3cfCZYiZwGecRjjZHgvzV9yQnqGCXbM", 6 | "https://ethplorer.io/address/0x1be31049320bfd61991c9fa7a2bbb1aa4782ec9f" 7 | ] 8 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | #out/test/** 4 | #out/**/*.map 5 | src/** 6 | .gitignore 7 | .gitmodules 8 | tsconfig.json 9 | tslint.json 10 | *.db 11 | *.tlog 12 | !dbconfig.json 13 | # webpack 14 | #node_modules - packages in devDependencies are automatically ignored 15 | webpack.config.js 16 | #!node_modules/oracledb 17 | **/*.map 18 | .github 19 | test/** 20 | build.yml 21 | precompile.js 22 | out 23 | -------------------------------------------------------------------------------- /precompile.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs-extra"); 2 | const del = require("del"); 3 | 4 | // Delete .out directory 5 | del.sync("./out"); 6 | 7 | // copy templates/* 8 | fs.copySync("./src/cli/templates", "./out/src/cli/templates"); 9 | 10 | // copy test resources 11 | fs.copySync("./src/cli/test/resources", "./out/src/cli/test/resources"); 12 | fs.copySync("./src/cli/test/testWorkspace", "./out/src/cli/test/testWorkspace"); 13 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "max-line-length": [ 4 | false, 5 | 100 6 | ], 7 | "no-string-throw": true, 8 | "no-unused-expression": true, 9 | "no-duplicate-variable": true, 10 | "curly": true, 11 | "class-name": true, 12 | "semicolon": [ 13 | true, 14 | "always", 15 | "ignore-bound-class-methods" 16 | ], 17 | "triple-equals": true, 18 | "quotemark": [ 19 | true, 20 | "double", 21 | "avoid-escape" 22 | ], 23 | "indent": true 24 | }, 25 | "defaultSeverity": "warning" 26 | } -------------------------------------------------------------------------------- /src/cli/test/resources/dbconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "DEV": { 3 | "connectString": "oradew", 4 | "users": [ 5 | { 6 | "user": "dev", 7 | "password": "welcome", 8 | "schemas": ["schema1", "schema2"] 9 | }, 10 | { 11 | "user": "dev1", 12 | "password": "welcome1", 13 | "default": true, 14 | "disabled": false 15 | }, 16 | { 17 | "user": "dev2", 18 | "password": "welcome2", 19 | "default": false, 20 | "disabled": true 21 | } 22 | ] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/cli/test/resources/dbconfig.default.json: -------------------------------------------------------------------------------- 1 | { 2 | "DEV": { 3 | "connectString": "localhost/orclpdb", 4 | "users": [ 5 | { 6 | "user": "hr", 7 | "password": "welcome" 8 | } 9 | ] 10 | }, 11 | "TEST": { 12 | "connectString": "localhost/orclpdb", 13 | "users": [ 14 | { 15 | "user": "hr", 16 | "password": "welcome" 17 | } 18 | ] 19 | }, 20 | "UAT": { 21 | "connectString": "localhost/orclpdb", 22 | "users": [ 23 | { 24 | "user": "hr", 25 | "password": "welcome" 26 | } 27 | ] 28 | } 29 | } -------------------------------------------------------------------------------- /src/cli/test/resources/oradewrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "compile.force": true, 3 | "customField": "this is it.", 4 | "source.pattern": { 5 | "packageSpec": "./src/{schema-name}/pck/{object-name}-spec.sql", 6 | "packageBody": "./src/{schema-name}/pck/{object-name}-body.sql", 7 | "trigger": "./src/{schema-name}/trigger-{object-name}.sql", 8 | "view": "./src/{schema-name}/view-{object-name}.sql", 9 | "function": "./test/src/{schema-name}/FUNCTIONS/{object-name}.sql", 10 | "procedure": "./src/PROCEDURES/{object-name}.sql", 11 | "table": "./Data -base/{schema-name}/TABS/T1/{object-name}_tab.sql" 12 | } 13 | } -------------------------------------------------------------------------------- /src/extension/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | 3 | import { runTests } from "@vscode/test-electron"; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, "../../"); 10 | 11 | // The path to the extension test script 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, "./suite/index"); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 17 | } catch (err) { 18 | console.error("Failed to run tests"); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true, // set this to false to include "out" folder in search results 8 | "dist": true 9 | }, 10 | "editor.codeActionsOnSave": { 11 | "source.fixAll.tslint": true 12 | }, 13 | "editor.formatOnSave": true, 14 | "editor.trimAutoWhitespace": true, 15 | "files.trimTrailingWhitespace": true, 16 | // Turn off tsc task auto detection since we have the necessary task as npm scripts 17 | "typescript.tsc.autoDetect": "off", 18 | "git.confirmSync": true, 19 | "gitlens.views.repositories.location": "scm", 20 | "editor.tabSize": 2, 21 | "typescript.tsdk": "node_modules\\typescript\\lib" 22 | } 23 | -------------------------------------------------------------------------------- /src/cli/test/testWorkspace/oradewrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "package.input": [ 3 | "./scripts/**/initial*.sql", 4 | "./src/**/VIEWS/*.sql", 5 | "./src/**/TYPES/*.sql", 6 | "./src/**/TYPE_BODIES/*.sql", 7 | "./src/**/TRIGGERS/*.sql", 8 | "./src/**/PACKAGES/*.sql", 9 | "./src/**/PACKAGE_BODIES/*.sql", 10 | "./src/**/FUNCTIONS/*.sql", 11 | "./src/**/PROCEDURES/*.sql", 12 | "./scripts/**/final*.sql", 13 | "!./**/*EXCLUDE*.sql" 14 | ], 15 | "package.output": "./deploy/{schema-name}.sql", 16 | "package.encoding": "utf8", 17 | "package.templating": false, 18 | "source.input": [ 19 | "./src/**/*.sql" 20 | ], 21 | "compile.warnings": "NONE", 22 | "compile.force": false, 23 | "version.number": "0.0.1", 24 | "version.description": "New feature", 25 | "version.releaseDate": "2099-01-01", 26 | "test.input": [ 27 | "./test/**/*.test.sql" 28 | ] 29 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true 11 | }, 12 | { 13 | "type": "npm", 14 | "script": "start:watch", 15 | "problemMatcher": [], 16 | "isBackground": true, 17 | "detail": "start development (webpack)" 18 | }, 19 | { 20 | "type": "npm", 21 | "script": "install", 22 | "problemMatcher": [], 23 | "label": "npm: install", 24 | "detail": "install dependencies from package" 25 | }, 26 | { 27 | "type": "npm", 28 | "script": "utest", 29 | "problemMatcher": [], 30 | "label": "npm: utest", 31 | "detail": "run unit tests" 32 | } 33 | ] 34 | } -------------------------------------------------------------------------------- /src/cli/gulpfile.ts: -------------------------------------------------------------------------------- 1 | // Imports make type with errors for functions 2 | const gulp = require("gulp"); 3 | 4 | import { packageTask } from "./tasks/package"; 5 | import { initTask } from "./tasks/init"; 6 | import { importTask } from "./tasks/import"; 7 | import { runTask } from "./tasks/run"; 8 | import { compileTask, compileOnSaveTask, compileTestTask } from "./tasks/compile"; 9 | import { generateTask } from "./tasks/generate"; 10 | import { createTask } from "./tasks/create"; 11 | 12 | gulp.task("init", initTask); 13 | gulp.task("create", createTask); 14 | 15 | gulp.task("compile", compileTask); 16 | gulp.task("compileOnSave", compileOnSaveTask); 17 | gulp.task("watch", compileOnSaveTask); //Alias 18 | gulp.task("test", compileTestTask); 19 | 20 | gulp.task("import", importTask); 21 | 22 | gulp.task("package", packageTask); 23 | 24 | gulp.task("run", runTask); 25 | gulp.task("deploy", runTask); //Alias 26 | 27 | gulp.task("generate", generateTask); 28 | -------------------------------------------------------------------------------- /src/extension/common/telemetry.ts: -------------------------------------------------------------------------------- 1 | import TelemetryReporter from "vscode-extension-telemetry"; 2 | 3 | const pkgJson = require("../../../package.json"); 4 | 5 | // all events will be prefixed with this event name 6 | const extensionId = pkgJson.name; 7 | 8 | // extension version will be reported as a property with each event 9 | const extensionVersion = pkgJson.version; 10 | 11 | // the application insights key (also known as instrumentation key) 12 | const key = "864f39be-f021-4e3b-98fb-18690e2e7066"; 13 | 14 | export class Telemetry { 15 | public static reporter: TelemetryReporter; 16 | 17 | public static initialize() { 18 | Telemetry.reporter = new TelemetryReporter(extensionId, extensionVersion, key); 19 | } 20 | 21 | public static sendEvent = (eventName: string, properties?: any, measurements?: any) => { 22 | try { 23 | Telemetry.reporter.sendTelemetryEvent(eventName, properties, measurements); 24 | } catch {} 25 | }; 26 | 27 | public static deactivate() { 28 | Telemetry.reporter.dispose(); 29 | } 30 | } 31 | 32 | Telemetry.initialize(); 33 | -------------------------------------------------------------------------------- /src/cli/tasks/generate.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import { argv } from "yargs"; 3 | import chalk from "chalk"; 4 | 5 | import { outputFilePromise } from "../common/utility"; 6 | import { getGenerator } from "../common/base"; 7 | 8 | export const generateTask = async ({ 9 | env = argv.env || "DEV", 10 | func = argv.func, 11 | file = argv.file, 12 | object = argv.object, 13 | output = argv.output as string, 14 | user = argv.user as string, 15 | }) => { 16 | try { 17 | if (!func) { 18 | throw Error("Func cannot be empty."); 19 | } 20 | 21 | const resp = await getGenerator({ func, file, env, object, user }); 22 | 23 | // Save to output argument if it exists 24 | // otherwise save to generated file in ./script directory 25 | const outputPath = output 26 | ? path.resolve(output) 27 | : path.resolve(`./scripts/${resp.obj.owner}/file_${object}_${new Date().getTime()}.sql`); 28 | 29 | await outputFilePromise(outputPath, resp.result); 30 | console.log(`${outputPath} ${chalk.green("created.")}`); 31 | } catch (err) { 32 | console.error(err.message); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 mickeypearce 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/cli/common/nedb.ts: -------------------------------------------------------------------------------- 1 | import * as Datastore from "nedb"; 2 | import { join } from "path"; 3 | 4 | const filename = join(process.env.ORADEW_STORAGE_DIR || __dirname, "exported.db"); 5 | 6 | const db = new Datastore({ 7 | filename, 8 | autoload: true, 9 | }); 10 | 11 | const user = process.env.username; 12 | 13 | const upsertDdlTime = ({ owner, objectName, objectType }, lastDdlTime, env) => 14 | new Promise((res, rej) => { 15 | db.update( 16 | // Where 17 | { owner, objectName, objectType, env }, 18 | // Set 19 | { $set: { lastDdlTime, savedTime: new Date(), user } }, 20 | // Options 21 | { upsert: true, returnUpdatedDocs: true }, 22 | // Then 23 | (err, numReplaced, upsert) => { 24 | if (err) { 25 | rej(err); 26 | } 27 | res(upsert); 28 | } 29 | ); 30 | }); 31 | 32 | const getDdlTime = ({ owner, objectName, objectType }, env) => 33 | new Promise((res, rej) => { 34 | db.find({ owner, objectName, objectType, env }, (err, findObj) => { 35 | if (err) { 36 | rej(err); 37 | } 38 | res(findObj.length !== 0 ? findObj[0].lastDdlTime : null); 39 | }); 40 | }); 41 | 42 | export { getDdlTime, upsertDdlTime }; 43 | -------------------------------------------------------------------------------- /src/cli/schemas/oradewrc-generate-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "JSON schema for Oradew Generator configuration file", 3 | "$schema": "http://json-schema.org/draft-06/schema#", 4 | "type": "object", 5 | "properties": { 6 | "generator.define": { 7 | "default": [ 8 | { 9 | "label": "#1", 10 | "description": "Define your generator in oradewrc-generate.json", 11 | "function": "" 12 | } 13 | ], 14 | "description": "Array of generator definitions.", 15 | "type": "array", 16 | "items": { 17 | "type": "object", 18 | "properties": { 19 | "label": { 20 | "type": "string", 21 | "description": "Generator identifier" 22 | }, 23 | "function": { 24 | "type": "string", 25 | "description": "Generator function name on DB. \nex.DB func.spec.: FUNCTION genUpdateStatement(object_type IN VARCHAR2, name IN VARCHAR2, schema IN VARCHAR2, selected_object IN VARCHAR2) RETURN CLOB;" 26 | }, 27 | "description": { 28 | "string": "string" 29 | }, 30 | "output": { 31 | "description": "File path with generated content. If omitted generated filename is created.", 32 | "string": "string" 33 | } 34 | }, 35 | "required": ["label", "function"] 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/cli/test/resources/oradewrc.default.json: -------------------------------------------------------------------------------- 1 | { 2 | "package.input": [ 3 | "./scripts/**/initial*.sql", 4 | "./src/**/*.sql", 5 | "./scripts/**/final*.sql" 6 | ], 7 | "package.exclude": [ 8 | "./scripts/**/+(file|run)*.sql" 9 | ], 10 | "package.output": "./deploy/{schema-name}.sql", 11 | "package.encoding": "utf8", 12 | "package.templating": false, 13 | "source.input": [ 14 | "./src/**/*.sql" 15 | ], 16 | "source.encoding": "utf8", 17 | "compile.warnings": "NONE", 18 | "compile.force": true, 19 | "compile.stageFile": false, 20 | "version.number": "0.0.1", 21 | "version.description": "New feature", 22 | "version.releaseDate": "2099-01-01", 23 | "test.input": [ 24 | "./test/**/*.test.sql" 25 | ], 26 | "import.getDdlFunction": "dbms_metadata.get_ddl", 27 | "source.pattern": { 28 | "packageSpec": "./src/{schema-name}/PACKAGES/{object-name}.sql", 29 | "packageBody": "./src/{schema-name}/PACKAGE_BODIES/{object-name}.sql", 30 | "trigger": "./src/{schema-name}/TRIGGERS/{object-name}.sql", 31 | "typeSpec": "./src/{schema-name}/TYPES/{object-name}.sql", 32 | "typeBody": "./src/{schema-name}/TYPE_BODIES/{object-name}.sql", 33 | "view": "./src/{schema-name}/VIEWS/{object-name}.sql", 34 | "function": "./src/{schema-name}/FUNCTIONS/{object-name}.sql", 35 | "procedure": "./src/{schema-name}/PROCEDURES/{object-name}.sql", 36 | "table": "./src/{schema-name}/TABLES/{object-name}.sql", 37 | "synonym": "./src/{schema-name}/SYNONYMS/{object-name}.sql", 38 | "apex": "./src/{schema-name}/APEX/{object-name}.sql" 39 | }, 40 | "import.ease": false 41 | } -------------------------------------------------------------------------------- /src/extension/common/activation.ts: -------------------------------------------------------------------------------- 1 | import { window, commands } from "vscode"; 2 | import { sync as commandExists } from "command-exists"; 3 | import { existsSync } from "fs-extra"; 4 | import { ConfigurationManager } from "./configuration-manager"; 5 | 6 | let settings = ConfigurationManager.getInstance(); 7 | 8 | // Git and NodeJS are mandatory prerequsites, SQl*Plus is a warning only 9 | export const existPrerequisites = (): boolean => { 10 | let bCheck = true; 11 | if (!commandExists("git")) { 12 | window.showErrorMessage(`Oradew: "git" command (Git) is required in command-line.`); 13 | bCheck = false; 14 | } 15 | if (!commandExists("node")) { 16 | window.showErrorMessage(`Oradew: "node" command (Node.js) is required in command-line.`); 17 | bCheck = false; 18 | } 19 | // Optional - SQL*Plus or SQLcl is required only for certain oradew commands 20 | if (!commandExists("sqlplus") && !commandExists("sql")) { 21 | window.showWarningMessage( 22 | `Oradew: "sqlplus" (SQL*Plus) or "sql" (SQLcl) command is required in command-line for certain commands.` 23 | ); 24 | } 25 | return bCheck; 26 | }; 27 | 28 | // Set context variable which is then used to enable keybindings, context menus in package.json.. 29 | export const setInitialized = () => { 30 | // Existing dbConfigPath is extension activation point, together with prerequisites 31 | if (existsSync(settings.databaseConfigFile) && existPrerequisites()) { 32 | commands.executeCommand("setContext", "inOradewProject", true); 33 | } else { 34 | commands.executeCommand("setContext", "inOradewProject", false); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /src/extension/controllers/file-controller.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "path"; 2 | import { window, QuickPickOptions, QuickPickItem } from "vscode"; 3 | 4 | import { ConfigurationManager } from "../common/configuration-manager"; 5 | import { EnvironmentController } from "./environment-controller"; 6 | import { matchOutputFiles } from "@Cli/common/dbobject"; 7 | import { WorkspaceConfig } from "@Cli/common/config"; 8 | 9 | const { workspaceConfigFile, workspaceDir } = ConfigurationManager.getInstance(); 10 | 11 | export class FileController { 12 | private _environmentController: EnvironmentController; 13 | 14 | constructor(environmentController: EnvironmentController) { 15 | this._environmentController = environmentController; 16 | } 17 | 18 | public pickPackageScript = async (): Promise => { 19 | // It doesn't reload if a field changes, so here... 20 | const config = new WorkspaceConfig(workspaceConfigFile); 21 | const env = this._environmentController.currentPick; 22 | const output = config.get({ field: "package.output", env }); 23 | 24 | const files = matchOutputFiles(output, { cwd: workspaceDir }); 25 | 26 | let items: QuickPickItem[] = files.map((file) => ({ 27 | label: file, 28 | description: resolve(workspaceDir, file), 29 | })); 30 | 31 | if (items.length === 0) { 32 | window.showWarningMessage(`Oradew: Cannot match any packaged file.`); 33 | return null; 34 | } 35 | 36 | const options: QuickPickOptions = { 37 | placeHolder: `Select script file to run (${this._environmentController.currentPick} environment)`, 38 | matchOnDescription: true, 39 | matchOnDetail: true, 40 | }; 41 | return window.showQuickPick(items, options).then((item) => (item ? item.label : null)); 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /src/cli/test/base.test.ts: -------------------------------------------------------------------------------- 1 | const assert = require("assert"); 2 | 3 | import { fromGlobsToFilesArray, getGlobMatches, isGlobMatch } from "../common/globs"; 4 | // import { resolve } from "path"; 5 | 6 | const dir = "./out/src/cli/test/testWorkspace"; //resolve(__dirname, "testWorkspace"); 7 | 8 | describe("#base", function () { 9 | it("fromGlobsToFilesArray", function () { 10 | let matches = [ 11 | dir + "/src/HR/FUNCTIONS/FUNC_TEST.sql", 12 | dir + "/src/HR/FUNCTIONS/FUNC_TEST1.sql", 13 | ]; 14 | let match = fromGlobsToFilesArray([dir + "/src/**/*.sql"]); 15 | assert.deepEqual(match, matches); 16 | }); 17 | it("getGlobMatches", function () { 18 | let matches = [ 19 | dir + "/src/HR/FUNCTIONS/FUNC_TEST.sql", 20 | dir + "/src/HR/FUNCTIONS/FUNC_TEST1.sql", 21 | ]; 22 | let match = getGlobMatches([dir + "/src/**/*.sql"], matches); 23 | assert.deepEqual(match, matches); 24 | 25 | let matchesNot = ["./test/src/HR/FUNCTIONS/FUNC_TEST.sql"]; 26 | let matchNot = getGlobMatches( 27 | ["./test/src/**/*.sql", "!./test/src/HR/FUNCTIONS/FUNC_TEST1.sql"], 28 | matchesNot 29 | ); 30 | assert.deepEqual(matchNot, matchesNot); 31 | }); 32 | it("isGlobMatch", function () { 33 | const matches = ["./test/src/FUNCTIONS/FUNC_TEST1.sql"]; 34 | let match = isGlobMatch(["./test/src/**/*.sql"], matches); 35 | assert.ok(match); 36 | 37 | let matchcase = isGlobMatch(["./test/src/**/*.SQL"], matches); 38 | assert.ok(matchcase); 39 | // Exclude file from glob 40 | let matchIgnore = isGlobMatch(["./test/src/**/*.sql", `!./test/src/FUNCTIONS/*.sql`], matches); 41 | assert.ok(!matchIgnore); 42 | 43 | const matchesNotActualFile = ["./test/src/USERX/FUNCTIONS/FUNC_TEST1.sql"]; 44 | let matchNotActualFile = isGlobMatch(["./test/src/**/*.sql"], matchesNotActualFile); 45 | assert.ok(matchNotActualFile); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /src/cli/common/globs.ts: -------------------------------------------------------------------------------- 1 | // const glob = require("fast-glob"); not preserving order 2 | import { sync } from "globby"; 3 | import * as multimatch from "multimatch"; 4 | 5 | import { pipe, compact, uniq, sortBy, identity, map, isEqual } from "lodash/fp"; 6 | 7 | import { rootPrepend, splitLines } from "./utility"; 8 | import { getChangesNotStaged } from "./git"; 9 | 10 | // Get array of files matched by glob patterns array 11 | export function fromGlobsToFilesArray(globArray, options?) { 12 | return sync(globArray, { ...options, caseSensitiveMatch: false }).map(rootPrepend); 13 | } 14 | 15 | // Get filepaths from matchArray (file paths) matched by globArray 16 | // matchArray is not necesarry actual files on disk 17 | // (Intersection between globArray matches and matchArray) 18 | export function getGlobMatches(globArray, matchArray) { 19 | return multimatch(matchArray, globArray, { nocase: true }); 20 | } 21 | 22 | // True if matchArray equals globArray matches 23 | // Matches against a list instead of the filesystem 24 | export function isGlobMatch(globArray, matchArray) { 25 | const matches = multimatch(matchArray, globArray, { nocase: true }); 26 | return isEqual(matchArray, matches); 27 | } 28 | 29 | // Get array of files from output stream string 30 | export function fromStdoutToFilesArray(stdout) { 31 | return pipe( 32 | // Generate array from lines 33 | splitLines, 34 | // Remove empty items and duplicates 35 | compact, 36 | uniq, 37 | // Scripts first 38 | sortBy(identity), 39 | // Add ./ if it doesn't already exists 40 | map(rootPrepend) 41 | )(stdout); 42 | } 43 | 44 | export const getOnlyChangedFiles = async (source) => { 45 | // Get array of changed files from git 46 | const stdout = await getChangesNotStaged(); 47 | const changed = fromStdoutToFilesArray(stdout); 48 | // Get array of files matched by source array parameter 49 | return getGlobMatches(source, changed); 50 | }; 51 | -------------------------------------------------------------------------------- /src/extension/controllers/generator-controller.ts: -------------------------------------------------------------------------------- 1 | import { QuickPickItem, QuickPickOptions, window } from "vscode"; 2 | import { existsSync, readJsonSync } from "fs-extra"; 3 | import { ConfigurationManager } from "../common/configuration-manager"; 4 | import { getDefaultsFromSchema } from "@Cli/common/config"; 5 | import { properties as genSchema } from "@Cli/schemas/oradewrc-generate-schema.json"; 6 | 7 | interface IGenerator { 8 | label: string; 9 | function: string; 10 | description?: string; 11 | output?: string; 12 | } 13 | 14 | export class GeneratorController { 15 | // Config file path 16 | file: string; 17 | defaults: any; 18 | // Json config object 19 | object: any; 20 | 21 | constructor() { 22 | this.file = null; 23 | this.defaults = getDefaultsFromSchema(genSchema); 24 | } 25 | 26 | getConfigFile = (): string => { 27 | return ConfigurationManager.getInstance().generatorConfigFile; 28 | }; 29 | 30 | read = (): void => { 31 | this.file = this.getConfigFile(); 32 | this.object = existsSync(this.file) ? readJsonSync(this.file) : this.defaults; 33 | }; 34 | 35 | getDefinitions = (): Array => { 36 | this.read(); 37 | return this.object["generator.define"]; 38 | }; 39 | 40 | private createGeneratorList = (): QuickPickItem[] => { 41 | return this.getDefinitions().map((generator) => { 42 | return { 43 | label: generator.label, 44 | description: generator.description, 45 | detail: generator.function, 46 | }; 47 | }); 48 | }; 49 | 50 | public getGeneratorFunction = async (): Promise => { 51 | let gens: QuickPickItem[] = await this.createGeneratorList(); 52 | const options: QuickPickOptions = { 53 | placeHolder: "Select generator", 54 | matchOnDescription: true, 55 | matchOnDetail: true, 56 | }; 57 | 58 | return window.showQuickPick(gens, options).then((item) => (item ? item.detail : null)); 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /src/cli/tasks/create.ts: -------------------------------------------------------------------------------- 1 | import { outputFileSync } from "fs-extra"; 2 | // import * as gulp from "gulp"; 3 | const gulp = require("gulp"); 4 | import { argv } from "yargs"; 5 | 6 | import { getObjectsInfoByType } from "../common/base"; 7 | import { getPathFromObjectInfo, getObjectTypes } from "../common/dbobject"; 8 | import { isGlobMatch } from "../common/globs"; 9 | import { exportFilesFromDbAsync } from "./import"; 10 | import { workspaceConfig as config } from "../common/config"; 11 | import { dbConfig } from "../common/config"; 12 | 13 | /* 14 | * Merge task (-t) from branch (-b) to current branch. 15 | * Usage ex. -t XXXX-5123 -b version-5.1.0 16 | **/ 17 | // const cherryPickFromJiraTask = async () => { 18 | // const branch = argv.b || "develop"; 19 | // const task = argv.t; 20 | // if (task == null) { throw Error("Task cannot be empty. ex. -t XXXX-1234"); } 21 | 22 | // const stdout = await git.cherryPickByGrepAndBranch(task, branch); 23 | // console.log(`Files changed: ${stdout}`); 24 | // }; 25 | 26 | const createSrcFromDbObjects = async ({ env = argv.env || "DEV", file = argv.file }) => { 27 | const source = file || config.get({ field: "source.input", env }); 28 | const schemas = dbConfig.getSchemas(env as string); 29 | const objectTypes = getObjectTypes(); 30 | try { 31 | for (const owner of schemas) { 32 | const objs = await getObjectsInfoByType(env, owner, objectTypes); 33 | for (const obj of objs) { 34 | const path = getPathFromObjectInfo(owner, obj.OBJECT_TYPE, obj.OBJECT_NAME); 35 | if (path !== "") { 36 | // is path inside "source" glob? 37 | if (isGlobMatch(source, [path])) { 38 | outputFileSync(path, ""); 39 | console.log("Created file " + path); 40 | } 41 | } 42 | } 43 | } 44 | } catch (error) { 45 | console.error(error.message); 46 | } 47 | }; 48 | 49 | export async function createTask() { 50 | return gulp.series(createSrcFromDbObjects, exportFilesFromDbAsync)(); 51 | } 52 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": ["--extensionDevelopmentPath=${workspaceFolder}"], 14 | "outFiles": ["${workspaceFolder}/dist/**/*.js"], 15 | "preLaunchTask": "npm: start:watch", 16 | "sourceMaps": false, 17 | "env": { 18 | // For CLI 19 | "ORADEW_SILENT": "false" 20 | }, 21 | "stopOnEntry": false 22 | }, 23 | { 24 | "name": "Extension Tests", 25 | "type": "extensionHost", 26 | "request": "launch", 27 | "runtimeExecutable": "${execPath}", 28 | "args": [ 29 | "--extensionDevelopmentPath=${workspaceFolder}", 30 | "--extensionTestsPath=${workspaceFolder}/out/test" 31 | ], 32 | "outFiles": ["${workspaceFolder}/out/test/**/*.js"], 33 | "preLaunchTask": "npm: watch" 34 | }, 35 | { 36 | "name": "Gulp", 37 | "type": "node", 38 | "request": "launch", 39 | "cwd": "${workspaceFolder}/test", 40 | "program": "${workspaceFolder}/node_modules/gulp/bin/gulp.js", 41 | "env": { 42 | "dbConfigPath": "dbconfig.json" 43 | }, 44 | "args": [ 45 | "--cwd=${workspaceFolder}/test", 46 | "--gulpfile=${workspaceFolder}/out/gulpfile.js", 47 | "run", 48 | "--env=DEV", 49 | "--file=${workspaceFolder}/test/src/HR/FUNCTIONS/FUNC_TEST.sql" 50 | // "--object=FUNC_TEST1" 51 | ], 52 | "outFiles": ["${workspaceFolder}/out/**/*.js"], 53 | "showAsyncStacks": true, 54 | "sourceMaps": true, 55 | "preLaunchTask": "npm: watch" 56 | } 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /src/extension/common/configuration-manager.ts: -------------------------------------------------------------------------------- 1 | import { workspace } from "vscode"; 2 | import { resolve } from "path"; 3 | 4 | export class ConfigurationManager { 5 | public databaseConfigFile: string; 6 | public workspaceConfigFile: string; 7 | public generatorConfigFile: string; 8 | public chatty: boolean; 9 | public cliExecutable: string; 10 | public cliCommand: string; 11 | public envVariables?: { [id: string]: string }; 12 | public workspaceDir: string; 13 | 14 | private static _instance: ConfigurationManager; 15 | 16 | public static getInstance(): ConfigurationManager { 17 | if (!ConfigurationManager._instance) { 18 | ConfigurationManager._instance = new ConfigurationManager(); 19 | } 20 | 21 | return ConfigurationManager._instance; 22 | } 23 | 24 | private constructor() { 25 | this.initializeSettings(); 26 | } 27 | 28 | initializeSettings() { 29 | const oradewConfiguration = workspace.getConfiguration("oradew"); 30 | this.workspaceDir = workspace.workspaceFolders ? workspace.workspaceFolders![0].uri.fsPath : ""; 31 | 32 | const configParamWsConfigPath: string = oradewConfiguration.get("workspaceConfigFile"); 33 | this.workspaceConfigFile = resolve( 34 | configParamWsConfigPath.replace("${workspaceFolder}", this.workspaceDir) 35 | ); 36 | 37 | const configParamDbConfigPath: string = oradewConfiguration.get("databaseConfigFile"); 38 | this.databaseConfigFile = resolve( 39 | configParamDbConfigPath.replace("${workspaceFolder}", this.workspaceDir) 40 | ); 41 | 42 | const configParamGeneratorPath: string = oradewConfiguration.get("generatorConfigFile"); 43 | this.generatorConfigFile = resolve( 44 | configParamGeneratorPath.replace("${workspaceFolder}", this.workspaceDir) 45 | ); 46 | 47 | this.chatty = oradewConfiguration.get("chatty"); 48 | this.cliExecutable = oradewConfiguration.get("cliExecutable"); 49 | this.cliCommand = oradewConfiguration.get("cliCommand"); 50 | this.envVariables = oradewConfiguration.get("envVariables"); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/extension/common/selection.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | 3 | let offsetToPosition = (text: string, offset: number): vscode.Position => { 4 | const res = text.substr(0, offset); 5 | const lines = res.split(/\r\n|\r|\n/); 6 | const lineNumber = lines.length - 1; 7 | const charNumber = offset - res.lastIndexOf("\n") - 1; 8 | return new vscode.Position(lineNumber, charNumber); 9 | }; 10 | 11 | let selectPattern = (editor: vscode.TextEditor, allText: string, pattern): string => { 12 | if (editor) { 13 | let match; 14 | let selectionTest; 15 | let cursorLine = editor.selection.active.line; 16 | while (match = pattern.exec(allText)) { 17 | let startPoint = offsetToPosition(allText, match.index); 18 | let endPoint = offsetToPosition(allText, pattern.lastIndex); 19 | if (startPoint.line <= cursorLine && cursorLine <= endPoint.line) { 20 | selectionTest = new vscode.Selection(startPoint, endPoint); 21 | editor.selection = selectionTest; 22 | return editor.document.getText(editor.selection); 23 | } 24 | } 25 | } 26 | return null; 27 | }; 28 | 29 | export let selectAll = (editor: vscode.TextEditor): string => { 30 | const allText = editor.document.getText(); 31 | editor.selection = new vscode.Selection(editor.document.positionAt(0), editor.document.positionAt(allText.length)); 32 | return editor.document.getText(editor.selection); 33 | }; 34 | 35 | export let selectCurrentStatement = (editor: vscode.TextEditor): string => { 36 | const allText = editor.document.getText(); 37 | let selectedText = (editor.document.getText(editor.selection)); 38 | if (!selectedText || selectedText.length === 0) { 39 | selectedText = 40 | (selectPattern(editor, allText, /(? 9 | new Promise((res, rej) => { 10 | const isSilent = (process.env.ORADEW_SILENT || "true") === "true"; 11 | gitExec({ args, quiet: isSilent }, (err, stdout) => { 12 | if (err) { 13 | rej(err); 14 | } 15 | res(stdout); 16 | }); 17 | }); 18 | 19 | export const getChanges = () => exec({ args: `diff --name-only HEAD ${src}` }); 20 | export const getChangesStaged = () => exec({ args: `diff --name-only --staged ${src}` }); 21 | 22 | // Get tracked and untracked files 23 | export const getChangesNotStaged = () => 24 | exec({ args: `diff --name-only ${src} & git ls-files --others ${src}` }); 25 | 26 | // Files from commit or branch to the head (only from src and scripts dir) 27 | export const getCommitedFilesSincePoint = (from) => 28 | exec({ 29 | args: `log --diff-filter=ACMR --name-only --pretty="" ${from}..head ${src} ./scripts`, 30 | }); 31 | 32 | // Files from specific commits (only from src and scripts dir) 33 | export const getCommitedFilesByCommits = (commits: string[]) => 34 | exec({ 35 | args: `show --diff-filter=ACMR --name-only --pretty="" ${commits.join(" ")} ${src} ./scripts`, 36 | }); 37 | 38 | // Get first commit on the current branch 39 | export const getFirstCommitOnBranch = () => 40 | exec({ 41 | // First commit that is "reffered by some branch or tag" 42 | // args: `rev-list --simplify-by-decoration -1 head --skip=1` 43 | // Get latest commit that is tagged 44 | args: `rev-list -1 --tags`, 45 | }); 46 | 47 | export const cherryPickByGrepAndBranch = (grep, branch) => 48 | exec({ 49 | args: `rev-list --reverse --grep=${grep} ${branch} | git cherry-pick -n --stdin`, 50 | }); 51 | 52 | export const getStashedFiles = () => exec({ args: `stash show --name-only` }); 53 | export const stash = () => exec({ args: `stash clear & git stash save --keep-index` }); 54 | export const unstash = () => exec({ args: `add --all & git stash pop` }); 55 | 56 | // Create and checkout new branch 57 | export const branch = (branchName) => exec({ args: `checkout -b ${branchName}` }); 58 | -------------------------------------------------------------------------------- /src/cli/process.ts: -------------------------------------------------------------------------------- 1 | import child = require("promisify-child-process"); 2 | import { SpawnOptions } from "child_process"; 3 | 4 | import { resolve } from "path"; 5 | 6 | export class OradewProcess { 7 | wsConfigPath: string; 8 | gulpPathJs: string; 9 | gulpFile: string; 10 | gulpParams: Array; 11 | processEnv: SpawnOptions; 12 | cliPath: string; 13 | 14 | constructor(tmConfig: { 15 | workspaceDir: string; // Workspace folder 16 | cliDir: string; // CLI folder 17 | storageDir: string; // Storage folder 18 | dbConfigPath?: string; // ./dbconfig.json by default 19 | wsConfigPath?: string; // ./oradewrc.json by default 20 | isSilent?: boolean; //gulp option: --silent 21 | isColor?: boolean; //gulp option:--color 22 | cliExecutable: string; //DB CLI executable (ex. sqlplus) 23 | cliCommand: string; //DB CLI command literal 24 | envVariables?: { [id: string]: string }; // ENV vars (ex: "NLS_LANG": "AMERICAN_AMERICA.cp1252" ) 25 | }) { 26 | const { 27 | workspaceDir, 28 | cliDir, 29 | storageDir, 30 | wsConfigPath, 31 | dbConfigPath, 32 | isSilent, 33 | isColor, 34 | cliExecutable, 35 | cliCommand, 36 | envVariables, 37 | } = tmConfig; 38 | 39 | this.gulpPathJs = resolve(cliDir, "node_modules/gulp/bin/gulp.js"); 40 | this.gulpFile = resolve(cliDir, "dist/gulpfile.js"); 41 | 42 | this.cliPath = resolve(cliDir, "dist/oradew.js"); 43 | 44 | this.gulpParams = [ 45 | `${this.gulpPathJs}`, 46 | "--cwd", 47 | `${workspaceDir}`, 48 | "--gulpfile", 49 | `${this.gulpFile}`, 50 | // Set only when true, had some problems with false 51 | ...(isColor ? ["--color", "true"] : []), 52 | ...(isSilent ? ["--silent", "true"] : []), 53 | ]; 54 | 55 | this.processEnv = { 56 | env: { 57 | ...process.env, 58 | NODE_OPTIONS: "--no-deprecation", 59 | ORADEW_STORAGE_DIR: storageDir, 60 | ORADEW_DB_CONFIG_PATH: dbConfigPath, 61 | ORADEW_WS_CONFIG_PATH: wsConfigPath, 62 | ORADEW_CLI_EXECUTABLE: cliExecutable, 63 | ORADEW_CLI_COMMAND: cliCommand, 64 | ORADEW_SILENT: isSilent.toString(), 65 | ...(envVariables || {}), 66 | }, 67 | // inherit stdio 68 | stdio: "inherit", 69 | }; 70 | } 71 | 72 | execute = async (argv) => { 73 | const params = [...this.gulpParams, ...argv.slice(2)]; 74 | // console.log("Executing oradew task: " + params.join(" ")); 75 | 76 | // Execute process 77 | await child.spawn(process.execPath, params, this.processEnv); 78 | // ls.stdout.on("data", (data) => { 79 | // console.log(`stdout: ${data}`); 80 | // }); 81 | // ls.on("error", (data) => { 82 | // console.log(`error: ${data}`); 83 | // }); 84 | }; 85 | } 86 | -------------------------------------------------------------------------------- /src/cli/test/utility.test.ts: -------------------------------------------------------------------------------- 1 | const assert = require("assert"); 2 | import { includesCaseInsensitive } from "../common/utility"; 3 | import { WorkspaceConfig, getDefaultsFromSchema } from "../common/config"; 4 | 5 | const configDefault = new WorkspaceConfig(); 6 | const configCustomrc = new WorkspaceConfig(__dirname + "/resources/oradewrc.json"); 7 | 8 | const templateOradewrc = require(__dirname + "/resources/oradewrc.default.json"); 9 | 10 | import { properties as wsConfSchema } from "../schemas/oradewrc-schema.json"; 11 | 12 | describe("#Utility Default Config in ./", function () { 13 | it("should extract defaults from schema", function () { 14 | const defaults = getDefaultsFromSchema(wsConfSchema); 15 | assert.deepEqual(defaults, templateOradewrc); 16 | }); 17 | 18 | it("should compile.force be true by default", function () { 19 | let force = configDefault.get("compile.force"); 20 | assert.equal(force, true); 21 | }); 22 | it("should test.input be ... by default", function () { 23 | let testInput = configDefault.get("test.input"); 24 | assert.deepEqual(testInput, ["./test/**/*.test.sql"]); 25 | }); 26 | }); 27 | 28 | describe("#Utility Config", function () { 29 | it("should package.templating be false by default", function () { 30 | let tmp = configCustomrc.get("package.templating"); 31 | assert.equal(tmp, false); 32 | }); 33 | it("should compile.force be true from base config", function () { 34 | let force = configCustomrc.get("compile.force"); 35 | assert.equal(force, true); 36 | }); 37 | it("should get custom field from base config", function () { 38 | let custom = configCustomrc.get("customField"); 39 | assert.equal(custom, "this is it."); 40 | }); 41 | it("should get custom field from TEST config", function () { 42 | let custom = configCustomrc.get({ field: "customField", env: "TEST" }); 43 | assert.equal(custom, "no this."); 44 | }); 45 | it("should get compile.force from TEST, actually from base", function () { 46 | let custom = configCustomrc.get({ field: "compile.force", env: "TEST" }); 47 | assert.equal(custom, true); 48 | }); 49 | it("should get package.templating from TEST, actually from default", function () { 50 | let tmp = configCustomrc.get({ field: "package.templating", env: "TEST" }); 51 | assert.equal(tmp, false); 52 | }); 53 | }); 54 | 55 | describe("#includesCaseInsensitive", function () { 56 | it("should return whether array contains string - case insensitive", function () { 57 | assert.equal(includesCaseInsensitive(["A"], "a"), true); 58 | assert.equal(includesCaseInsensitive(["A", "b"], "a"), true); 59 | assert.equal(includesCaseInsensitive(["A", "b"], "A"), true); 60 | assert.equal(includesCaseInsensitive(["A"], "1"), false); 61 | assert.equal(includesCaseInsensitive([], "1"), false); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | "use strict"; 4 | 5 | const path = require("path"); 6 | const CopyPlugin = require("copy-webpack-plugin"); 7 | const { CleanWebpackPlugin } = require("clean-webpack-plugin"); 8 | 9 | /**@type {import('webpack').Configuration}*/ 10 | const config = { 11 | target: "node", // vscode extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/ 12 | // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/ 13 | entry: { 14 | cli: "./src/cli/cli.ts", 15 | gulpfile: "./src/cli/gulpfile.ts", 16 | extension: "./src/extension/extension.ts", 17 | }, 18 | output: { 19 | // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/ 20 | path: path.resolve(__dirname, "dist"), 21 | filename: "[name].js", 22 | libraryTarget: "commonjs2", 23 | devtoolModuleFilenameTemplate: "../[resource-path]", 24 | }, 25 | node: { 26 | // __dirname: true, // leave the __dirname behavior intact 27 | __dirname: false, 28 | __filename: false, 29 | }, 30 | devtool: "source-map", 31 | externals: { 32 | vscode: "commonjs vscode", // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/ 33 | oracledb: "commonjs oracledb", 34 | gulp: "commonjs2 gulp", 35 | // @todo move them to internal or replace 36 | "gulp-git": "commonjs2 gulp-git", 37 | "gulp-todo": "commonjs2 gulp-todo", 38 | }, 39 | resolve: { 40 | // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader 41 | extensions: [".ts", ".js", ".json"], 42 | alias: { 43 | "@Cli": path.resolve(__dirname, "src/cli/"), 44 | }, 45 | }, 46 | plugins: [ 47 | new CleanWebpackPlugin(), 48 | new CopyPlugin({ 49 | patterns: [ 50 | { from: "src/cli/schemas", to: "schemas" }, 51 | { from: "src/cli/templates", to: "templates" }, 52 | { from: "src/cli/oradew.js", to: "oradew.js" }, 53 | { from: "src/extension/images", to: "images" }, 54 | // { 55 | // from: "lib/versioned/**", 56 | // to: "", 57 | // context: "node_modules/gulp-cli/", 58 | // }, 59 | ], 60 | }), 61 | ], 62 | module: { 63 | rules: [ 64 | { 65 | test: /\.ts$/, 66 | exclude: /node_modules/, 67 | use: [ 68 | { 69 | loader: "ts-loader", 70 | // options: { 71 | // compilerOptions: { 72 | // "module": "es6", // override `tsconfig.json` so that TypeScript emits native JavaScript modules. 73 | // }, 74 | // }, 75 | }, 76 | ], 77 | }, 78 | ], 79 | }, 80 | optimization: { 81 | minimize: false, 82 | }, 83 | // stats: { 84 | // // Ignore warnings due to yarg's dynamic module loading 85 | // warningsFilter: [/node_modules\/yargs/], 86 | 87 | // }, 88 | // stats: "errors-only", 89 | }; 90 | 91 | module.exports = config; 92 | -------------------------------------------------------------------------------- /src/cli/tasks/run.ts: -------------------------------------------------------------------------------- 1 | import { argv } from "yargs"; 2 | import * as fs from "fs-extra"; 3 | import * as path from "path"; 4 | import { pipe, replace, trim } from "lodash/fp"; 5 | import { decode } from "iconv-lite"; 6 | 7 | import { parseForErrors } from "../common/db"; 8 | import { runFileAsScript } from "../common/base"; 9 | import { getLogFilename, printResults } from "../common/utility"; 10 | import { workspaceConfig as config } from "../common/config"; 11 | import { matchOutputFiles } from "../common/dbobject"; 12 | 13 | export async function runTask({ 14 | file = argv.file, 15 | env = argv.env || "DEV", 16 | user = argv.user as string, 17 | }) { 18 | // Convert to array as parameters can be arrays (--file a --file b) 19 | let filesToRun = file && [].concat(file); 20 | 21 | // Match file from package.output pattern if no --file 22 | if (!filesToRun) { 23 | const output = config.get({ field: "package.output", env }); 24 | filesToRun = matchOutputFiles(output); 25 | } 26 | 27 | if (filesToRun.length !== 1) { 28 | console.log(`Multiple or none scripts detected: ${filesToRun}`); 29 | console.log(`Use "--file" parameter to run a script.`); 30 | return; 31 | } 32 | 33 | const filePath = path.resolve(filesToRun[0]); 34 | 35 | if (!fs.existsSync(filePath)) { 36 | console.log(`File does not exist: ${filePath}`); 37 | console.log(`Use "Package" command to create a deployment script.`); 38 | return; 39 | } 40 | 41 | const outputFileName = path.basename(filePath); 42 | const outputDirectory = path.dirname(filePath); 43 | 44 | // Default log file that packaged scripts spools to 45 | const logPath = path.join(outputDirectory, getLogFilename(outputFileName)); 46 | 47 | // Append 'env' to the log's filename to differentiate beetwen logs 48 | const logPathEnv = path.join(outputDirectory, getLogFilename(`${outputFileName}-${env}`)); 49 | 50 | // Simple output err colorizer 51 | const sanitize = (text) => 52 | pipe( 53 | // Remove carriage returns 54 | replace(/\r\n/g, "\n"), 55 | // Remove double new-lines 56 | replace(/(\n\r)+/g, "\n"), 57 | trim 58 | // Color red to the line that contains ERROR 59 | // replace(/^.*ERROR.*$/gm, chalk.red("$&")), 60 | // Color orange to the line that contains Warning 61 | // replace(/^.*Warning:.*$/gm, chalk.yellow("$&")) 62 | )(text); 63 | 64 | try { 65 | const { stdout, obj } = await runFileAsScript(filePath, env, user); 66 | 67 | const srcEncoding = config.get({ field: "source.encoding", env }); 68 | const decoded = decode(stdout as Buffer, srcEncoding); 69 | const out = sanitize(decoded); 70 | 71 | const errors = parseForErrors(out); 72 | 73 | // Prints errors in problem matcher format (one error per line) 74 | printResults({ errors, obj, env, file: filePath }); 75 | 76 | // Outputs stdout 77 | console.log("=============================== STDOUT ==============================="); 78 | console.log(out); 79 | 80 | // Add env suffix to log file if it exists 81 | if (fs.existsSync(logPath)) { 82 | fs.renameSync(logPath, logPathEnv); 83 | } 84 | } catch (error) { 85 | console.error(`${error.message}`); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/cli/common/utility.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "path"; 2 | import { outputFile } from "fs-extra"; 3 | import { exec } from "child_process"; 4 | 5 | import chalk from "chalk"; 6 | import * as Table from "cli-table"; 7 | 8 | const promisify = (func) => (...args) => 9 | new Promise((resolve, reject) => 10 | func(...args, (err, result) => (err ? reject(err) : resolve(result))) 11 | ); 12 | 13 | export const execPromise = promisify(exec); 14 | export const outputFilePromise = promisify(outputFile); 15 | 16 | export const removeNewlines = (str) => str.replace(/\r\n|\r|\n/gi, " "); 17 | 18 | // alternative /\r?\n/ 19 | export const splitLines = (str) => str.split(/\r\n|\r|\n/); 20 | 21 | // Conditionally prepends char if it doesn't starts already 22 | // prependCheck("a")("aabc") => 'aabc' 23 | // prependCheck("a")("bbc") => 'abbc' 24 | export const prependCheck = (val) => (str) => (str.startsWith(val) ? str : `${val}${str}`); 25 | 26 | // Add ./ if it doesn't already exists 27 | export const rootPrepend = prependCheck("./"); 28 | // Remove ./ from path 29 | export const rootRemove = (str) => str.replace(/\.\//, ""); 30 | 31 | /** 32 | * Includes - case insensitive. 33 | * * arr includes str? 34 | * @param {array} arr 35 | * @param {string} str 36 | * @returns {boolean} 37 | */ 38 | export const includesCaseInsensitive = (arr, str) => { 39 | let upp = arr.map((v) => v.toUpperCase()); 40 | return upp.includes(str.toUpperCase()); 41 | }; 42 | 43 | /** 44 | * Includes paths - absolute or relative. 45 | * * arrPaths includes path? 46 | * @param {array} arrPaths 47 | * @param {string} path 48 | * @returns {boolean} 49 | */ 50 | export const includesPaths = (arrPaths, path) => { 51 | let absPaths = arrPaths.map((p) => resolve(p)); 52 | let absPath = resolve(path); 53 | return absPaths.includes(absPath); 54 | }; 55 | 56 | export const getLogFilename = (filename) => `spool__${filename}.log`; 57 | 58 | export const printResults = (resp) => { 59 | // Print column names and rows data 60 | if (resp.result) { 61 | let rows = resp.result.rows; 62 | if (rows) { 63 | // Replace null values with '(null)' 64 | rows = rows.map((r) => r.map((v) => (v === null ? "(null)" : v))); 65 | const table = new Table({ 66 | head: resp.result.metaData.map((col) => col.name), 67 | style: { head: ["cyan"] }, 68 | }); 69 | table.push(...rows); 70 | console.log(table.toString()); 71 | } 72 | // Print affected rows 73 | if (resp.result.rowsAffected) { 74 | console.log( 75 | // chalk.magenta( 76 | `${resp.result.rowsAffected} ${resp.result.rowsAffected === 1 ? "row" : "rows"} affected.` 77 | 78 | // ) 79 | ); 80 | } 81 | } 82 | // Print dbms output 83 | if (resp.lines && resp.lines.length !== 0) { 84 | console.log(chalk.blue(resp.lines.join("\n"))); 85 | } 86 | 87 | // Generate status msg 88 | const status = resp.errors.hasErrors() ? chalk.bgRed("Failure") : chalk.green("Success"); 89 | console.log(`${status} => ${resp.obj.owner}@${resp.env} $${resp.file}`); 90 | // Concat errors to problem matcher format 91 | const errMsg = resp.errors.toString(); 92 | if (errMsg) { 93 | console.log(`${errMsg}`); 94 | } 95 | }; 96 | -------------------------------------------------------------------------------- /src/cli/test/db.test.ts: -------------------------------------------------------------------------------- 1 | const assert = require("assert"); 2 | 3 | import { getDefaultsFromSchema, dbConfig, DBConfig } from "../common/config"; 4 | 5 | const templateDbconfig = require(__dirname + "/resources/dbconfig.default.json"); 6 | 7 | describe("#db getConfiguration", function () { 8 | it("should return default cfg", function () { 9 | let cfg = { 10 | env: "DEV", 11 | connectString: "localhost/orclpdb", 12 | user: "hr", 13 | password: "welcome", 14 | }; 15 | let cfgNonUser = dbConfig.getConfiguration("DEV", "XXX"); 16 | assert.deepEqual(cfgNonUser, cfg); 17 | 18 | // No user parameter 19 | let cfgNullUser = dbConfig.getConfiguration("DEV"); 20 | assert.deepEqual(cfgNullUser, cfg); 21 | 22 | // Also empty user parameter should be same result 23 | let cfgEmptyUser = dbConfig.getConfiguration("DEV", ""); 24 | assert.deepEqual(cfgEmptyUser, cfg); 25 | }); 26 | }); 27 | 28 | import { properties as dbConfSchema } from "../schemas/dbconfig-schema.json"; 29 | 30 | describe("#DBConfig", function () { 31 | const dbconfig = dbConfig; 32 | const defaults = getDefaultsFromSchema(dbConfSchema); 33 | 34 | it("should extract defaults from schema", function () { 35 | assert.deepEqual(defaults, templateDbconfig); 36 | }); 37 | 38 | it("should get defaults from dbConfigInstance", function () { 39 | assert.deepEqual(dbconfig.object, templateDbconfig); 40 | }); 41 | 42 | it("should return all distinct dev users", function () { 43 | let users = dbconfig.getSchemas(); 44 | assert.deepEqual(users, ["HR"]); 45 | }); 46 | 47 | // it("should return connectString", function() { 48 | // let connectString = dbconfig.getConnectString("DEV"); 49 | // assert.deepEqual(connectString, defaults.DEV.connectString); 50 | // }); 51 | 52 | // it("should return usersObjects", function() { 53 | // let connectString = dbconfig.getUserObjects("DEV"); 54 | // assert.deepEqual(connectString, defaults.DEV.users); 55 | // }); 56 | 57 | let cfgDefault = { 58 | env: "DEV", 59 | connectString: "oradew", 60 | user: "dev1", 61 | password: "welcome1", 62 | default: true, 63 | disabled: false, 64 | }; 65 | 66 | let cfgUser = { 67 | env: "DEV", 68 | connectString: "oradew", 69 | user: "dev", 70 | password: "welcome", 71 | schemas: ["schema1", "schema2"], 72 | }; 73 | 74 | const dbConfigInstance1 = new DBConfig("./src/cli/test/resources/dbconfig.json"); 75 | 76 | it("should get all schemas from Custom file", function () { 77 | let users = dbConfigInstance1.getSchemas(); 78 | assert.deepEqual(users, ["SCHEMA1", "SCHEMA2", "DEV1"]); 79 | }); 80 | 81 | it("should get default different configurations", function () { 82 | // get default, null user 83 | let cfgDefaultActual = dbConfigInstance1.getConfiguration("DEV"); 84 | assert.deepEqual(cfgDefaultActual, cfgDefault); 85 | 86 | // get default, non existing user 87 | cfgDefaultActual = dbConfigInstance1.getConfiguration("DEV", "bla"); 88 | assert.deepEqual(cfgDefaultActual, cfgDefault); 89 | 90 | // get default, disabled user 91 | let cfgUserActualDisabled = dbConfigInstance1.getConfiguration("DEV", "dev2"); 92 | assert.deepEqual(cfgUserActualDisabled, cfgDefault); 93 | 94 | // get configuration by existing user 95 | let cfgUserActual = dbConfigInstance1.getConfiguration("DEV", "dev"); 96 | assert.deepEqual(cfgUserActual, cfgUser); 97 | 98 | // get configuration by schema - case insensitive! 99 | let cfgSchemaActual = dbConfigInstance1.getConfiguration("DEV", "schEma2"); 100 | assert.deepEqual(cfgSchemaActual, cfgUser); 101 | }); 102 | }); 103 | -------------------------------------------------------------------------------- /src/cli/tasks/import.ts: -------------------------------------------------------------------------------- 1 | import * as map2 from "vinyl-map2"; 2 | import { argv } from "yargs"; 3 | import * as gulp from "gulp"; 4 | 5 | import * as fs from "fs-extra"; 6 | import * as path from "path"; 7 | import chalk from "chalk"; 8 | import * as convertEncoding from "gulp-convert-encoding"; 9 | 10 | import { workspaceConfig as config } from "../common/config"; 11 | import { getOnlyChangedFiles } from "../common/globs"; 12 | import { exportFile, resolveObjectInfo } from "../common/base"; 13 | import { getPathFromObjectInfo } from "../common/dbobject"; 14 | 15 | export const exportFilesFromDbAsync = async ({ 16 | file, 17 | env, 18 | changed = false, 19 | ease = false, 20 | quiet = false, 21 | }) => 22 | new Promise(async (res, rej) => { 23 | const p = await exportFilesFromDb({ file, env, changed, ease, quiet }); 24 | p.on("end", res); 25 | p.on("error", rej); 26 | }); 27 | 28 | const exportFilesFromDb = async ({ 29 | file = argv.file, 30 | env = argv.env || "DEV", 31 | changed = argv.changed || false, 32 | ease = argv.ease || false, 33 | quiet = argv.quiet || false, 34 | }) => { 35 | const source = config.get({ field: "source.input", env }); 36 | const src = file || (changed ? await getOnlyChangedFiles(source) : source); 37 | const getFunctionName = config.get({ 38 | field: "import.getDdlFunction", 39 | env, 40 | }); 41 | const encoding = config.get({ field: "source.encoding", env }); 42 | 43 | const processFile = async (code, file, done) => { 44 | let res; 45 | try { 46 | res = await exportFile(code, file, env, ease, getFunctionName, done); 47 | if (!quiet && res.exported) { 48 | console.log(`${chalk.green("Imported")} <= ${res.obj.owner}@${env} $${file}`); 49 | } 50 | } catch (error) { 51 | console.error(error.message); 52 | } 53 | }; 54 | 55 | // gulp4 rejects empty src 56 | if (src.length === 0) { 57 | src.push("nonvalidfile"); 58 | } 59 | return gulp 60 | .src(src, { base: "./", allowEmpty: true }) 61 | .pipe(convertEncoding({ from: encoding })) // convert first to utf8, as code is passed allong if not exported from db (--ease) 62 | .pipe(map2(processFile)) 63 | .pipe(convertEncoding({ to: encoding })) 64 | .pipe(gulp.dest(".")); 65 | 66 | // .on('end', () => ((!quiet) && console.log('Done.'))) 67 | }; 68 | 69 | const exportObjectFromDb = async ({ 70 | env = argv.env || "DEV", 71 | object = argv.object, 72 | user = argv.user, 73 | file = argv.file, 74 | }) => { 75 | try { 76 | if (!object) { 77 | throw Error("Object cannot be empty."); 78 | } 79 | 80 | const objs = await resolveObjectInfo(env, object, user as string, file); 81 | 82 | // Create array of abs file paths 83 | let files = objs.map((obj) => { 84 | const relativePath = getPathFromObjectInfo(obj.OWNER, obj.OBJECT_TYPE, obj.OBJECT_NAME); 85 | return path.resolve(relativePath); 86 | }); 87 | 88 | // Import files 89 | files.forEach((file) => fs.outputFileSync(file, "")); 90 | await exportFilesFromDbAsync({ file: files, env }); 91 | } catch (err) { 92 | console.error(err.message); 93 | } 94 | }; 95 | 96 | export function importTask({ 97 | env = argv.env || "DEV", 98 | file = argv.file, 99 | changed = (argv.changed as boolean) || false, 100 | ease = argv.ease, 101 | quiet = (argv.quiet as boolean) || false, 102 | object = argv.object, 103 | }) { 104 | // ease is a string 'true' or 'false' in parameter 105 | let s_ease = ease || config.get({ field: "import.ease", env }); 106 | let b_ease = s_ease.toString() === "true"; 107 | if (object) { 108 | return exportObjectFromDb({ env, object }); 109 | } else { 110 | return exportFilesFromDbAsync({ file, env, changed, ease: b_ease, quiet }); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/cli/schemas/dbconfig-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "JSON schema for Oradew dbconfig file", 3 | "$schema": "http://json-schema.org/draft-06/schema#", 4 | "type": "object", 5 | "properties": { 6 | "DEV": { 7 | "type": "object", 8 | "$ref": "#/definitions/ConnectionConfig", 9 | "default": { 10 | "connectString": "localhost/orclpdb", 11 | "users": [ 12 | { 13 | "user": "hr", 14 | "password": "welcome" 15 | } 16 | ] 17 | } 18 | }, 19 | "TEST": { 20 | "type": "object", 21 | "$ref": "#/definitions/ConnectionConfig", 22 | "default": { 23 | "connectString": "localhost/orclpdb", 24 | "users": [ 25 | { 26 | "user": "hr", 27 | "password": "welcome" 28 | } 29 | ] 30 | } 31 | }, 32 | "UAT": { 33 | "type": "object", 34 | "$ref": "#/definitions/ConnectionConfig", 35 | "default": { 36 | "connectString": "localhost/orclpdb", 37 | "users": [ 38 | { 39 | "user": "hr", 40 | "password": "welcome" 41 | } 42 | ] 43 | } 44 | } 45 | }, 46 | "definitions": { 47 | "ConnectionConfig": { 48 | "type": "object", 49 | "properties": { 50 | "connectString": { 51 | "type": "string", 52 | "description": "The Oracle database instance. \nThe string can be an Easy Connect string, a Net Service Name from a tnsnames.ora file, or the name of a local Oracle Database instance." 53 | }, 54 | "users": { 55 | "type": "array", 56 | "description": "Array of database user objects", 57 | "items": { 58 | "type": "object", 59 | "properties": { 60 | "user": { 61 | "type": "string", 62 | "description": "Database user id" 63 | }, 64 | "password": { 65 | "type": "string", 66 | "description": "Password to be used for connecting" 67 | }, 68 | "walletConnectString": { 69 | "type": "string", 70 | "description": "The Oracle database instance. \nThe string can be an Easy Connect string, a Net Service Name from a tnsnames.ora file, or the name of a local Oracle Database instance. Please specify connectString at user level if you have connection credentials stored in an oracle wallet. The password will be optional if you have an auto-login wallet. Specify schema name as user." 71 | }, 72 | "askForPassword": { 73 | "description": "Specify if password should be typed when executing command instead of stored in this file", 74 | "type": "boolean", 75 | "default": false 76 | }, 77 | "schemas": { 78 | "description": "User schema array. \nIf user doesn't own any object, at least one schema must be specified.", 79 | "type": "array" 80 | }, 81 | "default": { 82 | "description": "Default user for environment? \nWhenever user can't be extracted from path, default user configuration (\"default\": true) will be used for connection. (ex. for deploy)", 83 | "type": "boolean", 84 | "default": false 85 | }, 86 | "disabled": { 87 | "description": "User disabled? \nDisabled user is not available for connection.", 88 | "type": "boolean", 89 | "default": false 90 | } 91 | }, 92 | "required": [ 93 | "user" 94 | ], 95 | "default": { 96 | "user": "hr", 97 | "password": "welcome" 98 | } 99 | } 100 | } 101 | }, 102 | "required": [ 103 | "users" 104 | ] 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /src/extension/controllers/environment-controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | QuickPickItem, 3 | window, 4 | StatusBarAlignment, 5 | StatusBarItem, 6 | QuickPickOptions, 7 | ExtensionContext, 8 | } from "vscode"; 9 | import { ConfigurationManager } from "../common/configuration-manager"; 10 | import { Telemetry } from "../common/telemetry"; 11 | 12 | import { readJson, existsSync } from "fs-extra"; 13 | 14 | export class EnvironmentController { 15 | private static readonly NONE: QuickPickItem = { 16 | label: "", 17 | description: "Select environment when executing command", 18 | }; 19 | private _context: ExtensionContext; 20 | private _dbConfigPath: string; 21 | private static _statusBar: StatusBarItem; 22 | 23 | // This is saved for use by commands 24 | public get currentEnvironment(): string { 25 | return this._context.workspaceState.get("currEnv", "DEV"); 26 | } 27 | public set currentEnvironment(env: string) { 28 | this.currentPick = env; 29 | this._context.workspaceState.update("currEnv", env); 30 | } 31 | 32 | // This is temporary. used for 'getDBuser' command in process of single task execution 33 | // to pass parameter between env and user commands 34 | public currentPick: string; 35 | 36 | public constructor(context: ExtensionContext) { 37 | this._context = context; 38 | this._dbConfigPath = ConfigurationManager.getInstance().databaseConfigFile; 39 | EnvironmentController._statusBar = window.createStatusBarItem(StatusBarAlignment.Left, 10); 40 | EnvironmentController._statusBar.tooltip = "Oradew: Set DB Environment"; 41 | EnvironmentController._statusBar.command = `oradew.setDbEnvironment`; 42 | this.updateStatusBar(); 43 | this.currentPick = this.currentEnvironment; 44 | } 45 | 46 | private updateStatusBar = () => { 47 | EnvironmentController._statusBar.text = `$(gear) Env: ${this.currentEnvironment}`; 48 | if (existsSync(this._dbConfigPath)) { 49 | EnvironmentController._statusBar.show(); 50 | } else { 51 | EnvironmentController._statusBar.hide(); 52 | } 53 | }; 54 | 55 | // Create environment pick list from dbconfig file 56 | private createEnvironmentList = (): QuickPickItem[] => { 57 | return readJson(this._dbConfigPath).then((config) => { 58 | return Object.keys(config).reduce((acc, value) => { 59 | return [ 60 | ...acc, 61 | { 62 | label: value, 63 | description: config[value].connectString, 64 | picked: this.currentEnvironment === value, 65 | }, 66 | ]; 67 | }, []); 68 | }) as any; 69 | }; 70 | 71 | // Environment picker 72 | public pickEnvironment = async (addNoneOption?: boolean): Promise => { 73 | let envs: QuickPickItem[] = await this.createEnvironmentList(); 74 | const options: QuickPickOptions = { 75 | placeHolder: "Select DB environment for executing command", 76 | matchOnDescription: true, 77 | matchOnDetail: true, 78 | }; 79 | if (addNoneOption === true) { 80 | envs.push(EnvironmentController.NONE); 81 | } 82 | return window.showQuickPick(envs, options).then((item) => { 83 | if (item) { 84 | this.currentPick = item.label; 85 | return this.currentPick; 86 | } else { 87 | return null; 88 | } 89 | }); 90 | }; 91 | 92 | // Returns "currentEnvironment" if it is set, otherwise let's you pick from the list 93 | public getEnvironment = async (): Promise => { 94 | if (this.currentEnvironment === EnvironmentController.NONE.label) { 95 | return this.pickEnvironment(false); 96 | } else { 97 | return this.currentEnvironment; 98 | } 99 | }; 100 | 101 | public setDbEnvironment = async (): Promise => { 102 | const pickEnv = await this.pickEnvironment(true); 103 | if (pickEnv) { 104 | this.currentEnvironment = pickEnv; 105 | this.updateStatusBar(); 106 | } 107 | Telemetry.sendEvent("setDbEnvironment", { env: pickEnv }); 108 | }; 109 | 110 | public clearDbEnvironment = async (): Promise => { 111 | this.currentEnvironment = EnvironmentController.NONE.label; 112 | this.updateStatusBar(); 113 | Telemetry.sendEvent("clearDbEnvironment"); 114 | }; 115 | 116 | public dispose() { 117 | EnvironmentController._statusBar.dispose(); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/extension/controllers/user-controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | QuickPickItem, 3 | window, 4 | StatusBarAlignment, 5 | StatusBarItem, 6 | QuickPickOptions, 7 | ExtensionContext, 8 | } from "vscode"; 9 | import { readJson, existsSync } from "fs-extra"; 10 | 11 | import { ConfigurationManager } from "../common/configuration-manager"; 12 | import { Telemetry } from "../common/telemetry"; 13 | import { EnvironmentController } from "./environment-controller"; 14 | 15 | export class UserController { 16 | private static readonly NONE: QuickPickItem = { 17 | label: "", 18 | description: "Select user when executing command", 19 | }; 20 | private static readonly AUTO: QuickPickItem = { 21 | label: "", 22 | description: "User will be extracted from file path", 23 | }; 24 | private _context: ExtensionContext; 25 | private _dbConfigPath: string; 26 | private _environmentController: EnvironmentController; 27 | private static _statusBar: StatusBarItem; 28 | private get currentUser(): string { 29 | return this._context.workspaceState.get("currUser", UserController.AUTO.label); 30 | } 31 | private set currentUser(user: string) { 32 | this._context.workspaceState.update("currUser", user); 33 | } 34 | 35 | public constructor(context: ExtensionContext, environmentController: EnvironmentController) { 36 | this._context = context; 37 | this._dbConfigPath = ConfigurationManager.getInstance().databaseConfigFile; 38 | this._environmentController = environmentController; 39 | UserController._statusBar = window.createStatusBarItem(StatusBarAlignment.Left, 9); 40 | UserController._statusBar.tooltip = "Oradew: Set DB User"; 41 | UserController._statusBar.command = `oradew.setDbUser`; 42 | this.updateStatusBar(); 43 | } 44 | 45 | private updateStatusBar = () => { 46 | UserController._statusBar.text = `User: ${this.currentUser}`; 47 | if (existsSync(this._dbConfigPath)) { 48 | UserController._statusBar.show(); 49 | } else { 50 | UserController._statusBar.hide(); 51 | } 52 | }; 53 | 54 | // Create user pick list from dbconfig file 55 | private createUserList = (env): QuickPickItem[] => { 56 | return readJson(this._dbConfigPath).then((config) => { 57 | const users = config[env]?.users.filter((val) => !val.disabled) || []; 58 | return users.map((value) => ({ 59 | label: value.user.toUpperCase(), 60 | description: `${value.user}@${config[env].connectString}`, 61 | picked: this.currentUser === value.user, 62 | })); 63 | }); 64 | }; 65 | 66 | // User picker 67 | // addNoneOption = true is called from set command (status bar) 68 | public pickUser = async (addNoneOption?: boolean): Promise => { 69 | let currEnv; 70 | if (addNoneOption === true) { 71 | currEnv = this._environmentController.currentEnvironment; 72 | } else { 73 | currEnv = this._environmentController.currentPick; 74 | } 75 | let envs: QuickPickItem[] = await this.createUserList(currEnv); 76 | const options: QuickPickOptions = { 77 | placeHolder: "Select DB user for executing command", 78 | matchOnDescription: true, 79 | matchOnDetail: true, 80 | }; 81 | envs.push(UserController.AUTO); 82 | if (addNoneOption === true) { 83 | envs.push(UserController.NONE); 84 | } 85 | return window.showQuickPick(envs, options).then((item) => (item ? item.label : null)); 86 | }; 87 | 88 | // Returns "currentUser" if it is set, otherwise let's you pick from the list 89 | public getUser = async (): Promise => { 90 | if (this.currentUser === UserController.NONE.label) { 91 | return this.pickUser(false); 92 | } else { 93 | return this.currentUser; 94 | } 95 | }; 96 | 97 | public setDbUser = async (): Promise => { 98 | const pickedUser = await this.pickUser(true); 99 | if (pickedUser) { 100 | this.currentUser = pickedUser; 101 | this.updateStatusBar(); 102 | } 103 | Telemetry.sendEvent("setDbUser", { user: pickedUser }); 104 | }; 105 | 106 | public clearDbUser = async (): Promise => { 107 | this.currentUser = UserController.NONE.label; 108 | this.updateStatusBar(); 109 | Telemetry.sendEvent("clearDbUser"); 110 | }; 111 | 112 | public dispose() { 113 | UserController._statusBar.dispose(); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, 4 | "target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */, 5 | "outDir": "out" /* Redirect output structure to the directory. */, 6 | "lib": ["es6"] /* Specify library files to be included in the compilation. */, 7 | "sourceMap": true /* Generates corresponding '.map' file. */, 8 | // "rootDir": "src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, 9 | "noImplicitAny": false /* Raise error on expressions and declarations with an implied 'any' type. */, 10 | "noUnusedLocals": true /* Report errors on unused locals. */, 11 | "allowJs": true /* Allow javascript files to be compiled. */, 12 | "plugins": [ 13 | { 14 | "name": "typescript-tslint-plugin" 15 | } 16 | ], 17 | "resolveJsonModule": true, 18 | "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */, 19 | // "checkJs": true, /* Report errors in .js files. */ 20 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 21 | // "declaration": true /* Generates corresponding '.d.ts' file. */, 22 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 23 | // "outFile": "./", /* Concatenate and emit output to single file. */ 24 | // "composite": true, /* Enable project compilation */ 25 | // "removeComments": true, /* Do not emit comments to output. */ 26 | // "noEmit": true, /* Do not emit outputs. */ 27 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 28 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 29 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 30 | /* Strict Type-Checking Options */ 31 | // "strict": true /* Enable all strict type-checking options. */, 32 | // "strictNullChecks": true, /* Enable strict null checks. */ 33 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 34 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 35 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 36 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 37 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 38 | /* Additional Checks */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | /* Module Resolution Options */ 43 | // "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, 44 | "baseUrl": "./" /* Base directory to resolve non-absolute module names. */, 45 | "paths": { 46 | "@Cli/*": ["src/cli/*"] 47 | } /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 48 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 49 | // "typeRoots": [], /* List of folders to include type definitions from. */ 50 | // "types": ["jasmine"], /* Type declaration files to be included in compilation. */ 51 | // "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 52 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 53 | /* Source Map Options */ 54 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 55 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 56 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 57 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 58 | /* Experimental Options */ 59 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 60 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 61 | }, 62 | // "exclude": ["node_modules", ".vscode-test", "./out", "./scripts", "./dist", "webpack.config.js"] 63 | "include": ["src/**/*.ts", "./node_modules/vscode/vscode.d.ts", "./node_modules/vscode/lib/*"] 64 | } 65 | -------------------------------------------------------------------------------- /src/cli/tasks/init.ts: -------------------------------------------------------------------------------- 1 | const gulp = require("gulp"); 2 | import { argv } from "yargs"; 3 | import * as inquirer from "inquirer"; 4 | import * as multiDest from "gulp-multi-dest"; 5 | import del from "del"; 6 | import * as fs from "fs-extra"; 7 | import * as path from "path"; 8 | 9 | import * as git from "../common/git"; 10 | import { workspaceConfig as config } from "../common/config"; 11 | import { getSourceStructure, replaceVarsInPattern } from "../common/dbobject"; 12 | import { dbConfig } from "../common/config"; 13 | 14 | const createSrcEmpty = (done) => { 15 | try { 16 | const schemas = dbConfig.getSchemas(); 17 | const dirs = getSourceStructure(); 18 | for (const owner of schemas) { 19 | dirs.forEach((pattern) => { 20 | const dirPath = replaceVarsInPattern(pattern, owner); 21 | return fs.ensureDirSync(dirPath); 22 | }); 23 | } 24 | done(); 25 | 26 | // console.log(chalk.green("Src empty structure created.")); 27 | } catch (err) { 28 | console.error(err); 29 | } 30 | }; 31 | 32 | const createDbConfigFile = async ({}) => { 33 | // Create db config file if it doesn't exists already... 34 | if (!fs.existsSync(dbConfig.fileBase as string)) { 35 | dbConfig.createFile(); 36 | 37 | console.log( 38 | `This utility will walk you through creating a dbconfig.json file. 39 | It only covers basic items for DB connection for one environment (DEV). 40 | You can edit, add DB environments or users later. 41 | 42 | Press ^C at any time to quit or enter to skip.` 43 | ); 44 | 45 | let res = await inquirer.prompt([ 46 | { 47 | type: "input", 48 | name: "connectString", 49 | message: "Connection string?", 50 | }, 51 | { 52 | type: "input", 53 | name: "user", 54 | message: "Username?", 55 | }, 56 | { 57 | type: "input", 58 | name: "password", 59 | message: "Password?", 60 | }, 61 | ]); 62 | // Save prompts to config file, leave defaults if empty 63 | dbConfig.set("DEV.connectString", res.connectString || dbConfig.get("DEV.connectString")); 64 | dbConfig.set("DEV.users[0].user", res.user || dbConfig.get("DEV.users[0].user")); 65 | dbConfig.set("DEV.users[0].password", res.password || dbConfig.get("DEV.users[0].password")); 66 | console.log(`${dbConfig.fileBase} updated.`); 67 | } 68 | }; 69 | 70 | const createProjectFiles = ({ env = argv.env || "DEV" }) => { 71 | // Create scripts dir for every user 72 | // and copy scripts templates 73 | dbConfig.load(); 74 | const schemas = dbConfig.getSchemas(env as string); 75 | const scriptsDirs = schemas.map((v) => `./scripts/${v}`); 76 | gulp 77 | .src([ 78 | path.join(__dirname, "/templates/scripts/initial*.sql"), 79 | path.join(__dirname, "/templates/scripts/final*.sql"), 80 | ]) 81 | .pipe(multiDest(scriptsDirs)); 82 | 83 | // Array of test directoris with schema in path, if it don't already exists 84 | const testsDirs = schemas.filter((v) => !fs.existsSync(`./test/${v}`)).map((v) => `./test/${v}`); 85 | 86 | gulp.src([path.join(__dirname, "/templates/test/*.test.sql")]).pipe(multiDest(testsDirs)); 87 | 88 | let src = []; 89 | if (!fs.existsSync("./.gitignore")) { 90 | src.push(path.join(__dirname, "/templates/.gitignore")); 91 | } 92 | 93 | if (src.length === 0) { 94 | src.push("nonvalidfile"); 95 | } 96 | return gulp 97 | .src(src, { 98 | allowEmpty: true, 99 | base: path.join(__dirname, "/templates/"), 100 | }) 101 | .pipe(gulp.dest(".")) 102 | .on("end", () => { 103 | if (schemas === null || schemas === undefined || schemas.length === 0) { 104 | console.error("Workspace could not be initialized, please check dbconfig.json file."); 105 | throw new Error("Workspace could not be initialized, please check dbconfig.json file."); 106 | } else { 107 | console.log(`Workspace structure initialized for user(s): ${schemas}`); 108 | } 109 | }); 110 | }; 111 | 112 | const cleanProject = () => { 113 | // Delete temp directories 114 | return del(["./scripts/*", "./deploy/*", "./**/*.log"]).then((rDel) => { 115 | if (rDel.length !== 0) { 116 | console.log("Workspace cleaned."); 117 | } 118 | }); 119 | }; 120 | 121 | const initGit = async ({}) => { 122 | let isInitialized; 123 | try { 124 | isInitialized = await git.exec({ 125 | args: "rev-parse --is-inside-work-tree", 126 | }); 127 | } catch (error) { 128 | isInitialized = false; 129 | } 130 | 131 | if (!isInitialized) { 132 | let answer = await inquirer.prompt({ 133 | type: "confirm", 134 | name: "repo", 135 | message: `Initialize git repository?`, 136 | default: true, 137 | }); 138 | 139 | if (answer.repo) { 140 | await git.exec({ args: "init" }); 141 | console.log("Repository initialized."); 142 | } 143 | } 144 | }; 145 | 146 | const initConfigFile = async ({}) => { 147 | let answer = await inquirer.prompt({ 148 | type: "confirm", 149 | name: "ws", 150 | message: `Do you want to edit oradewrc.json file?`, 151 | default: false, 152 | }); 153 | if (!answer.ws) { 154 | return; 155 | } 156 | let res = await inquirer.prompt([ 157 | { 158 | type: "input", 159 | name: "number", 160 | message: "Version number [major.minor.patch]?", 161 | }, 162 | { 163 | type: "input", 164 | name: "description", 165 | message: "Version description?", 166 | }, 167 | { 168 | type: "input", 169 | name: "releaseDate", 170 | message: "Version release date [YYYY-MM-DD]?", 171 | }, 172 | ]); 173 | // Save prompts to config file, leave defaults if empty 174 | config.set("version.number", res.number || config.get("version.number")); 175 | config.set("version.description", res.description || config.get("version.description")); 176 | config.set("version.releaseDate", res.releaseDate || config.get("version.releaseDate")); 177 | console.log(`${config.getFileEnv()} updated.`); 178 | }; 179 | 180 | export async function initTask() { 181 | return gulp.series( 182 | createDbConfigFile, 183 | cleanProject, 184 | createProjectFiles, 185 | createSrcEmpty, 186 | initConfigFile, 187 | initGit, 188 | (done) => { 189 | console.log("done!"); 190 | done(); 191 | } 192 | )(); 193 | } 194 | -------------------------------------------------------------------------------- /src/cli/tasks/compile.ts: -------------------------------------------------------------------------------- 1 | import { argv } from "yargs"; 2 | import * as gulp from "gulp"; 3 | import * as convertEncoding from "gulp-convert-encoding"; 4 | import chalk from "chalk"; 5 | 6 | import { printResults, includesPaths } from "../common/utility"; 7 | import { workspaceConfig as config } from "../common/config"; 8 | import { getOnlyChangedFiles } from "../common/globs"; 9 | import { compileFile, compileSelection } from "../common/base"; 10 | import * as git from "../common/git"; 11 | import { exportFilesFromDbAsync } from "../tasks/import"; 12 | 13 | import * as noop from "gulp-noop"; 14 | import * as data from "gulp-data"; 15 | 16 | const compileFilesToDb = async ({ 17 | file = argv.file, 18 | env = argv.env || "DEV", 19 | changed = argv.changed, 20 | force = argv.force, 21 | user = argv.user, 22 | }) => { 23 | const source = config.get({ field: "source.input", env }); 24 | const src = file || (changed ? await getOnlyChangedFiles(source) : source); 25 | const warnings = config.get({ field: "compile.warnings", env }); 26 | const stageFile = config.get({ field: "compile.stageFile", env }); 27 | const encoding = config.get({ field: "source.encoding", env }); 28 | 29 | const processFile = async (file, done) => { 30 | let resp; 31 | try { 32 | // Compile file and get errors 33 | resp = await compileFile(file.contents, file.path, env, force, warnings, user); 34 | printResults(resp); 35 | // Stage file if no errors 36 | if (stageFile && !resp.errors.hasErrors()) { 37 | await git.exec({ args: `add "${resp.file}"` }); 38 | } 39 | } catch (error) { 40 | console.error(error.message); 41 | } finally { 42 | // Return compiled resp object 43 | done(null, resp); 44 | } 45 | }; 46 | 47 | // gulp4 rejects empty src 48 | if (src.length === 0) { 49 | src.push("nonvalidfile"); 50 | } 51 | return ( 52 | gulp 53 | .src(src, { allowEmpty: true }) 54 | // Default encoding to: 'utf8' 55 | .pipe(convertEncoding({ from: encoding })) 56 | // Compile file and emmit response 57 | .pipe(data(processFile)) 58 | // End stream as there is no destination 59 | .on("data", noop) 60 | 61 | // .on('end', () => console.log('Done.')); 62 | ); 63 | }; 64 | 65 | // unused 66 | // const compileEverywhere = async ({ file, env }) => { 67 | // if (!file) { throw Error("File cannot be empty."); } 68 | // // Compile to env 69 | // const results = await compileFilesToDbAsync({ file, env }); 70 | // // If no errors deploy 71 | // if (!results.some(file => file.errors.hasErrors())) { 72 | // await compileFilesToDbAsync({ file, env: "TEST" }); 73 | // await compileFilesToDbAsync({ file, env: "UAT" }); 74 | // } 75 | // }; 76 | 77 | export const compileOnSaveTask = ({ env = argv.env || "DEV" }) => { 78 | // Watch for files changes in source dir 79 | const source = config.get("source.input"); 80 | const watcher = gulp.watch(source, { awaitWriteFinish: true }); 81 | console.log(chalk.magenta(`Watching for file changes in ${source} ...`)); 82 | watcher.on("change", async (file) => { 83 | // Print pattern for start problem matching 84 | console.log(`\nStarting compilation...`); 85 | // Compile changes in working tree 86 | const files = await getOnlyChangedFiles(source); 87 | await compileFilesToDbAsync({ env, file: files }); 88 | // Always compile saved path (even if nothing changes) 89 | if (!includesPaths(files, file)) { 90 | await compileFilesToDbAsync({ env, file }); 91 | } 92 | // Print pattern that ends problem matching 93 | console.log("Compilation complete."); 94 | }); 95 | }; 96 | 97 | const compileFilesToDbAsync = async ({ file, env, changed = false, force = true }) => { 98 | let results = []; 99 | return new Promise(async (res, rej) => { 100 | const p = await compileFilesToDb({ file, env, changed, force }); 101 | // Collect results 102 | p.on("data", (resp) => results.push(resp.data)); 103 | // Return results 104 | p.on("end", () => res(results)); 105 | p.on("error", rej); 106 | }); 107 | }; 108 | 109 | const mergeLocalAndDbChanges = async ({ file, env, changed }) => { 110 | const source = config.get({ field: "source.input", env }); 111 | const src = file || (changed ? await getOnlyChangedFiles(source) : source); 112 | 113 | if (src.length !== 0) { 114 | try { 115 | await git.stash(); 116 | await exportFilesFromDbAsync({ file: src, env, quiet: true }); 117 | await git.unstash(); 118 | } catch (error) { 119 | // Git throws error when changes need merging 120 | console.log(error.message); 121 | } 122 | } 123 | }; 124 | 125 | const compileAndMergeFilesToDb = async ({ file, env, changed, force }) => { 126 | try { 127 | // Compile and get error results 128 | const results: any = await compileFilesToDbAsync({ file, env, changed, force }); 129 | // Merge unstaged (if any dirty file) 130 | if (results.some((file) => file.errors && file.errors.hasDirt())) { 131 | mergeLocalAndDbChanges({ file, env, changed }); 132 | } 133 | 134 | // Update todo.md 135 | 136 | // extractTodos(); 137 | } catch (error) { 138 | throw error; 139 | } 140 | }; 141 | 142 | export const compileTestTask = ({ env = argv.env || "DEV" }) => { 143 | const input = config.get({ field: "test.input", env }); 144 | return compileFilesToDbAsync({ file: input, env }); 145 | }; 146 | 147 | const compileObjectToDb = async ({ 148 | file = argv.file, 149 | env = argv.env || "DEV", 150 | object = argv.object, 151 | line = argv.line, 152 | user = argv.user, 153 | }) => { 154 | try { 155 | if (!object) { 156 | throw Error("Object cannot be empty."); 157 | } 158 | let resp = await compileSelection(object, file, env, line, user); 159 | printResults(resp); 160 | } catch (err) { 161 | console.error(err.message); 162 | } 163 | }; 164 | 165 | export function compileTask({ 166 | env = argv.env || "DEV", 167 | file = argv.file, 168 | changed = (argv.changed as boolean) || false, 169 | object = argv.object, 170 | line = argv.line, 171 | force = argv.force, 172 | }) { 173 | // force is a string 'true' or 'false' in parameter 174 | let s_force = force || config.get({ field: "compile.force", env }); 175 | let b_force = s_force.toString() === "true"; 176 | if (object) { 177 | return compileObjectToDb({ file, env, object, line }); 178 | } else { 179 | return compileAndMergeFilesToDb({ file, env, changed, force: b_force }); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [0.3.29] - 2021-08-02 4 | 5 | - Added compatibility with connections having credentials stored in autologin Oracle wallets [#58](https://github.com/mickeypearce/oradew-vscode/issues/58) 6 | - Added auto-selection of query in "Run Current Statement" command [#24](https://github.com/mickeypearce/oradew-vscode/issues/24) 7 | - Added "env" parameter to "Init" command 8 | 9 | ## [0.3.28] - 2021-04-02 10 | 11 | - Minor fixes 12 | 13 | ## [0.3.27] - 2020-12-06 14 | 15 | - Added setting "oradew.cliCommand" [#50](https://github.com/mickeypearce/oradew-vscode/issues/50) 16 | 17 | ## [0.3.26] - 2020-11-16 18 | 19 | - Fixed bug [#43](https://github.com/mickeypearce/oradew-vscode/issues/43) 20 | 21 | ## [0.3.25] - 2020-10-23 22 | 23 | - Added compatibility for APEX applications [#39](https://github.com/mickeypearce/oradew-vscode/issues/39) (thanks, @yevon) 24 | 25 | ## [0.3.24] - 2020-07-10 26 | 27 | - Enabled ".SQL" filename extension [#35](https://github.com/mickeypearce/oradew-vscode/issues/35) 28 | 29 | ## [0.3.23] - 2020-07-03 30 | 31 | - Added "askForPassword" to DB users (dbconfig.json) [#34](https://github.com/mickeypearce/oradew-vscode/issues/34) 32 | 33 | ## [0.3.22] - 2020-06-05 34 | 35 | - Added multi-schema packaging (schema based deployment scripts) 36 | - Added script selection to Deploy command 37 | 38 | ## [0.3.20] - 2020-05-22 39 | 40 | - Added "Set DB User" command 41 | 42 | ## [0.3.19] - 2020-05-14 43 | 44 | - Added "oradew" task contribution 45 | 46 | ## [0.3.18] - 2020-05-07 47 | 48 | - Minor bug fixes and changes 49 | 50 | ## [0.3.17] - 2020-02-11 51 | 52 | - Upgraded init command 53 | 54 | ## [0.3.16] - 2020-01-08 55 | 56 | - Added setting for environment variables: "oradew.envVariables" (ex.: "NLS_LANG", "ORACLE_HOME", etc) [#28](https://github.com/mickeypearce/oradew-vscode/issues/28) 57 | 58 | ## [0.3.15] - 2019-12-06 59 | 60 | - Upgraded node-oracledb to v4.1.0 61 | 62 | ## [0.3.14] - 2019-11-08 63 | 64 | - Added synonyms to source objects [#31](https://github.com/mickeypearce/oradew-vscode/issues/31) 65 | 66 | ## [0.3.13] - 2019-10-25 67 | 68 | - Added problem matcher for scripts and SQLcl as a default CLI [#28](https://github.com/mickeypearce/oradew-vscode/issues/28) 69 | 70 | ## [0.3.12] - 2019-10-04 71 | 72 | - Added configuration for custom source structure [#27](https://github.com/mickeypearce/oradew-vscode/issues/27) 73 | 74 | ## [0.3.11] - 2019-07-25 75 | 76 | - Maintanance 77 | 78 | ## [0.3.10] - 2019-05-25 79 | 80 | - Check prerequisites on startup [#23](https://github.com/mickeypearce/oradew-vscode/issues/23) 81 | - Added telemetry on command's usage 82 | 83 | ## [0.3.9] - 2019-05-15 84 | 85 | - Added "source.encoding" config [#21](https://github.com/mickeypearce/oradew-vscode/issues/21) 86 | 87 | ## [0.3.8] - 2019-05-08 88 | 89 | - Fix importing all source [#20](https://github.com/mickeypearce/oradew-vscode/issues/20) 90 | 91 | ## [0.3.7] - 2019-04-12 92 | 93 | - Rename "source" to "source.input" config 94 | - Fix "cli-install" when run from extension directory 95 | 96 | ## [0.3.6] - 2019-04-04 97 | 98 | - Added "Package Delta" command for packaging version changes 99 | - Added "package.exclude" config parameter 100 | - Added "Clear DB Environment" command 101 | 102 | ## [0.3.3] - 2019-03-27 103 | 104 | - Simplify CLI commands 105 | 106 | ## [0.3.1] - 2019-03-19 107 | 108 | - Added "Toggle Compile Watch" command for compiling on save 109 | - Added "compile.stageFile" configuration 110 | - Added setting to print additional info from tasks: "oradew.chatty" 111 | 112 | ## [0.3.0] - 2019-03-05 113 | 114 | - Added "Set DB Environment" command and status bar indicator [#17](https://github.com/mickeypearce/oradew-vscode/issues/17) 115 | - Added setting for global dbconfig file: "oradew.databaseConfigFile" [#17](https://github.com/mickeypearce/oradew-vscode/issues/17) 116 | - Added version number to deployment package 117 | 118 | ## [0.2.0] - 2019-02-06 119 | 120 | - !!! Upgraded node-oracledb to v3.1.1 which INCLUDES pre-built binaries for Node 6, 8, 10 and 11. [#15](https://github.com/mickeypearce/oradew-vscode/issues/15) 121 | - You may have to upgrade Node, if your version (e.g. 9) is no longer supported. 122 | - Generators have a separate config file (oradewrc-generate.json) 123 | - Added "disabled" and "schemas" properties to DB users (dbconfig.json) 124 | 125 | ## [0.1.8] - 2019-01-17 126 | 127 | - Generate BOL instead of ChangeLog file 128 | - Added ENV names to spooled logs (deploy) 129 | - Keybinding (ctrl+alt+enter) to re-run last command 130 | 131 | ## [0.1.7] - 2018-12-12 132 | 133 | - Added command line (CLI) [#11](https://github.com/mickeypearce/oradew-vscode/issues/11) 134 | 135 | ## [0.1.6] - 2018-11-27 136 | 137 | - Added "Compile" commands for UAT, bug fixes... 138 | 139 | ## [0.1.5] - 2018-11-02 140 | 141 | - Added environment-specific configurations [#10](https://github.com/mickeypearce/oradew-vscode/issues/10) 142 | - Added PL/SQL code generators ("Generate" command) 143 | - Fixed keybinding conflicts and activations [#12](https://github.com/mickeypearce/oradew-vscode/issues/12) 144 | 145 | ## [0.1.4] - 2018-10-12 146 | 147 | - Added "import.getDdlFunction" configuration: Use a custom DB function to import object's DDL. 148 | - Added Tables to Source objects 149 | - Added "Run Selected Statement" (Ctrl+Enter) command: Execute SQL and PL/SQL statements. 150 | - Added default values for settings (oradewrc.json) 151 | 152 | ## [0.1.2] - 2018-09-06 153 | 154 | - Added "Populate Package Input" command 155 | - Added "Edit db configuration" and "Use default workspace configuration" option to Initialize command 156 | 157 | ## [0.1.1] - 2018-08-07 158 | 159 | - Fixed spaces in workspace folder path [#5](https://github.com/mickeypearce/oradew-vscode/issues/5) 160 | 161 | ## [0.1.0] - 2018-07-04 162 | 163 | - Added multi-schema support [PR #8](https://github.com/mickeypearce/oradew-vscode/issues/8) 164 | 165 | ## [0.0.5] - 2018-06-08 166 | 167 | - Added Types to object types [PR #7](https://github.com/mickeypearce/oradew-vscode/issues/7) (thanks, @chambery) 168 | - Added Import Selected Object [#4](https://github.com/mickeypearce/oradew-vscode/issues/4) 169 | 170 | ## [0.0.4] - 2018-05-22 171 | 172 | - !!! Upgraded node-oracledb binary to Node.js 9 - win32 - x64. 173 | - Added "compile.force" parameter 174 | - Added json schema to oradewrc.json config file 175 | - Added triggers to object types 176 | 177 | ## [0.0.3] - 2018-05-03 178 | 179 | - Added "DEFINE OFF" to generated package script 180 | - Added Run Test command 181 | - Preserve exported objects DB data (moved to storagePath) 182 | 183 | ## [0.0.2] - 2018-04-20 184 | 185 | - Added "package.template" parameter 186 | -------------------------------------------------------------------------------- /src/cli/schemas/oradewrc-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "JSON schema for Oradew configuration file", 3 | "$schema": "http://json-schema.org/draft-06/schema#", 4 | "type": "object", 5 | "properties": { 6 | "package.input": { 7 | "default": [ 8 | "./scripts/**/initial*.sql", 9 | "./src/**/*.sql", 10 | "./scripts/**/final*.sql" 11 | ], 12 | "description": "Glob pattern for packaging files into deployment script.", 13 | "type": "array" 14 | }, 15 | "package.output": { 16 | "default": "./deploy/{schema-name}.sql", 17 | "description": "Deployment script file path. \nVariable {schema-name} is used to group files by schema into separate scripts. It can be used anywhere in the path or removed altogether.", 18 | "type": "string" 19 | }, 20 | "package.encoding": { 21 | "default": "utf8", 22 | "description": "Deployment script encoding. \n(utf8, win1250, ...)", 23 | "type": "string" 24 | }, 25 | "package.templating": { 26 | "default": false, 27 | "description": "Package templating of config variables in sql files. \nex.usage in sql code: '${config[\"version.number\"]}'", 28 | "type": "boolean" 29 | }, 30 | "package.exclude": { 31 | "default": [ 32 | "./scripts/**/+(file|run)*.sql" 33 | ], 34 | "description": "Glob pattern to exclude files from package input.", 35 | "type": "array" 36 | }, 37 | "source.input": { 38 | "default": [ 39 | "./src/**/*.sql" 40 | ], 41 | "description": "Glob pattern for Source files.", 42 | "type": "array" 43 | }, 44 | "source.encoding": { 45 | "default": "utf8", 46 | "description": "Source files encoding. \n(utf8, win1250, ...)", 47 | "type": "string" 48 | }, 49 | "compile.warnings": { 50 | "default": "NONE", 51 | "description": "Compilation warning scopes.", 52 | "type": "string", 53 | "enum": [ 54 | "NONE", 55 | "ALL", 56 | "PERFORMANCE", 57 | "INFORMATIONAL", 58 | "SEVERE" 59 | ] 60 | }, 61 | "compile.force": { 62 | "default": true, 63 | "description": "Ignore conflict detection of local file changes and overwrite object on DB.", 64 | "type": "boolean" 65 | }, 66 | "compile.stageFile": { 67 | "default": false, 68 | "description": "Automatically stage file after is succesfully compiled (git add).", 69 | "type": "boolean" 70 | }, 71 | "version.number": { 72 | "default": "0.0.1", 73 | "description": "Version number.", 74 | "type": "string" 75 | }, 76 | "version.description": { 77 | "default": "New feature", 78 | "description": "Version description.", 79 | "type": "string" 80 | }, 81 | "version.releaseDate": { 82 | "default": "2099-01-01", 83 | "description": "Version release date.", 84 | "type": "string", 85 | "format": "date" 86 | }, 87 | "test.input": { 88 | "default": [ 89 | "./test/**/*.test.sql" 90 | ], 91 | "description": "Glob pattern for tests.", 92 | "type": "array" 93 | }, 94 | "import.getDdlFunction": { 95 | "default": "dbms_metadata.get_ddl", 96 | "description": "DDL function name. Use your own DB function to customize import of object's DDL. \"DBMS_METADATA.GET_DDL\" by default. \nex.custom DB func.spec.: FUNCTION GetDDL(object_type IN VARCHAR2, name IN VARCHAR2, schema IN VARCHAR2 DEFAULT NULL) RETURN CLOB;", 97 | "type": "string" 98 | }, 99 | "source.pattern": { 100 | "description": "Define source structure for object types", 101 | "type": "object", 102 | "default": { 103 | "packageSpec": "./src/{schema-name}/PACKAGES/{object-name}.sql", 104 | "packageBody": "./src/{schema-name}/PACKAGE_BODIES/{object-name}.sql", 105 | "trigger": "./src/{schema-name}/TRIGGERS/{object-name}.sql", 106 | "typeSpec": "./src/{schema-name}/TYPES/{object-name}.sql", 107 | "typeBody": "./src/{schema-name}/TYPE_BODIES/{object-name}.sql", 108 | "view": "./src/{schema-name}/VIEWS/{object-name}.sql", 109 | "function": "./src/{schema-name}/FUNCTIONS/{object-name}.sql", 110 | "procedure": "./src/{schema-name}/PROCEDURES/{object-name}.sql", 111 | "table": "./src/{schema-name}/TABLES/{object-name}.sql", 112 | "synonym": "./src/{schema-name}/SYNONYMS/{object-name}.sql", 113 | "apex": "./src/{schema-name}/APEX/{object-name}.sql" 114 | }, 115 | "properties": { 116 | "packageSpec": { 117 | "default": "./src/{schema-name}/PACKAGES/{object-name}.sql", 118 | "description": "Pattern for package specifications", 119 | "type": "string" 120 | }, 121 | "packageBody": { 122 | "default": "./src/{schema-name}/PACKAGE_BODIES/{object-name}.sql", 123 | "description": "Pattern for package bodies", 124 | "type": "string" 125 | }, 126 | "trigger": { 127 | "default": "./src/{schema-name}/TRIGGERS/{object-name}.sql", 128 | "description": "Pattern for triggers", 129 | "type": "string" 130 | }, 131 | "typeSpec": { 132 | "default": "./src/{schema-name}/TYPES/{object-name}.sql", 133 | "description": "Pattern for type specifications", 134 | "type": "string" 135 | }, 136 | "typeBody": { 137 | "default": "./src/{schema-name}/TYPE_BODIES/{object-name}.sql", 138 | "description": "Pattern for type bodies", 139 | "type": "string" 140 | }, 141 | "view": { 142 | "default": "./src/{schema-name}/VIEWS/{object-name}.sql", 143 | "description": "Pattern for views", 144 | "type": "string" 145 | }, 146 | "function": { 147 | "default": "./src/{schema-name}/FUNCTIONS/{object-name}.sql", 148 | "description": "Pattern for functions", 149 | "type": "string" 150 | }, 151 | "procedure": { 152 | "default": "./src/{schema-name}/PROCEDURES/{object-name}.sql", 153 | "description": "Pattern for procedures", 154 | "type": "string" 155 | }, 156 | "table": { 157 | "default": "./src/{schema-name}/TABLES/{object-name}.sql", 158 | "description": "Pattern for tables", 159 | "type": "string" 160 | }, 161 | "synonym": { 162 | "default": "./src/{schema-name}/SYNONYMS/{object-name}.sql", 163 | "description": "Pattern for synonyms", 164 | "type": "string" 165 | }, 166 | "apex": { 167 | "default": "./src/{schema-name}/APEX/{object-name}.sql", 168 | "description": "Pattern for apex applications", 169 | "type": "string" 170 | } 171 | } 172 | }, 173 | "import.ease": { 174 | "default": false, 175 | "description": "When set to 'true' it will import only DB objects that changed on DB in comparision to project Source files", 176 | "type": "boolean" 177 | } 178 | } 179 | } -------------------------------------------------------------------------------- /src/cli/cli.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "path"; 2 | import { homedir } from "os"; 3 | import * as program from "commander"; 4 | 5 | import { OradewProcess } from "./process"; 6 | import { version } from "../../package.json"; 7 | 8 | // ENVironment variables: 9 | 10 | // ORADEW_CWD: workspace directory (current working dir by default) 11 | // ORADEW_COLOR: gulp output in colors (true by default) 12 | // ORADEW_SILENT: without gulp output (true by default) 13 | // ORADEW_DB_CONFIG_PATH: DB config path (./dbconfig.json by default) 14 | // ORADEW_WS_CONFIG_PATH: Workspace config path (./oradewrc.json by default) 15 | // ORADEW_STORAGE_DIR: Storage directory 16 | // ORADEW_CLI_EXECUTABLE: DB executable cli 17 | 18 | // Examples in powershell: 19 | // $env:ORADEW_SILENT="false" 20 | // $env:ORADEW_DB_CONFIG_PATH="./.vscode/dbconfig.json" 21 | // Examples in bash: 22 | // export ORADEW_STORAGE_DIR=/root/.vscode-server/data/User/workspaceStorage/23e6f2d18f63fad4517cc3121396a6d9/mp.oradew-vscode/ 23 | 24 | // WS is current dir by default 25 | const workspaceDir = process.env.ORADEW_CWD || process.cwd(); 26 | 27 | // Extension location path 28 | const cliDir = resolve(__dirname, ".."); 29 | 30 | const storageDir = process.env.ORADEW_STORAGE_DIR || homedir(); 31 | 32 | // Configs are in WS dir by default 33 | const dbConfigPath = process.env.ORADEW_DB_CONFIG_PATH || resolve(workspaceDir, "dbconfig.json"); 34 | const wsConfigPath = process.env.ORADEW_WS_CONFIG_PATH || resolve(workspaceDir, "oradewrc.json"); 35 | 36 | const isColor = (process.env.ORADEW_COLOR || "true") === "true"; 37 | const isSilent = (process.env.ORADEW_SILENT || "true") === "true"; 38 | 39 | const cliExecutable = process.env.ORADEW_CLI_EXECUTABLE || "sql"; 40 | const cliCommand = process.env.ORADEW_CLI_COMMAND; 41 | 42 | const oradewProcess = new OradewProcess({ 43 | workspaceDir, 44 | cliDir, 45 | storageDir, 46 | dbConfigPath, 47 | wsConfigPath, 48 | isSilent, 49 | isColor, 50 | cliExecutable, 51 | cliCommand, 52 | }); 53 | const execute = () => oradewProcess.execute(process.argv); 54 | 55 | program.name("oradew").version(version).usage(" [options]"); 56 | 57 | program.on("--help", function () { 58 | console.log(""); 59 | console.log("(Use `oradew --help` for command options.)"); 60 | console.log(""); 61 | console.log("Environment variables:"); 62 | console.log("ORADEW_CWD Workspace directory (current working dir by default)"); 63 | console.log("ORADEW_DB_CONFIG_PATH DB config file path (./dbconfig.json by default)"); 64 | console.log("ORADEW_WS_CONFIG_PATH Workspace config file path (./oradewrc.json by default)"); 65 | console.log("ORADEW_STORAGE_DIR Storage directory (user's home directory by default)"); 66 | console.log("ORADEW_CLI_EXECUTABLE OracleDB executable CLI (sql or sqlplus, 'sql' by default)"); 67 | console.log("ORADEW_COLOR Debug: Gulp outputs in colors (true by default)"); 68 | console.log("ORADEW_SILENT Debug: Gulp outputs silent (true by default)"); 69 | }); 70 | 71 | program 72 | .command("init") 73 | .description("Initialize a new workspace") 74 | .option("--env ", "DB Environment. DEV if not specified.") 75 | .action(() => execute()); 76 | 77 | program 78 | .command("create") 79 | .description("Create Source files and import from DB objects") 80 | .option("--env ", "DB Environment. DEV if not specified.") 81 | .option("--file ", 'File path or a glob. "source.input" if not specified.') 82 | .action(() => execute()); 83 | 84 | program 85 | .command("compile") 86 | .description("Compile Source files to DB") 87 | .option("--env ", "DB Environment. DEV if not specified.") 88 | .option("--file ", 'File path or a glob. "source.input" if not specified.') 89 | .option("--changed", "Only files in working tree (changes). False by default.") 90 | .option("--force", "Ignore conflict and overwrite DB object. True by default.") 91 | .option("--object ", "PL/SQL statement (query or block - in double quotes) to compile") 92 | .option("--line ", "Line offset of a statement in file. 1 by default.") 93 | .option("--user ", "DB User. (extracted from file path) if not specified.") 94 | .action(() => execute()); 95 | 96 | program 97 | .command("import") 98 | .description("Import Source files from DB") 99 | .option("--env ", "DB Environment. DEV if not specified.") 100 | .option("--file ", 'File path or a glob. "source.input" if not specified.') 101 | .option("--changed", "Only files in working tree (changes). False by default.") 102 | .option("--ease", "Only files (objects) that changed on DB. False by default.") 103 | .option("--quiet", "Suppress console output. False by default.") 104 | .option("--object ", "DB object name to import") 105 | .option("--user ", "DB User. (extracted from file path) if not specified.") 106 | .action(() => execute()); 107 | 108 | program 109 | .command("package") 110 | .description("Package files to deployment script") 111 | .option("--env ", "DB Environment. DEV if not specified.") 112 | .option("--delta", "Changed files from latest tagged commit up to head") 113 | .option("--from ", "Changed files from specified commit up to head") 114 | .option("--commit ", "Changed files from specific commit(s)") 115 | .option("--append", 'Append file paths not yet included to "package.input"') 116 | .action(() => execute()); 117 | 118 | program 119 | .command("deploy") 120 | .alias("run") 121 | .description("Run script (with SQL*Plus or SQLcl)") 122 | .option("--env ", "DB Environment. DEV if not specified.") 123 | .option("--file ", 'File path. "package.output" if not specified.') 124 | .option("--user ", "DB User. (extracted from file path) if not specified.") 125 | .action(() => execute()); 126 | 127 | program 128 | .command("test") 129 | .description("Run unit tests") 130 | .option("--env ", "DB Environment. DEV if not specified.") 131 | .option("--user ", "DB User. (extracted from file path) if not specified.") 132 | .action(() => execute()); 133 | 134 | program 135 | .command("generate") 136 | .description("Code generator") 137 | .option("--env ", "DB Environment. DEV if not specified.") 138 | .option("--func ", "Generator function name on DB (required)") 139 | .option("--file ", "File path. (func. params: ./src/${schema}/${object_type}/${name}.sql)") 140 | .option("--object ", "Object name. (func. param mapping: selected_object)") 141 | .option("--output ", "Output file path. Generated file name if not specified.") 142 | .option("--user ", "DB User. (extracted from file path) if not specified.") 143 | .action(() => execute()); 144 | 145 | program 146 | .command("watch") 147 | .description("Compile when Source file changes") 148 | .option("--env ", "DB Environment. DEV if not specified.") 149 | .option("--user ", "DB User. (extracted from file path) if not specified.") 150 | .action(() => execute()); 151 | 152 | export { program }; 153 | -------------------------------------------------------------------------------- /src/cli/common/config.ts: -------------------------------------------------------------------------------- 1 | import { includesCaseInsensitive } from "./utility"; 2 | import { existsSync, readJsonSync, outputJsonSync } from "fs-extra"; 3 | import { get, set, map, pipe, filter, flatMap, uniq, toUpper } from "lodash/fp"; 4 | import { parse, resolve } from "path"; 5 | 6 | // Build default object from json schema defaults 7 | // simplified - only defaults from first level in tree 8 | export function getDefaultsFromSchema(schema) { 9 | // const template = require(schema).properties; 10 | return Object.keys(schema).reduce((acc, value) => { 11 | return { ...acc, [value]: schema[value].default }; 12 | }, {}); 13 | } 14 | 15 | interface IUser { 16 | user: string; 17 | password?: string; 18 | askForPassword?: boolean; 19 | walletConnectString?: string; 20 | default?: boolean; 21 | disabled?: boolean; 22 | schemas?: string[]; 23 | } 24 | 25 | export interface IConnectionConfig extends IUser { 26 | env: string; 27 | connectString: string; 28 | } 29 | 30 | import { properties as dbConfSchema } from "../schemas/dbconfig-schema.json"; 31 | 32 | export class DBConfig { 33 | defaults: Object; 34 | fileBase: string; 35 | object: Object; 36 | constructor(fileBase) { 37 | // Defaults DB configuration object 38 | this.defaults = getDefaultsFromSchema(dbConfSchema); 39 | this.fileBase = fileBase || "./dbconfig.json"; 40 | // DB config JSON Object 41 | this.object = null; 42 | this.load(); 43 | } 44 | 45 | // Create config file with default values 46 | createFile() { 47 | return outputJsonSync(this.fileBase, this.defaults); 48 | } 49 | 50 | load() { 51 | try { 52 | this.object = readJsonSync(this.fileBase); 53 | } catch (e) { 54 | // Defaults 55 | console.log(`Cannot find ${this.fileBase} file...`); 56 | this.object = this.defaults; 57 | } 58 | } 59 | 60 | get(field) { 61 | return get(field)(this.object); 62 | } 63 | set(field, value) { 64 | this.object = set(field)(value)(this.object); 65 | return outputJsonSync(this.fileBase, this.object); 66 | } 67 | 68 | // _getConnectString = (env = "DEV") => this.object[env].connectString; 69 | // _getUserObjects = (env = "DEV") => this.object[env].users; 70 | 71 | // Get "users" object array from json 72 | // filter out disabled 73 | _getAllUsersByEnv = (env) => (data): IUser[] => { 74 | return pipe( 75 | get(env), 76 | get("users"), 77 | filter((v: IUser) => !v.disabled) 78 | )(data); 79 | }; 80 | 81 | /** 82 | * Get all schemas for environment. 83 | * *If User has no objects, Schemas are used 84 | * 85 | * @param {string} env 86 | * @returns {string[]} user 87 | */ 88 | getSchemas = (env = "DEV") => { 89 | return pipe( 90 | this._getAllUsersByEnv(env), 91 | flatMap((v) => (v.schemas ? v.schemas : [v.user])), 92 | map(toUpper), 93 | uniq 94 | )(this.object); 95 | }; 96 | 97 | /** 98 | ** Get connection configuration. Extracted from DB config file. 99 | * Filter by env and user 100 | * User parameter is optional, return default configuration if cannot be determined (user non existent) 101 | * @param {string} env 102 | * @param {string} [user] 103 | * @returns {IConnectionConfig} Connection config 104 | */ 105 | getConfiguration = (env, user?): IConnectionConfig => { 106 | if (!env) { 107 | throw Error(`No env parameter.`); 108 | } 109 | if (!this.object[env]) { 110 | throw Error(`Cannot find ${env} environment in dbconfig.json.`); 111 | } 112 | 113 | // Head of flattened object that we return 114 | const head = { env, connectString: this.object[env].connectString }; 115 | 116 | // First filter by env param, if only one config return 117 | let byEnv = this._getAllUsersByEnv(env)(this.object); 118 | 119 | if (!byEnv) { 120 | throw Error(`dbconfig.json: Invalid structure. Cannot find "${env}" env.`); 121 | } 122 | 123 | if (byEnv.length === 0) { 124 | throw Error(`dbconfig.json: No user for "${env}" env.`); 125 | } 126 | if (byEnv.length === 1) { 127 | return { ...head, ...byEnv[0] }; 128 | } 129 | 130 | // If user param exists, filter by v.user or v.schema 131 | let byUser = user 132 | ? filter( 133 | (v: IUser) => 134 | v.user.toUpperCase() === user.toUpperCase() || 135 | // includesCaseInsensitive([v.user], user) || 136 | (v.schemas && includesCaseInsensitive(v.schemas, user)) 137 | )(byEnv) 138 | : byEnv; 139 | 140 | if (byUser.length === 1) { 141 | return { ...head, ...byUser[0] }; 142 | } 143 | // non existing user -> go for default 144 | // if (byUser.length === 0) { 145 | // byUser = byEnv; 146 | // } 147 | 148 | // We couldn't match by user so go for default for env 149 | let byDefault = filter({ default: true } as IUser)(byEnv); 150 | 151 | if (byDefault.length === 1) { 152 | return { ...head, ...byDefault[0] }; 153 | } else { 154 | throw Error( 155 | `dbconfig.json: No match for user "${user}" in "${env}". Add/Enable missing user or set default user for env ("default": true).` 156 | ); 157 | } 158 | }; 159 | } 160 | export const dbConfig = new DBConfig(process.env.ORADEW_DB_CONFIG_PATH); 161 | 162 | /** 163 | * Workspace configuration 164 | * 165 | * * Base config applies to all environemnts. 166 | * But can be exteneded with env specific configs 167 | * 168 | * Config file extending sequence: defaults <== (BASE) <== (DEV, TEST, UAT, ...) 169 | * 170 | * (base): ./oradewrc.json 171 | * 172 | * Env specific configs: 173 | * DEV: ./oradewrc.dev.json (optional) 174 | * TEST: ./oradewrc.test.json (optional) 175 | * UAT: ./oradewrc.uat.json (optional) 176 | * ... 177 | * customEnv: ./oradewrc.customEnv.json (optional) 178 | */ 179 | import { properties as wsConfSchema } from "../schemas/oradewrc-schema.json"; 180 | 181 | export class WorkspaceConfig { 182 | defaults: object; 183 | filePathBase: string; 184 | object: object; 185 | constructor(fileBase?: string) { 186 | // Defaults configuration object 187 | this.defaults = getDefaultsFromSchema(wsConfSchema); 188 | this.filePathBase = fileBase || "./oradewrc.json"; 189 | 190 | this.object = {}; 191 | 192 | // Base default config file 193 | this.object["BASE"] = null; 194 | this.getConfigObject("BASE"); 195 | 196 | // env objects are created on demand: this.object["DEV"], .... 197 | } 198 | 199 | getFileEnv(env = "BASE") { 200 | if (env === "BASE") { 201 | return this.filePathBase; 202 | } 203 | let parsed = parse(this.filePathBase); 204 | return resolve(parsed.dir, `${parsed.name}.${env}${parsed.ext}`); 205 | } 206 | 207 | readFile(env) { 208 | let filename = this.getFileEnv(env); 209 | let res = existsSync(filename) ? readJsonSync(filename, { encoding: "utf8" }) : {}; 210 | return res; 211 | } 212 | 213 | getConfigObject(env) { 214 | // Base configs extends from defaults.. 215 | if (!this.object["BASE"]) { 216 | const objectBase = this.readFile("BASE"); 217 | this.object["BASE"] = { ...this.defaults, ...objectBase }; 218 | } 219 | 220 | // Create object for env - extended with base configs 221 | if (!this.object[env]) { 222 | const object = this.readFile(env); 223 | this.object[env] = { ...this.object["BASE"], ...object }; 224 | } 225 | } 226 | 227 | // Input param can be object: { field, env } 228 | // or just string "field" (env is BASE by default) 229 | // If Field is empty whole config object is returned 230 | get(param) { 231 | let field, env; 232 | if (typeof param === "object") { 233 | ({ field, env } = param); 234 | } else { 235 | field = param; 236 | env = "BASE"; 237 | } 238 | 239 | this.getConfigObject(env); 240 | return field ? this.object[env][field] : this.object[env]; 241 | } 242 | 243 | set(field, value, env = "BASE") { 244 | // Update local object 245 | if (this.object[env]) { 246 | this.object[env][field] = value; 247 | } 248 | 249 | // If BASE changes discard all other env because are inherited 250 | if (env === "BASE") { 251 | let _base = this.object["BASE"]; 252 | this.object = {}; 253 | this.object["BASE"] = _base; 254 | } 255 | 256 | // Add property to env file 257 | const filename = this.getFileEnv(env); 258 | const object = this.readFile(env); 259 | return outputJsonSync(filename, { 260 | ...object, 261 | ...{ [field]: value }, 262 | }); 263 | } 264 | } 265 | export const workspaceConfig = new WorkspaceConfig(process.env.ORADEW_WS_CONFIG_PATH); 266 | // export const createConfig = file => new Config(file); 267 | -------------------------------------------------------------------------------- /src/extension/task-provider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { OradewProcess } from "@Cli/process"; 3 | 4 | let oradewProcess: OradewProcess; 5 | 6 | interface OradewTaskDefinition extends vscode.TaskDefinition { 7 | name: string; 8 | params: string[]; 9 | } 10 | 11 | export class OradewTaskProvider implements vscode.TaskProvider { 12 | static OradewType: string = "oradew"; 13 | private oradewPromise: Thenable | undefined = undefined; 14 | 15 | constructor(pod: OradewProcess) { 16 | oradewProcess = pod; 17 | } 18 | 19 | public provideTasks(): Thenable | undefined { 20 | if (!this.oradewPromise) { 21 | this.oradewPromise = getOradewTasks(); 22 | } 23 | return this.oradewPromise; 24 | } 25 | 26 | public resolveTask(_task: vscode.Task): vscode.Task | undefined { 27 | const name = _task.definition.name; 28 | const params = _task.definition.params; 29 | 30 | if (name && params) { 31 | // resolveTask requires that the same definition object be used. !!!!! 32 | const definition: OradewTaskDefinition = _task.definition; 33 | return createOradewTask(definition); 34 | } 35 | return undefined; 36 | } 37 | } 38 | 39 | export function createCompileOnSaveTask(): vscode.Task { 40 | let _def: OradewTaskDefinition = createOradewTaskDefinition({ 41 | name: "compileOnSave", 42 | params: [ 43 | "compileOnSave", 44 | "--env", 45 | "${command:oradew.getEnvironment}", 46 | "--user", 47 | "${command:oradew.getUser}", 48 | ], 49 | }); 50 | let _task = createOradewTask(_def); 51 | _task.isBackground = true; 52 | _task.presentationOptions = { 53 | reveal: vscode.TaskRevealKind.Silent, 54 | }; 55 | return _task; 56 | } 57 | 58 | let _channel: vscode.OutputChannel; 59 | function getOutputChannel(): vscode.OutputChannel { 60 | if (!_channel) { 61 | _channel = vscode.window.createOutputChannel("Oradew Auto Detection"); 62 | } 63 | return _channel; 64 | } 65 | 66 | function createOradewTaskDefinition({ name, params }): OradewTaskDefinition { 67 | return { 68 | type: OradewTaskProvider.OradewType, 69 | name, 70 | params, 71 | }; 72 | } 73 | 74 | function createOradewTask(definition: OradewTaskDefinition): vscode.Task { 75 | let _task = new vscode.Task( 76 | definition, 77 | vscode.TaskScope.Workspace, 78 | definition.name, 79 | OradewTaskProvider.OradewType, 80 | new vscode.ProcessExecution("node", [oradewProcess.cliPath, ...definition.params], { 81 | env: oradewProcess.processEnv.env, 82 | }), 83 | "$oracle-plsql" 84 | ); 85 | return _task; 86 | } 87 | 88 | async function getOradewTasks(): Promise { 89 | let result: vscode.Task[] = []; 90 | let emptyTasks: vscode.Task[] = []; 91 | 92 | try { 93 | result.push( 94 | createOradewTask( 95 | createOradewTaskDefinition({ 96 | name: "generator", 97 | params: [ 98 | "generate", 99 | "--env", 100 | "${command:oradew.getEnvironment}", 101 | "--func", 102 | "${command:oradew.getGeneratorFunction}", 103 | "--file", 104 | "${file}", 105 | "--object", 106 | "${selectedText}", 107 | // ...(generator.output ? ["--output", generator.output] : []) 108 | "--user", 109 | "${command:oradew.getUser}", 110 | ], 111 | }) 112 | ) 113 | ); 114 | 115 | result.push( 116 | createOradewTask( 117 | createOradewTaskDefinition({ 118 | name: "init", 119 | params: ["init", "--env", "${command:oradew.getEnvironment}"], 120 | }) 121 | ) 122 | ); 123 | 124 | result.push( 125 | createOradewTask( 126 | createOradewTaskDefinition({ 127 | name: "create", 128 | params: ["create", "--env", "${command:oradew.getEnvironment}"], 129 | }) 130 | ) 131 | ); 132 | 133 | result.push( 134 | createOradewTask( 135 | createOradewTaskDefinition({ 136 | name: "compile--changed", 137 | params: [ 138 | "compile", 139 | "--env", 140 | "${command:oradew.getEnvironment}", 141 | "--changed", 142 | "true", 143 | "--user", 144 | "${command:oradew.getUser}", 145 | ], 146 | }) 147 | ) 148 | ); 149 | 150 | result.push( 151 | createOradewTask( 152 | createOradewTaskDefinition({ 153 | name: "compile--file", 154 | params: [ 155 | "compile", 156 | "--env", 157 | "${command:oradew.getEnvironment}", 158 | "--file", 159 | "${file}", 160 | "--user", 161 | "${command:oradew.getUser}", 162 | ], 163 | }) 164 | ) 165 | ); 166 | 167 | result.push( 168 | createOradewTask( 169 | createOradewTaskDefinition({ 170 | name: "compile", 171 | params: [ 172 | "compile", 173 | "--env", 174 | "${command:oradew.getEnvironment}", 175 | "--user", 176 | "${command:oradew.getUser}", 177 | ], 178 | }) 179 | ) 180 | ); 181 | 182 | result.push( 183 | createOradewTask( 184 | createOradewTaskDefinition({ 185 | name: "compile--object", 186 | params: [ 187 | "compile", 188 | "--env", 189 | "${command:oradew.getEnvironment}", 190 | "--file", 191 | "${file}", 192 | "--object", 193 | "${command:oradew.selectCurrentStatement}", 194 | "--line", 195 | "${lineNumber}", 196 | "--user", 197 | "${command:oradew.getUser}", 198 | ], 199 | }) 200 | ) 201 | ); 202 | 203 | result.push( 204 | createOradewTask( 205 | createOradewTaskDefinition({ 206 | name: "import", 207 | params: ["import", "--env", "${command:oradew.getEnvironment}"], 208 | }) 209 | ) 210 | ); 211 | 212 | result.push( 213 | createOradewTask( 214 | createOradewTaskDefinition({ 215 | name: "import--file", 216 | params: [ 217 | "import", 218 | "--env", 219 | "${command:oradew.getEnvironment}", 220 | "--file", 221 | "${file}", 222 | "--ease", 223 | "false", 224 | ], 225 | }) 226 | ) 227 | ); 228 | 229 | result.push( 230 | createOradewTask( 231 | createOradewTaskDefinition({ 232 | name: "import--object", 233 | params: [ 234 | "import", 235 | "--env", 236 | "${command:oradew.getEnvironment}", 237 | "--object", 238 | "${selectedText}", 239 | "--user", 240 | "${command:oradew.getUser}", 241 | "--file", 242 | "${file}", 243 | ], 244 | }) 245 | ) 246 | ); 247 | 248 | result.push( 249 | createOradewTask( 250 | createOradewTaskDefinition({ 251 | name: "package", 252 | params: ["package", "--env", "${command:oradew.getEnvironment}"], 253 | }) 254 | ) 255 | ); 256 | 257 | result.push( 258 | createOradewTask( 259 | createOradewTaskDefinition({ 260 | name: "package--delta", 261 | params: ["package", "--env", "${command:oradew.getEnvironment}", "--delta"], 262 | }) 263 | ) 264 | ); 265 | 266 | result.push( 267 | createOradewTask( 268 | createOradewTaskDefinition({ 269 | name: "deploy", 270 | params: [ 271 | "run", 272 | "--env", 273 | "${command:oradew.pickEnvironment}", 274 | "--user", 275 | "${command:oradew.getUser}", 276 | "--file", 277 | "${command:oradew.pickPackageScript}", 278 | ], 279 | }) 280 | ) 281 | ); 282 | 283 | result.push( 284 | createOradewTask( 285 | createOradewTaskDefinition({ 286 | name: "run--file", 287 | params: [ 288 | "run", 289 | "--env", 290 | "${command:oradew.getEnvironment}", 291 | "--file", 292 | "${file}", 293 | "--user", 294 | "${command:oradew.getUser}", 295 | ], 296 | }) 297 | ) 298 | ); 299 | 300 | result.push( 301 | createOradewTask( 302 | createOradewTaskDefinition({ 303 | name: "test", 304 | params: [ 305 | "test", 306 | "--env", 307 | "${command:oradew.getEnvironment}", 308 | "--user", 309 | "${command:oradew.getUser}", 310 | ], 311 | }) 312 | ) 313 | ); 314 | 315 | return result; 316 | } catch (err) { 317 | let channel = getOutputChannel(); 318 | if (err.stderr) { 319 | channel.appendLine(err.stderr); 320 | } 321 | if (err.stdout) { 322 | channel.appendLine(err.stdout); 323 | } 324 | channel.appendLine("Auto detecting oradew tasts failed." + err); 325 | channel.show(true); 326 | return emptyTasks; 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /src/cli/common/dbobject.ts: -------------------------------------------------------------------------------- 1 | const micromatch = require("micromatch"); 2 | 3 | import { rootRemove, rootPrepend } from "./utility"; 4 | import { parse, resolve, relative, dirname } from "path"; 5 | import { invert } from "lodash/fp"; 6 | import { fromGlobsToFilesArray } from "./globs"; 7 | import { workspaceConfig as config } from "./config"; 8 | 9 | // const config = new WorkspaceConfig( 10 | // __dirname + "/schemas/oradewrc.default.json" 11 | // ); 12 | 13 | //new WorkspaceConfig(); 14 | 15 | const patternSrcObject = config.get("source.pattern"); 16 | // { 17 | // "packageSpec": "./src/{schema-name}/pck/{object-name}-spec.sql", 18 | // "packageBody": "./src/{schema-name}/pck/{object-name}-body.sql", 19 | // "trigger": "./src/{schema-name}/trigger-{object-name}.sql", 20 | // "view": "./src/{schema-name}/view-{object-name}.sql", 21 | // "function": "./test/src/{schema-name}/FUNCTIONS/{object-name}.sql", 22 | // "procedure": "./src/PROCEDURES/{object-name}.sql", 23 | // } 24 | 25 | function getObjectTypeFromSrcPath(path) { 26 | // Filter patterns that match a path 27 | const foundPattern = Object.keys(patternSrcObject).filter((element) => { 28 | const pattern = patternSrcObject[element]; 29 | // We use globs for matching, so replace variables with wildcard (*) 30 | const globPattern = pattern.replace(/{schema-name}|{object-name}/gi, "*"); 31 | // Globpattern have to be without ./, but We have it in config for unknown reason... 32 | return micromatch.isMatch(path, globPattern, { 33 | format: rootRemove, 34 | nocase: true, 35 | }); 36 | }); 37 | return foundPattern[0]; 38 | } 39 | 40 | const patternPckOutput = config.get("package.output"); 41 | // "./deploy/{schema-name}.sql" 42 | function isDeployPath(path) { 43 | const globPattern = patternPckOutput.replace(/{schema-name}|{object-name}/gi, "*"); 44 | return micromatch.isMatch(path, globPattern, { 45 | format: rootRemove, 46 | nocase: true, 47 | }); 48 | } 49 | 50 | // Match ./deploy/{schema-name}.sql to actual files 51 | export function matchOutputFiles(outputFilePattern, options?) { 52 | const globPattern = outputFilePattern.replace(/{schema-name}|{object-name}/gi, "*"); 53 | const files = fromGlobsToFilesArray(globPattern, options); 54 | return files; 55 | } 56 | 57 | const mapToOraObjectType = { 58 | packageSpec: "PACKAGE", 59 | procedure: "PROCEDURE", 60 | function: "FUNCTION", 61 | packageBody: "PACKAGE BODY", 62 | view: "VIEW", 63 | trigger: "TRIGGER", 64 | typeSpec: "TYPE", 65 | typeBody: "TYPE BODY", 66 | table: "TABLE", 67 | synonym: "SYNONYM", 68 | apex: "APEX", 69 | }; 70 | 71 | const mapToOraObjectTypeAlt = { 72 | packageSpec: "PACKAGE_SPEC", 73 | packageBody: "PACKAGE_BODY", 74 | typeSpec: "TYPE_SPEC", 75 | typeBody: "TYPE_BODY", 76 | }; 77 | 78 | const mapfromOraObjectType = invert(mapToOraObjectType); 79 | 80 | /** 81 | * Object def 82 | * @typedef {{owner: string, objectType?: string, objectType1?: string, objectName?: string, isSource?: string, isScript?: string}} ObjectDefinition 83 | */ 84 | 85 | /** 86 | ** Extract object info from file path 87 | * SRC: patterns array defined in "source.pattern" 88 | * deployScript: pattern "package.output" 89 | * script otherwire 90 | * @param {string} path 91 | * @returns {ObjectDefinition} object 92 | */ 93 | export function getObjectInfoFromPath(path) { 94 | if (!path) { 95 | return { owner: undefined }; 96 | } 97 | 98 | let schema, objectName, objectType; 99 | 100 | // Convert path to relative 101 | const absPath = resolve(path); 102 | const base = resolve("./"); 103 | const relPath = relative(base, absPath); 104 | 105 | // Convert path to posix with ./ 106 | const pathPosix = rootPrepend(relPath.replace(/\\/gi, "/")); 107 | 108 | // Is it a SRC file? 109 | objectType = getObjectTypeFromSrcPath(pathPosix); 110 | 111 | if (objectType) { 112 | // pattern="./src/{schema-name}/PACKAGES/{object-name}-spec.sql" 113 | const patternSrc = patternSrcObject[objectType]; 114 | 115 | /** extact schema*/ 116 | // Get left and right of schema path 117 | // p1 = ["./src/", "/PACKAGES/{object-name}-spec.sql"] 118 | // escape '.' (.sql) as its a regex's special char 119 | const schemaSplit = patternSrc.split("{schema-name}").map((v) => v.replace(".", "\\.")); 120 | 121 | // Convert to regex so we can find and replace 122 | // p1 = ["/.\/src\//", "/\/PACKAGES\/\w+-spec.sql/"] 123 | const schemaRegex = schemaSplit.map( 124 | (v) => new RegExp(v.replace("{object-name}", "\\w+"), "gi") 125 | ); 126 | 127 | // Remove both from path 128 | // ex: path: "./src/HR/PACKAGES/pck1-spec.sql" 129 | // replace ["./src/", "/PACKAGES/\w+-spec.sql"] with "", leaving "HR" 130 | // actually: schema = pathPosix.replace(schemaRegex[0], "").replace(schemaRegex[1], ""); 131 | schema = schemaRegex.reduce((acc, val) => acc.replace(val, ""), pathPosix); 132 | 133 | /** extact object-name*/ 134 | const objectSplit = patternSrc.split("{object-name}").map((v) => v.replace(".", "\\.")); 135 | const objectRegex = objectSplit.map( 136 | (v) => new RegExp(v.replace("{schema-name}", "\\w+"), "gi") 137 | ); 138 | objectName = objectRegex.reduce((acc, val) => acc.replace(val, ""), pathPosix); 139 | 140 | // Map to ora types 141 | const objectTypeOra = mapToOraObjectType[objectType] || objectType; 142 | const objectTypeOraAlt = mapToOraObjectTypeAlt[objectType] || objectTypeOra; 143 | 144 | return { 145 | owner: schema, 146 | objectName, 147 | objectType: objectTypeOra, 148 | objectType1: objectTypeOraAlt, 149 | isSource: true, 150 | isScript: false, 151 | }; 152 | } 153 | 154 | // Is it a deploy script? 155 | const isDeploy = isDeployPath(pathPosix); 156 | if (isDeploy) { 157 | // pattern="./deploy/{schema-name}.sql" 158 | const patternPck = patternPckOutput; // patternDeployObject[objectType]; 159 | 160 | /** extact schema*/ 161 | // Get left and right of schema path 162 | // p1 = ["./deploy/", ".sql"] 163 | const schemaSplit = patternPck.split("{schema-name}"); 164 | schema = pathPosix 165 | .replace(new RegExp(schemaSplit[0], "gi"), "") 166 | .replace(new RegExp(schemaSplit[1], "gi"), ""); 167 | objectName = parse(absPath).name; 168 | return { 169 | owner: schema, 170 | objectType: "deployScript", 171 | objectType1: "deployScript", 172 | objectName, 173 | isSource: false, 174 | isScript: true, 175 | }; 176 | } 177 | 178 | // If pattern is not configured for object type, we set objecttype=script 179 | // and objectname=filename, schema is second dir in path 180 | // we assume it as a script 181 | if (!objectType) { 182 | const pathSplit = pathPosix.split("/"); 183 | 184 | // If path don't include schema (too short :), we set it to undefined which means default schema will be used 185 | // ./scripts/HR/initial_dml.sql (4) or ./deploy/Release.sql (3) 186 | schema = pathSplit.length > 3 ? pathSplit[2] : undefined; 187 | objectName = parse(absPath).name; 188 | return { 189 | owner: schema, 190 | objectType: "script", 191 | objectType1: "script", 192 | objectName, 193 | isSource: false, 194 | isScript: true, 195 | }; 196 | } 197 | } 198 | 199 | export function replaceVarsInPattern(pattern = "", owner, name?) { 200 | return pattern.replace("{schema-name}", owner).replace("{object-name}", name); 201 | } 202 | 203 | /** 204 | * 205 | * @param {string} owner 206 | * @param {string} oraType 207 | * @param {string} name 208 | * @returns {string} Relative path 209 | */ 210 | export function getPathFromObjectInfo(owner, oraType, name) { 211 | // Get pattern for object type 212 | const type = mapfromOraObjectType[oraType] || oraType; 213 | const pattern = patternSrcObject[type]; 214 | // Replace variables in pattern with values 215 | const path = replaceVarsInPattern(pattern, owner, name); 216 | return path; 217 | } 218 | 219 | export function getPackageOutputPath({ owner }) { 220 | // Replace variables in pattern with values 221 | const path = replaceVarsInPattern(patternPckOutput, owner); 222 | return path; 223 | } 224 | 225 | /** 226 | * Return patters array without file (dir structure) 227 | * 228 | * @returns {Array} source dir structure 229 | * example: 230 | * from 231 | * {"packageSpec": "./src/{schema-name}/PACKAGES/{object-name}.sql", 232 | * "packageBody": "./src/{schema-name}/PACKAGE_BODIES/{object-name}.sql"} 233 | * to 234 | * ["./src/{schema-name}/PACKAGES", 235 | * "./src/{schema-name}/PACKAGE_BODIES"] 236 | */ 237 | 238 | export function getSourceStructure() { 239 | return Object.keys(patternSrcObject).map((el) => dirname(patternSrcObject[el])); 240 | } 241 | 242 | export function getObjectTypes() { 243 | return Object.values(mapToOraObjectType); 244 | } 245 | 246 | // const sourceInput = config.get("source.input"); 247 | 248 | /** 249 | * Return Source root folders array 250 | * 251 | * @returns {Array} Root folders 252 | */ 253 | /* 254 | export function getSourceRoot() { 255 | const roots = sourceInput.map((el) => { 256 | return rootRemove(el).split("/")[0]; 257 | }); 258 | return uniq(roots); 259 | } 260 | 261 | // Get src root dir 262 | // ./src by default 263 | export function srcDir() { 264 | return ( 265 | getSourceRoot() 266 | .map((el) => rootPrepend(el)) 267 | .join(" ") || "./src" 268 | ); 269 | } 270 | */ 271 | -------------------------------------------------------------------------------- /src/cli/common/base.ts: -------------------------------------------------------------------------------- 1 | import { pipe, replace, trim, trimCharsEnd, template } from "lodash/fp"; 2 | import { parse } from "path"; 3 | 4 | import * as db from "./db"; 5 | import { splitLines, execPromise } from "./utility"; 6 | import { getObjectInfoFromPath } from "./dbobject"; 7 | import { dbConfig } from "./config"; 8 | 9 | const U_AUTO = ""; 10 | 11 | /** 12 | *Extract user info from path and match with DB conn configuration 13 | */ 14 | function matchDbUser(file, env, user, changeOwner) { 15 | const oinfo = getObjectInfoFromPath(file); 16 | // If user is set (not auto), we change owner 17 | let obj = user === U_AUTO ? oinfo : { ...oinfo, owner: user }; 18 | const connCfg = dbConfig.getConfiguration(env, obj.owner); 19 | // When there is no user configuration for the owner 20 | // we can force change the object owner to default user 21 | if (changeOwner) { 22 | obj.owner = connCfg.user.toUpperCase(); 23 | } 24 | return { obj, connCfg }; 25 | } 26 | 27 | // let obj = {}; 28 | 29 | export const exportFile = async (code, file, env, ease, getFunctionName, done) => { 30 | let exported = null; 31 | let conn; 32 | let obj, connCfg; 33 | try { 34 | ({ obj, connCfg } = matchDbUser(file, env, U_AUTO, false)); 35 | 36 | conn = await db.getConnection(connCfg); 37 | // try { 38 | if (!ease || (await db.isDifferentDdlTime(conn, obj, env))) { 39 | // Get Db object code as string 40 | let lob = await db.getObjectDdl(conn, getFunctionName, obj); 41 | lob = pipe( 42 | // Remove whitespaces 43 | trim, 44 | // Remove NUL chars that are added to large files ?! 45 | replace(/\x00/g, ""), 46 | // Remove disable/enable line that is added at the end of the trigger 47 | replace(/\nALTER TRIGGER+.*/g, "") 48 | )(lob); 49 | // Return a value async with callback 50 | done(null, lob); 51 | // Mark object as exported 52 | await db.syncDdlTime(conn, obj, env); 53 | exported = true; 54 | } else { 55 | // Return local code to continue gulp pipe 56 | done(null, code); 57 | exported = false; 58 | // } 59 | // } catch (error) { 60 | // console.error(error.message); 61 | } 62 | } catch (error) { 63 | console.error(error.message); 64 | done(null, code); 65 | exported = false; 66 | } finally { 67 | db.closeConnection(conn); 68 | } 69 | return { obj, exported }; 70 | }; 71 | 72 | function simpleParse(code) { 73 | // Trim empties and slash (/) from code if it exists 74 | code = pipe(trim, trimCharsEnd("/"), trim)(code); 75 | 76 | // Trim semicolon (;) if it doesn't end with "END;" or "END ; etc" 77 | if (!/END(\s*\w*);$/gi.test(code)) { 78 | code = trimCharsEnd(";")(code); 79 | } 80 | return code; 81 | } 82 | 83 | function getLineAndPosition(code, offset) { 84 | let lines = splitLines(code.substring(0, offset)); 85 | let line = lines.length; 86 | let position = lines.pop().length + 1; 87 | return { line, position }; 88 | } 89 | 90 | export const compileFile = async (code, file, env, force, warnings, user = U_AUTO) => { 91 | const { obj, connCfg } = matchDbUser(file, env, user, true); 92 | 93 | code = simpleParse(code); 94 | 95 | let errors; 96 | let lines = []; 97 | let result = {}; 98 | let conn; 99 | try { 100 | conn = await db.getConnection(connCfg); 101 | // Generate error if we havent the latest obj version 102 | // and we arent forcing compile 103 | if ((await db.isDifferentDdlTime(conn, obj, env)) && !force) { 104 | errors = db.getErrorObjectChanged(); 105 | } else { 106 | // Otherwise compile object to Db with warning scope 107 | result = await db.compile(conn, code.toString(), warnings); 108 | // Mark object as exported as we have the latest version 109 | await db.syncDdlTime(conn, obj, env); 110 | // Getting errors for this object from Db 111 | errors = await db.getErrors(conn, obj); 112 | lines = await db.getDbmsOutput(conn); 113 | } 114 | } catch (error) { 115 | const { line, position } = getLineAndPosition(code, error.offset); 116 | let msg = error.message; 117 | errors = db.getErrorSystem(msg, 1, line, position); 118 | } finally { 119 | db.closeConnection(conn); 120 | } 121 | // Return results, errors array, file and env params 122 | return { 123 | obj, 124 | file, 125 | env, 126 | errors, 127 | result, 128 | lines, 129 | }; 130 | }; 131 | 132 | export const compileSelection = async (code, file, env, lineOffset, user = U_AUTO) => { 133 | const { obj, connCfg } = matchDbUser(file, env, user, true); 134 | 135 | code = simpleParse(code); 136 | 137 | let errors; 138 | let lines = []; 139 | let result = {}; 140 | let conn; 141 | 142 | try { 143 | conn = await db.getConnection(connCfg); 144 | result = await db.compile(conn, code.toString()); 145 | errors = db.createErrorList(); 146 | lines = await db.getDbmsOutput(conn); 147 | } catch (error) { 148 | // Oracle returns character offset of error 149 | const { line, position } = getLineAndPosition(code, error.offset); 150 | let msg = error.message; 151 | errors = db.getErrorSystem(msg, lineOffset, line, position); 152 | } finally { 153 | db.closeConnection(conn); 154 | } 155 | // Return results, errors array, file and env params 156 | // dbmsoutput lines 157 | return { 158 | obj, 159 | file, 160 | env, 161 | errors, 162 | result, 163 | lines, 164 | }; 165 | }; 166 | 167 | export const runFileAsScript = async (file, env, user = U_AUTO) => { 168 | const { obj, connCfg } = matchDbUser(file, env, user, true); 169 | 170 | const connString = await db.getConnectionString(connCfg); 171 | 172 | const cwd = parse(file).dir; 173 | const filename = parse(file).base; 174 | const cliExec = process.env.ORADEW_CLI_EXECUTABLE; 175 | const cliCommand = process.env.ORADEW_CLI_COMMAND; 176 | 177 | let cmd; 178 | if (cliCommand === "null") { 179 | const isSqlPlus = parse(cliExec).name.toLowerCase() === "sqlplus"; 180 | if (isSqlPlus) { 181 | cmd = `(echo connect ${connString} && echo start ${filename} && echo show errors) | "${cliExec}" -S /nolog`; 182 | } else { 183 | cmd = `exit | "${cliExec}" -S ${connString} @"${filename}"`; 184 | } 185 | } else { 186 | cmd = template(cliCommand)({ cliExec, connString, filename }); 187 | } 188 | 189 | // We execute from file directory (change cwd) 190 | // mainly because of spooling to dir of the file (packaged script) 191 | // buffer: 5MB 192 | let stdout = await execPromise(cmd, { encoding: "buffer", maxBuffer: 1024 * 5000, cwd }); 193 | return { stdout, obj }; 194 | }; 195 | 196 | export const getObjectsInfoByType = async (env, owner, objectTypes) => { 197 | const connCfg = dbConfig.getConfiguration(env, owner); 198 | let conn; 199 | let result = []; 200 | try { 201 | conn = await db.getConnection(connCfg); 202 | for (let objectType of objectTypes) { 203 | const objects = await db.getObjectsInfo(conn, { owner, objectType }); 204 | result = result.concat(objects); 205 | } 206 | } catch (error) { 207 | throw error; 208 | } finally { 209 | db.closeConnection(conn); 210 | } 211 | return result; 212 | }; 213 | 214 | export const resolveObjectInfo = async (env, objName, user = U_AUTO, file) => { 215 | let { connCfg } = matchDbUser(file, env, user, false); 216 | 217 | let conn; 218 | let result; 219 | let conn1; 220 | try { 221 | let schema, part1, part2; 222 | let objectName; 223 | conn = await db.getConnection(connCfg); 224 | // Try to resolve object name for every context [0..9] (obj type) 225 | for (let context = 0; context < 10; context++) { 226 | try { 227 | ({ schema, part1, part2 } = await db.getNameResolve(conn, { 228 | name: objName, 229 | context, 230 | })); 231 | } catch (error) { 232 | // Triggers throw 06564 if not correct context (3)? 233 | // ORA-06564: object does not exist 234 | // ORA-04047: object specified is incompatible with the flag specified 235 | if (![6564, 4047].includes(error.errorNum)) { 236 | throw error; 237 | } 238 | } 239 | // Break if we got objectName 240 | objectName = part1 || part2; 241 | if (objectName) { 242 | break; 243 | } 244 | } 245 | 246 | if (!objectName) { 247 | throw Error(`object ${objName} does not exist for user ${user}.`); 248 | } 249 | 250 | // Get connection to object actual schema 251 | connCfg = dbConfig.getConfiguration(env, schema); 252 | conn1 = await db.getConnection(connCfg); 253 | result = await db.getObjectsInfo(conn1, { 254 | owner: schema, 255 | objectName, 256 | }); 257 | } catch (error) { 258 | throw error; 259 | } finally { 260 | db.closeConnection(conn); 261 | db.closeConnection(conn1); 262 | } 263 | return result; 264 | }; 265 | 266 | export const getGenerator = async ({ func, file, env, object, user = U_AUTO }) => { 267 | const { obj, connCfg } = matchDbUser(file, env, user, true); 268 | 269 | let result = {}; 270 | let conn; 271 | try { 272 | conn = await db.getConnection(connCfg); 273 | result = await db.getGeneratorFunction(conn, func, obj, object); 274 | } catch (error) { 275 | throw error; 276 | } finally { 277 | db.closeConnection(conn); 278 | } 279 | return { 280 | obj, 281 | file, 282 | env, 283 | result, 284 | }; 285 | }; 286 | -------------------------------------------------------------------------------- /src/cli/test/dbobject.test.ts: -------------------------------------------------------------------------------- 1 | const assert = require("assert"); 2 | 3 | import { resolve } from "path"; 4 | 5 | import { 6 | getObjectInfoFromPath, 7 | getPathFromObjectInfo, 8 | getSourceStructure, 9 | } from "../common/dbobject"; 10 | 11 | describe("#getObjectInfo with default structure", function () { 12 | it("src: should get object type body", function () { 13 | assert.deepEqual(getObjectInfoFromPath("./src/HR/PACKAGE_BODIES/my_pck1.sql"), { 14 | "owner": "HR", 15 | "objectType": "PACKAGE BODY", 16 | "objectType1": "PACKAGE_BODY", 17 | "objectName": "my_pck1", 18 | "isSource": true, 19 | "isScript": false, 20 | }); 21 | }); 22 | it("src: should get object type spec", function () { 23 | assert.deepEqual(getObjectInfoFromPath("./src/HR/PACKAGES/my_pck1.sql"), { 24 | "owner": "HR", 25 | "objectType": "PACKAGE", 26 | "objectType1": "PACKAGE_SPEC", 27 | "objectName": "my_pck1", 28 | "isSource": true, 29 | "isScript": false, 30 | }); 31 | }); 32 | it("script: should get object type script and schema", function () { 33 | assert.deepEqual(getObjectInfoFromPath("./scripts/HR/initial_dml.sql"), { 34 | "owner": "HR", 35 | "objectType": "script", 36 | "objectType1": "script", 37 | "objectName": "initial_dml", 38 | "isSource": false, 39 | "isScript": true, 40 | }); 41 | }); 42 | it("deploy: should get object type script", function () { 43 | assert.deepEqual(getObjectInfoFromPath("./Run.sql"), { 44 | "owner": undefined, 45 | "objectType": "script", 46 | "objectType1": "script", 47 | "objectName": "Run", 48 | "isSource": false, 49 | "isScript": true, 50 | }); 51 | }); 52 | it("deploy: should get object type deployScript", function () { 53 | assert.deepEqual(getObjectInfoFromPath("./deploy/HR.sql"), { 54 | "owner": "HR", 55 | "objectType": "deployScript", 56 | "objectType1": "deployScript", 57 | "objectName": "HR", 58 | "isSource": false, 59 | "isScript": true, 60 | }); 61 | }); 62 | it("deploy: should get object type deployScript - SQL ext", function () { 63 | assert.deepEqual(getObjectInfoFromPath("./deploy/HR.SQL"), { 64 | "owner": "HR", 65 | "objectType": "deployScript", 66 | "objectType1": "deployScript", 67 | "objectName": "HR", 68 | "isSource": false, 69 | "isScript": true, 70 | }); 71 | }); 72 | it("script: should get object type script - no dir", function () { 73 | assert.deepEqual(getObjectInfoFromPath("./file.sql"), { 74 | "owner": undefined, 75 | "objectType": "script", 76 | "objectType1": "script", 77 | "objectName": "file", 78 | "isSource": false, 79 | "isScript": true, 80 | }); 81 | }); 82 | 83 | it("src: should get object type body - windows sep", function () { 84 | assert.deepEqual(getObjectInfoFromPath(".\\src\\HR\\PACKAGE_BODIES\\my_pck1.sql"), { 85 | "owner": "HR", 86 | "objectType": "PACKAGE BODY", 87 | "objectType1": "PACKAGE_BODY", 88 | "objectName": "my_pck1", 89 | "isSource": true, 90 | "isScript": false, 91 | }); 92 | }); 93 | 94 | it("src: should get object with 'sql' in its name", function () { 95 | assert.deepEqual(getObjectInfoFromPath("./src/HR/PACKAGE_BODIES/UpdateMysqlFromOracle.sql"), { 96 | "owner": "HR", 97 | "objectType": "PACKAGE BODY", 98 | "objectType1": "PACKAGE_BODY", 99 | "objectName": "UpdateMysqlFromOracle", 100 | "isSource": true, 101 | "isScript": false, 102 | }); 103 | }); 104 | }); 105 | 106 | describe("#getPath from default structure", function () { 107 | it("should get dir structure for packages", function () { 108 | assert.deepEqual( 109 | getPathFromObjectInfo("HR", "PACKAGE BODY", "my_pck1"), 110 | "./src/HR/PACKAGE_BODIES/my_pck1.sql" 111 | ); 112 | assert.deepEqual( 113 | getPathFromObjectInfo("HR", "PACKAGE", "my_pck1"), 114 | "./src/HR/PACKAGES/my_pck1.sql" 115 | ); 116 | }); 117 | }); 118 | 119 | describe("#getObjectInfo with custom config", function () { 120 | // delete the cached module and reload with different config 121 | var decache = require("decache"); 122 | decache("../common/dbobject"); 123 | process.env["ORADEW_WS_CONFIG_PATH"] = __dirname + "/resources/oradewrc.json"; 124 | const getObjectInfoFromPath = require("../common/dbobject").getObjectInfoFromPath; 125 | 126 | it("src: should get object type body", function () { 127 | assert.deepEqual(getObjectInfoFromPath("./src/HR/pck/my_pck1-body.sql"), { 128 | "owner": "HR", 129 | "objectType": "PACKAGE BODY", 130 | "objectType1": "PACKAGE_BODY", 131 | "objectName": "my_pck1", 132 | "isSource": true, 133 | "isScript": false, 134 | }); 135 | }); 136 | it("src: should get object type body - SQL ext", function () { 137 | assert.deepEqual(getObjectInfoFromPath("./src/HR/pck/my_pck1-body.SQL"), { 138 | "owner": "HR", 139 | "objectType": "PACKAGE BODY", 140 | "objectType1": "PACKAGE_BODY", 141 | "objectName": "my_pck1", 142 | "isSource": true, 143 | "isScript": false, 144 | }); 145 | }); 146 | it("src: should get object type spec", function () { 147 | assert.deepEqual(getObjectInfoFromPath("./src/HR/pck/my_pck1-spec.sql"), { 148 | "owner": "HR", 149 | "objectType": "PACKAGE", 150 | "objectType1": "PACKAGE_SPEC", 151 | "objectName": "my_pck1", 152 | "isSource": true, 153 | "isScript": false, 154 | }); 155 | }); 156 | it("src: should get object type trigger", function () { 157 | assert.deepEqual(getObjectInfoFromPath("./src/HR/trigger-my_trg.sql"), { 158 | "owner": "HR", 159 | "objectType": "TRIGGER", 160 | "objectType1": "TRIGGER", 161 | "objectName": "my_trg", 162 | "isSource": true, 163 | "isScript": false, 164 | }); 165 | }); 166 | it("src: should get object type view", function () { 167 | assert.deepEqual(getObjectInfoFromPath("./src/HR/view-my_trg.sql"), { 168 | "owner": "HR", 169 | "objectType": "VIEW", 170 | "objectType1": "VIEW", 171 | "objectName": "my_trg", 172 | "isSource": true, 173 | "isScript": false, 174 | }); 175 | }); 176 | it("src: should get object type function without schema", function () { 177 | assert.deepEqual(getObjectInfoFromPath("./src/PROCEDURES/func1.sql"), { 178 | "owner": "", 179 | "objectType": "PROCEDURE", 180 | "objectType1": "PROCEDURE", 181 | "objectName": "func1", 182 | "isSource": true, 183 | "isScript": false, 184 | }); 185 | }); 186 | it("src: should get object type body from ABSOLUTE path", function () { 187 | assert.deepEqual(getObjectInfoFromPath(resolve("test/src/HR/FUNCTIONS/FUNC_TEST.sql")), { 188 | "owner": "HR", 189 | "objectType": "FUNCTION", 190 | "objectType1": "FUNCTION", 191 | "objectName": "FUNC_TEST", 192 | "isSource": true, 193 | "isScript": false, 194 | }); 195 | }); 196 | it("src: should get object type body from ABSOLUTE path - windows sep", function () { 197 | assert.deepEqual(getObjectInfoFromPath(resolve("test\\src\\HR\\FUNCTIONS\\FUNC_TEST.sql")), { 198 | "owner": "HR", 199 | "objectType": "FUNCTION", 200 | "objectType1": "FUNCTION", 201 | "objectName": "FUNC_TEST", 202 | "isSource": true, 203 | "isScript": false, 204 | }); 205 | }); 206 | it("src: should get object info from dir not starting with src with space", function () { 207 | assert.deepEqual(getObjectInfoFromPath(resolve("Data -base/SIURETE/TABS/T1/tabela_TAB.sql")), { 208 | "owner": "SIURETE", 209 | "objectType": "TABLE", 210 | "objectType1": "TABLE", 211 | "objectName": "tabela", 212 | "isSource": true, 213 | "isScript": false, 214 | }); 215 | }); 216 | }); 217 | 218 | describe("#getPath from custom structure", function () { 219 | // delete the cached module and reload with different config 220 | var decache = require("decache"); 221 | decache("../common/dbobject"); 222 | process.env["ORADEW_WS_CONFIG_PATH"] = __dirname + "/resources/oradewrc.json"; 223 | const getPathFromObjectInfo = require("../common/dbobject").getPathFromObjectInfo; 224 | it("should get custom dir structure for packages", function () { 225 | assert.deepEqual( 226 | getPathFromObjectInfo("HR", "PACKAGE BODY", "my_pck1"), 227 | "./src/HR/pck/my_pck1-body.sql" 228 | ); 229 | assert.deepEqual( 230 | getPathFromObjectInfo("HR", "PACKAGE", "my_pck1"), 231 | "./src/HR/pck/my_pck1-spec.sql" 232 | ); 233 | }); 234 | }); 235 | 236 | describe("#getSourceStructure", function () { 237 | it("should get default structure", function () { 238 | assert.deepEqual(getSourceStructure(), [ 239 | "./src/{schema-name}/PACKAGES", 240 | "./src/{schema-name}/PACKAGE_BODIES", 241 | "./src/{schema-name}/TRIGGERS", 242 | "./src/{schema-name}/TYPES", 243 | "./src/{schema-name}/TYPE_BODIES", 244 | "./src/{schema-name}/VIEWS", 245 | "./src/{schema-name}/FUNCTIONS", 246 | "./src/{schema-name}/PROCEDURES", 247 | "./src/{schema-name}/TABLES", 248 | "./src/{schema-name}/SYNONYMS", 249 | "./src/{schema-name}/APEX", 250 | ]); 251 | }); 252 | }); 253 | /* 254 | describe("#srcDir", function () { 255 | it("should get default source root directory", function () { 256 | assert.deepEqual(srcDir(), "./src"); 257 | }); 258 | }); 259 | */ 260 | -------------------------------------------------------------------------------- /src/extension/extension.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import * as vscode from "vscode"; 4 | 5 | import { ConfigurationManager } from "./common/configuration-manager"; 6 | import { setInitialized } from "./common/activation"; 7 | import { Telemetry } from "./common/telemetry"; 8 | import { GeneratorController } from "./controllers/generator-controller"; 9 | import { EnvironmentController } from "./controllers/environment-controller"; 10 | import { UserController } from "./controllers/user-controller"; 11 | import { FileController } from "./controllers/file-controller"; 12 | import { OradewTaskProvider, createCompileOnSaveTask } from "./task-provider"; 13 | import { OradewProcess } from "@Cli/process"; 14 | import { selectCurrentStatement } from "../extension/common/selection"; 15 | 16 | let oradewTaskProvider: vscode.Disposable | undefined; 17 | let environmentController: EnvironmentController; 18 | let userController: UserController; 19 | let generatorController: GeneratorController; 20 | let fileController: FileController; 21 | let oradewProcess: OradewProcess; 22 | 23 | // The extension deactivate method is asynchronous, so we handle the disposables ourselves instead of using extensonContext.subscriptions. 24 | const disposables: vscode.Disposable[] = []; 25 | 26 | export function activate(context: vscode.ExtensionContext) { 27 | let settings = ConfigurationManager.getInstance(); 28 | const { 29 | workspaceDir, 30 | chatty, 31 | workspaceConfigFile, 32 | databaseConfigFile, 33 | cliExecutable, 34 | cliCommand, 35 | envVariables, 36 | } = settings; 37 | 38 | // Reload if dbconfig created... 39 | let watcher = vscode.workspace.createFileSystemWatcher(databaseConfigFile); 40 | watcher.onDidCreate(() => { 41 | deactivate(); 42 | activate(context); 43 | }); 44 | 45 | // Set context inOradewProject 46 | setInitialized(); 47 | 48 | // Reactivate extension when settings.json changes as databaseConfigPath file 49 | // which is activation trigger can be defined in settings 50 | vscode.workspace.onDidChangeConfiguration(() => { 51 | settings.initializeSettings(); 52 | deactivate(); 53 | activate(context); 54 | }); 55 | 56 | const cliDir = context.extensionPath; 57 | const storageDir = context.storagePath || context.extensionPath; 58 | 59 | // OradewProcess is used for getting processEnv variable which contains the same env variables that are passed to Oradew CLI 60 | oradewProcess = new OradewProcess({ 61 | workspaceDir, 62 | cliDir, 63 | storageDir, 64 | dbConfigPath: databaseConfigFile, 65 | wsConfigPath: workspaceConfigFile, 66 | isSilent: !chatty, 67 | isColor: true, 68 | cliExecutable, 69 | cliCommand, 70 | envVariables, 71 | }); 72 | 73 | generatorController = new GeneratorController(); 74 | environmentController = new EnvironmentController(context); 75 | userController = new UserController(context, environmentController); 76 | 77 | oradewTaskProvider = vscode.tasks.registerTaskProvider( 78 | OradewTaskProvider.OradewType, 79 | new OradewTaskProvider(oradewProcess) 80 | ); 81 | 82 | fileController = new FileController(environmentController); 83 | 84 | // Internal command: env paramater selection in commands 85 | let cmdGetEnvironment = vscode.commands.registerCommand( 86 | "oradew.getEnvironment", 87 | environmentController.getEnvironment 88 | ); 89 | // Internal command: used for deploy task command 90 | let cmdPickEnvironment = vscode.commands.registerCommand( 91 | "oradew.pickEnvironment", 92 | environmentController.pickEnvironment 93 | ); 94 | 95 | let cmdSetDbEnvironment = vscode.commands.registerCommand( 96 | "oradew.setDbEnvironment", 97 | environmentController.setDbEnvironment 98 | ); 99 | 100 | let cmdClearDbEnvironment = vscode.commands.registerCommand( 101 | "oradew.clearDbEnvironment", 102 | environmentController.clearDbEnvironment 103 | ); 104 | 105 | // Internal command: function paramater selection in generator task 106 | let cmdGetGeneratorFunction = vscode.commands.registerCommand( 107 | "oradew.getGeneratorFunction", 108 | generatorController.getGeneratorFunction 109 | ); 110 | 111 | // Internal command: user paramater selection in commands 112 | let cmdGetUser = vscode.commands.registerCommand("oradew.getUser", userController.getUser); 113 | // Internal command: used for deploy task command 114 | let cmdPickUser = vscode.commands.registerCommand("oradew.pickUser", userController.pickUser); 115 | 116 | let cmdSetDbUser = vscode.commands.registerCommand("oradew.setDbUser", userController.setDbUser); 117 | 118 | // Internal command: file paramater selection in deploy command 119 | let cmdPickPackageScript = vscode.commands.registerCommand( 120 | "oradew.pickPackageScript", 121 | fileController.pickPackageScript 122 | ); 123 | 124 | /************** */ 125 | 126 | let cmdTaskGenerate = vscode.commands.registerCommand("oradew.generateTask", async () => { 127 | vscode.commands.executeCommand("workbench.action.tasks.runTask", "oradew: generator"); 128 | Telemetry.sendEvent("generateTask"); 129 | }); 130 | let cmdTaskInitProject = vscode.commands.registerCommand("oradew.initProjectTask", async () => { 131 | await vscode.commands.executeCommand("workbench.action.tasks.runTask", "oradew: init"); 132 | setInitialized(); 133 | Telemetry.sendEvent("initProjectTask"); 134 | }); 135 | let cmdTaskCreateProject = vscode.commands.registerCommand("oradew.createProjectTask", () => { 136 | vscode.commands.executeCommand("workbench.action.tasks.runTask", "oradew: create"); 137 | Telemetry.sendEvent("createProjectTask"); 138 | }); 139 | let cmdTaskCompile = vscode.commands.registerCommand("oradew.compileTask", () => { 140 | vscode.commands.executeCommand("workbench.action.tasks.runTask", "oradew: compile--changed"); 141 | Telemetry.sendEvent("compileTask"); 142 | }); 143 | let cmdTaskCompileAll = vscode.commands.registerCommand("oradew.compileAllTask", () => { 144 | vscode.commands.executeCommand("workbench.action.tasks.runTask", "oradew: compile"); 145 | Telemetry.sendEvent("compileAllTask"); 146 | }); 147 | let cmdTaskCompileFile = vscode.commands.registerCommand("oradew.compileFileTask", () => { 148 | vscode.commands.executeCommand("workbench.action.tasks.runTask", "oradew: compile--file"); 149 | Telemetry.sendEvent("compileFileTask"); 150 | }); 151 | let cmdTaskCompileObject = vscode.commands.registerCommand("oradew.compileObjectTask", () => { 152 | vscode.commands.executeCommand("workbench.action.tasks.runTask", "oradew: compile--object"); 153 | Telemetry.sendEvent("compileObjectTask"); 154 | }); 155 | let cmdTaskExport = vscode.commands.registerCommand("oradew.importTask", () => { 156 | vscode.commands.executeCommand("workbench.action.tasks.runTask", "oradew: import"); 157 | Telemetry.sendEvent("importTask"); 158 | }); 159 | let cmdTaskExportFile = vscode.commands.registerCommand("oradew.importFileTask", () => { 160 | vscode.commands.executeCommand("workbench.action.tasks.runTask", "oradew: import--file"); 161 | Telemetry.sendEvent("importFileTask"); 162 | }); 163 | let cmdTaskExportObject = vscode.commands.registerCommand("oradew.importObjectTask", () => { 164 | vscode.commands.executeCommand("workbench.action.tasks.runTask", "oradew: import--object"); 165 | Telemetry.sendEvent("importObjectTask"); 166 | }); 167 | let cmdTaskPackage = vscode.commands.registerCommand("oradew.packageTask", () => { 168 | vscode.commands.executeCommand("workbench.action.tasks.runTask", "oradew: package"); 169 | Telemetry.sendEvent("packageTask"); 170 | }); 171 | let cmdTaskPackageDelta = vscode.commands.registerCommand("oradew.packageDeltaTask", () => { 172 | vscode.commands.executeCommand("workbench.action.tasks.runTask", "oradew: package--delta"); 173 | Telemetry.sendEvent("packageDeltaTask"); 174 | }); 175 | let cmdTaskDeploy = vscode.commands.registerCommand("oradew.deployTask", () => { 176 | vscode.commands.executeCommand("workbench.action.tasks.runTask", "oradew: deploy"); 177 | Telemetry.sendEvent("deployTask"); 178 | }); 179 | let cmdTaskRunFile = vscode.commands.registerCommand("oradew.runFileTask", () => { 180 | vscode.commands.executeCommand("workbench.action.tasks.runTask", "oradew: run--file"); 181 | Telemetry.sendEvent("runFileTask"); 182 | }); 183 | let cmdTaskTest = vscode.commands.registerCommand("oradew.testTask", () => { 184 | vscode.commands.executeCommand("workbench.action.tasks.runTask", "oradew: test"); 185 | Telemetry.sendEvent("testTask"); 186 | }); 187 | let taskExec: Thenable; 188 | let cmdTaskCompileOnSave = vscode.commands.registerCommand("oradew.compileOnSaveTask", () => { 189 | // Get our task from active executions (terminate doesn't delete from taskexecution!) 190 | // let taskExec = vscode.tasks.taskExecutions.filter( 191 | // t => t.task.name === "compileOnSave" 192 | // )[0]; 193 | // If if doesn't exist - execute, otherwise terminate. 194 | if (!taskExec) { 195 | let _task = createCompileOnSaveTask(); 196 | taskExec = vscode.tasks.executeTask(_task); 197 | } else { 198 | taskExec.then((task) => task.terminate()); 199 | taskExec = null; 200 | } 201 | Telemetry.sendEvent("compileOnSaveTask"); 202 | }); 203 | 204 | let cmdSelectCurrentStatement = vscode.commands.registerCommand("oradew.selectCurrentStatement", function () { 205 | Telemetry.sendEvent("selectCurrentStatement"); 206 | return selectCurrentStatement(vscode.window.activeTextEditor); 207 | }); 208 | 209 | disposables.push(cmdSelectCurrentStatement); 210 | 211 | disposables.push(cmdTaskGenerate); 212 | disposables.push(cmdTaskInitProject); 213 | disposables.push(cmdTaskCreateProject); 214 | disposables.push(cmdTaskCompile); 215 | disposables.push(cmdTaskCompileAll); 216 | disposables.push(cmdTaskCompileFile); 217 | disposables.push(cmdTaskCompileObject); 218 | disposables.push(cmdTaskExport); 219 | disposables.push(cmdTaskExportFile); 220 | disposables.push(cmdTaskExportObject); 221 | disposables.push(cmdTaskPackage); 222 | disposables.push(cmdTaskPackageDelta); 223 | disposables.push(cmdTaskDeploy); 224 | disposables.push(cmdTaskRunFile); 225 | disposables.push(cmdTaskTest); 226 | disposables.push(cmdTaskCompileOnSave); 227 | 228 | // Internal 229 | disposables.push(cmdGetEnvironment); 230 | disposables.push(cmdPickEnvironment); 231 | disposables.push(cmdGetGeneratorFunction); 232 | disposables.push(cmdGetUser); 233 | disposables.push(cmdPickUser); 234 | disposables.push(cmdSetDbUser); 235 | disposables.push(cmdPickPackageScript); 236 | 237 | disposables.push(cmdSetDbEnvironment); 238 | disposables.push(cmdClearDbEnvironment); 239 | 240 | // Telemetry 241 | disposables.push(Telemetry.reporter); 242 | } 243 | 244 | // this method is called when your extension is deactivated 245 | export function deactivate(): void { 246 | if (oradewTaskProvider) { 247 | oradewTaskProvider.dispose(); 248 | } 249 | if (environmentController) { 250 | environmentController.dispose(); 251 | } 252 | if (userController) { 253 | userController.dispose(); 254 | } 255 | Telemetry.deactivate(); 256 | disposables.forEach((d) => d.dispose()); 257 | } 258 | -------------------------------------------------------------------------------- /src/cli/tasks/package.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import { pipe, map, mapValues, groupBy, clone, isEqual, uniq } from "lodash/fp"; 3 | 4 | import * as template from "gulp-template"; 5 | import * as map2 from "vinyl-map2"; 6 | import * as concat from "gulp-group-concat"; 7 | import * as noop from "gulp-noop"; 8 | import * as todo from "gulp-todo"; 9 | import { argv } from "yargs"; 10 | const gulp = require("gulp"); 11 | import * as convertEncoding from "gulp-convert-encoding"; 12 | import chalk from "chalk"; 13 | 14 | import { getObjectInfoFromPath, getPackageOutputPath } from "../common/dbobject"; 15 | import { getLogFilename, rootPrepend, rootRemove } from "../common/utility"; 16 | import { workspaceConfig as config } from "../common/config"; 17 | import { fromGlobsToFilesArray, fromStdoutToFilesArray, getGlobMatches } from "../common/globs"; 18 | import { 19 | getFirstCommitOnBranch, 20 | getCommitedFilesSincePoint, 21 | getCommitedFilesByCommits, 22 | } from "../common/git"; 23 | 24 | const timestampHeader = `-- File created: ${new Date()} with Oradew for VS Code`; 25 | 26 | const wrapDBObject = (code, file, done) => { 27 | const obj = getObjectInfoFromPath(file); 28 | 29 | const header = ` 30 | PROMPT *********************************************************** 31 | PROMPT ${obj.owner}: {${obj.objectType}} ${obj.objectName} 32 | PROMPT *********************************************************** 33 | `; 34 | 35 | // Append slash char (/) to execute block in sqlplus to Source files 36 | const ending = obj.isSource ? "\n/" : ""; 37 | 38 | done(null, header + code + ending); 39 | }; 40 | 41 | const packageSrcFromFile = ({ env = argv.env }) => { 42 | const input = config.get({ field: "package.input", env }); 43 | const pckEncoding = config.get({ field: "package.encoding", env }); 44 | const templating = config.get({ field: "package.templating", env }); 45 | const version = config.get({ field: "version.number", env }); 46 | const srcEncoding = config.get({ field: "source.encoding", env }); 47 | const exclude = config.get({ field: "package.exclude", env }); 48 | 49 | const templateObject = { 50 | config: config.get({ env }), 51 | data: { 52 | // Dates in format YYYY-MM-DD 53 | now: new Date().toISOString().substring(0, 10), 54 | }, 55 | }; 56 | 57 | const wrapScript = (code, file, done) => { 58 | const obj = getObjectInfoFromPath(file); 59 | const outputPath = getPackageOutputPath(obj); 60 | const outputFileName = path.basename(outputPath); 61 | 62 | // Log is spooled to "package.output" filename with prefix and .log extension 63 | // spool__Run.sql.log by default 64 | const deployPrepend = `${timestampHeader} 65 | SPOOL ${getLogFilename(outputFileName)} 66 | SET FEEDBACK ON 67 | SET ECHO OFF 68 | SET VERIFY OFF 69 | SET DEFINE OFF 70 | PROMPT INFO: Deploying version ${version} ... 71 | `; 72 | const deployAppend = ` 73 | COMMIT; 74 | SPOOL OFF 75 | `; 76 | done(null, deployPrepend + code + deployAppend); 77 | }; 78 | 79 | console.log( 80 | `Packaging version ${version}... 81 | Bundling files from "package.input": ${input} to deployment scripts:` 82 | ); 83 | 84 | // --Warn If src encoding is changed but package encoding default... 85 | if (srcEncoding !== pckEncoding && pckEncoding === "utf8") { 86 | console.warn( 87 | `${chalk.yellow( 88 | "WARN" 89 | )} source.encoding (${srcEncoding}) is different than package.encoding (${pckEncoding})` 90 | ); 91 | } 92 | 93 | // Output path is based on file info.. 94 | // "package.output": "./deploy/{schema-name}/run.sql" for example 95 | 96 | // First convert globs to actual file paths 97 | const inputFiles = fromGlobsToFilesArray(input, { 98 | ignore: exclude, 99 | }); 100 | 101 | // Then map to objects array of "input" file path and "output" file path 102 | // { 103 | // input: './src/SCHEMA1/PACKAGE_BODIES/PCK_1.sql', 104 | // output: './deploy/SCHEMA1/run.sql' 105 | // }, .... 106 | const mapFileToOutput = inputFiles.map((path) => { 107 | const obj = getObjectInfoFromPath(path); 108 | const outputPath = getPackageOutputPath(obj); 109 | return { 110 | input: path, 111 | output: outputPath, 112 | }; 113 | }); 114 | 115 | // gulp-group-concat input should look like this: 116 | // let outputMapping = { 117 | // './deploy/SCHEMA1/run.sql': ['src/SCHEMA1/PACKAGE_BODIES/PCK_1.sql'], 118 | // './deploy/SCHEMA2/run.sql': ['src/SCHEMA2/PACKAGES/PCK_1.sql', 'src/SCHEMA2/PACKAGE_BODIES/PCK_2.sql'] 119 | // }; 120 | // removeRoot as plugin doesn't work with ./'s 121 | let outputMapping = pipe( 122 | groupBy("output"), 123 | mapValues(pipe(map("input"), map(rootRemove))) 124 | )(mapFileToOutput); 125 | 126 | return ( 127 | gulp 128 | .src(inputFiles, { allowEmpty: true }) 129 | // First convert to utf8, run through the pipes and back to desired encoding 130 | .pipe(convertEncoding({ from: srcEncoding })) 131 | // Replace template variables, ex. config["config.variable"] 132 | .pipe(templating ? template(templateObject) : noop()) 133 | // Adds object header and ending to every file 134 | .pipe(map2(wrapDBObject)) 135 | // Concat files to one or more output files 136 | .pipe(concat(outputMapping)) 137 | // Wrap every script with header and ending 138 | .pipe(map2(wrapScript)) 139 | // Convert to desired encoding 140 | .pipe(convertEncoding({ to: pckEncoding })) 141 | .pipe(gulp.dest(".")) 142 | .on("end", () => 143 | console.log( 144 | `${Object.keys(outputMapping) 145 | .map((val) => `${val} ${chalk.green("packaged!")}`) 146 | .join("\n")}` 147 | ) 148 | ) 149 | ); 150 | }; 151 | 152 | const createDeployInputFromGit = async ({ 153 | env = argv.env, 154 | from = argv.from, 155 | commit = argv.commit, 156 | append = argv.append, 157 | }) => { 158 | try { 159 | console.log("Retrieving changed paths from git history..."); 160 | // Get changed file paths from git history 161 | let stdout; 162 | if (commit) { 163 | // Convert to array as parameters can be arrays (--commit a --commit b) 164 | let commits = [].concat(commit); 165 | console.log(`From commit(s): ${commits}`); 166 | stdout = await getCommitedFilesByCommits(commits); 167 | } else { 168 | let firstCommit = from || ((await getFirstCommitOnBranch()) as string).trim(); 169 | console.log(`Starting from commit: ${firstCommit} up to the head.`); 170 | stdout = await getCommitedFilesSincePoint(firstCommit); 171 | } 172 | 173 | const changedPaths = fromStdoutToFilesArray(stdout); 174 | 175 | // Exclude files that are not generally icluded 176 | const allSqlFiles = ["./**/*.sql"]; 177 | const all = getGlobMatches(allSqlFiles, changedPaths); 178 | // Exclude excludes by config 179 | let excludeGlobs = config.get({ field: "package.exclude", env }); 180 | const newInput = fromGlobsToFilesArray(all, { 181 | ignore: excludeGlobs, 182 | }).sort(); 183 | 184 | if (newInput.length === 0) { 185 | console.log(`No changed files found or no tagged commit to start from.`); 186 | return; 187 | } 188 | 189 | // Get saved package input from config file 190 | let savedInput = clone(config.get("package.input") || []).sort(); 191 | 192 | const inputToSave = append ? uniq(savedInput.concat(newInput)).sort() : newInput; 193 | 194 | if (isEqual(savedInput, inputToSave)) { 195 | console.log(`No new file paths added to package input.`); 196 | return; 197 | } 198 | 199 | // Save new input to config 200 | config.set("package.input", inputToSave); 201 | 202 | console.log( 203 | `${newInput.join("\n")} \n./oradewrc.json ${chalk.magenta("package.input updated.")}` 204 | ); 205 | } catch (error) { 206 | console.error(error.message); 207 | console.error("Probably no commits or tags. Create some."); 208 | } 209 | }; 210 | 211 | const generateBOLContent = function (paths) { 212 | // Create Db objects from paths array 213 | let dbo = paths.map((path) => { 214 | let obj = getObjectInfoFromPath(path); 215 | let exclude = path.startsWith("!"); 216 | // return {...obj, exclude}; 217 | return Object.assign({}, obj, { exclude }); 218 | }); 219 | 220 | // Group to structure: 221 | // { "owner": { "objectType": [ 'objectName' ] }} 222 | let o = pipe( 223 | groupBy("owner"), 224 | mapValues(pipe(groupBy("objectType"), mapValues(map("objectName")))) 225 | )(dbo); 226 | 227 | // Build markdown 228 | let c = ""; 229 | for (let owner in o) { 230 | c = `${c}### ${owner}\n`; 231 | for (let i_objectType in o[owner]) { 232 | let objectType = o[owner][i_objectType]; 233 | c = `${c}#### ${i_objectType}\n`; 234 | objectType.forEach((val) => { 235 | c = `${c}- ${val}\n`; 236 | }); 237 | c = `${c}\n`; 238 | } 239 | } 240 | return c; 241 | }; 242 | 243 | const makeBillOfLading = ({ env = argv.env }) => { 244 | const file = path.join(__dirname, "/templates/BOL*.md"); 245 | 246 | // Generate change log from deploy input array 247 | const input = config.get({ field: "package.input", env }); 248 | const excludeGlobs = config.get({ field: "package.exclude", env }); 249 | const all = fromGlobsToFilesArray(input, { 250 | ignore: excludeGlobs, 251 | }); 252 | 253 | const content = generateBOLContent(all); // await git.getChangeLog(); 254 | const templateObject = { 255 | config: config.get({ env }), 256 | data: { 257 | // Dates in format YYYY-MM-DD 258 | now: new Date().toISOString().substring(0, 10), 259 | }, 260 | }; 261 | const outputFile = config.get({ field: "package.output", env }); 262 | // OutputFile can contain {schema-user} varibable... 263 | // Get first level directory for now 264 | const outputDirectory = rootPrepend(path.dirname(outputFile).split(path.posix.sep)[1]); 265 | 266 | // Add content to template object 267 | templateObject.data["content"] = content; 268 | return ( 269 | gulp 270 | .src(file) 271 | .pipe(template(templateObject)) 272 | // Prepend timestamp header 273 | .pipe( 274 | map2((code, file, done) => { 275 | done(null, `\n` + code); 276 | }) 277 | ) 278 | .pipe(gulp.dest(outputDirectory)) 279 | .on("end", () => console.log(`${outputDirectory}/BOL.md created`)) 280 | ); 281 | }; 282 | 283 | const extractTodos = ({ env = argv.env }) => { 284 | const src = config.get({ field: "source.input", env }); 285 | 286 | return gulp 287 | .src(src, { base: "./" }) 288 | .pipe(todo()) 289 | .pipe(todo.reporter("vscode")) 290 | .pipe(gulp.dest("./")) 291 | .on("end", () => console.log("./TODO.md created")); 292 | }; 293 | 294 | export const packageTask = async ({ 295 | delta = argv.delta, 296 | from = argv.from, 297 | commit = argv.commit, 298 | append = argv.append, 299 | }) => { 300 | // If delta or from or commit, first populate package input 301 | let tasks = [ 302 | ...[delta || from || commit ? createDeployInputFromGit : []], 303 | packageSrcFromFile, 304 | extractTodos, 305 | makeBillOfLading, 306 | // (done) => { console.log(chalk.green("done.")); done(); } 307 | ]; 308 | return gulp.series(...tasks)(); 309 | }; 310 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Oradew - Oracle (PL/SQL) Development Workspace 2 | 3 | [![Build Status](https://dev.azure.com/mickeypearce0384/Oradew/_apis/build/status/mickeypearce.oradew-vscode)](https://dev.azure.com/mickeypearce0384/Oradew/_build/latest?definitionId=1) 4 | 5 | This extension enables you to develop your Oracle (PL/SQL) project in Visual Studio Code. It enables you to: 6 | 7 | - Manage PL/SQL source code with version control (Git) 8 | - Compile files and Run statements with ORA errors problem matching 9 | - Package files into a single SQL deployment script 10 | - Deploy to multiple environments in one click 11 | 12 | ![Compile Demo](demo.gif) 13 | 14 | ## Installation 15 | 16 | Install the extension and start with `Oradew: Initialize Workspace` command. 17 | 18 | ### Prerequisites (local) 19 | 20 | - Node.js 14.6, or later 21 | - Git 22 | - SQL\*Plus or SQLcl 23 | 24 | ### Container 25 | 26 | An Oradew VS Code development container is available [here](https://github.com/mickeypearce/oradew-vscode-container) with all prerequisites preinstalled. 27 | 28 | ## Workspace 29 | 30 | A default workspace structure: 31 | 32 | ``` 33 | ./deploy Deployment package 34 | ./scripts SQL Scripts (DDL, DML, files, etc) 35 | ./src Source with PL/SQL objects 36 | ./test Unit tests 37 | dbconfig.json DB environment configuration (required) 38 | oradewrc.json Workspace configuration 39 | ``` 40 | 41 | ## Commands 42 | 43 | ### Basic Workflow 44 | 45 | **Setup** 46 | 47 | - `Initialize Workspace` - Create configuration files (`dbconfig.json` and `oradewrc.json`) and a clean workspace structure 48 | - `Create Source from DB` - Create Source files from existing DB objects 49 | 50 | **Build** 51 | 52 | - `Compile Changed Files` (F6) - Compile changed Source files in working tree to DB 53 | - `Compile Current File` - Compile Source object (or any file with a single SQL or PL/SQL statement) 54 | - `Run Current File as Script` (F5) - Execute a SQL script (with SQL\*Plus or SQLcl) 55 | - `Run Current Statement` (Ctrl+Enter) - Execute a SQL query or PL/SQL statement with autoCommit and dbms_output enabled 56 | 57 | **Install** 58 | 59 | - `Package` (F9) - Generate a deployment script from project files 60 | - `Deploy...` - Run deployment script on the selected environment (with SQL\*Plus or SQLcl) 61 | 62 | ### Additional 63 | 64 | - `Import Source` (Shift+F6) - Walk Source files and import matching DB object from DB 65 | - `Import Current File` - Import matching DB object 66 | - `Import Selected Object` - Import new object from DB into a Source file 67 | - `Compile Source` - Walk Source files and compile each file to DB 68 | - `Toggle Compile Watch` - Start/End compilaton on save. Compile working tree automatically whenever a Source file changes. 69 | - `Package Delta` (Shift+F9) - Package current version changes. Command extracts changed file paths from Git history - starting from latest tagged commit (last version) up to the last commit (HEAD) 70 | - `Run tests` - Compile unit test files 71 | - `Generate...` Generate PL/SQL code with a code generator 72 | 73 | ### Switch environment 74 | 75 | - `Set DB Environment` - Set DB environment that will be used when executing commands. Pick list is generated from `dbconfig.json`. The default value is `DEV`. 76 | - `Clear DB Environment` - Set DB environment to ``. This means that you choose DB environment every time you execute DB command. 77 | 78 | ### Switch user 79 | 80 | - `Set DB User` - Set DB user that will be used when executing commands. Pick list is generated from `dbconfig.json`. The default value is `` (user extracted from file path). 81 | 82 | ## Configuration 83 | 84 | ### DB environment 85 | 86 | Only `dbconfig.json` file is required for the workspace activation and successful connection with your database. Multiple DB environments with multi-users per environment are supported. 87 | 88 | You can create `dbconfig.json` manually in the root folder of your workspace or execute `Init Workspace` command. 89 | 90 | A minimal example with `DEV` environment and a single schema-user follows: 91 | 92 | ```json 93 | { 94 | "DEV": { 95 | "connectString": "localhost/orclpdb", 96 | "users": [{ "user": "hr", "password": "welcome" }] 97 | } 98 | } 99 | ``` 100 | 101 | For credentials stored in oracle wallets, or auto-login oracle wallets use `"walletConnectString"` instead of `"password"` property. [(more info)](https://github.com/mickeypearce/oradew-vscode/wiki/wallets) 102 | 103 | ### Workspace 104 | 105 | Workspace supports a base configuration file (`oradewrc.json`) and an additional configuration file for each environment (`oradewrc.DEV.json`, `oradewrc.TEST.json`, etc.). The base configuration settings apply to all environments, unless an environment specific configuration file exists that extends the base. 106 | 107 | Default values will be used in the case workspace configuration file is not present. The following settings are available (defaults): 108 | 109 | ```json 110 | { 111 | "compile.warnings": "NONE", 112 | "compile.force": true, 113 | "compile.stageFile": false, 114 | "source.input": ["./src/**/*.sql"], 115 | "source.encoding": "utf8", 116 | "source.pattern": { 117 | "packageSpec": "./src/{schema-name}/PACKAGES/{object-name}.sql", 118 | "packageBody": "./src/{schema-name}/PACKAGE_BODIES/{object-name}.sql", 119 | "trigger": "./src/{schema-name}/TRIGGERS/{object-name}.sql", 120 | "typeSpec": "./src/{schema-name}/TYPES/{object-name}.sql", 121 | "typeBody": "./src/{schema-name}/TYPE_BODIES/{object-name}.sql", 122 | "view": "./src/{schema-name}/VIEWS/{object-name}.sql", 123 | "function": "./src/{schema-name}/FUNCTIONS/{object-name}.sql", 124 | "procedure": "./src/{schema-name}/PROCEDURES/{object-name}.sql", 125 | "table": "./src/{schema-name}/TABLES/{object-name}.sql", 126 | "synonym": "./src/{schema-name}/SYNONYMS/{object-name}.sql", 127 | "apex": "./src/{schema-name}/APEX/{object-name}.sql" 128 | }, 129 | "import.ease": false, 130 | "import.getDdlFunction": "dbms_metadata.get_ddl", 131 | "package.input": ["./scripts/**/initial*.sql", "./src/**/*.sql", "./scripts/**/final*.sql"], 132 | "package.exclude": ["./scripts/**/+(file|run)*.sql"], 133 | "package.output": "./deploy/{schema-name}.sql", 134 | "package.encoding": "utf8", 135 | "package.templating": false, 136 | "test.input": ["./test/**/*.test.sql"], 137 | "version.number": "0.0.1", 138 | "version.description": "New feature", 139 | "version.releaseDate": "2099-01-01" 140 | } 141 | ``` 142 | 143 | - `compile.warnings` - PL/SQL compilation warning scopes. The default value is `NONE`. 144 | - `compile.force` - Ignore conflict detection. If object you are compiling has changed on DB (has a different DDL timestamp), you are prevented from overriding the changes with a merge step. Resolve merge conflicts if necessary and than compile again. Set to `false` to turn on conflict detection. The default value is `true`. 145 | - `compile.stageFile` - Automatically stage file after succesfully compiled (git add). Default value is `false`. 146 | - `source.input` - Glob pattern for Source files. Used by general `Compile`, `Import` and `Create` command to match files that are targeted. For example, to compile only "HR" schema and exclude "HR" tables, set: ["./src/HR/**/\*.sql", "!./src/HR/TABLES/\*.sql"]. 147 | - `source.encoding` - Encoding of Source files. (ex.: "utf8", "win1250", ...) The default value is `utf8`. 148 | - `source.pattern` - Define custom source structure by specifing mappings for different object types. Ommited object types won't get exported. Path variables {schema-name} can also be omitted. Single schema pck ex: {"packageSpec": "./src/pck/{object-name}-spec.sql", "packageBody": "./src/pck/{object-name}-body.sql"} 149 | - `import.ease` - When set to `true`, it will import only DB objects that changed on DB in comparision to project Source files. Default value is `false`. 150 | - `import.getDdlFunction` - Custom Get_DDL function name. Use your own DB function to customize import of object's DDL. It is used by `Import` commands. The default value is `DBMS_METADATA.GET_DDL`. 151 | - `package.input` - Array of globs for packaging files into deployment script file (package.output). `Package Delta` command populates it with changed file paths. 152 | - `package.output` - Deployment script file path. Script is created with `Package` command by bundling input Source files and Scripts; wrapped with "SPOOL log" and "COMMIT;". Path variable {schema-name} is used to group files by schema into separate scripts. It can be used anywhere in the path or omitted altogether. 153 | - `package.exclude` - Array of globs for excluding files from packaging. Scripts that start with "file" or "run" by default. 154 | - `package.encoding` - Encoding of deployment script file. (ex.: "utf8", "win1250", ...) The default value is `utf8`. 155 | - `package.templating` - Turn on templating of config variables. Use existing ('\${config[\"version.releaseDate\"]}') or declare a new variable in config file and than use it in your sql file. Variables are replaced with actual values during packaging. The default value is `false`. 156 | - `test.input` - Array of globs for matching test files. Executed with `Run tests` command. 157 | - `version.number` - Version number 158 | - `version.description` - Version description 159 | - `version.releaseDate` - Version release date 160 | 161 | ### APEX applications compatibility 162 | 163 | Minimum required APEX version is 5.1.4, otherwise APEX applications are not imported. 164 | 165 | ### Code Generator 166 | 167 | Write a PL/SQL function on database, add a definition to configuration file (`oradewrc-generate.json`) and then use `Generate...` command to execute your generator. A new file with the generated content will be created in your workspace. 168 | 169 | #### Function specificaton 170 | 171 | The generator function on DB has to have the following specification (parameters): 172 | 173 | ```sql 174 | FUNCTION updateStatement( 175 | object_type IN VARCHAR2, -- derived from path of currently open ${file} 176 | name IN VARCHAR2, -- derived from path of currently open ${file} 177 | schema IN VARCHAR2, -- derived from path of currently open ${file} 178 | selected_object IN VARCHAR2 -- ${selectedText} in editor 179 | ) RETURN CLOB; 180 | ``` 181 | 182 | Function parameters are derived from currently opened file and selected text in your editor when the generator is executed. The first three parameters (`object_type`, `name`, `schema`) are deconstructed from the path of the currently opened \${file} as `./src/${schema}/${object_type}/${name}.sql`, whereas `selected_object` is the currently \${selectedText} in editor. 183 | 184 | #### Generator definition 185 | 186 | Create a configuration file `oradewrc-generate.json` in your workspace root with a definiton: 187 | 188 | ```json 189 | "generator.define": [ 190 | { 191 | "label": "Update Statement", 192 | "function": "utl_generate.updateStatement", 193 | "description": "Generate update statement for a table" 194 | } 195 | ] 196 | ``` 197 | 198 | The `label` and `function` properties are required for a generator to be succesfully defined (`description` is optional). Use `output` property to specify a file path of the generated content (also optional). If the `output` is omitted a file with unique filename will be created in ./scripts directory. 199 | 200 | NOTE: Generators have a separate repository over here: [Oradew Code Generators](https://github.com/mickeypearce/oradew-generators). Your contributions are welcomed! 201 | 202 | ## Command Line 203 | 204 | You can execute Oradew commands from the command line (Oradew CLI). 205 | 206 | ### Installation 207 | 208 | ```bash 209 | # From the extension folder %USERPROFILE%/.vscode/extensions/mp.oradew-vscode-... 210 | > npm run install-cli 211 | ``` 212 | 213 | This will install `oradew` CLI command. 214 | 215 | If you are installing from the repository you must first compile the source code: 216 | 217 | ```bash 218 | > git clone https://github.com/mickeypearce/oradew-vscode 219 | > npm start && npm run install-cli 220 | ``` 221 | 222 | ### Usage 223 | 224 | ```bash 225 | # (Use `oradew --help` for command options.) 226 | > oradew --help 227 | Usage: oradew [options] 228 | 229 | Commands: 230 | init [options] Initialize a new workspace 231 | create [options] Create Source files from DB objects 232 | compile [options] Compile Source files to DB 233 | import [options] Import Source files from DB 234 | package [options] Package files to deployment script 235 | deploy|run [options] Run script (with SQL*Plus or SQLcl) 236 | test [options] Run unit tests 237 | generate [options] Code generator 238 | watch [options] Compile when Source file changes 239 | ``` 240 | 241 | ### Example 242 | 243 | ```bash 244 | # Create simple dbconfig file and run "Hello World" on DEV environment 245 | > echo {"DEV": {"connectString": "localhost/orclpdb", "users": [{"user": "hr", "password": "welcome"}]}} > dbconfig.json 246 | > oradew compile --object "select 'world' as hello from dual" 247 | 248 | # Simple Dev Workflow 249 | > oradew watch 250 | > oradew package 251 | > oradew deploy --env TEST 252 | ``` 253 | 254 | ## Aditional information 255 | 256 | [How-tos](https://github.com/mickeypearce/oradew-vscode/wiki/howtos) 257 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oradew-vscode", 3 | "displayName": "Oradew", 4 | "description": "Develop Oracle (PL/SQL) project in VS Code.", 5 | "version": "0.3.29", 6 | "publisher": "mp", 7 | "engines": { 8 | "vscode": "^1.61.0" 9 | }, 10 | "license": "MIT", 11 | "categories": [ 12 | "Other" 13 | ], 14 | "bin": { 15 | "oradew": "./dist/oradew.js" 16 | }, 17 | "keywords": [ 18 | "plsql", 19 | "PL/SQL", 20 | "oracle", 21 | "workspace" 22 | ], 23 | "homepage": "https://github.com/mickeypearce/oradew-vscode", 24 | "repository": { 25 | "type": "git", 26 | "url": "https://github.com/mickeypearce/oradew-vscode.git" 27 | }, 28 | "bugs": { 29 | "url": "https://github.com/mickeypearce/oradew-vscode/issues" 30 | }, 31 | "icon": "dist/images/oradew_icon_128.png", 32 | "activationEvents": [ 33 | "workspaceContains:dbconfig.json", 34 | "onCommand:oradew.*", 35 | "*" 36 | ], 37 | "main": "./dist/extension", 38 | "contributes": { 39 | "menus": { 40 | "editor/title": [ 41 | { 42 | "command": "oradew.compileFileTask", 43 | "when": "inOradewProject && resourceExtname =~ /.sql|.SQL/", 44 | "group": "navigation@1", 45 | "alt": "oradew.compileObjectTask" 46 | }, 47 | { 48 | "command": "oradew.runFileTask", 49 | "when": "inOradewProject && resourceExtname =~ /.sql|.SQL/", 50 | "group": "navigation@2" 51 | } 52 | ], 53 | "editor/context": [ 54 | { 55 | "command": "oradew.compileFileTask", 56 | "when": "inOradewProject && resourceExtname =~ /.sql|.SQL/ && !editorHasSelection", 57 | "group": "Oradew" 58 | }, 59 | { 60 | "command": "oradew.importFileTask", 61 | "when": "inOradewProject && resourceExtname =~ /.sql|.SQL/ && !editorHasSelection", 62 | "group": "Oradew" 63 | }, 64 | { 65 | "command": "oradew.importObjectTask", 66 | "when": "inOradewProject && resourceExtname =~ /.sql|.SQL/ && editorHasSelection", 67 | "group": "Oradew" 68 | }, 69 | { 70 | "command": "oradew.compileObjectTask", 71 | "when": "inOradewProject && resourceExtname =~ /.sql|.SQL/", 72 | "group": "Oradew" 73 | }, 74 | { 75 | "command": "oradew.generateTask", 76 | "when": "inOradewProject && resourceExtname =~ /.sql|.SQL/ && editorHasSelection", 77 | "group": "Oradew" 78 | } 79 | ], 80 | "explorer/context": [ 81 | { 82 | "command": "oradew.initProjectTask", 83 | "group": "Oradew@1", 84 | "category": "Oradew" 85 | }, 86 | { 87 | "command": "oradew.compileTask", 88 | "group": "Oradew@2", 89 | "category": "Oradew", 90 | "when": "inOradewProject" 91 | }, 92 | { 93 | "command": "oradew.testTask", 94 | "group": "Oradew@3", 95 | "category": "Oradew", 96 | "when": "inOradewProject" 97 | }, 98 | { 99 | "command": "oradew.packageTask", 100 | "group": "Oradew@4", 101 | "category": "Oradew", 102 | "when": "inOradewProject" 103 | } 104 | ] 105 | }, 106 | "keybindings": [ 107 | { 108 | "command": "oradew.compileObjectTask", 109 | "key": "ctrl+enter", 110 | "when": "inOradewProject" 111 | }, 112 | { 113 | "command": "oradew.runFileTask", 114 | "key": "f5", 115 | "when": "inOradewProject" 116 | }, 117 | { 118 | "command": "oradew.importTask", 119 | "key": "shift+f6", 120 | "when": "inOradewProject" 121 | }, 122 | { 123 | "command": "oradew.compileTask", 124 | "key": "f6", 125 | "when": "inOradewProject" 126 | }, 127 | { 128 | "command": "oradew.packageTask", 129 | "key": "f9", 130 | "when": "inOradewProject" 131 | }, 132 | { 133 | "command": "oradew.packageDeltaTask", 134 | "key": "shift+f9", 135 | "when": "inOradewProject" 136 | }, 137 | { 138 | "command": "workbench.action.tasks.reRunTask", 139 | "key": "ctrl+alt+enter", 140 | "when": "inOradewProject" 141 | } 142 | ], 143 | "problemMatchers": [ 144 | { 145 | "name": "oracle-plsql", 146 | "owner": "external", 147 | "fileLocation": [ 148 | "absolute" 149 | ], 150 | "pattern": [ 151 | { 152 | "regexp": "^.*\\$(.*)$", 153 | "file": 1 154 | }, 155 | { 156 | "regexp": "^(\\d+)\\/(\\d+)\\s+(WARNING|ERROR|INFO)\\s+(.*)$", 157 | "line": 1, 158 | "column": 2, 159 | "severity": 3, 160 | "message": 4, 161 | "loop": true 162 | } 163 | ], 164 | "background": { 165 | "activeOnStart": true, 166 | "beginsPattern": "^.*Starting compilation...$", 167 | "endsPattern": "Compilation complete." 168 | } 169 | } 170 | ], 171 | "commands": [ 172 | { 173 | "command": "oradew.setDbEnvironment", 174 | "title": "Set DB Enviroment", 175 | "category": "Oradew" 176 | }, 177 | { 178 | "command": "oradew.clearDbEnvironment", 179 | "title": "Clear DB Enviroment", 180 | "category": "Oradew" 181 | }, 182 | { 183 | "command": "oradew.setDbUser", 184 | "title": "Set DB User", 185 | "category": "Oradew" 186 | }, 187 | { 188 | "command": "oradew.initProjectTask", 189 | "title": "Initialize Workspace", 190 | "category": "Oradew" 191 | }, 192 | { 193 | "command": "oradew.createProjectTask", 194 | "title": "Create Source from DB", 195 | "category": "Oradew" 196 | }, 197 | { 198 | "command": "oradew.runFileTask", 199 | "title": "Run Current File As Script", 200 | "category": "Oradew", 201 | "icon": { 202 | "light": "dist/images/run_icon.png", 203 | "dark": "dist/images/run_icon.png" 204 | } 205 | }, 206 | { 207 | "command": "oradew.compileTask", 208 | "title": "Compile Changed Files", 209 | "category": "Oradew" 210 | }, 211 | { 212 | "command": "oradew.compileAllTask", 213 | "title": "Compile Source", 214 | "category": "Oradew" 215 | }, 216 | { 217 | "command": "oradew.compileFileTask", 218 | "title": "Compile Current File", 219 | "category": "Oradew", 220 | "icon": { 221 | "light": "dist/images/compile_icon.png", 222 | "dark": "dist/images/compile_icon_alt.png" 223 | } 224 | }, 225 | { 226 | "command": "oradew.compileObjectTask", 227 | "title": "Run Current Statement", 228 | "category": "Oradew", 229 | "icon": { 230 | "light": "dist/images/compile_icon_alt.png", 231 | "dark": "dist/images/compile_icon.png" 232 | } 233 | }, 234 | { 235 | "command": "oradew.compileOnSaveTask", 236 | "title": "Toggle Compile Watch", 237 | "category": "Oradew" 238 | }, 239 | { 240 | "command": "oradew.importTask", 241 | "title": "Import Source", 242 | "category": "Oradew" 243 | }, 244 | { 245 | "command": "oradew.importFileTask", 246 | "title": "Import Current File", 247 | "category": "Oradew" 248 | }, 249 | { 250 | "command": "oradew.importObjectTask", 251 | "title": "Import Selected Object", 252 | "category": "Oradew" 253 | }, 254 | { 255 | "command": "oradew.testTask", 256 | "title": "Run Tests", 257 | "category": "Oradew" 258 | }, 259 | { 260 | "command": "oradew.packageTask", 261 | "title": "Package", 262 | "category": "Oradew" 263 | }, 264 | { 265 | "command": "oradew.packageDeltaTask", 266 | "title": "Package Delta", 267 | "category": "Oradew" 268 | }, 269 | { 270 | "command": "oradew.deployTask", 271 | "title": "Deploy...", 272 | "category": "Oradew" 273 | }, 274 | { 275 | "command": "oradew.generateTask", 276 | "title": "Generate...", 277 | "category": "Oradew" 278 | } 279 | ], 280 | "jsonValidation": [ 281 | { 282 | "fileMatch": "dbconfig.json", 283 | "url": "./dist/schemas/dbconfig-schema.json" 284 | }, 285 | { 286 | "fileMatch": "oradewrc.json", 287 | "url": "./dist/schemas/oradewrc-schema.json" 288 | }, 289 | { 290 | "fileMatch": "oradewrc.*.json", 291 | "url": "./dist/schemas/oradewrc-schema.json" 292 | }, 293 | { 294 | "fileMatch": "oradewrc-generate.json", 295 | "url": "./dist/schemas/oradewrc-generate-schema.json" 296 | } 297 | ], 298 | "taskDefinitions": [ 299 | { 300 | "type": "oradew", 301 | "required": [ 302 | "name", 303 | "params" 304 | ], 305 | "properties": { 306 | "name": { 307 | "type": "string", 308 | "description": "The Oradew task name" 309 | }, 310 | "params": { 311 | "type": "array", 312 | "description": "The Oradew task parameters" 313 | } 314 | } 315 | } 316 | ], 317 | "configuration": [ 318 | { 319 | "type": "object", 320 | "title": "Oradew Settings", 321 | "properties": { 322 | "oradew.generatorConfigFile": { 323 | "type": "string", 324 | "default": "${workspaceFolder}/oradewrc-generate.json", 325 | "required": false, 326 | "description": "The path to the code generator configuration file" 327 | }, 328 | "oradew.workspaceConfigFile": { 329 | "type": "string", 330 | "default": "${workspaceFolder}/oradewrc.json", 331 | "required": false, 332 | "description": "The path to the workspace configuration file" 333 | }, 334 | "oradew.databaseConfigFile": { 335 | "type": "string", 336 | "default": "${workspaceFolder}/dbconfig.json", 337 | "required": false, 338 | "description": "The path to the database configuration file" 339 | }, 340 | "oradew.chatty": { 341 | "type": "boolean", 342 | "default": false, 343 | "required": false, 344 | "description": "Enable informational output (in Gulp tasks)" 345 | }, 346 | "oradew.cliExecutable": { 347 | "type": "string", 348 | "default": "sql", 349 | "required": false, 350 | "description": "The path to CLI executable for Database (SQL*Plus or SQLcl)" 351 | }, 352 | "oradew.cliCommand": { 353 | "type": "string", 354 | "default": "null", 355 | "required": false, 356 | "description": "CLI command literal. Eg: \"exit | sql -S ${connString} @${filename}\"" 357 | }, 358 | "oradew.envVariables": { 359 | "type": "object", 360 | "default": {}, 361 | "description": "Set environment variables. Eg: ORACLE_HOME, NLS_LANG..." 362 | } 363 | } 364 | } 365 | ] 366 | }, 367 | "scripts": { 368 | "vscode:prepublish": "webpack --mode production", 369 | "start": "npm ci && webpack --mode development", 370 | "start:watch": "npm start -- --watch", 371 | "compile": "npm run precompile && tsc -p ./", 372 | "watch": "npm run precompile && tsc -watch -p ./", 373 | "test": "npm run compile && node out/src/extension/test/runTest", 374 | "utest": "npm run compile && node ./node_modules/mocha/bin/mocha out/src/cli/test/*test.js", 375 | "build": "vsce package", 376 | "publish": "vsce publish", 377 | "precompile": "node precompile.js", 378 | "install-cli": "npm install -g ./ --ignore-scripts --no-optional", 379 | "fix": "rd node_modules /S /Q & del package-lock.json & npm install --no-optional", 380 | "format": "prettier --check \"src/**/*.{js,ts}\"", 381 | "format:fix": "npm run format -- --write", 382 | "tslint": "tslint --project tsconfig.json --config tslint.json", 383 | "tslint:fix": "npm run tslint -- --fix" 384 | }, 385 | "devDependencies": { 386 | "@types/globby": "^9.1.0", 387 | "@types/gulp": "^4.0.9", 388 | "@types/lodash": "^4.14.171", 389 | "@types/mocha": "^7.0.2", 390 | "@types/node": "^18.16.00", 391 | "@types/oracledb": "^5.2.5", 392 | "@types/vscode": "^1.61.0", 393 | "@types/yargs": "^15.0.14", 394 | "@vscode/test-electron": "^2.3.2", 395 | "@vscode/vsce": "^2.19.0", 396 | "clean-webpack-plugin": "^4.0.0", 397 | "cli-table": "^0.3.6", 398 | "command-exists": "^1.2.9", 399 | "commander": "^4.0.0", 400 | "copy-webpack-plugin": "^6.4.1", 401 | "decache": "^4.6.0", 402 | "del": "4.0.0", 403 | "fs-extra": "^9.1.0", 404 | "globby": "^11.0.4", 405 | "gulp-convert-encoding": "^2.1.0", 406 | "gulp-data": "^1.3.1", 407 | "gulp-group-concat": "^1.1.6", 408 | "gulp-multi-dest": "^1.3.7", 409 | "gulp-noop": "^1.0.1", 410 | "gulp-template": "^5.0.0", 411 | "inquirer": "^6.5.2", 412 | "lodash": "^4.17.21", 413 | "mocha": "^10.2.0", 414 | "multimatch": "^4.0.0", 415 | "nedb": "^1.8.0", 416 | "prettier": "2.0.5", 417 | "promisify-child-process": "^4.1.1", 418 | "ts-loader": "^9.4.2", 419 | "tslint": "6.1.2", 420 | "typescript": "^3.9.10", 421 | "typescript-tslint-plugin": "^0.5.5", 422 | "vinyl-map2": "^1.2.1", 423 | "vscode-extension-telemetry": "^0.1.7", 424 | "webpack": "^5.85.0", 425 | "webpack-cli": "^5.1.1", 426 | "yargs": "^15.4.1" 427 | }, 428 | "dependencies": { 429 | "gulp": "^4.0.2", 430 | "gulp-git": "^2.10.1", 431 | "gulp-todo": "^5.5.0", 432 | "oracledb": "6.0.0" 433 | } 434 | } --------------------------------------------------------------------------------