@bitblit/ratchet-node-only 6.0.146-alpha → 6.0.147-alpha
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/package.json +4 -3
- package/src/build/ratchet-node-only-info.ts +19 -0
- package/src/ci/apply-ci-env-variables-to-files.spec.ts +30 -0
- package/src/ci/apply-ci-env-variables-to-files.ts +98 -0
- package/src/ci/ci-run-information-util.ts +48 -0
- package/src/ci/ci-run-information.ts +9 -0
- package/src/cli/abstract-ratchet-cli-handler.ts +33 -0
- package/src/cli/cli-ratchet.ts +34 -0
- package/src/cli/ratchet-cli-handler.ts +24 -0
- package/src/csv/csv-ratchet.spec.ts +59 -0
- package/src/csv/csv-ratchet.ts +211 -0
- package/src/export-builder/export-map-builder-config.ts +10 -0
- package/src/export-builder/export-map-builder-target-config.ts +5 -0
- package/src/export-builder/export-map-builder.spec.ts +22 -0
- package/src/export-builder/export-map-builder.ts +157 -0
- package/src/files/files-to-static-class.spec.ts +26 -0
- package/src/files/files-to-static-class.ts +101 -0
- package/src/files/unique-file-rename.ts +80 -0
- package/src/http/local-file-server.ts +129 -0
- package/src/http/local-server-cert.ts +72 -0
- package/src/jwt/jwt-ratchet-config.ts +18 -0
- package/src/jwt/jwt-ratchet-like.ts +19 -0
- package/src/jwt/jwt-ratchet.spec.ts +85 -0
- package/src/jwt/jwt-ratchet.ts +204 -0
- package/src/stream/buffer-writable.ts +16 -0
- package/src/stream/multi-stream.ts +15 -0
- package/src/stream/node-stream-ratchet.spec.ts +19 -0
- package/src/stream/node-stream-ratchet.ts +70 -0
- package/src/stream/string-writable.spec.ts +16 -0
- package/src/stream/string-writable.ts +14 -0
- package/src/third-party/angular/angular-aot-rollup-plugin.ts +18 -0
- package/src/third-party/common-crawl/common-crawl-service.ts +220 -0
- package/src/third-party/common-crawl/model/common-crawl-fetch-options.ts +12 -0
- package/src/third-party/common-crawl/model/common-crawl-scan.ts +11 -0
- package/src/third-party/common-crawl/model/domain-index-entry-raw.ts +14 -0
- package/src/third-party/common-crawl/model/index-entry-raw.ts +8 -0
- package/src/third-party/common-crawl/model/warc-entry-raw.ts +5 -0
- package/src/third-party/common-crawl/model/warc-entry.ts +5 -0
- package/src/third-party/git/git-ratchet.spec.ts +11 -0
- package/src/third-party/git/git-ratchet.ts +107 -0
- package/src/third-party/slack/publish-ci-release-to-slack.spec.ts +28 -0
- package/src/third-party/slack/publish-ci-release-to-slack.ts +84 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import fs, { Stats } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { CliRatchet } from '../cli/cli-ratchet.js';
|
|
4
|
+
import { ExportMapBuilderConfig } from './export-map-builder-config.js';
|
|
5
|
+
import { Logger } from '@bitblit/ratchet-common/logger/logger';
|
|
6
|
+
import { EsmRatchet } from '@bitblit/ratchet-common/lang/esm-ratchet';
|
|
7
|
+
import { ErrorRatchet } from '@bitblit/ratchet-common/lang/error-ratchet';
|
|
8
|
+
import { StringRatchet } from '@bitblit/ratchet-common/lang/string-ratchet';
|
|
9
|
+
|
|
10
|
+
export class ExportMapBuilder {
|
|
11
|
+
public static process(cfg: ExportMapBuilderConfig): Record<string, any> {
|
|
12
|
+
Logger.info('Building export map : %j', cfg);
|
|
13
|
+
|
|
14
|
+
cfg.targetPackageJsonFile = cfg.targetPackageJsonFile ?? path.join(EsmRatchet.fetchDirName(import.meta.url), 'package.json');
|
|
15
|
+
cfg.includes = cfg.includes ?? [new RegExp('.*')];
|
|
16
|
+
cfg.excludes = cfg.excludes ?? [];
|
|
17
|
+
|
|
18
|
+
if (!fs.statSync(cfg.targetPackageJsonFile).isFile()) {
|
|
19
|
+
throw ErrorRatchet.fErr('targetPackageJsonFile: %s does not exist or is not a file', cfg.targetPackageJsonFile);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
cfg.sourceRoot = cfg.sourceRoot ?? path.join(path.dirname(cfg.targetPackageJsonFile), 'src');
|
|
23
|
+
if (!fs.statSync(cfg.sourceRoot).isDirectory()) {
|
|
24
|
+
throw ErrorRatchet.fErr('sourceRoot: %s does not exist or is not a folder', cfg.sourceRoot);
|
|
25
|
+
}
|
|
26
|
+
cfg.targets = cfg.targets ?? [
|
|
27
|
+
{
|
|
28
|
+
type: 'import',
|
|
29
|
+
prefix: './lib',
|
|
30
|
+
suffix: '.js',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
type: 'types',
|
|
34
|
+
prefix: './lib',
|
|
35
|
+
suffix: '.d.ts',
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
Logger.info('Using sourceRoot %s and targets %j', cfg.sourceRoot, cfg.targets);
|
|
40
|
+
const exports: Record<string, any> = {};
|
|
41
|
+
// TODO: This cant be right can it? need to actually use this
|
|
42
|
+
for (const _f of cfg.includes) {
|
|
43
|
+
ExportMapBuilder.processSingleFile(cfg.sourceRoot, cfg, exports);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Parse package.json
|
|
47
|
+
const parsedPackage: any = JSON.parse(fs.readFileSync(cfg.targetPackageJsonFile).toString());
|
|
48
|
+
parsedPackage['exports'] = exports;
|
|
49
|
+
|
|
50
|
+
if (cfg.dryRun) {
|
|
51
|
+
Logger.info('DryRun : Would have updated package json to : \n%j', parsedPackage);
|
|
52
|
+
} else {
|
|
53
|
+
// Update here
|
|
54
|
+
fs.writeFileSync(cfg.targetPackageJsonFile, JSON.stringify(parsedPackage, null, 2));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return exports;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private static pathMatchesOneOfRegExp(fileName: string, reg: RegExp[]): boolean {
|
|
61
|
+
let rval: boolean = false;
|
|
62
|
+
if (StringRatchet.trimToNull(fileName) && reg.length > 0) {
|
|
63
|
+
rval = true;
|
|
64
|
+
}
|
|
65
|
+
return rval;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private static findExports(fileName: string): string[] {
|
|
69
|
+
const text: string = StringRatchet.trimToEmpty(fs.readFileSync(fileName).toString());
|
|
70
|
+
const words: string[] = text.split(/[ \t\n]+/);
|
|
71
|
+
const exports: string[] = [];
|
|
72
|
+
for (let i = 0; i < words.length - 2; i++) {
|
|
73
|
+
if (words[i] === 'export') {
|
|
74
|
+
if (words[i + 1] === 'class' || words[i + 1] === 'interface') {
|
|
75
|
+
let next: string = words[i + 2];
|
|
76
|
+
if (next.endsWith('{')) {
|
|
77
|
+
next = next.substring(0, next.length - 1); // String trailing
|
|
78
|
+
}
|
|
79
|
+
if (next.indexOf('<') !== -1) {
|
|
80
|
+
// Strip generics
|
|
81
|
+
next = next.substring(0, next.indexOf('<'));
|
|
82
|
+
}
|
|
83
|
+
exports.push(next);
|
|
84
|
+
} else if (words[i + 1] === 'default') {
|
|
85
|
+
// TODO: handle default
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return exports;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private static processSingleFile(fileName: string, cfg: ExportMapBuilderConfig, inRecord: Record<string, any>): Record<string, any> {
|
|
94
|
+
const rval: Record<string, any> = Object.assign({}, inRecord ?? {});
|
|
95
|
+
if (fs.existsSync(fileName)) {
|
|
96
|
+
if (ExportMapBuilder.pathMatchesOneOfRegExp(fileName, cfg.includes)) {
|
|
97
|
+
if (!ExportMapBuilder.pathMatchesOneOfRegExp(fileName, cfg.excludes)) {
|
|
98
|
+
const stats: Stats = fs.statSync(fileName);
|
|
99
|
+
if (stats.isDirectory()) {
|
|
100
|
+
const contFiles: string[] = fs.readdirSync(fileName);
|
|
101
|
+
Logger.info('Found %d files in %s to process', contFiles.length, fileName);
|
|
102
|
+
contFiles.forEach((f) => ExportMapBuilder.processSingleFile(path.join(fileName, f), cfg, inRecord));
|
|
103
|
+
} else if (stats.isFile()) {
|
|
104
|
+
const exports: string[] = ExportMapBuilder.findExports(fileName);
|
|
105
|
+
exports.forEach((s) => {
|
|
106
|
+
if (inRecord[s]) {
|
|
107
|
+
throw ErrorRatchet.fErr('Collision on name %s : %s vs %s', fileName, inRecord[s], s);
|
|
108
|
+
} else {
|
|
109
|
+
let subPath: string = fileName.substring(cfg.sourceRoot.length);
|
|
110
|
+
subPath = subPath.split('\\').join('/'); // Package.json uses unix separators even on windows
|
|
111
|
+
if (subPath.endsWith('.ts')) {
|
|
112
|
+
subPath = subPath.substring(0, subPath.length - 3);
|
|
113
|
+
}
|
|
114
|
+
cfg.targets.forEach((tgt) => {
|
|
115
|
+
inRecord[s] = inRecord[s] || {};
|
|
116
|
+
const targetFileName: string = StringRatchet.trimToEmpty(tgt.prefix) + subPath + StringRatchet.trimToEmpty(tgt.suffix);
|
|
117
|
+
inRecord[s][tgt.type] = targetFileName;
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
} else {
|
|
122
|
+
Logger.error('Skipping - neither file nor directory : %s', fileName);
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
Logger.error('Skipping - fails exclude check : %s', fileName);
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
Logger.error('Skipping - fails include check : %s', fileName);
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
Logger.warn('Could not find file %s', fileName);
|
|
132
|
+
}
|
|
133
|
+
return rval;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
And, in case you are running this command line...
|
|
138
|
+
TODO: should use switches to allow setting the various non-filename params
|
|
139
|
+
**/
|
|
140
|
+
public static async runFromCliArgs(args: string[]): Promise<Record<string, any>> {
|
|
141
|
+
if (args.length < 3) {
|
|
142
|
+
Logger.infoP('Usage: ratchet-export-builder'); // {packageJsonFile}');
|
|
143
|
+
return null;
|
|
144
|
+
} else {
|
|
145
|
+
const _idx: number = CliRatchet.indexOfCommandArgument('export-builder');
|
|
146
|
+
//const jsonFile: string = process.argv[idx + 1];
|
|
147
|
+
|
|
148
|
+
const cfg: ExportMapBuilderConfig = {
|
|
149
|
+
//targetPackageJsonFile: jsonFile
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
Logger.info('Running ExportMapBuilder from command line arguments');
|
|
153
|
+
|
|
154
|
+
return ExportMapBuilder.process(cfg);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { FilesToStaticClass } from './files-to-static-class.js';
|
|
3
|
+
//import { fileURLToPath, URL } from 'url';
|
|
4
|
+
//import { Logger } from '../../common/logger';
|
|
5
|
+
import { describe, expect, test } from 'vitest';
|
|
6
|
+
import { EsmRatchet } from '@bitblit/ratchet-common/lang/esm-ratchet';
|
|
7
|
+
|
|
8
|
+
const testDirname: string = EsmRatchet.fetchDirName(import.meta.url);
|
|
9
|
+
|
|
10
|
+
describe('#filesToStaticClass', function () {
|
|
11
|
+
test('should convert files to a static class', async () => {
|
|
12
|
+
const out: string = await FilesToStaticClass.process(
|
|
13
|
+
[
|
|
14
|
+
path.join(testDirname, 'files-to-static-class.ts'),
|
|
15
|
+
path.join(testDirname, '../cli/cli-ratchet.ts'),
|
|
16
|
+
path.join(testDirname, '../third-party/git'),
|
|
17
|
+
],
|
|
18
|
+
'Test',
|
|
19
|
+
);
|
|
20
|
+
//Logger.info('xx: %s', out);
|
|
21
|
+
|
|
22
|
+
expect(out).not.toBeNull();
|
|
23
|
+
expect(out.length).toBeGreaterThan(0);
|
|
24
|
+
console.info('\n\n' + out);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Takes in a list of static files, and a class name, and generates a static
|
|
3
|
+
class containing each of the files in a map. This is to allow static
|
|
4
|
+
content to be passed through webpack safely
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs, { Stats } from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { CliRatchet } from '../cli/cli-ratchet.js';
|
|
10
|
+
import { Logger } from '@bitblit/ratchet-common/logger/logger';
|
|
11
|
+
import { RequireRatchet } from '@bitblit/ratchet-common/lang/require-ratchet';
|
|
12
|
+
|
|
13
|
+
export class FilesToStaticClass {
|
|
14
|
+
public static async process(inFileNames: string[], outClassName: string, outFileName: string = null): Promise<string> {
|
|
15
|
+
if (!inFileNames) {
|
|
16
|
+
throw new Error('fileNames must be defined');
|
|
17
|
+
}
|
|
18
|
+
if (!outClassName) {
|
|
19
|
+
throw new Error('outClassName must be defined');
|
|
20
|
+
}
|
|
21
|
+
if (inFileNames.length === 0) {
|
|
22
|
+
Logger.warn('Warning - no files supplied to process');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Preprocess to remove non-existent files and convert folders to files
|
|
26
|
+
const fileNames: string[] = [];
|
|
27
|
+
inFileNames.forEach((f) => FilesToStaticClass.processFilename(f, fileNames));
|
|
28
|
+
|
|
29
|
+
Logger.info('Generating class %s from files %j (output file: %s)', outClassName, fileNames, outFileName);
|
|
30
|
+
|
|
31
|
+
let rval = '/** \n';
|
|
32
|
+
rval += '* Holder for the constants to be used by consumers \n';
|
|
33
|
+
rval += '* Moves it into code so that it can survive a trip through WebPack \n';
|
|
34
|
+
rval += '*/ \n\n';
|
|
35
|
+
rval += 'export class ' + outClassName + ' { \n';
|
|
36
|
+
rval += ' public static readonly VALUES:Record<string, string> = { \n';
|
|
37
|
+
|
|
38
|
+
// If we reached here we can assume all the files exist and are files
|
|
39
|
+
for (let i = 0; i < fileNames.length; i++) {
|
|
40
|
+
// Remove both forward and back slashes...
|
|
41
|
+
let trimmed: string = fileNames[i].substring(fileNames[i].lastIndexOf('/') + 1);
|
|
42
|
+
trimmed = trimmed.substring(trimmed.lastIndexOf('\\') + 1);
|
|
43
|
+
const contents: string = fs.readFileSync(fileNames[i]).toString();
|
|
44
|
+
rval += i > 0 ? ',' : '';
|
|
45
|
+
rval += '"' + trimmed + '":' + JSON.stringify(contents) + '\n';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
rval += '}; \n';
|
|
49
|
+
rval += '}';
|
|
50
|
+
|
|
51
|
+
if (outFileName) {
|
|
52
|
+
Logger.info('Writing to %s', outFileName);
|
|
53
|
+
fs.writeFileSync(outFileName, rval);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return rval;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private static processFilename(fileName: string, targetArrayInPlace: string[]): void {
|
|
60
|
+
RequireRatchet.notNullOrUndefined(targetArrayInPlace, 'targetArrayInPlace');
|
|
61
|
+
if (fs.existsSync(fileName)) {
|
|
62
|
+
const stats: Stats = fs.statSync(fileName);
|
|
63
|
+
if (stats.isDirectory()) {
|
|
64
|
+
const contFiles: string[] = fs.readdirSync(fileName);
|
|
65
|
+
Logger.info('Found %d files in %s to process', contFiles.length, fileName);
|
|
66
|
+
contFiles.forEach((f) => FilesToStaticClass.processFilename(path.join(fileName, f), targetArrayInPlace));
|
|
67
|
+
} else if (stats.isFile()) {
|
|
68
|
+
targetArrayInPlace.push(fileName);
|
|
69
|
+
} else {
|
|
70
|
+
Logger.error('Skipping - neither file nor directory : %s', fileName);
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
Logger.warn('Could not find file %s', fileName);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
And, in case you are running this command line...
|
|
79
|
+
TODO: should use switches to allow setting the various non-filename params
|
|
80
|
+
**/
|
|
81
|
+
public static async runFromCliArgs(args: string[]): Promise<string> {
|
|
82
|
+
if (args.length < 3) {
|
|
83
|
+
Logger.infoP('Usage: ratchet-files-to-static-class {outFileName} {outClassName} {file0} ... {fileN}');
|
|
84
|
+
return null;
|
|
85
|
+
} else {
|
|
86
|
+
const idx: number = CliRatchet.indexOfCommandArgument('files-to-static-class');
|
|
87
|
+
const outFileName: string = process.argv[idx + 1];
|
|
88
|
+
const outClassName: string = process.argv[idx + 2];
|
|
89
|
+
const files: string[] = process.argv.slice(idx + 3);
|
|
90
|
+
|
|
91
|
+
Logger.info(
|
|
92
|
+
'Running FilesToStaticClass from command line arguments Target: %s TargetClass: %s InFiles: %j',
|
|
93
|
+
outFileName,
|
|
94
|
+
outClassName,
|
|
95
|
+
files,
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
return FilesToStaticClass.process(files, outClassName, outFileName);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Given a folder name, it renames all the files within it to a roughly
|
|
3
|
+
globally unique name (basically just timestamps) while retaining the
|
|
4
|
+
extensions. Mainly used to merge multiple folders that have files with the
|
|
5
|
+
same names
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs, { Stats } from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import { CliRatchet } from '../cli/cli-ratchet.js';
|
|
11
|
+
import { Logger } from '@bitblit/ratchet-common/logger/logger';
|
|
12
|
+
import { BooleanRatchet } from '@bitblit/ratchet-common/lang/boolean-ratchet';
|
|
13
|
+
import { StringRatchet } from '@bitblit/ratchet-common/lang/string-ratchet';
|
|
14
|
+
|
|
15
|
+
export class UniqueFileRename {
|
|
16
|
+
public static async process(inFolder: string, recursive: boolean = false, dryRun: boolean = false): Promise<number> {
|
|
17
|
+
let rval: number = 0;
|
|
18
|
+
if (!inFolder || inFolder.trim().length === 0) {
|
|
19
|
+
throw new Error('folder must be defined');
|
|
20
|
+
}
|
|
21
|
+
if (!fs.existsSync(inFolder)) {
|
|
22
|
+
throw new Error(inFolder + ' does not exist');
|
|
23
|
+
}
|
|
24
|
+
const stats: Stats = fs.statSync(inFolder);
|
|
25
|
+
if (!stats.isDirectory()) {
|
|
26
|
+
throw new Error(inFolder + ' is not a folder');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const contFiles: string[] = fs.readdirSync(inFolder);
|
|
30
|
+
for (const cFile of contFiles) {
|
|
31
|
+
//for (let i = 0; i < contFiles.length; i++) {
|
|
32
|
+
const full: string = path.join(inFolder, cFile);
|
|
33
|
+
const s2: Stats = fs.statSync(full);
|
|
34
|
+
if (s2.isFile()) {
|
|
35
|
+
const newNamePart: string = Date.now() + '_' + rval;
|
|
36
|
+
const idx: number = cFile.lastIndexOf('.');
|
|
37
|
+
const newName: string = idx > -1 ? newNamePart + cFile.substring(idx) : newNamePart;
|
|
38
|
+
const newFull: string = path.join(inFolder, newName);
|
|
39
|
+
if (dryRun) {
|
|
40
|
+
Logger.info('Would have renamed %s to %s', full, newFull);
|
|
41
|
+
} else {
|
|
42
|
+
Logger.info('Renaming %s to %s', full, newFull);
|
|
43
|
+
fs.renameSync(full, newFull);
|
|
44
|
+
}
|
|
45
|
+
rval++;
|
|
46
|
+
} else if (s2.isDirectory()) {
|
|
47
|
+
if (recursive) {
|
|
48
|
+
rval += await UniqueFileRename.process(full, recursive, dryRun);
|
|
49
|
+
} else {
|
|
50
|
+
Logger.info('Ignoring %s - folder, and recursive not specified', full);
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
Logger.info('Ignoring %s - neither file nor folder', full);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return rval;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
And, in case you are running this command line...
|
|
62
|
+
TODO: should use switches to allow setting the various non-filename params
|
|
63
|
+
**/
|
|
64
|
+
public static async runFromCliArgs(args: string[]): Promise<string> {
|
|
65
|
+
if (args.length !== 3) {
|
|
66
|
+
Logger.infoP('Usage: ratchet-unique-file-rename {folder} {recursive} {dryrun}');
|
|
67
|
+
Logger.infoP(args.length);
|
|
68
|
+
return null;
|
|
69
|
+
} else {
|
|
70
|
+
const idx: number = CliRatchet.indexOfCommandArgument('unique-file-rename');
|
|
71
|
+
const folder: string = process.argv[idx + 1];
|
|
72
|
+
const recursive: boolean = BooleanRatchet.parseBool(process.argv[idx + 2]);
|
|
73
|
+
const dryrun: boolean = BooleanRatchet.parseBool(process.argv[idx + 3]);
|
|
74
|
+
|
|
75
|
+
Logger.info('Running UniqueFileName from command line arguments Folder: %s Recursive: %s DryRun: %s', folder, recursive, dryrun);
|
|
76
|
+
|
|
77
|
+
return StringRatchet.safeString(UniqueFileRename.process(folder, recursive, dryrun));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Logger } from '@bitblit/ratchet-common/logger/logger';
|
|
2
|
+
import { LoggerLevelName } from '@bitblit/ratchet-common/logger/logger-level-name';
|
|
3
|
+
import http, { IncomingMessage, Server, ServerResponse } from 'http';
|
|
4
|
+
import https from 'https';
|
|
5
|
+
import { LocalServerCert } from './local-server-cert.js';
|
|
6
|
+
import { EsmRatchet } from '@bitblit/ratchet-common/lang/esm-ratchet';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import mime from 'mime-types';
|
|
10
|
+
import { SimpleArgRatchet } from '@bitblit/ratchet-common/lang/simple-arg-ratchet';
|
|
11
|
+
import { ErrorRatchet } from '@bitblit/ratchet-common/lang/error-ratchet';
|
|
12
|
+
import { NumberRatchet } from '@bitblit/ratchet-common/lang/number-ratchet';
|
|
13
|
+
import { BooleanRatchet } from '@bitblit/ratchet-common/lang/boolean-ratchet';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Very simple file server like 'serve', but can run https for things that
|
|
17
|
+
* need that (like most apis these days)
|
|
18
|
+
*/
|
|
19
|
+
export class LocalFileServer {
|
|
20
|
+
private server: Server;
|
|
21
|
+
private urlRoot: string;
|
|
22
|
+
|
|
23
|
+
constructor(
|
|
24
|
+
private port: number = 8888,
|
|
25
|
+
private https: boolean = false,
|
|
26
|
+
private fileRoot: string = EsmRatchet.fetchDirName(import.meta.url),
|
|
27
|
+
) {
|
|
28
|
+
if (fs.existsSync(fileRoot)) {
|
|
29
|
+
if (!fs.statSync(fileRoot).isDirectory()) {
|
|
30
|
+
throw ErrorRatchet.fErr('Cannot start with %s - it is not a directory', fileRoot);
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
throw ErrorRatchet.fErr('Cannot start with %s - it does not exist', fileRoot);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async runServer(): Promise<boolean> {
|
|
38
|
+
return new Promise<boolean>((res, rej) => {
|
|
39
|
+
try {
|
|
40
|
+
Logger.info('Starting file server on port %d at root %s', this.port, this.fileRoot);
|
|
41
|
+
|
|
42
|
+
if (this.https) {
|
|
43
|
+
const options = {
|
|
44
|
+
key: LocalServerCert.CLIENT_KEY_PEM,
|
|
45
|
+
cert: LocalServerCert.CLIENT_CERT_PEM,
|
|
46
|
+
};
|
|
47
|
+
Logger.info(
|
|
48
|
+
'Starting https server - THIS SERVER IS NOT SECURE! The KEYS are in the code! Testing Server Only - Use at your own risk!',
|
|
49
|
+
);
|
|
50
|
+
this.server = https.createServer(options, this.requestHandler.bind(this)).listen(this.port);
|
|
51
|
+
this.urlRoot = 'https://localhost:' + this.port;
|
|
52
|
+
} else {
|
|
53
|
+
this.server = http.createServer(this.requestHandler.bind(this)).listen(this.port);
|
|
54
|
+
this.urlRoot = 'http://localhost:' + this.port;
|
|
55
|
+
}
|
|
56
|
+
Logger.info('File server is listening');
|
|
57
|
+
|
|
58
|
+
// Also listen for SIGINT
|
|
59
|
+
process.on('SIGINT', () => {
|
|
60
|
+
Logger.info('Caught SIGINT - shutting down test server...');
|
|
61
|
+
this.server.close();
|
|
62
|
+
res(true);
|
|
63
|
+
});
|
|
64
|
+
} catch (err) {
|
|
65
|
+
Logger.error('Local server failed : %s', err, err);
|
|
66
|
+
rej(err);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async requestHandler(request: IncomingMessage, response: ServerResponse): Promise<any> {
|
|
72
|
+
const reqPath: string = request.url.includes('?') ? request.url.substring(0, request.url.indexOf('?')) : request.url;
|
|
73
|
+
const filePath: string = path.join(this.fileRoot, reqPath);
|
|
74
|
+
if (fs.existsSync(filePath)) {
|
|
75
|
+
const stats: fs.Stats = fs.statSync(filePath);
|
|
76
|
+
if (stats.isFile()) {
|
|
77
|
+
let mimetype: string = mime.contentType(filePath);
|
|
78
|
+
if (mimetype === 'video/mp2t') {
|
|
79
|
+
// Not very likely for me!
|
|
80
|
+
mimetype = 'text/x-typescript';
|
|
81
|
+
}
|
|
82
|
+
const buf: Buffer = fs.readFileSync(filePath);
|
|
83
|
+
response.setHeader('Content-Type', mimetype);
|
|
84
|
+
response.statusCode = 200;
|
|
85
|
+
response.end(buf);
|
|
86
|
+
} else if (stats.isDirectory()) {
|
|
87
|
+
this.writeFolderListToResponse(reqPath, filePath, response);
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
response.statusCode = 404;
|
|
91
|
+
response.setHeader('Content-Type', 'text/html');
|
|
92
|
+
response.end(`<html><body>No such file: ${reqPath}</body></html>`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
public writeFolderListToResponse(rootpth: string, pth: string, response: ServerResponse): void {
|
|
97
|
+
response.statusCode = 200;
|
|
98
|
+
response.setHeader('Content-Type', 'text/html');
|
|
99
|
+
|
|
100
|
+
let body: string = '<html><body><h1>Files in ' + pth + '</h1><ul>';
|
|
101
|
+
|
|
102
|
+
if (rootpth !== '/') {
|
|
103
|
+
let sub: string = rootpth.substring(0, rootpth.lastIndexOf('/'));
|
|
104
|
+
sub = sub.length === 0 ? '/' : sub;
|
|
105
|
+
body += '<li><a href="' + sub + '">..</a></li>';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
fs.readdirSync(pth).forEach((file) => {
|
|
109
|
+
let fullUrl: string = this.urlRoot + rootpth;
|
|
110
|
+
fullUrl += fullUrl.endsWith('/') ? file : '/' + file;
|
|
111
|
+
body += `<li><a href="${fullUrl}">${file}</a></li>`;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
body += '</ul></body></html>';
|
|
115
|
+
|
|
116
|
+
response.end(body);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public static async runLocalFileServerFromCliArgs(args: string[]): Promise<void> {
|
|
120
|
+
Logger.setLevel(LoggerLevelName.debug);
|
|
121
|
+
const pArgs: Record<string, string> = SimpleArgRatchet.parseSingleArgs(args, ['root', 'port', 'https']);
|
|
122
|
+
const port: number = pArgs['port'] ? NumberRatchet.safeNumber(pArgs['port']) : 8888;
|
|
123
|
+
const https: boolean = pArgs['https'] ? BooleanRatchet.parseBool(pArgs['https']) : false;
|
|
124
|
+
const root: string = pArgs['root'] ?? EsmRatchet.fetchDirName(import.meta.url);
|
|
125
|
+
const testServer: LocalFileServer = new LocalFileServer(port, https, root);
|
|
126
|
+
const res: boolean = await testServer.runServer();
|
|
127
|
+
Logger.info('Res was : %s', res);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export class LocalServerCert {
|
|
2
|
+
public static readonly CLIENT_CSR: string =
|
|
3
|
+
'-----BEGIN CERTIFICATE REQUEST-----\n' +
|
|
4
|
+
'MIICwjCCAaoCAQAwfTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk5WMRIwEAYDVQQH\n' +
|
|
5
|
+
'DAlMYXMgVmVnYXMxDjAMBgNVBAoMBVBsdW1hMRkwFwYDVQQDDBB3d3cuaGV5cGx1\n' +
|
|
6
|
+
'bWEuY29tMSIwIAYJKoZIhvcNAQkBFhNjd2Vpc3NAaGV5cGx1bWEuY29tMIIBIjAN\n' +
|
|
7
|
+
'BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Gh1SD1VciosMgh3MHF7IyKgNTu2\n' +
|
|
8
|
+
'DjGMoORJV4VNKzrjp7UOdKnD3so0Xx3A6bUTYAL8Vtc+EG+8LZjUpBIOwuKzzbVo\n' +
|
|
9
|
+
't16+yt3YXBxzTZPU9g7sFsXx42RoNispIFq0enRtJrq/zq8izZxUZdGn9XD2DidL\n' +
|
|
10
|
+
'AedtRGjb8cmRuP6wmMRPBOZjct4ZjsSiTyhPa6kpt8V7Pa7vm0HGxcHiDAGp6zoy\n' +
|
|
11
|
+
'GHfBdsqeBdGbkGT1ZPfs9kpbtXPm82Sckd0p3oY3fJn0rZqpTAb8qcdGcnheYtqX\n' +
|
|
12
|
+
'FSjX7EoGsIXAK+oj25MvtfoZFMk4rjQ7FkHbgGk0iMHLN0kNjJzN0ysN/wIDAQAB\n' +
|
|
13
|
+
'oAAwDQYJKoZIhvcNAQELBQADggEBAMbpCdoqmY9crolsh5y9YtYDLRIwisTjTjU1\n' +
|
|
14
|
+
'Xzp1MurSzGIdHLokU+fdVWTIzn3uOu24yTQouTUUoYWHT4YgN4wELdDydfNxWvyl\n' +
|
|
15
|
+
'r34QV5B0FZbRV2sNz/3C1UX/Uor4af1Yv+QYlGHspgj+WIAEkNQ3xQIo9+I/miR+\n' +
|
|
16
|
+
'2VSlydtyGvmzipgv6CAwOsrQsIw7DkpVmnqIjgjPSXlGCgeKM9S1D/CwNwZnVA/e\n' +
|
|
17
|
+
'DF1SzDkJKl60/n+xZGYl/OtkH9vB8T6fHqk0iMxXuVUxI137fwEJwIQB5L6hFyJa\n' +
|
|
18
|
+
'L4hbjq7Cull4qOhXDby+fNJT9Ic7VCosJBXBHxHPsEnY2+TZAJo=\n' +
|
|
19
|
+
'-----END CERTIFICATE REQUEST-----\n';
|
|
20
|
+
|
|
21
|
+
public static readonly CLIENT_CERT_PEM: string =
|
|
22
|
+
'-----BEGIN CERTIFICATE-----\n' +
|
|
23
|
+
'MIIDgTCCAmkCFDKASki0c6HD75dCdIZZ3vXq4eQeMA0GCSqGSIb3DQEBCwUAMH0x\n' +
|
|
24
|
+
'CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOVjESMBAGA1UEBwwJTGFzIFZlZ2FzMQ4w\n' +
|
|
25
|
+
'DAYDVQQKDAVQbHVtYTEZMBcGA1UEAwwQd3d3LmhleXBsdW1hLmNvbTEiMCAGCSqG\n' +
|
|
26
|
+
'SIb3DQEJARYTY3dlaXNzQGhleXBsdW1hLmNvbTAeFw0yMzAxMjMxMDU2MDlaFw0y\n' +
|
|
27
|
+
'MzAyMjIxMDU2MDlaMH0xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOVjESMBAGA1UE\n' +
|
|
28
|
+
'BwwJTGFzIFZlZ2FzMQ4wDAYDVQQKDAVQbHVtYTEZMBcGA1UEAwwQd3d3LmhleXBs\n' +
|
|
29
|
+
'dW1hLmNvbTEiMCAGCSqGSIb3DQEJARYTY3dlaXNzQGhleXBsdW1hLmNvbTCCASIw\n' +
|
|
30
|
+
'DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANhodUg9VXIqLDIIdzBxeyMioDU7\n' +
|
|
31
|
+
'tg4xjKDkSVeFTSs646e1DnSpw97KNF8dwOm1E2AC/FbXPhBvvC2Y1KQSDsLis821\n' +
|
|
32
|
+
'aLdevsrd2Fwcc02T1PYO7BbF8eNkaDYrKSBatHp0bSa6v86vIs2cVGXRp/Vw9g4n\n' +
|
|
33
|
+
'SwHnbURo2/HJkbj+sJjETwTmY3LeGY7Eok8oT2upKbfFez2u75tBxsXB4gwBqes6\n' +
|
|
34
|
+
'Mhh3wXbKngXRm5Bk9WT37PZKW7Vz5vNknJHdKd6GN3yZ9K2aqUwG/KnHRnJ4XmLa\n' +
|
|
35
|
+
'lxUo1+xKBrCFwCvqI9uTL7X6GRTJOK40OxZB24BpNIjByzdJDYyczdMrDf8CAwEA\n' +
|
|
36
|
+
'ATANBgkqhkiG9w0BAQsFAAOCAQEAWQG2tvWY+cyeeumD/7WKTBNaBjg4EAe+1mnZ\n' +
|
|
37
|
+
'KQsg0gGUL0kWsqCkg4xEqIojkKMjs62uS6ballEyWawygYd91OaJLFopNu+Dxk4N\n' +
|
|
38
|
+
'5GWKpriPr02vI6rMUZNtCmsooukEShr5ufFWb4WLnk4NXQlBCXTHbmIf7Z82UOMw\n' +
|
|
39
|
+
'ONZdZyKLqlA0Z6SWYBp2gO32puww6dUU0DAKkIIx1SN8i8UKvowRAy13bugPtyau\n' +
|
|
40
|
+
'NknlE3J1+Gab1hHCMRdKFZPKy8nc7LWUNZhgKdY82IC/k5FSW32Wibfog1TwWRJR\n' +
|
|
41
|
+
'ceTW4EN4P7ZmdHGMYkIplc7Qcx0mraY2HRqmjA33j3cNcY5UsQ==\n' +
|
|
42
|
+
'-----END CERTIFICATE-----\n';
|
|
43
|
+
|
|
44
|
+
public static readonly CLIENT_KEY_PEM: string =
|
|
45
|
+
'-----BEGIN RSA PRIVATE KEY-----\n' +
|
|
46
|
+
'MIIEogIBAAKCAQEA2Gh1SD1VciosMgh3MHF7IyKgNTu2DjGMoORJV4VNKzrjp7UO\n' +
|
|
47
|
+
'dKnD3so0Xx3A6bUTYAL8Vtc+EG+8LZjUpBIOwuKzzbVot16+yt3YXBxzTZPU9g7s\n' +
|
|
48
|
+
'FsXx42RoNispIFq0enRtJrq/zq8izZxUZdGn9XD2DidLAedtRGjb8cmRuP6wmMRP\n' +
|
|
49
|
+
'BOZjct4ZjsSiTyhPa6kpt8V7Pa7vm0HGxcHiDAGp6zoyGHfBdsqeBdGbkGT1ZPfs\n' +
|
|
50
|
+
'9kpbtXPm82Sckd0p3oY3fJn0rZqpTAb8qcdGcnheYtqXFSjX7EoGsIXAK+oj25Mv\n' +
|
|
51
|
+
'tfoZFMk4rjQ7FkHbgGk0iMHLN0kNjJzN0ysN/wIDAQABAoIBAHzZ9yAQUqWk8w6C\n' +
|
|
52
|
+
'l9EZB4PDzE4p/uS9bXa9fhrCSz0vonv1FzvzXY/BdOmTTuMGlwEDd/XaBHKTJCvi\n' +
|
|
53
|
+
'SnvF90I0bKu3h4yTWtvLlbG+sD8HlQvInCifVuhr2zu1Nur1qb4kQXzgrRxfKmMZ\n' +
|
|
54
|
+
'WA/OH2qZGzwbK0kT7ZRUMuCR/EKPjYw9KP6pMF8nxXUjSm+g3YwgIB8kiPeDCV6C\n' +
|
|
55
|
+
'0/Ecpv5qMqcoYTg9f9KyBNmY3U5ZgbYrdwTDSMrrTwZKSHhTktdF0SEfYrVGLLEU\n' +
|
|
56
|
+
'vfQQlQmfcc5Z3+cz99BH1BeTNaCPtEaXjgvQYwkWlSxnY6QUE0p1qq9Jy0xaVEx9\n' +
|
|
57
|
+
'8LReuHECgYEA/hhJ6b2XV1WLtszFO8MRMzlyMuvBc3Ot0dsuJ7r1W3WOF5X7poSU\n' +
|
|
58
|
+
'xG0xe+n6Kubmi3tGhzS7BN4TEO9/SSE2gIQTk9zwAMuf+mZJQcG0Qz1iftVp5nnM\n' +
|
|
59
|
+
'zi205vBLLq2Pmk7wbhTIBO8J190Dli1/fuvk/cmJrA60Ys8v3y4e820CgYEA2gfV\n' +
|
|
60
|
+
'Q3eHRxk3wl4On71mMovY7lMx6+S8K8hBGo26A5FIu6N6v2jxKnQ5YphwFgjtkxgs\n' +
|
|
61
|
+
'LspAXmxRwFMapZP3d/nFeBhlOzNly7tqIdDOeNqEcUzEx7pwGXpWGZEHOYs1fvU+\n' +
|
|
62
|
+
'gU/1N8q9DBvr0B+XGeYR3NNdljajo6pwWZ6ed5sCgYAGmkz5ZPLU0yVBR8rsRaJh\n' +
|
|
63
|
+
'yWFdT2EEhgIDTQXDBImxqblahYw3hIR1Ij1B8g+NI9jj0P1BMC6X7sliDEcreFB5\n' +
|
|
64
|
+
'QHVdx0T5UFFE6XmH2ue7Q5IWp6cL1ShsRyXHRoE9okb0BI8c3S9haXDBCj44ndAN\n' +
|
|
65
|
+
'VUXrDlykevFXC/k7fHBTdQKBgHjIshpoEycOD1e753oS4JTL6GdO6271DlFq5LYj\n' +
|
|
66
|
+
'IZNsXtCkJhH3vvJ35Hp8XEu4snQ0hfV90d79PuS+pRppOETct8pqKVp8hL4ymv8U\n' +
|
|
67
|
+
'v+0vkQN7NeA3pnZW0W/kag400nP8xJ26f+xiggw9Q4vOlFSioe6loUjgCBNZDlh3\n' +
|
|
68
|
+
'iO5VAoGASXkcv39B9/8FuWQ7rwhXHEubxOOwZZzSBU1wtLj2qHyPGoeU9ZpsmdrL\n' +
|
|
69
|
+
'XS9w1Jy0e49qmbBjzTqAEBw2nn/JVMFHyI/s//JVF7Q9GfBZAF5XF6mJ24yzTKgF\n' +
|
|
70
|
+
'kSoO5T+7s8NXi0eAIBe4CkWCBX7kWEZtu46GuVhsrC3oEazuLOs=\n' +
|
|
71
|
+
'-----END RSA PRIVATE KEY-----\n';
|
|
72
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { LoggerLevelName } from '@bitblit/ratchet-common/logger/logger-level-name';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Functions to help with creating and decoding JWTs
|
|
5
|
+
*
|
|
6
|
+
* JWTRatchet accepts promises for its inputs for the simple reason that best practice dictates that the keys
|
|
7
|
+
* should never be in the code, which means it is likely somewhere else. That MIGHT be somewhere synchronous
|
|
8
|
+
* like an environmental variable, but it could very likely be someplace remote like a secure key store. By
|
|
9
|
+
* accepting promises here, we make it easy to do JwtRatchet construction in a place (like an IOT container)
|
|
10
|
+
* that itself must be synchronous
|
|
11
|
+
*/
|
|
12
|
+
export interface JwtRatchetConfig {
|
|
13
|
+
encryptionKeyPromise: Promise<string | string[]>;
|
|
14
|
+
decryptKeysPromise?: Promise<string[]>;
|
|
15
|
+
jtiGenerator?: () => string;
|
|
16
|
+
decryptOnlyKeyUseLogLevel?: LoggerLevelName;
|
|
17
|
+
parseFailureLogLevel?: LoggerLevelName;
|
|
18
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { LoggerLevelName } from '@bitblit/ratchet-common/logger/logger-level-name';
|
|
2
|
+
import { JwtTokenBase } from '@bitblit/ratchet-common/jwt/jwt-token-base';
|
|
3
|
+
import { ExpiredJwtHandling } from '@bitblit/ratchet-common/jwt/expired-jwt-handling';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Classes implementing this interface have Functions to help with creating and decoding JWTs
|
|
7
|
+
*/
|
|
8
|
+
export interface JwtRatchetLike {
|
|
9
|
+
get encryptionKeyPromise(): Promise<string | string[]>;
|
|
10
|
+
get decryptKeysPromise(): Promise<string[]>;
|
|
11
|
+
get jtiGenerator(): () => string;
|
|
12
|
+
get decryptOnlyKeyUseLogLevel(): LoggerLevelName;
|
|
13
|
+
get parseFailureLogLevel(): LoggerLevelName;
|
|
14
|
+
decodeToken<T extends JwtTokenBase>(payloadString: string, expiredHandling?: ExpiredJwtHandling): Promise<T>;
|
|
15
|
+
encryptionKeyArray(): Promise<string[]>;
|
|
16
|
+
selectRandomEncryptionKey(): Promise<string>;
|
|
17
|
+
createTokenString(payload: any, expirationSeconds?: number, overrideEncryptionKey?: string): Promise<string>;
|
|
18
|
+
refreshJWTString(tokenString: string, allowExpired?: boolean, expirationSeconds?: number): Promise<string>;
|
|
19
|
+
}
|