Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • jw_sonar
  • jw_sonar_backup
  • main
  • move-to-esm-only
  • open_tool_for_self_hosting
  • v5.0
  • v4.1
  • v4.2
8 results

Target

Select target project
  • dojo_project/projects/shared/nodesharedcode
1 result
Select Git revision
  • jw_sonar
  • jw_sonar_backup
  • main
  • move-to-esm-only
  • open_tool_for_self_hosting
  • v5.0
  • v4.1
  • v4.2
8 results
Show changes
Commits on Source (26)
Showing with 571 additions and 7 deletions
......@@ -6,6 +6,9 @@ This repo contains some code that can be shared across node projects of Dojo.
These packages are needed :
- `ajv`
- `json5`
- `tar-stream`
- `winston`
## How to use it
......@@ -13,5 +16,5 @@ These packages are needed :
By adding this repo as submodule
```bash
git submodule add ssh://git@ssh.hesge.ch:10572/isc/projects/dojo/projects/shared/nodesharedcode.git shared
git submodule add ../../shared/nodesharedcode.git shared
```
\ No newline at end of file
import fs from 'node:fs';
import path from 'node:path';
import tar from 'tar-stream';
import stream from 'node:stream';
import { Writable } from 'stream';
import zlib from 'zlib';
class ArchiveHelper {
private async explore(absoluteBasePath: string, rootPath: string, pack: tar.Pack) {
for ( let file of await fs.promises.readdir(rootPath) ) {
if ( file === 'output.tar' ) {
continue;
}
file = path.join(rootPath, file);
const stat = await fs.promises.stat(file);
if ( stat.isDirectory() ) {
await this.explore(absoluteBasePath, file, pack);
continue;
}
const entry = pack.entry({
name: file.replace(absoluteBasePath, ''),
size: stat.size
}, (err) => {
if ( err ) {
throw err;
}
});
const stream = fs.createReadStream(file);
stream.pipe(entry);
}
}
private async compress(folderPath: string, tarDataStream: stream.Writable) {
const pack = tar.pack();
await this.explore(folderPath, folderPath, pack);
pack.pipe(zlib.createGzip()).pipe(tarDataStream);
pack.finalize();
}
public async getBase64(folderPath: string): Promise<string> {
let data: any;
const tarDataStream = new stream.Writable({
write(this: Writable, chunk: Buffer, _encoding: BufferEncoding, next: (error?: Error | null) => void) {
if ( data ) {
data += chunk.toString('hex');
} else {
data = chunk.toString('hex');
}
next();
}
});
await this.compress(folderPath, tarDataStream);
await (new Promise((resolve, reject) => {
tarDataStream.on('close', () => {
resolve(0);
});
}));
return Buffer.from(data, 'hex').toString('base64');
}
}
export default new ArchiveHelper();
\ No newline at end of file
import Ajv, { ErrorObject, JTDSchemaType } from 'ajv/dist/jtd';
import fs from 'fs';
import ExerciceResultsFile from '../types/Dojo/ExerciceResultsFile';
import JSON5 from 'json5';
class ExerciceHelper {
validateResultFile(resultsFilePathOrStr: string, isFile: boolean = true): { results: ExerciceResultsFile | undefined, isValid: boolean, errors: Array<ErrorObject<string, Record<string, any>, unknown> | string> | null | undefined } {
const ajv = new Ajv();
const schema: JTDSchemaType<ExerciceResultsFile> = {
properties : {
success: { type: 'boolean' }
},
optionalProperties : {
successfulTests: { type: 'uint32' },
failedTests : { type: 'uint32' },
successfulTestsList: {
elements: {
type: 'string'
}
},
failedTestsList : {
elements: {
type: 'string'
}
}
},
additionalProperties: false
};
const validator = ajv.compile(schema);
try {
const results = JSON5.parse(isFile ? fs.readFileSync(resultsFilePathOrStr, 'utf8') : resultsFilePathOrStr);
const isValid = validator(results);
if ( isValid ) {
if ( results.successfulTests === undefined && results.successfulTestsList !== undefined ) {
results.successfulTests = results.successfulTestsList.length;
}
if ( results.failedTests === undefined && results.failedTestsList !== undefined ) {
results.failedTests = results.failedTestsList.length;
}
}
return {
results: isValid ? results : results as any,
isValid: isValid,
errors : validator.errors
};
} catch ( error ) {
return {
results: undefined,
isValid: false,
errors : [ `JSON5 invalid : ${ JSON.stringify(error) }` ]
};
}
}
}
export default new ExerciceHelper();
\ No newline at end of file
import fs from 'fs/promises';
import path from 'path';
class Toolbox {
public urlToPath(url: string): string {
return url.replace(/^([a-z]{3,5}:\/{2})?[a-z.@]+(:[0-9]{1,5})?.(.*)/, '$3').replace('.git', '');
}
/*
Source of getAllFiles and getTotalSize (modified for this project): https://coderrocketfuel.com/article/get-the-total-size-of-all-files-in-a-directory-using-node-js
*/
private async getAllFiles(dirPath: string, arrayOfFiles: Array<string> = []): Promise<Array<string>> {
let files = await fs.readdir(dirPath);
await Promise.all(files.map(async file => {
if ( (await fs.stat(dirPath + '/' + file)).isDirectory() ) {
arrayOfFiles = await this.getAllFiles(dirPath + '/' + file, arrayOfFiles);
} else {
arrayOfFiles.push(path.join(dirPath, file));
}
}));
return arrayOfFiles;
};
private async getTotalSize(directoryPath: string): Promise<number> {
const arrayOfFiles = await this.getAllFiles(directoryPath);
let totalSize = 0;
for ( const filePath of arrayOfFiles ) {
totalSize += (await fs.stat(filePath)).size;
}
return totalSize;
};
get fs() {
return {
getAllFiles : this.getAllFiles.bind(this),
getTotalSize: this.getTotalSize.bind(this)
};
}
public snakeToCamel(str: string): string {
return str.toLowerCase().replace(/([-_][a-z])/g, (group: string) => group.toUpperCase().replace('-', '').replace('_', ''));
}
public getKeysWithPrefix(obj: object, prefix: string): Array<string> {
return Object.keys(obj).filter(key => key.startsWith(prefix));
}
}
......
......@@ -8,6 +8,7 @@ declare global {
toBoolean: () => boolean;
capitalizingFirstLetter: () => string;
capitalizeName: () => string;
convertWithEnvVars: () => string;
}
}
......@@ -17,6 +18,7 @@ function registerAll() {
registerStringToBoolean();
registerStringCapitalizingFirstLetter();
registerStringCapitalizeName();
registerStringConvertWithEnvVars();
}
function registerBigIntJson() {
......@@ -55,6 +57,14 @@ function registerStringCapitalizeName() {
};
}
function registerStringConvertWithEnvVars() {
String.prototype.convertWithEnvVars = function (this: string): string {
return this.replace(/\${?([a-zA-Z0-9_]+)}?/g, (_match: string, p1: string) => {
return process.env[p1] || '';
});
};
}
registerAll();
......
Source: recursive-readdir-files
===
Modified for Dojo
===
## Usage
```js
import recursiveReaddirFiles from 'recursive-readdir-files';
const files = await recursiveReaddirFiles(process.cwd(), {
ignored: /\/(node_modules|\.git)/
});
// `files` is an array
console.log(files);
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
// [
// {
// dev: 16777233,
// mode: 33188,
// nlink: 1,
// uid: 501,
// gid: 20,
// rdev: 0,
// blksize: 4096,
// ino: 145023089,
// size: 89,
// blocks: 8,
// atimeMs: 1649303678077.934,
// mtimeMs: 1649303676847.1777,
// ctimeMs: 1649303676847.1777,
// birthtimeMs: 1649301118132.6782,
// atime: 2022-04-07T03:54:38.078Z,
// mtime: 2022-04-07T03:54:36.847Z,
// ctime: 2022-04-07T03:54:36.847Z,
// birthtime: 2022-04-07T03:11:58.133Z,
// name: 'watch.ts',
// path: '/Users/xxx/watch.ts',
// ext: 'ts'
// },
// // ...
// ]
```
Or
```js
recursiveReaddirFiles(process.cwd(), {
ignored: /\/(node_modules|\.git)/
}, (filepath, state) => {
console.log(filepath);
// 👉 /Users/xxx/watch.ts
console.log(state.isFile()); // 👉 true
console.log(state.isDirectory()); // 👉 false
console.log(state);
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
// {
// dev: 16777233,
// mode: 33188,
// nlink: 1,
// uid: 501,
// gid: 20,
// rdev: 0,
// blksize: 4096,
// ino: 145023089,
// size: 89,
// blocks: 8,
// atimeMs: 1649303678077.934,
// mtimeMs: 1649303676847.1777,
// ctimeMs: 1649303676847.1777,
// birthtimeMs: 1649301118132.6782,
// atime: 2022-04-07T03:54:38.078Z,
// mtime: 2022-04-07T03:54:36.847Z,
// ctime: 2022-04-07T03:54:36.847Z,
// birthtime: 2022-04-07T03:11:58.133Z,
// name: 'watch.ts',
// path: '/Users/xxx/watch.ts',
// ext: 'ts'
// }
})
```
## Options
```ts
export interface RecursiveReaddirFilesOptions {
/**
* Ignore files
* @example `/\/(node_modules|\.git)/`
*/
ignored?: RegExp;
/**
* Specifies a list of `glob` patterns that match files to be included in compilation.
* @example `/(\.json)$/`
*/
include?: RegExp;
/**
* Specifies a list of files to be excluded from compilation.
* @example `/(package\.json)$/`
*/
exclude?: RegExp;
/** Provide filtering methods to filter data. */
filter?: (item: IFileDirStat) => boolean;
/** Do not give the absolute path but the relative one from the root folder */
replacePathByRelativeOne?: boolean;
/** Remove stats that are not necessary for transfert */
liteStats?: boolean;
}
```
## Result
```ts
import fs from 'node:fs';
export interface IFileDirStat extends Partial<fs.Stats> {
/**
* @example `/a/sum.jpg` => `sum.jpg`
*/
name: string;
/**
* @example `/basic/src/utils/sum.ts`
*/
path: string;
/**
* @example `/a/b.jpg` => `jpg`
*/
ext?: string;
}
declare type Callback = (filepath: string, stat: IFileDirStat) => void;
export default function recursiveReaddirFiles(rootPath: string, options?: RecursiveReaddirFilesOptions, callback?: Callback): Promise<IFileDirStat[]>;
export { recursiveReaddirFiles };
export declare const getStat: (filepath: string) => Promise<IFileDirStat>;
/**
* Get ext
* @param {String} filePath `/a/b.jpg` => `jpg`
*/
export declare const getExt: (filePath: string) => string;
```
## License
Licensed under the MIT License.
import fs from 'node:fs';
import path from 'node:path';
export interface RecursiveReaddirFilesOptions {
/**
* Ignore files
* @example `/\/(node_modules|\.git)/`
*/
ignored?: RegExp;
/**
* Specifies a list of `glob` patterns that match files to be included in compilation.
* @example `/(\.json)$/`
*/
include?: RegExp;
/**
* Specifies a list of files to be excluded from compilation.
* @example `/(package\.json)$/`
*/
exclude?: RegExp;
/** Provide filtering methods to filter data. */
filter?: (item: IFileDirStat) => boolean;
/** Do not give the absolute path but the relative one from the root folder */
replacePathByRelativeOne?: boolean;
/** Remove stats that are not necessary for transfert */
liteStats?: boolean;
}
export interface IFileDirStat extends Partial<fs.Stats> {
/**
* @example `/a/sum.jpg` => `sum.jpg`
*/
name: string;
/**
* @example `/basic/src/utils/sum.ts`
*/
path: string;
/**
* @example `/a/b.jpg` => `jpg`
*/
ext?: string;
}
type Callback = (filepath: string, stat: IFileDirStat) => void;
class RecursiveFilesStats {
async explore(rootPath: string, options: RecursiveReaddirFilesOptions = {}, callback?: Callback): Promise<IFileDirStat[]> {
return this.getFiles(`${ path.resolve(rootPath) }/`, rootPath, options, [], callback);
}
private async getFiles(absoluteBasePath: string, rootPath: string, options: RecursiveReaddirFilesOptions = {}, files: IFileDirStat[] = [], callback?: Callback): Promise<IFileDirStat[]> {
const {
ignored, include, exclude, filter
} = options;
const filesData = await fs.promises.readdir(rootPath);
const fileDir: IFileDirStat[] = filesData.map((file) => ({
name: file, path: path.join(rootPath, file)
})).filter((item) => {
if ( include && include.test(item.path) ) {
return true;
}
if ( exclude && exclude.test(item.path) ) {
return false;
}
if ( ignored ) {
return !ignored.test(item.path);
}
return true;
});
if ( callback ) {
fileDir.map(async (item: IFileDirStat) => {
const stat = await this.getStat(item.path, absoluteBasePath, options);
if ( stat.isDirectory!() ) {
await this.getFiles(absoluteBasePath, item.path, options, [], callback);
}
callback(item.path, stat);
});
} else {
await Promise.all(fileDir.map(async (item: IFileDirStat) => {
const stat = await this.getStat(item.path, absoluteBasePath, options);
if ( stat.isDirectory!() ) {
const arr = await this.getFiles(absoluteBasePath, item.path, options, []);
files = files.concat(arr);
} else if ( stat.isFile!() ) {
files.push(stat);
}
}));
}
return files.filter((item) => {
if ( filter && typeof filter === 'function' ) {
return filter(item);
}
return true;
});
}
private async getStat(filepath: string, absoluteRootPath: string, options: RecursiveReaddirFilesOptions): Promise<IFileDirStat> {
const stat = (await fs.promises.stat(filepath)) as IFileDirStat;
stat.ext = '';
if ( stat.isFile!() ) {
stat.ext = this.getExt(filepath);
stat.name = path.basename(filepath);
stat.path = path.resolve(filepath);
}
if ( options.replacePathByRelativeOne && stat.path ) {
stat.path = stat.path.replace(absoluteRootPath, '');
}
if ( options.liteStats ) {
delete stat.dev;
delete stat.nlink;
delete stat.uid;
delete stat.gid;
delete stat.rdev;
delete stat.blksize;
delete stat.ino;
delete stat.blocks;
delete stat.atimeMs;
delete stat.mtimeMs;
delete stat.ctimeMs;
delete stat.birthtimeMs;
delete stat.atime;
//delete stat.mtime;
delete stat.ctime;
//delete stat.birthtime;
//delete stat.mode;
}
return stat;
};
/**
* Get ext
* @param {String} filePath `/a/b.jpg` => `jpg`
*/
private getExt(filePath: string): string {
return path.extname(filePath).replace(/^\./, '').toLowerCase();
}
}
export default new RecursiveFilesStats();
......@@ -2,7 +2,7 @@ interface DojoResponse<T> {
timestamp: string;
code: number;
description: string;
sessionToken: string;
sessionToken: string | null;
data: T;
}
......
import ImmutableFileDescriptor from './ImmutableFileDescriptor';
interface EnonceFile {
dojoEnonceVersion: number,
version: number,
immutable: Array<ImmutableFileDescriptor>
result: {
container: string, volume: string
}
}
export default EnonceFile;
\ No newline at end of file
interface ExerciceResultsFile {
success: boolean;
successfulTests?: number;
failedTests?: number;
successfulTestsList?: Array<string>;
failedTestsList?: Array<string>;
}
export default ExerciceResultsFile;
\ No newline at end of file
interface ImmutableFileDescriptor {
description: string,
path: string,
isDirectory: boolean,
}
export default ImmutableFileDescriptor;
\ No newline at end of file
interface GitlabFile {
file_name: string,
file_path: string,
size: number,
encoding: string,
content_sha256: string,
ref: string,
blob_id: string,
commit_id: string,
last_commit_id: string,
execute_filemode: boolean,
content: string,
}
export default GitlabFile;
\ No newline at end of file
......@@ -2,10 +2,10 @@ import GitlabUser from './GitlabUser';
interface GitlabMember extends GitlabUser {
'access_level': number,
'created_at': string,
'created_by': GitlabUser,
'expires_at': string | null
access_level: number,
created_at: string,
created_by: GitlabUser,
expires_at: string | null
}
......
......@@ -8,7 +8,9 @@ enum GitlabRoutes {
REPOSITORY_MEMBER_ADD = '/projects/{{id}}/members',
REPOSITORY_MEMBERS_GET = '/projects/{{id}}/members/all',
REPOSITORY_VARIABLES_ADD = '/projects/{{id}}/variables',
REPOSITORY_BRANCHES_PROTECT = '/projects/{{id}}/protected_branches'
REPOSITORY_BRANCHES_PROTECT = '/projects/{{id}}/protected_branches',
REPOSITORY_TREE = '/projects/{{id}}/repository/tree',
REPOSITORY_FILE = '/projects/{{id}}/repository/files/{{filePath}}',
}
......
import GitlabTreeFileType from './GitlabTreeFileType';
interface GitlabTreeFile {
id: number,
name: string,
type: GitlabTreeFileType,
path: string,
mode: string
}
export default GitlabTreeFile;
\ No newline at end of file
enum GitlabTreeFileType {
TREE = 'tree',
BLOB = 'blob',
COMMIT = 'commit'
}
export default GitlabTreeFileType;