@mikeyt23/node-cli-utils 1.4.1 → 2.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -87
- package/dist/cjs/NodeCliUtilsConfig.d.ts +21 -0
- package/dist/cjs/NodeCliUtilsConfig.d.ts.map +1 -0
- package/dist/cjs/NodeCliUtilsConfig.js +41 -0
- package/dist/cjs/TarballUtility.d.ts +53 -0
- package/dist/cjs/TarballUtility.d.ts.map +1 -0
- package/dist/cjs/TarballUtility.js +149 -0
- package/dist/cjs/certUtils.d.ts +30 -0
- package/dist/cjs/certUtils.d.ts.map +1 -0
- package/dist/cjs/certUtils.js +219 -0
- package/dist/cjs/dbMigrationUtils.d.ts +39 -0
- package/dist/cjs/dbMigrationUtils.d.ts.map +1 -0
- package/dist/cjs/dbMigrationUtils.js +205 -0
- package/dist/cjs/dotnetUtils.d.ts +25 -0
- package/dist/cjs/dotnetUtils.d.ts.map +1 -0
- package/dist/cjs/dotnetUtils.js +59 -0
- package/dist/cjs/esmSpecific.d.mts +2 -0
- package/dist/cjs/esmSpecific.d.mts.map +1 -0
- package/dist/cjs/esmSpecific.mjs +10 -0
- package/dist/cjs/generalUtils.d.ts +323 -0
- package/dist/cjs/generalUtils.d.ts.map +1 -0
- package/dist/cjs/generalUtils.js +652 -0
- package/dist/cjs/generalUtilsInternal.d.ts +9 -0
- package/dist/cjs/generalUtilsInternal.d.ts.map +1 -0
- package/dist/cjs/generalUtilsInternal.js +217 -0
- package/dist/cjs/index.d.ts +4 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +25 -0
- package/dist/cjs/package.json +5 -0
- package/dist/cjs/runWhileParentAlive.d.ts +2 -0
- package/dist/cjs/runWhileParentAlive.d.ts.map +1 -0
- package/dist/cjs/runWhileParentAlive.js +161 -0
- package/dist/esm/NodeCliUtilsConfig.d.ts +21 -0
- package/dist/esm/NodeCliUtilsConfig.d.ts.map +1 -0
- package/dist/esm/NodeCliUtilsConfig.js +35 -0
- package/dist/esm/TarballUtility.d.ts +53 -0
- package/dist/esm/TarballUtility.d.ts.map +1 -0
- package/dist/esm/TarballUtility.js +143 -0
- package/dist/esm/certUtils.d.ts +30 -0
- package/dist/esm/certUtils.d.ts.map +1 -0
- package/dist/esm/certUtils.js +185 -0
- package/dist/esm/dbMigrationUtils.d.ts +39 -0
- package/dist/esm/dbMigrationUtils.d.ts.map +1 -0
- package/dist/esm/dbMigrationUtils.js +194 -0
- package/dist/esm/dotnetUtils.d.ts +25 -0
- package/dist/esm/dotnetUtils.d.ts.map +1 -0
- package/dist/esm/dotnetUtils.js +52 -0
- package/dist/esm/esmSpecific.d.mts +2 -0
- package/dist/esm/esmSpecific.d.mts.map +1 -0
- package/dist/esm/esmSpecific.mjs +6 -0
- package/dist/esm/generalUtils.d.ts +323 -0
- package/dist/esm/generalUtils.d.ts.map +1 -0
- package/dist/esm/generalUtils.js +591 -0
- package/dist/esm/generalUtilsInternal.d.ts +9 -0
- package/dist/esm/generalUtilsInternal.d.ts.map +1 -0
- package/dist/esm/generalUtilsInternal.js +185 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/runWhileParentAlive.d.ts +2 -0
- package/dist/esm/runWhileParentAlive.d.ts.map +1 -0
- package/dist/esm/runWhileParentAlive.js +153 -0
- package/package.json +67 -10
- package/index.js +0 -627
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import fsp from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { mkdirp, requireString, requireValidPath, spawnAsync, trace, whichSync } from './generalUtils.js';
|
|
5
|
+
import { config } from './NodeCliUtilsConfig.js';
|
|
6
|
+
/**
|
|
7
|
+
* This utility class exists so we can mock the `which` dependency in unit tests without resorting to libraries that hack the import system.
|
|
8
|
+
*/
|
|
9
|
+
export class TarballUtility {
|
|
10
|
+
whichSyncFn;
|
|
11
|
+
constructor(whichSyncFn) {
|
|
12
|
+
this.whichSyncFn = whichSyncFn;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Creates a gzipped tarball from a directory by spawning a process to run OS-installed `tar` to avoid pulling in npm package dependencies.
|
|
16
|
+
* Note that Windows has tar since Windows 10 1803 (see https://devblogs.microsoft.com/commandline/windows10v1803/.
|
|
17
|
+
*
|
|
18
|
+
* It's possible this isn't 100% reliable due to differences in `tar` versions across platforms. If better normalization
|
|
19
|
+
* is required, consider using the npm package `node-tar` instead.
|
|
20
|
+
* @param directoryToTarball The directory to tarball. The directory name will be used as the root directory in the tarball
|
|
21
|
+
* @param tarballPath The path to the tarball to create - must end with '.tar.gz'
|
|
22
|
+
* @param options See {@link CreateTarballOptions}
|
|
23
|
+
*/
|
|
24
|
+
createTarball = async (directoryToTarball, tarballPath, options) => {
|
|
25
|
+
requireValidPath('directoryToTarball', directoryToTarball);
|
|
26
|
+
requireString('tarballPath', tarballPath);
|
|
27
|
+
const defaultOptions = { excludes: [] };
|
|
28
|
+
const mergedOptions = { ...defaultOptions, ...options };
|
|
29
|
+
if (!this.whichSyncFn('tar').location) {
|
|
30
|
+
throw new Error('tar command not found - please install tar on your OS to use this method, or consider using the npm package node-tar instead');
|
|
31
|
+
}
|
|
32
|
+
if (!fs.existsSync(directoryToTarball)) {
|
|
33
|
+
throw new Error(`directoryToTarball does not exist: ${directoryToTarball}`);
|
|
34
|
+
}
|
|
35
|
+
if (tarballPath.endsWith('.tar.gz') === false) {
|
|
36
|
+
throw new Error(`tarballPath must end with '.tar.gz': ${tarballPath}`);
|
|
37
|
+
}
|
|
38
|
+
const directoryToTarballParentDir = path.dirname(directoryToTarball);
|
|
39
|
+
const directoryToTarballName = path.basename(directoryToTarball);
|
|
40
|
+
const outputDirectory = path.dirname(tarballPath);
|
|
41
|
+
const tarballName = path.basename(tarballPath);
|
|
42
|
+
if (!fs.existsSync(outputDirectory)) {
|
|
43
|
+
trace(`tarballPath directory does not exist - creating '${outputDirectory}'`);
|
|
44
|
+
await mkdirp(outputDirectory);
|
|
45
|
+
}
|
|
46
|
+
else if (fs.existsSync(tarballPath)) {
|
|
47
|
+
trace(`removing existing tarball '${tarballName}' before creating new one`);
|
|
48
|
+
await fsp.unlink(tarballPath);
|
|
49
|
+
}
|
|
50
|
+
const excludesArgs = mergedOptions.excludes.length > 0 ? mergedOptions.excludes.map(exclude => `--exclude=${exclude}`) : [];
|
|
51
|
+
const verboseFlag = config.traceEnabled ? ['-v'] : [];
|
|
52
|
+
const args = [...(verboseFlag), '-czf', tarballPath, '-C', directoryToTarballParentDir, ...excludesArgs, directoryToTarballName];
|
|
53
|
+
const result = await spawnAsync('tar', args);
|
|
54
|
+
if (result.code !== 0) {
|
|
55
|
+
throw new Error(`tar command failed with code ${result.code}`);
|
|
56
|
+
}
|
|
57
|
+
trace('tarball created: ' + tarballPath);
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Unpacks a gzipped tarball by spawning a process to run OS-installed `tar` to avoid pulling in npm package dependencies.
|
|
61
|
+
* This method will throw an error if the unpackDirectory is not empty, unless the `throwOnNonEmptyUnpackDir` option is set to false.
|
|
62
|
+
* @param tarballPath The path to the tarball to unpack
|
|
63
|
+
* @param unpackDirectory The directory to unpack the tarball into
|
|
64
|
+
* @param options The options to use when unpacking the tarball. See {@link TarballUnpackOptions}.
|
|
65
|
+
*/
|
|
66
|
+
unpackTarball = async (tarballPath, unpackDirectory, options) => {
|
|
67
|
+
requireValidPath('tarballPath', tarballPath);
|
|
68
|
+
requireString('unpackDirectory', unpackDirectory);
|
|
69
|
+
if (!this.whichSyncFn('tar').location) {
|
|
70
|
+
throw new Error('tar command not found - please install tar on your OS to use this method, or consider using the npm package node-tar instead');
|
|
71
|
+
}
|
|
72
|
+
const defaultOptions = { createDirIfNotExists: false, stripComponents: 0, throwOnNonEmptyUnpackDir: true };
|
|
73
|
+
const mergedOptions = { ...defaultOptions, ...options };
|
|
74
|
+
if (mergedOptions.stripComponents < 0) {
|
|
75
|
+
throw new Error('stripComponents must be greater than or equal to 0 if provided');
|
|
76
|
+
}
|
|
77
|
+
const unpackedDirExists = fs.existsSync(unpackDirectory);
|
|
78
|
+
if (unpackedDirExists && !this.isDirectory(unpackDirectory)) {
|
|
79
|
+
throw new Error(`unpackDirectory exists but is not a directory: ${unpackDirectory}`);
|
|
80
|
+
}
|
|
81
|
+
if (mergedOptions.createDirIfNotExists && !unpackedDirExists) {
|
|
82
|
+
await this.tryCreateDirectory(unpackDirectory);
|
|
83
|
+
}
|
|
84
|
+
if (!mergedOptions.createDirIfNotExists && !unpackedDirExists) {
|
|
85
|
+
throw new Error(`unpackDirectory does not exist: ${unpackDirectory}`);
|
|
86
|
+
}
|
|
87
|
+
if (mergedOptions.throwOnNonEmptyUnpackDir && this.dirIsNotEmpty(unpackDirectory)) {
|
|
88
|
+
throw new Error(`unpackDirectory exists but is not empty: ${unpackDirectory}`);
|
|
89
|
+
}
|
|
90
|
+
const verboseFlag = config.traceEnabled ? ['-v'] : [];
|
|
91
|
+
const args = [...(verboseFlag), '-xzf', tarballPath, '-C', unpackDirectory, '--strip-components', mergedOptions.stripComponents.toString()];
|
|
92
|
+
const result = await spawnAsync('tar', args);
|
|
93
|
+
if (result.code !== 0) {
|
|
94
|
+
throw new Error(`tar command failed with code ${result.code}`);
|
|
95
|
+
}
|
|
96
|
+
trace(`tarball unpacked at ${unpackDirectory}`);
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* A more opinionated version of {@link unpackTarball} that assumes you want to create the directory and strip the first directory out of the unpacked files.
|
|
100
|
+
* @param tarballPath The path to the tarball to unpack
|
|
101
|
+
* @param unpackDirectory The directory to unpack the tarball into - will be created if it doesn't exist and will throw if it exists but is not empty
|
|
102
|
+
* @param stripComponents The number of leading directories to strip out of the unpacked files, defaults to 1
|
|
103
|
+
*/
|
|
104
|
+
unpackTarballContents = async (tarballPath, unpackDirectory, stripComponents = 1) => {
|
|
105
|
+
await this.unpackTarball(tarballPath, unpackDirectory, { stripComponents, createDirIfNotExists: true });
|
|
106
|
+
};
|
|
107
|
+
isDirectory = (path) => {
|
|
108
|
+
try {
|
|
109
|
+
const stats = fs.statSync(path);
|
|
110
|
+
return stats.isDirectory();
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
dirIsNotEmpty = (dirPath) => {
|
|
117
|
+
try {
|
|
118
|
+
const stats = fs.statSync(dirPath);
|
|
119
|
+
return stats.isDirectory() && fs.readdirSync(dirPath).length > 0;
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
tryCreateDirectory = async (dirPath) => {
|
|
126
|
+
try {
|
|
127
|
+
await mkdirp(dirPath);
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
if (err instanceof Error) {
|
|
131
|
+
throw new Error(`Error creating unpackDirectory: ${err.message}`);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
throw new Error(`Error creating unpackDirectory: ${err}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const defaultUtil = new TarballUtility(whichSync);
|
|
140
|
+
export const createTarball = defaultUtil.createTarball;
|
|
141
|
+
export const unpackTarball = defaultUtil.unpackTarball;
|
|
142
|
+
export const unpackTarballContents = defaultUtil.unpackTarballContents;
|
|
143
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiVGFyYmFsbFV0aWxpdHkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvVGFyYmFsbFV0aWxpdHkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLE1BQU0sU0FBUyxDQUFBO0FBQ3hCLE9BQU8sR0FBRyxNQUFNLGtCQUFrQixDQUFBO0FBQ2xDLE9BQU8sSUFBSSxNQUFNLFdBQVcsQ0FBQTtBQUM1QixPQUFPLEVBQUUsTUFBTSxFQUFFLGFBQWEsRUFBRSxnQkFBZ0IsRUFBRSxVQUFVLEVBQUUsS0FBSyxFQUFFLFNBQVMsRUFBRSxNQUFNLG1CQUFtQixDQUFBO0FBQ3pHLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQTtBQWdCaEQ7O0dBRUc7QUFDSCxNQUFNLE9BQU8sY0FBYztJQUNqQixXQUFXLENBQWtCO0lBRXJDLFlBQVksV0FBNkI7UUFDdkMsSUFBSSxDQUFDLFdBQVcsR0FBRyxXQUFXLENBQUE7SUFDaEMsQ0FBQztJQUVEOzs7Ozs7Ozs7T0FTRztJQUNILGFBQWEsR0FBRyxLQUFLLEVBQUUsa0JBQTBCLEVBQUUsV0FBbUIsRUFBRSxPQUE4QixFQUFFLEVBQUU7UUFDeEcsZ0JBQWdCLENBQUMsb0JBQW9CLEVBQUUsa0JBQWtCLENBQUMsQ0FBQTtRQUMxRCxhQUFhLENBQUMsYUFBYSxFQUFFLFdBQVcsQ0FBQyxDQUFBO1FBRXpDLE1BQU0sY0FBYyxHQUFHLEVBQUUsUUFBUSxFQUFFLEVBQUUsRUFBRSxDQUFBO1FBQ3ZDLE1BQU0sYUFBYSxHQUFHLEVBQUUsR0FBRyxjQUFjLEVBQUUsR0FBRyxPQUFPLEVBQUUsQ0FBQTtRQUV2RCxJQUFJLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxLQUFLLENBQUMsQ0FBQyxRQUFRLEVBQUU7WUFDckMsTUFBTSxJQUFJLEtBQUssQ0FBQyw4SEFBOEgsQ0FBQyxDQUFBO1NBQ2hKO1FBRUQsSUFBSSxDQUFDLEVBQUUsQ0FBQyxVQUFVLENBQUMsa0JBQWtC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
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrapper function for calling openssl to generate a self-signed cert to be used for developing a local website with trusted https.
|
|
3
|
+
* @param url The url to generate a cert for. This will be used as the common name (CN) in the cert as well as the filename for the generated cert files.
|
|
4
|
+
* @param outputDirectory The directory to write the generated cert files to. Defaults to './cert'.
|
|
5
|
+
*/
|
|
6
|
+
export declare function generateCertWithOpenSsl(url: string, outputDirectory?: string): Promise<void>;
|
|
7
|
+
/**
|
|
8
|
+
* Uses Powershell to install a cert to the local machine's trusted root store. Must have admin permissions.
|
|
9
|
+
*
|
|
10
|
+
* If the cert is already installed, this method will do nothing.
|
|
11
|
+
* @param urlOrCertFilename The url or cert filename to install. The url + '.pfx' or the cert filename passed must match a file that exists in the certDirectory.
|
|
12
|
+
* @param certDirectory The directory to look for the cert file in. Defaults to './cert'.
|
|
13
|
+
*/
|
|
14
|
+
export declare function winInstallCert(urlOrCertFilename: string, certDirectory?: string): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Uses Powershell to uninstall a cert from the local machine's trusted root store. Must have admin permissions.
|
|
17
|
+
* @param urlOrSubject The url or subject of the cert to uninstall. If the cert was installed with the winInstallCert method, this will be the url passed to that method.
|
|
18
|
+
*/
|
|
19
|
+
export declare function winUninstallCert(urlOrSubject: string): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Does not actually do anything - just outputs the manual instructions for installing a cert for use by chrome on linux.
|
|
22
|
+
*/
|
|
23
|
+
export declare function linuxInstallCert(): void;
|
|
24
|
+
/**
|
|
25
|
+
* Uses Powershell to check if a cert is already installed to the local machine's trusted root store.
|
|
26
|
+
* @param urlOrSubject The url or subject of the cert to check. If the cert was installed with the winInstallCert method, this will be the url passed to that method.
|
|
27
|
+
* @returns `true` if the cert is already installed, `false` otherwise.
|
|
28
|
+
*/
|
|
29
|
+
export declare function winCertAlreadyInstalled(urlOrSubject: string): Promise<boolean>;
|
|
30
|
+
//# sourceMappingURL=certUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"certUtils.d.ts","sourceRoot":"","sources":["../../src/certUtils.ts"],"names":[],"mappings":"AASA;;;;GAIG;AACH,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,MAAM,EAAE,eAAe,GAAE,MAAiB,iBAuD5F;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,iBAAiB,EAAE,MAAM,EAAE,aAAa,SAAW,iBA8BvF;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,YAAY,EAAE,MAAM,iBAY1D;AAED;;GAEG;AACH,wBAAgB,gBAAgB,SAU/B;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAmBpF"}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import fsp from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import * as nodeCliUtils from './generalUtils.js';
|
|
5
|
+
import { log } from './generalUtils.js';
|
|
6
|
+
const requiresAdminMessage = `➡️ Important: Requires admin permissions`;
|
|
7
|
+
const powershellHackPrefix = `$env:PSModulePath = [Environment]::GetEnvironmentVariable('PSModulePath', 'Machine'); `;
|
|
8
|
+
/**
|
|
9
|
+
* Wrapper function for calling openssl to generate a self-signed cert to be used for developing a local website with trusted https.
|
|
10
|
+
* @param url The url to generate a cert for. This will be used as the common name (CN) in the cert as well as the filename for the generated cert files.
|
|
11
|
+
* @param outputDirectory The directory to write the generated cert files to. Defaults to './cert'.
|
|
12
|
+
*/
|
|
13
|
+
export async function generateCertWithOpenSsl(url, outputDirectory = './cert') {
|
|
14
|
+
nodeCliUtils.requireString('url', url);
|
|
15
|
+
throwIfMaybeBadUrlChars(url);
|
|
16
|
+
const isMac = nodeCliUtils.isPlatformMac();
|
|
17
|
+
const spawnArgs = { cwd: outputDirectory };
|
|
18
|
+
log('- checking if openssl is installed');
|
|
19
|
+
let brewOpenSslPath = '';
|
|
20
|
+
if (!isMac) {
|
|
21
|
+
const openSslPath = nodeCliUtils.whichSync('openssl').location;
|
|
22
|
+
if (!openSslPath) {
|
|
23
|
+
throw Error('openssl is required but was not found');
|
|
24
|
+
}
|
|
25
|
+
log(`- using openssl at: ${openSslPath}`);
|
|
26
|
+
}
|
|
27
|
+
else if (isMac) {
|
|
28
|
+
const brewOpenSslDirectory = getBrewOpensslPath();
|
|
29
|
+
if (!brewOpenSslDirectory) {
|
|
30
|
+
throw Error('openssl (brew version) is required but was not found');
|
|
31
|
+
}
|
|
32
|
+
brewOpenSslPath = `${getBrewOpensslPath()}/bin/openssl`;
|
|
33
|
+
if (!fs.existsSync(brewOpenSslPath)) {
|
|
34
|
+
throw Error(`openssl (brew version) is required but was not found at: ${brewOpenSslPath}`);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
log(`- using openssl at: ${brewOpenSslPath}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
nodeCliUtils.ensureDirectory(outputDirectory);
|
|
41
|
+
const keyName = url + '.key';
|
|
42
|
+
const crtName = url + '.crt';
|
|
43
|
+
const pfxName = url + '.pfx';
|
|
44
|
+
const pfxPath = path.join(outputDirectory, pfxName);
|
|
45
|
+
if (fs.existsSync(pfxPath)) {
|
|
46
|
+
throw Error(`File ${pfxPath} already exists. Delete or rename this file if you want to generate a new cert.`);
|
|
47
|
+
}
|
|
48
|
+
log('- writing san.cnf file for use with openssl command');
|
|
49
|
+
const sanCnfContents = getSanCnfFileContents(url);
|
|
50
|
+
const sanCnfPath = path.join(outputDirectory, 'san.cnf');
|
|
51
|
+
await fsp.writeFile(sanCnfPath, sanCnfContents);
|
|
52
|
+
log(`- attempting to generate cert ${pfxName}`);
|
|
53
|
+
const genKeyAndCrtArgs = `req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes -keyout ${keyName} -out ${crtName} -subj /CN=${url} -config san.cnf`.split(' ');
|
|
54
|
+
const command = isMac ? brewOpenSslPath : 'openssl';
|
|
55
|
+
let result = await nodeCliUtils.spawnAsync(command, genKeyAndCrtArgs, spawnArgs);
|
|
56
|
+
if (result.code !== 0) {
|
|
57
|
+
throw Error(`openssl command to generate key and crt files failed with exit code ${result.code}`);
|
|
58
|
+
}
|
|
59
|
+
log('- converting key and crt to pfx');
|
|
60
|
+
const convertToPfxArgs = `pkcs12 -certpbe AES-256-CBC -export -out ${pfxName} -aes256 -inkey ${keyName} -in ${crtName} -password pass:`.split(' ');
|
|
61
|
+
result = await nodeCliUtils.spawnAsync(command, convertToPfxArgs, spawnArgs);
|
|
62
|
+
if (result.code !== 0) {
|
|
63
|
+
throw Error(`openssl command to convert key and crt files to a pfx failed with exit code ${result.code}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Uses Powershell to install a cert to the local machine's trusted root store. Must have admin permissions.
|
|
68
|
+
*
|
|
69
|
+
* If the cert is already installed, this method will do nothing.
|
|
70
|
+
* @param urlOrCertFilename The url or cert filename to install. The url + '.pfx' or the cert filename passed must match a file that exists in the certDirectory.
|
|
71
|
+
* @param certDirectory The directory to look for the cert file in. Defaults to './cert'.
|
|
72
|
+
*/
|
|
73
|
+
export async function winInstallCert(urlOrCertFilename, certDirectory = './cert') {
|
|
74
|
+
nodeCliUtils.requireString('urlOrCertFilename', urlOrCertFilename);
|
|
75
|
+
nodeCliUtils.requireValidPath('certDirectory', certDirectory);
|
|
76
|
+
throwIfMaybeBadUrlChars(urlOrCertFilename, 'urlOrCertFilename');
|
|
77
|
+
if (!nodeCliUtils.isPlatformWindows()) {
|
|
78
|
+
throw Error('This method is only supported on Windows');
|
|
79
|
+
}
|
|
80
|
+
log(requiresAdminMessage);
|
|
81
|
+
if (await winCertAlreadyInstalled(urlOrCertFilename)) {
|
|
82
|
+
log(`certificate for ${urlOrCertFilename} is already installed - to install it again, first uninstall it manually or with the winUninstallCert method`);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const certName = urlOrCertFilename.endsWith('.pfx') ? urlOrCertFilename : urlOrCertFilename + '.pfx';
|
|
86
|
+
const certPath = path.join(certDirectory, certName);
|
|
87
|
+
if (!fs.existsSync(certPath)) {
|
|
88
|
+
throw Error(`File ${certPath} does not exist. Generate this first if you want to install it.`);
|
|
89
|
+
}
|
|
90
|
+
const psCommand = `${powershellHackPrefix}Import-PfxCertificate -FilePath '${certPath}' -CertStoreLocation Cert:\\LocalMachine\\Root`;
|
|
91
|
+
const result = await nodeCliUtils.spawnAsync('powershell', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', psCommand]);
|
|
92
|
+
if (result.code !== 0) {
|
|
93
|
+
throw Error(`powershell command to install cert failed with exit code ${result.code}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Uses Powershell to uninstall a cert from the local machine's trusted root store. Must have admin permissions.
|
|
98
|
+
* @param urlOrSubject The url or subject of the cert to uninstall. If the cert was installed with the winInstallCert method, this will be the url passed to that method.
|
|
99
|
+
*/
|
|
100
|
+
export async function winUninstallCert(urlOrSubject) {
|
|
101
|
+
nodeCliUtils.requireString('urlOrSubject', urlOrSubject);
|
|
102
|
+
log(requiresAdminMessage);
|
|
103
|
+
const psCommand = `${powershellHackPrefix}Get-ChildItem Cert:\\LocalMachine\\Root | Where-Object { $_.Subject -match '${urlOrSubject}' } | Remove-Item`;
|
|
104
|
+
const result = await nodeCliUtils.spawnAsync('powershell', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', psCommand]);
|
|
105
|
+
if (result.code !== 0) {
|
|
106
|
+
throw Error(`powershell command to uninstall cert failed with exit code ${result.code}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Does not actually do anything - just outputs the manual instructions for installing a cert for use by chrome on linux.
|
|
111
|
+
*/
|
|
112
|
+
export function linuxInstallCert() {
|
|
113
|
+
const instructions = `Automated linux cert install not supported (chrome does not use system certs without significant extra configuration).
|
|
114
|
+
Manual Instructions:
|
|
115
|
+
- In Chrome, go to chrome://settings/certificates
|
|
116
|
+
- Select Authorities -> import
|
|
117
|
+
- Select your generated .crt file (in the ./cert/ directory by default - if you haven't generated it, see the generateCertWithOpenSsl method)
|
|
118
|
+
- Check box for "Trust certificate for identifying websites"
|
|
119
|
+
- Click OK
|
|
120
|
+
- Reload site`;
|
|
121
|
+
console.log(instructions);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Uses Powershell to check if a cert is already installed to the local machine's trusted root store.
|
|
125
|
+
* @param urlOrSubject The url or subject of the cert to check. If the cert was installed with the winInstallCert method, this will be the url passed to that method.
|
|
126
|
+
* @returns `true` if the cert is already installed, `false` otherwise.
|
|
127
|
+
*/
|
|
128
|
+
export async function winCertAlreadyInstalled(urlOrSubject) {
|
|
129
|
+
const psCommand = `${powershellHackPrefix}Get-ChildItem Cert:\\LocalMachine\\Root | Where-Object { $_.Subject -match '${urlOrSubject}' }`;
|
|
130
|
+
// The stdio option of 'pipe' is important here - if left to default of spawnAsync ('inherit'), stdout will be empty
|
|
131
|
+
const result = await nodeCliUtils.spawnAsync('powershell', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', psCommand], { stdio: ['inherit', 'pipe', 'pipe'] });
|
|
132
|
+
if (result.code !== 0) {
|
|
133
|
+
throw Error(`powershell command to find installed cert failed with exit code ${result.code}`);
|
|
134
|
+
}
|
|
135
|
+
const lines = nodeCliUtils.stringToNonEmptyLines(result.stdout);
|
|
136
|
+
for (const line of lines) {
|
|
137
|
+
if (line.includes(urlOrSubject)) {
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
function throwIfMaybeBadUrlChars(url, varName = 'url') {
|
|
144
|
+
if (url.includes(' ')) {
|
|
145
|
+
throw Error(`${varName} should not contain spaces`);
|
|
146
|
+
}
|
|
147
|
+
if (url.includes('/')) {
|
|
148
|
+
throw Error(`${varName} should not contain forward slashes`);
|
|
149
|
+
}
|
|
150
|
+
if (url.includes('\\')) {
|
|
151
|
+
throw Error(`${varName} should not contain backslashes`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function getBrewOpensslPath() {
|
|
155
|
+
const brewResult = nodeCliUtils.simpleSpawnSync('brew', ['--prefix', 'openssl']);
|
|
156
|
+
if (brewResult.error) {
|
|
157
|
+
throw Error('error attempting to find openssl installed by brew');
|
|
158
|
+
}
|
|
159
|
+
if (brewResult.stdoutLines.length === 0 || brewResult.stdoutLines.length > 1) {
|
|
160
|
+
throw new Error(`unexpected output from brew command 'brew --prefix openssl': ${brewResult.stdout}`);
|
|
161
|
+
}
|
|
162
|
+
return brewResult.stdoutLines[0];
|
|
163
|
+
}
|
|
164
|
+
function getSanCnfFileContents(url) {
|
|
165
|
+
return sanCnfTemplate.replace(/{{url}}/g, url);
|
|
166
|
+
}
|
|
167
|
+
// Newer cert requirements force the need for "extension info" with DNS and IP info, but openssl v1.x doesn't support that with the
|
|
168
|
+
// CLI option -addext, so we're using a san.cnf file instead and passing this into the CLI command with the -config option.
|
|
169
|
+
const sanCnfTemplate = `[req]
|
|
170
|
+
distinguished_name=req
|
|
171
|
+
x509_extensions = v3_req
|
|
172
|
+
prompt = no
|
|
173
|
+
|
|
174
|
+
[req_distinguished_name]
|
|
175
|
+
CN = {{url}}
|
|
176
|
+
|
|
177
|
+
[v3_req]
|
|
178
|
+
subjectAltName = @alt_names
|
|
179
|
+
|
|
180
|
+
[alt_names]
|
|
181
|
+
DNS.1 = {{url}}
|
|
182
|
+
IP.1 = 127.0.0.1
|
|
183
|
+
|
|
184
|
+
`;
|
|
185
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2VydFV0aWxzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NlcnRVdGlscy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsTUFBTSxTQUFTLENBQUE7QUFDeEIsT0FBTyxHQUFHLE1BQU0sa0JBQWtCLENBQUE7QUFDbEMsT0FBTyxJQUFJLE1BQU0sV0FBVyxDQUFBO0FBQzVCLE9BQU8sS0FBSyxZQUFZLE1BQU0sbUJBQW1CLENBQUE7QUFDakQsT0FBTyxFQUFFLEdBQUcsRUFBRSxNQUFNLG1CQUFtQixDQUFBO0FBRXZDLE1BQU0sb0JBQW9CLEdBQUcsMENBQTBDLENBQUE7QUFDdkUsTUFBTSxvQkFBb0IsR0FBRyx3RkFBd0YsQ0FBQTtBQUVySDs7OztHQUlHO0FBQ0gsTUFBTSxDQUFDLEtBQUssVUFBVSx1QkFBdUIsQ0FBQyxHQUFXLEVBQUUsa0JBQTBCLFFBQVE7SUFDM0YsWUFBWSxDQUFDLGFBQWEsQ0FBQyxLQUFLLEVBQUUsR0FBRyxDQUFDLENBQUE7SUFDdEMsdUJBQXVCLENBQUMsR0FBRyxDQUFDLENBQUE7SUFDNUIsTUFBTSxLQUFLLEdBQUcsWUFBWSxDQUFDLGFBQWEsRUFBRSxDQUFBO0lBQzFDLE1BQU0sU0FBUyxHQUFHLEVBQUUsR0FBRyxFQUFFLGVBQWUsRUFBRSxDQUFBO0lBRTFDLEdBQUcsQ0FBQyxvQ0FBb0MsQ0FBQyxDQUFBO0lBQ3pDLElBQUksZUFBZSxHQUFXLEVBQUUsQ0FBQTtJQUNoQyxJQUFJLENBQUMsS0FBSyxFQUFFO1FBQ1YsTUFBTSxXQUFXLEdBQUcsWUFBWSxDQUFDLFNBQVMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxRQUFRLENBQUE7UUFDOUQsSUFBSSxDQUFDLFdBQVcsRUFBRTtZQUNoQixNQUFNLEtBQUssQ0FBQyx1Q0FBdUMsQ0FBQyxDQUFBO1NBQ3JEO1FBQ0QsR0FBRyxDQUFDLHVCQUF1QixXQUFXLEVBQUUsQ0FBQyxDQUFBO0tBQzFDO1NBQU0sSUFBSSxLQUFLLEVB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
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrapper function for `dotnet ef`. If you don't pass `false` for `noBuild`, be sure the project has already been built by some other means.
|
|
3
|
+
*
|
|
4
|
+
* Docs for "dotnet ef" CLI: https://learn.microsoft.com/en-us/ef/core/cli/dotnet.
|
|
5
|
+
* @param projectPath Path to project that has the DbContext and Migration files used for the `--project` argument
|
|
6
|
+
* @param dbContextName The name of the DbContext class used for the `--context` argument
|
|
7
|
+
* @param args Arguments to pass to the `dotnet ef` CLI
|
|
8
|
+
* @param noBuild If true, the `--no-build` argument will be passed to the `dotnet ef` CLI (default: true)
|
|
9
|
+
*/
|
|
10
|
+
export declare function dotnetEfCommand(projectPath: string, dbContextName: string, args: string[], noBuild?: boolean): Promise<number>;
|
|
11
|
+
/**
|
|
12
|
+
* Wrapper function for `dotnet ef migrations list`.
|
|
13
|
+
* @param projectPath The path to the project that contains the DbContext and Migration files
|
|
14
|
+
* @param dbContextName The name of the DbContext class
|
|
15
|
+
*/
|
|
16
|
+
export declare function efMigrationsList(projectPath: string, dbContextName: string): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Wrapper function for `dotnet ef database update <migration_name>`.
|
|
19
|
+
* @param projectPath The path to the project that contains the DbContext and Migration files
|
|
20
|
+
* @param dbContextName The name of the DbContext class
|
|
21
|
+
* @param migrationName The name of the migration to update to (optional). If not provided, all migrations will be applied.
|
|
22
|
+
*/
|
|
23
|
+
export declare function efMigrationsUpdate(projectPath: string, dbContextName: string, migrationName?: string): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
*
|
|
26
|
+
* @param projectPath The path to the project that contains the DbContext and Migration files
|
|
27
|
+
* @param dbContextName The name of the DbContext class
|
|
28
|
+
* @param migrationName The name of the migration to add
|
|
29
|
+
* @param withBoilerplate If true, boilerplate will be added to the migration C# file and empty Up and Down SQL files will be created
|
|
30
|
+
*/
|
|
31
|
+
export declare function efAddMigration(projectPath: string, dbContextName: string, migrationName: string, withBoilerplate?: boolean): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
*
|
|
34
|
+
* @param projectPath The path to the project that contains the DbContext and Migration files
|
|
35
|
+
* @param dbContextName The name of the DbContext class
|
|
36
|
+
* @param skipConfirm If `true`, the user will not be prompted to confirm the removal of the last migration
|
|
37
|
+
*/
|
|
38
|
+
export declare function efRemoveLastMigration(projectPath: string, dbContextName: string, skipConfirm?: boolean): Promise<void>;
|
|
39
|
+
//# sourceMappingURL=dbMigrationUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dbMigrationUtils.d.ts","sourceRoot":"","sources":["../../src/dbMigrationUtils.ts"],"names":[],"mappings":"AAKA;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,UAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAKjI;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,iBAEhF;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,iBAE1G;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,eAAe,UAAQ,iBAY9H;AAED;;;;;GAKG;AACH,wBAAsB,qBAAqB,CAAC,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,WAAW,UAAQ,iBAe1G"}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { getConfirmation, log, requireString, requireValidPath, spawnAsync } from './generalUtils.js';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import fsp from 'node:fs/promises';
|
|
5
|
+
/**
|
|
6
|
+
* Wrapper function for `dotnet ef`. If you don't pass `false` for `noBuild`, be sure the project has already been built by some other means.
|
|
7
|
+
*
|
|
8
|
+
* Docs for "dotnet ef" CLI: https://learn.microsoft.com/en-us/ef/core/cli/dotnet.
|
|
9
|
+
* @param projectPath Path to project that has the DbContext and Migration files used for the `--project` argument
|
|
10
|
+
* @param dbContextName The name of the DbContext class used for the `--context` argument
|
|
11
|
+
* @param args Arguments to pass to the `dotnet ef` CLI
|
|
12
|
+
* @param noBuild If true, the `--no-build` argument will be passed to the `dotnet ef` CLI (default: true)
|
|
13
|
+
*/
|
|
14
|
+
export async function dotnetEfCommand(projectPath, dbContextName, args, noBuild = true) {
|
|
15
|
+
requireValidPath('projectPath', projectPath);
|
|
16
|
+
requireString('dbContextName', dbContextName);
|
|
17
|
+
const result = await spawnAsync('dotnet', ['ef', '--project', projectPath, ...args, '--context', dbContextName, ...(noBuild ? ['--no-build'] : [])]);
|
|
18
|
+
return result.code;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Wrapper function for `dotnet ef migrations list`.
|
|
22
|
+
* @param projectPath The path to the project that contains the DbContext and Migration files
|
|
23
|
+
* @param dbContextName The name of the DbContext class
|
|
24
|
+
*/
|
|
25
|
+
export async function efMigrationsList(projectPath, dbContextName) {
|
|
26
|
+
await dotnetEfCommand(projectPath, dbContextName, ['migrations', 'list']);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Wrapper function for `dotnet ef database update <migration_name>`.
|
|
30
|
+
* @param projectPath The path to the project that contains the DbContext and Migration files
|
|
31
|
+
* @param dbContextName The name of the DbContext class
|
|
32
|
+
* @param migrationName The name of the migration to update to (optional). If not provided, all migrations will be applied.
|
|
33
|
+
*/
|
|
34
|
+
export async function efMigrationsUpdate(projectPath, dbContextName, migrationName) {
|
|
35
|
+
await dotnetEfCommand(projectPath, dbContextName, ['database', 'update', ...(migrationName ? [migrationName] : [])]);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
*
|
|
39
|
+
* @param projectPath The path to the project that contains the DbContext and Migration files
|
|
40
|
+
* @param dbContextName The name of the DbContext class
|
|
41
|
+
* @param migrationName The name of the migration to add
|
|
42
|
+
* @param withBoilerplate If true, boilerplate will be added to the migration C# file and empty Up and Down SQL files will be created
|
|
43
|
+
*/
|
|
44
|
+
export async function efAddMigration(projectPath, dbContextName, migrationName, withBoilerplate = false) {
|
|
45
|
+
const projectDirectory = projectPath.endsWith('.csproj') ? projectPath.substring(0, projectPath.lastIndexOf('/')) : projectPath;
|
|
46
|
+
const migrationsOutputDir = getMigrationsProjectRelativePath(dbContextName);
|
|
47
|
+
await dotnetEfCommand(projectPath, dbContextName, ['migrations', 'add', migrationName, '-o', migrationsOutputDir]);
|
|
48
|
+
if (withBoilerplate) {
|
|
49
|
+
try {
|
|
50
|
+
await addDbMigrationBoilerplate(projectDirectory, dbContextName, migrationName);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
console.error(error);
|
|
54
|
+
await efRemoveLastMigration(projectPath, dbContextName, true);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
*
|
|
60
|
+
* @param projectPath The path to the project that contains the DbContext and Migration files
|
|
61
|
+
* @param dbContextName The name of the DbContext class
|
|
62
|
+
* @param skipConfirm If `true`, the user will not be prompted to confirm the removal of the last migration
|
|
63
|
+
*/
|
|
64
|
+
export async function efRemoveLastMigration(projectPath, dbContextName, skipConfirm = false) {
|
|
65
|
+
const lastMigrationName = await getLastMigrationName(projectPath, dbContextName);
|
|
66
|
+
if (!skipConfirm && !await getConfirmation(`Are you sure you want to remove the last migration: ➡️${lastMigrationName}?`)) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const returnCode = await dotnetEfCommand(projectPath, dbContextName, ['migrations', 'remove']);
|
|
70
|
+
if (returnCode !== 0) {
|
|
71
|
+
throw new Error(`dotnet ef migrations remove returned non-zero exit code: ${returnCode}`);
|
|
72
|
+
}
|
|
73
|
+
log(`Removing migration SQL script files for migration if they're empty`);
|
|
74
|
+
await deleteScriptFileIfEmpty(getScriptPath(projectPath, lastMigrationName, true));
|
|
75
|
+
await deleteScriptFileIfEmpty(getScriptPath(projectPath, lastMigrationName, false));
|
|
76
|
+
}
|
|
77
|
+
async function deleteScriptFileIfEmpty(scriptPath) {
|
|
78
|
+
if (fs.existsSync(scriptPath)) {
|
|
79
|
+
const scriptContents = fs.readFileSync(scriptPath, { encoding: 'utf8' });
|
|
80
|
+
if (scriptContents.trim().length === 0) {
|
|
81
|
+
await fsp.unlink(scriptPath);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
log(`⚠️ Skipping deletion of non-empty script file: ${scriptPath}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async function getLastMigrationName(projectPath, dbContextName) {
|
|
89
|
+
const migrationsDirectory = getMigrationsDirectory(projectPath, dbContextName);
|
|
90
|
+
const filenames = fs.readdirSync(migrationsDirectory);
|
|
91
|
+
const migrationNames = filenames.filter(filename => filename.endsWith('.cs') &&
|
|
92
|
+
!filename.endsWith('.Designer.cs') &&
|
|
93
|
+
!filename.endsWith('.ModelSnapshot.cs') &&
|
|
94
|
+
filename.includes('_')).map(filename => filename.substring(0, filename.length - 3));
|
|
95
|
+
const migrationNamesWithTimestamps = migrationNames.map(migrationName => {
|
|
96
|
+
const timestamp = migrationName.substring(0, 14);
|
|
97
|
+
const name = migrationName.substring(15);
|
|
98
|
+
return { timestamp, name };
|
|
99
|
+
});
|
|
100
|
+
log(`Found migrations: ${migrationNamesWithTimestamps.map(m => m.name).join(', ')}`);
|
|
101
|
+
log(`Found timestamps: ${migrationNamesWithTimestamps.map(m => m.timestamp).join(', ')}`);
|
|
102
|
+
const sortedMigrationNames = migrationNamesWithTimestamps.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
103
|
+
const lastMigrationName = sortedMigrationNames[sortedMigrationNames.length - 1].name;
|
|
104
|
+
return lastMigrationName;
|
|
105
|
+
}
|
|
106
|
+
function getMigrationsProjectRelativePath(dbContextName) {
|
|
107
|
+
return `Migrations/${dbContextName}Migrations`;
|
|
108
|
+
}
|
|
109
|
+
function getMigrationsDirectory(projectDirectory, dbContextName) {
|
|
110
|
+
return path.join(projectDirectory, `Migrations/${dbContextName}Migrations`);
|
|
111
|
+
}
|
|
112
|
+
function getScriptPath(projectDirectory, migrationName, isUp) {
|
|
113
|
+
return path.join(projectDirectory, `Scripts/${migrationName}${isUp ? '' : '_Down'}.sql`);
|
|
114
|
+
}
|
|
115
|
+
async function addDbMigrationBoilerplate(projectDirectory, dbContextName, migrationName) {
|
|
116
|
+
const migrationsOutputDir = getMigrationsDirectory(projectDirectory, dbContextName);
|
|
117
|
+
if (!fs.existsSync(migrationsOutputDir)) {
|
|
118
|
+
throw new Error(`Unable to add migration C# boilerplate - could not find migrations output directory: ${migrationsOutputDir}`);
|
|
119
|
+
}
|
|
120
|
+
log(`Checking for generated C# file 📄XXXX_${migrationName}.cs in directory 📁${migrationsOutputDir}`);
|
|
121
|
+
const filenamePattern = `_${migrationName}.cs`;
|
|
122
|
+
const filenames = fs.readdirSync(migrationsOutputDir).filter(filename => filename.endsWith(filenamePattern));
|
|
123
|
+
if (!filenames || filenames.length === 0) {
|
|
124
|
+
throw new Error(`Auto-generated migration file not found - migrations output directory has no C# files ending with : ${filenamePattern}`);
|
|
125
|
+
}
|
|
126
|
+
if (filenames.length > 1) {
|
|
127
|
+
throw new Error(`Auto-generated migration file not found - migrations output directory has multiple C# files with the same migration name: ${filenames.join(', ')}`);
|
|
128
|
+
}
|
|
129
|
+
const filename = filenames[0];
|
|
130
|
+
const filePath = path.join(migrationsOutputDir, filename);
|
|
131
|
+
if (!fs.existsSync(filePath)) {
|
|
132
|
+
throw new Error(`Issue generating file path for migration (not found): ${filePath}`);
|
|
133
|
+
}
|
|
134
|
+
log(`Adding boilerplate to file 📄${filePath}`);
|
|
135
|
+
const usingLine = 'using MikeyT.DbMigrations;';
|
|
136
|
+
const upLine = `MigrationScriptRunner.RunScript(migrationBuilder, "${migrationName}.sql");`;
|
|
137
|
+
const downLine = `MigrationScriptRunner.RunScript(migrationBuilder, "${migrationName}_Down.sql");`;
|
|
138
|
+
const fileContents = await fsp.readFile(filePath, { encoding: 'utf8' });
|
|
139
|
+
const lines = fileContents.replaceAll('\r', '').split('\n');
|
|
140
|
+
const newLines = [];
|
|
141
|
+
newLines.push(lines[0].trim());
|
|
142
|
+
newLines.push(usingLine);
|
|
143
|
+
let addUpLine = false;
|
|
144
|
+
let addDownLine = false;
|
|
145
|
+
let skipNextLineIfBlank = false;
|
|
146
|
+
for (let i = 1; i < lines.length; i++) {
|
|
147
|
+
if (skipNextLineIfBlank && lines[i].trim().length === 0) {
|
|
148
|
+
skipNextLineIfBlank = false;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (addUpLine) {
|
|
152
|
+
const newLine = lines[i].replace('{', `{\n\t\t\t${upLine}`);
|
|
153
|
+
newLines.push(newLine);
|
|
154
|
+
addUpLine = false;
|
|
155
|
+
skipNextLineIfBlank = true;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (addDownLine) {
|
|
159
|
+
const newLine = lines[i].replace('{', `{\n\t\t\t${downLine}`);
|
|
160
|
+
newLines.push(newLine);
|
|
161
|
+
addDownLine = false;
|
|
162
|
+
skipNextLineIfBlank = true;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
newLines.push(lines[i]);
|
|
166
|
+
if (lines[i].includes('void Up')) {
|
|
167
|
+
addUpLine = true;
|
|
168
|
+
}
|
|
169
|
+
if (lines[i].includes('void Down')) {
|
|
170
|
+
addDownLine = true;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const newFileContents = newLines.join('\n');
|
|
174
|
+
await fsp.writeFile(filePath, newFileContents, { encoding: 'utf8' });
|
|
175
|
+
log(`Updated file with boilerplate - please ensure it is correct: ${filePath}`);
|
|
176
|
+
const upScriptPath = path.join(projectDirectory, `Scripts/${migrationName}.sql`);
|
|
177
|
+
const downScriptPath = path.join(projectDirectory, `Scripts/${migrationName}_Down.sql`);
|
|
178
|
+
log('\nCreating corresponding empty sql files (no action will be taken if they already exist):');
|
|
179
|
+
log(` - 📄${upScriptPath}`);
|
|
180
|
+
log(` - 📄${downScriptPath}\n`);
|
|
181
|
+
if (!fs.existsSync(upScriptPath)) {
|
|
182
|
+
await fsp.writeFile(upScriptPath, '', { encoding: 'utf8' });
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
log('Skipping Up sql script (already exists)');
|
|
186
|
+
}
|
|
187
|
+
if (!fs.existsSync(downScriptPath)) {
|
|
188
|
+
await fsp.writeFile(downScriptPath, '', { encoding: 'utf8' });
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
log('Skipping Down sql script (already exists)');
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGJNaWdyYXRpb25VdGlscy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9kYk1pZ3JhdGlvblV0aWxzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxlQUFlLEVBQUUsR0FBRyxFQUFFLGFBQWEsRUFBRSxnQkFBZ0IsRUFBRSxVQUFVLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQTtBQUNyRyxPQUFPLElBQUksTUFBTSxXQUFXLENBQUE7QUFDNUIsT0FBTyxFQUFFLE1BQU0sU0FBUyxDQUFBO0FBQ3hCLE9BQU8sR0FBRyxNQUFNLGtCQUFrQixDQUFBO0FBRWxDOzs7Ozs7OztHQVFHO0FBQ0gsTUFBTSxDQUFDLEtBQUssVUFBVSxlQUFlLENBQUMsV0FBbUIsRUFBRSxhQUFxQixFQUFFLElBQWMsRUFBRSxPQUFPLEdBQUcsSUFBSTtJQUM5RyxnQkFBZ0IsQ0FBQyxhQUFhLEVBQUUsV0FBVyxDQUFDLENBQUE7SUFDNUMsYUFBYSxDQUFDLGVBQWUsRUFBRSxhQUFhLENBQUMsQ0FBQTtJQUM3QyxNQUFNLE1BQU0sR0FBRyxNQUFNLFVBQVUsQ0FBQyxRQUFRLEVBQUUsQ0FBQyxJQUFJLEVBQUUsV0FBVyxFQUFFLFdBQVcsRUFBRSxHQUFHLElBQUksRUFBRSxXQUFXLEVBQUUsYUFBYSxFQUFFLEdBQUcsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUMsWUFBWSxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQTtJQUNwSixPQUFPLE1BQU0sQ0FBQyxJQUFJLENBQUE7QUFDcEIsQ0FBQztBQUVEOzs7O0dBSUc7QUFDSCxNQUFNLENBQUMsS0FBSyxVQUFVLGdCQUFnQixDQUFDLFdBQW1CLEVBQUUsYUFBcUI7SUFDL0UsTUFBTSxlQUFlLENBQUMsV0FBVyxFQUFFLGFBQWEsRUFBRSxDQUFDLFlBQVksRUFBRSxNQUFNLENBQUMsQ0FBRSxDQUFBO0FBQzVFLENBQUM7QUFFRDs7Ozs7R0FLRztB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
|