@mxpicture/gcp-functions-backend 0.1.53 → 0.1.55
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/dist/change/ChangeHandler.d.ts +13 -0
- package/dist/change/ChangeHandler.js +143 -0
- package/dist/change/GitChanges.d.ts +34 -0
- package/dist/change/GitChanges.js +91 -0
- package/dist/change/index.d.ts +3 -0
- package/dist/change/index.js +3 -0
- package/dist/change/types.change.d.ts +32 -0
- package/dist/change/types.change.js +4 -0
- package/dist/generator/Generator.d.ts +34 -0
- package/dist/generator/Generator.js +110 -0
- package/dist/generator/GeneratorAnnotations.d.ts +9 -0
- package/dist/generator/GeneratorAnnotations.js +23 -0
- package/dist/generator/GeneratorBackend.d.ts +13 -0
- package/dist/generator/GeneratorBackend.js +131 -0
- package/dist/generator/GeneratorDoc.d.ts +9 -0
- package/dist/generator/GeneratorDoc.js +23 -0
- package/dist/generator/GeneratorFrontend.d.ts +12 -0
- package/dist/generator/GeneratorFrontend.js +94 -0
- package/dist/generator/GeneratorRoutes.d.ts +11 -0
- package/dist/generator/GeneratorRoutes.js +58 -0
- package/dist/generator/GeneratorZod.d.ts +26 -0
- package/dist/generator/GeneratorZod.js +151 -0
- package/dist/generator/index.d.ts +7 -0
- package/dist/generator/index.js +7 -0
- package/dist/generatorCommon/Barrel.d.ts +23 -0
- package/dist/generatorCommon/Barrel.js +74 -0
- package/dist/generatorCommon/Collector.d.ts +22 -0
- package/dist/generatorCommon/Collector.js +37 -0
- package/dist/generatorCommon/Evaluate.d.ts +12 -0
- package/dist/generatorCommon/Evaluate.js +120 -0
- package/dist/generatorCommon/Extractor.d.ts +35 -0
- package/dist/generatorCommon/Extractor.js +199 -0
- package/dist/generatorCommon/generator.common.d.ts +14 -0
- package/dist/generatorCommon/generator.common.js +45 -0
- package/dist/generatorCommon/index.d.ts +5 -0
- package/dist/generatorCommon/index.js +5 -0
- package/dist/meta/index.d.ts +9 -0
- package/dist/meta/index.js +8 -0
- package/dist/meta/meta.annotations.d.ts +11 -0
- package/dist/meta/meta.annotations.js +1 -0
- package/dist/meta/meta.common.d.ts +67 -0
- package/dist/meta/meta.common.js +126 -0
- package/dist/meta/meta.decorators.d.ts +26 -0
- package/dist/meta/meta.decorators.js +21 -0
- package/dist/meta/meta.enum.d.ts +88 -0
- package/dist/meta/meta.enum.js +100 -0
- package/dist/meta/meta.imports.d.ts +11 -0
- package/dist/meta/meta.imports.js +54 -0
- package/dist/meta/meta.main.d.ts +32 -0
- package/dist/meta/meta.main.js +10 -0
- package/dist/meta/meta.names.d.ts +8 -0
- package/dist/meta/meta.names.js +35 -0
- package/dist/meta/meta.properties.d.ts +62 -0
- package/dist/meta/meta.properties.js +37 -0
- package/dist/meta/meta.types.d.ts +109 -0
- package/dist/meta/meta.types.js +26 -0
- package/dist/vscode/OSUser.d.ts +15 -0
- package/dist/vscode/OSUser.js +62 -0
- package/dist/vscode/VSCodeCommon.d.ts +50 -0
- package/dist/vscode/VSCodeCommon.js +133 -0
- package/dist/vscode/VSCodeSettings.d.ts +10 -0
- package/dist/vscode/VSCodeSettings.js +41 -0
- package/dist/vscode/VSCodeWorkspace.d.ts +22 -0
- package/dist/vscode/VSCodeWorkspace.js +43 -0
- package/dist/vscode/index.d.ts +4 -0
- package/dist/vscode/index.js +4 -0
- package/package.json +11 -1
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ChangedHistory, ChangedResult, GitChangedContentsResult } from "./types.change.js";
|
|
2
|
+
import { GeneratorResultFile } from "../generator/Generator.js";
|
|
3
|
+
export declare class ChangeHandler {
|
|
4
|
+
protected _history: ChangedHistory | null;
|
|
5
|
+
constructor();
|
|
6
|
+
history(): Promise<ChangedHistory>;
|
|
7
|
+
writeHistory(): Promise<ChangedHistory | null>;
|
|
8
|
+
readChanges(sinceCommit?: string): Promise<GitChangedContentsResult>;
|
|
9
|
+
runGeneration(result: ChangedResult): GeneratorResultFile[];
|
|
10
|
+
writeFiles(files: GeneratorResultFile[]): Promise<void>;
|
|
11
|
+
buildTriggers(gitResult: GitChangedContentsResult): Promise<ChangedResult>;
|
|
12
|
+
commitChanges(): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { dirname, join, resolve } from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import { formatJson, templatesPathRead } from "../meta/meta.common.js";
|
|
5
|
+
import { GitChanges } from "./GitChanges.js";
|
|
6
|
+
import pkg from "json5";
|
|
7
|
+
import { changedHistorySchema, } from "./types.change.js";
|
|
8
|
+
import micromatch from "micromatch";
|
|
9
|
+
import { extract, createExtractors } from "../generatorCommon/Extractor.js";
|
|
10
|
+
import { Generator } from "../generator/Generator.js";
|
|
11
|
+
import { createGenerators } from "../generatorCommon/generator.common.js";
|
|
12
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const historyJsonDir = resolve(__dirname, "../../");
|
|
14
|
+
const historyJsonPath = join(historyJsonDir, "./history.json");
|
|
15
|
+
// export const execAsync = async (
|
|
16
|
+
// command: string,
|
|
17
|
+
// options?: ExecOptionsWithStringEncoding,
|
|
18
|
+
// ): Promise<string> =>
|
|
19
|
+
// new Promise<string>(async (resolve, reject) =>
|
|
20
|
+
// exec(command, options ?? {}, (error, stdout, stderr) =>
|
|
21
|
+
// error ? reject(stderr) : resolve(stdout),
|
|
22
|
+
// ),
|
|
23
|
+
// );
|
|
24
|
+
export class ChangeHandler {
|
|
25
|
+
_history = null;
|
|
26
|
+
constructor() { }
|
|
27
|
+
async history() {
|
|
28
|
+
if (this._history)
|
|
29
|
+
return this._history;
|
|
30
|
+
const content = await readFile(historyJsonPath, "utf8");
|
|
31
|
+
this._history = pkg.parse(content);
|
|
32
|
+
this._history = await changedHistorySchema.parseAsync(this._history);
|
|
33
|
+
return this._history;
|
|
34
|
+
}
|
|
35
|
+
async writeHistory() {
|
|
36
|
+
if (!this._history)
|
|
37
|
+
return null;
|
|
38
|
+
await mkdir(historyJsonDir, { recursive: true });
|
|
39
|
+
await writeFile(historyJsonPath, await formatJson(pkg.stringify(this._history)));
|
|
40
|
+
return this._history;
|
|
41
|
+
}
|
|
42
|
+
async readChanges(sinceCommit) {
|
|
43
|
+
const git = new GitChanges();
|
|
44
|
+
sinceCommit ??= (await this.history()).lastCommitHash;
|
|
45
|
+
if (!sinceCommit)
|
|
46
|
+
throw new Error("No commit hash found or provided");
|
|
47
|
+
return await git.readChangedFilesSince(sinceCommit);
|
|
48
|
+
}
|
|
49
|
+
runGeneration(result) {
|
|
50
|
+
// collect runners
|
|
51
|
+
const runners = [];
|
|
52
|
+
for (const trigger of result.triggers) {
|
|
53
|
+
for (const generator of trigger.generators) {
|
|
54
|
+
let foundRunner = runners.find((runner) => runner.gen === generator.gen);
|
|
55
|
+
if (!foundRunner) {
|
|
56
|
+
foundRunner = { gen: generator.gen, templateFiles: [] };
|
|
57
|
+
runners.push(foundRunner);
|
|
58
|
+
}
|
|
59
|
+
foundRunner.templateFiles.push(trigger.templateFile);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// run
|
|
63
|
+
const results = [];
|
|
64
|
+
for (const runner of runners)
|
|
65
|
+
results.push(...runner.gen.run(runner.templateFiles));
|
|
66
|
+
return results;
|
|
67
|
+
}
|
|
68
|
+
async writeFiles(files) {
|
|
69
|
+
return Generator.write(files);
|
|
70
|
+
}
|
|
71
|
+
async buildTriggers(gitResult) {
|
|
72
|
+
const changed = { ...gitResult, triggers: [] };
|
|
73
|
+
const generators = await createGenerators();
|
|
74
|
+
const addTrigger = (...trs) => {
|
|
75
|
+
for (const tr of trs) {
|
|
76
|
+
if (!changed.triggers.find((t) => t.templateFile.inputFilePath === tr.templateFile.inputFilePath))
|
|
77
|
+
changed.triggers.push(tr);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const templateFilePaths = await templatesPathRead();
|
|
81
|
+
const templates = extract(createExtractors(templateFilePaths));
|
|
82
|
+
for (const changedFile of changed.files) {
|
|
83
|
+
// check templates
|
|
84
|
+
const foundTemplate = templates.find((m) => m.repoInputFilePath === changedFile.repoFilePath);
|
|
85
|
+
if (foundTemplate) {
|
|
86
|
+
// add all generators
|
|
87
|
+
addTrigger({
|
|
88
|
+
generators,
|
|
89
|
+
templateFile: foundTemplate,
|
|
90
|
+
});
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
// check super generator
|
|
94
|
+
if (changedFile.repoFilePath ===
|
|
95
|
+
"gen/generator/src/generator/Generator.ts" ||
|
|
96
|
+
changedFile.repoFilePath === "gen/generator/src/generator/index.ts") {
|
|
97
|
+
// add all generators to all templates
|
|
98
|
+
addTrigger(...templates.map((templ) => ({
|
|
99
|
+
generators,
|
|
100
|
+
templateFile: templ,
|
|
101
|
+
})));
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
// check generators
|
|
105
|
+
if (micromatch.isMatch(changedFile.repoFilePath, "gen/generator/**")) {
|
|
106
|
+
const foundGen = generators.find((generator) => generator.filePath === changedFile.absFilePath);
|
|
107
|
+
if (foundGen) {
|
|
108
|
+
addTrigger(...templates.map((templ) => ({
|
|
109
|
+
generators: [foundGen],
|
|
110
|
+
templateFile: templ,
|
|
111
|
+
})));
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
addTrigger(...templates.map((templ) => ({
|
|
115
|
+
generators,
|
|
116
|
+
templateFile: templ,
|
|
117
|
+
})));
|
|
118
|
+
}
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
// check shared
|
|
122
|
+
if (micromatch.isMatch(changedFile.repoFilePath, "shared/**")) {
|
|
123
|
+
addTrigger(...templates.map((templ) => ({
|
|
124
|
+
generators,
|
|
125
|
+
templateFile: templ,
|
|
126
|
+
})));
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return changed;
|
|
131
|
+
}
|
|
132
|
+
async commitChanges() {
|
|
133
|
+
const git = new GitChanges();
|
|
134
|
+
const [hash, hist] = await Promise.all([
|
|
135
|
+
git.lastCommitHash(),
|
|
136
|
+
this.history(),
|
|
137
|
+
]);
|
|
138
|
+
if (!hash)
|
|
139
|
+
throw new Error("No last hash found");
|
|
140
|
+
hist.lastCommitHash = hash;
|
|
141
|
+
await this.writeHistory();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { SimpleGit } from "simple-git";
|
|
2
|
+
import { GitChangedResult, GitChangedContentsResult } from "./types.change.js";
|
|
3
|
+
export declare class GitChanges {
|
|
4
|
+
readonly rootDir: string;
|
|
5
|
+
constructor();
|
|
6
|
+
/**
|
|
7
|
+
* Checks if a specific file or directory has been changed since a given git commit.
|
|
8
|
+
* Includes both committed and uncommitted changes (staged + unstaged).
|
|
9
|
+
*
|
|
10
|
+
* @param target - Relative path to the file or directory to check.
|
|
11
|
+
* @param sinceCommit - The git commit hash (or ref like a tag/branch) to compare against.
|
|
12
|
+
* @returns A result object indicating whether changes were detected.
|
|
13
|
+
*/
|
|
14
|
+
hasChangedSince(path: string, sinceCommit: string): Promise<GitChangedResult>;
|
|
15
|
+
/**
|
|
16
|
+
* Reads the current content of all files that have changed since a given git commit
|
|
17
|
+
* within a specific file or directory scope.
|
|
18
|
+
* Includes both committed and uncommitted changes (staged + unstaged).
|
|
19
|
+
*
|
|
20
|
+
* Deleted files are excluded from the result since they no longer exist on disk.
|
|
21
|
+
*
|
|
22
|
+
* @param target - Relative path to the file or directory to check.
|
|
23
|
+
* @param sinceCommit - The git commit hash (or ref like a tag/branch) to compare against.
|
|
24
|
+
* @param cwd - The working directory of the git repository (defaults to process.cwd()).
|
|
25
|
+
* @returns A result object containing the current content of each changed file.
|
|
26
|
+
*/
|
|
27
|
+
readChangedFilesSince(sinceCommit: string, readContent?: boolean): Promise<GitChangedContentsResult>;
|
|
28
|
+
/**
|
|
29
|
+
* Creates a SimpleGit instance and verifies the cwd is a repo
|
|
30
|
+
* and the commit ref is valid.
|
|
31
|
+
*/
|
|
32
|
+
verifiedGit(commitRef?: string): Promise<SimpleGit>;
|
|
33
|
+
lastCommitHash(): Promise<string | null>;
|
|
34
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { simpleGit } from "simple-git";
|
|
5
|
+
import { VSCodeWorkspace } from "../vscode/VSCodeWorkspace.js";
|
|
6
|
+
export class GitChanges {
|
|
7
|
+
rootDir;
|
|
8
|
+
constructor() {
|
|
9
|
+
this.rootDir = VSCodeWorkspace.load().getRoot();
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Checks if a specific file or directory has been changed since a given git commit.
|
|
13
|
+
* Includes both committed and uncommitted changes (staged + unstaged).
|
|
14
|
+
*
|
|
15
|
+
* @param target - Relative path to the file or directory to check.
|
|
16
|
+
* @param sinceCommit - The git commit hash (or ref like a tag/branch) to compare against.
|
|
17
|
+
* @returns A result object indicating whether changes were detected.
|
|
18
|
+
*/
|
|
19
|
+
async hasChangedSince(path, sinceCommit) {
|
|
20
|
+
const resolvedTarget = resolve(this.rootDir, path);
|
|
21
|
+
if (!existsSync(resolvedTarget)) {
|
|
22
|
+
throw new Error(`Target path does not exist: ${resolvedTarget}`);
|
|
23
|
+
}
|
|
24
|
+
const git = await this.verifiedGit(sinceCommit);
|
|
25
|
+
// Compare commit to working directory (includes uncommitted changes)
|
|
26
|
+
const diff = await git.diffSummary([sinceCommit, "--", path]);
|
|
27
|
+
const changedFiles = diff.files.map((file) => file.file);
|
|
28
|
+
return {
|
|
29
|
+
path,
|
|
30
|
+
sinceCommit,
|
|
31
|
+
hasChanged: changedFiles.length > 0,
|
|
32
|
+
changedFiles,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Reads the current content of all files that have changed since a given git commit
|
|
37
|
+
* within a specific file or directory scope.
|
|
38
|
+
* Includes both committed and uncommitted changes (staged + unstaged).
|
|
39
|
+
*
|
|
40
|
+
* Deleted files are excluded from the result since they no longer exist on disk.
|
|
41
|
+
*
|
|
42
|
+
* @param target - Relative path to the file or directory to check.
|
|
43
|
+
* @param sinceCommit - The git commit hash (or ref like a tag/branch) to compare against.
|
|
44
|
+
* @param cwd - The working directory of the git repository (defaults to process.cwd()).
|
|
45
|
+
* @returns A result object containing the current content of each changed file.
|
|
46
|
+
*/
|
|
47
|
+
async readChangedFilesSince(sinceCommit, readContent) {
|
|
48
|
+
const git = await this.verifiedGit(sinceCommit);
|
|
49
|
+
// Compare commit to working directory (includes uncommitted changes)
|
|
50
|
+
const diff = await git.diffSummary([sinceCommit, "--", this.rootDir]);
|
|
51
|
+
const files = [];
|
|
52
|
+
for (const entry of diff.files) {
|
|
53
|
+
const repoFilePath = entry.file;
|
|
54
|
+
const absFilePath = resolve(this.rootDir, repoFilePath);
|
|
55
|
+
// Skip deleted files — they no longer exist on disk
|
|
56
|
+
if (!existsSync(absFilePath)) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
// Read from the file system to capture uncommitted changes
|
|
60
|
+
const content = readContent ? await readFile(absFilePath, "utf-8") : null;
|
|
61
|
+
files.push({ absFilePath, content, repoFilePath });
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
path: this.rootDir,
|
|
65
|
+
sinceCommit,
|
|
66
|
+
files,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Creates a SimpleGit instance and verifies the cwd is a repo
|
|
71
|
+
* and the commit ref is valid.
|
|
72
|
+
*/
|
|
73
|
+
async verifiedGit(commitRef) {
|
|
74
|
+
const git = simpleGit(this.rootDir);
|
|
75
|
+
if (!(await git.checkIsRepo()))
|
|
76
|
+
throw new Error(`Not a git repository: ${this.rootDir}`);
|
|
77
|
+
if (!commitRef)
|
|
78
|
+
return git;
|
|
79
|
+
try {
|
|
80
|
+
await git.revparse(["--verify", commitRef]);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
throw new Error(`Invalid git commit reference: "${commitRef}"`);
|
|
84
|
+
}
|
|
85
|
+
return git;
|
|
86
|
+
}
|
|
87
|
+
async lastCommitHash() {
|
|
88
|
+
const git = await this.verifiedGit();
|
|
89
|
+
return (await git.log()).latest?.hash ?? null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { MetaMainData } from "../meta/meta.main.js";
|
|
3
|
+
import { GeneratorEntry } from "../generatorCommon/generator.common.js";
|
|
4
|
+
export interface ChangedHistory {
|
|
5
|
+
lastCommitHash?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const changedHistorySchema: z.ZodObject<{
|
|
8
|
+
lastCommitHash: z.ZodOptional<z.ZodString>;
|
|
9
|
+
}, z.core.$strip>;
|
|
10
|
+
export interface ChangedTrigger {
|
|
11
|
+
templateFile: MetaMainData;
|
|
12
|
+
generators: GeneratorEntry[];
|
|
13
|
+
}
|
|
14
|
+
export interface ChangedResult extends GitChangedContentsResult {
|
|
15
|
+
triggers: ChangedTrigger[];
|
|
16
|
+
}
|
|
17
|
+
export interface GitChangedResult {
|
|
18
|
+
path: string;
|
|
19
|
+
sinceCommit: string;
|
|
20
|
+
hasChanged: boolean;
|
|
21
|
+
changedFiles: string[];
|
|
22
|
+
}
|
|
23
|
+
export interface GitChangedContent {
|
|
24
|
+
absFilePath: string;
|
|
25
|
+
repoFilePath: string;
|
|
26
|
+
content: string | null;
|
|
27
|
+
}
|
|
28
|
+
export interface GitChangedContentsResult {
|
|
29
|
+
path: string;
|
|
30
|
+
sinceCommit: string;
|
|
31
|
+
files: GitChangedContent[];
|
|
32
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { MetaFileExtension, MetaFileType } from "../meta/meta.enum.js";
|
|
2
|
+
import { TargetType } from "../meta/meta.common.js";
|
|
3
|
+
import { MetaMainData } from "../meta/meta.main.js";
|
|
4
|
+
import { MetaImport, MetaNames } from "../meta/meta.types.js";
|
|
5
|
+
import { Collector } from "../generatorCommon/Collector.js";
|
|
6
|
+
export interface GeneratorBase {
|
|
7
|
+
imports: MetaImport[];
|
|
8
|
+
code: string[];
|
|
9
|
+
}
|
|
10
|
+
export interface GeneratorCode extends GeneratorBase {
|
|
11
|
+
name: string;
|
|
12
|
+
}
|
|
13
|
+
export interface GeneratorResultFile extends GeneratorBase {
|
|
14
|
+
type: MetaFileType;
|
|
15
|
+
ext: MetaFileExtension;
|
|
16
|
+
inputFilePath: string;
|
|
17
|
+
targetFilePath: string;
|
|
18
|
+
importsCode: string[];
|
|
19
|
+
}
|
|
20
|
+
export declare const importsToCode: (imports: MetaImport[]) => string[];
|
|
21
|
+
export declare abstract class Generator {
|
|
22
|
+
readonly type: MetaFileType;
|
|
23
|
+
readonly ext: MetaFileExtension;
|
|
24
|
+
readonly targetType: TargetType;
|
|
25
|
+
readonly collector: Collector;
|
|
26
|
+
readonly useAdditionalImports: boolean;
|
|
27
|
+
constructor(type: MetaFileType, ext: MetaFileExtension, targetType: TargetType, collector: Collector, useAdditionalImports?: boolean);
|
|
28
|
+
run(interfaceData: MetaMainData[]): GeneratorResultFile[];
|
|
29
|
+
static write(files: GeneratorResultFile[]): Promise<void>;
|
|
30
|
+
protected addCode(res: GeneratorResultFile, code: string[]): void;
|
|
31
|
+
protected addImport(res: GeneratorResultFile, imp: MetaImport): void;
|
|
32
|
+
protected addImports(res: GeneratorResultFile, imports: MetaImport[]): void;
|
|
33
|
+
protected abstract runInterface(interfaceData: MetaMainData, names: MetaNames): GeneratorCode | undefined;
|
|
34
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { basename, dirname } from "node:path";
|
|
2
|
+
import { MetaFileExtension } from "../meta/meta.enum.js";
|
|
3
|
+
import { formatCode, formatJson, TargetType, toFilename, toTargetPath, } from "../meta/meta.common.js";
|
|
4
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
5
|
+
import { metaBackendNames, metaBasename, metaFrontendNames, metaNames, } from "../meta/meta.names.js";
|
|
6
|
+
export const importsToCode = (imports) => {
|
|
7
|
+
const code = [];
|
|
8
|
+
for (const imp of imports)
|
|
9
|
+
if (imp.props.length > 0)
|
|
10
|
+
code.push(`import ${imp.isType ? "type " : ""}{ ${[...imp.props].join(", ")} } from "${imp.path}"`);
|
|
11
|
+
return code;
|
|
12
|
+
};
|
|
13
|
+
export class Generator {
|
|
14
|
+
type;
|
|
15
|
+
ext;
|
|
16
|
+
targetType;
|
|
17
|
+
collector;
|
|
18
|
+
useAdditionalImports;
|
|
19
|
+
constructor(type, ext, targetType, collector, useAdditionalImports = false) {
|
|
20
|
+
this.type = type;
|
|
21
|
+
this.ext = ext;
|
|
22
|
+
this.targetType = targetType;
|
|
23
|
+
this.collector = collector;
|
|
24
|
+
this.useAdditionalImports = useAdditionalImports;
|
|
25
|
+
}
|
|
26
|
+
run(interfaceData) {
|
|
27
|
+
const results = [];
|
|
28
|
+
for (const data of interfaceData) {
|
|
29
|
+
let res = results.find((r) => r.inputFilePath === data.inputFilePath);
|
|
30
|
+
let append = false;
|
|
31
|
+
if (!res) {
|
|
32
|
+
append = true;
|
|
33
|
+
const targetFilename = toFilename({
|
|
34
|
+
filename: basename(data.inputFilePath),
|
|
35
|
+
type: this.type,
|
|
36
|
+
ext: this.ext,
|
|
37
|
+
generated: true,
|
|
38
|
+
});
|
|
39
|
+
res = {
|
|
40
|
+
inputFilePath: data.inputFilePath,
|
|
41
|
+
targetFilePath: toTargetPath(this.type, targetFilename, this.targetType),
|
|
42
|
+
imports: [],
|
|
43
|
+
importsCode: [],
|
|
44
|
+
code: [],
|
|
45
|
+
ext: this.ext,
|
|
46
|
+
type: this.type,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const extrName = metaBasename(data.templateName);
|
|
50
|
+
const names = this.targetType === TargetType.backend
|
|
51
|
+
? metaBackendNames(extrName)
|
|
52
|
+
: this.targetType === TargetType.frontend
|
|
53
|
+
? metaFrontendNames(extrName)
|
|
54
|
+
: metaNames(extrName);
|
|
55
|
+
this.collector.add(names.basename, this.targetType, { names });
|
|
56
|
+
const interfaceRes = this.runInterface(data, names);
|
|
57
|
+
if (!interfaceRes)
|
|
58
|
+
continue;
|
|
59
|
+
this.addCode(res, interfaceRes.code);
|
|
60
|
+
this.addImports(res, interfaceRes.imports);
|
|
61
|
+
if (this.useAdditionalImports && data.additionalImports)
|
|
62
|
+
this.addImports(res, data.additionalImports);
|
|
63
|
+
if (append)
|
|
64
|
+
results.push(res);
|
|
65
|
+
}
|
|
66
|
+
for (const result of results)
|
|
67
|
+
result.importsCode = importsToCode(result.imports);
|
|
68
|
+
return results;
|
|
69
|
+
}
|
|
70
|
+
static async write(files) {
|
|
71
|
+
const promises = [];
|
|
72
|
+
for (const file of files) {
|
|
73
|
+
const preCode = [...file.importsCode, "", ...file.code].join("\n");
|
|
74
|
+
promises.push((async () => {
|
|
75
|
+
let code;
|
|
76
|
+
if (file.ext === MetaFileExtension.ts)
|
|
77
|
+
code = await formatCode(preCode);
|
|
78
|
+
else if (file.ext === MetaFileExtension.json)
|
|
79
|
+
code = await formatJson(preCode);
|
|
80
|
+
else
|
|
81
|
+
code = preCode;
|
|
82
|
+
await mkdir(dirname(file.targetFilePath), { recursive: true });
|
|
83
|
+
return writeFile(file.targetFilePath, code);
|
|
84
|
+
})());
|
|
85
|
+
}
|
|
86
|
+
await Promise.all(promises);
|
|
87
|
+
}
|
|
88
|
+
addCode(res, code) {
|
|
89
|
+
if (res.code.length > 0)
|
|
90
|
+
res.code.push("");
|
|
91
|
+
res.code.push(...code);
|
|
92
|
+
}
|
|
93
|
+
addImport(res, imp) {
|
|
94
|
+
let foundImp = res.imports.find((i) => i.path === imp.path && i.isType === imp.isType);
|
|
95
|
+
if (!foundImp) {
|
|
96
|
+
foundImp = {
|
|
97
|
+
...imp,
|
|
98
|
+
props: [],
|
|
99
|
+
};
|
|
100
|
+
res.imports.push(foundImp);
|
|
101
|
+
}
|
|
102
|
+
for (const prop of imp.props)
|
|
103
|
+
if (!foundImp.props.find((p) => p === prop))
|
|
104
|
+
foundImp.props.push(prop);
|
|
105
|
+
}
|
|
106
|
+
addImports(res, imports) {
|
|
107
|
+
for (const imp of imports)
|
|
108
|
+
this.addImport(res, imp);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { MetaMainData } from "../meta/meta.main.js";
|
|
2
|
+
import { MetaNames } from "../meta/meta.types.js";
|
|
3
|
+
import { Generator, GeneratorCode } from "./Generator.js";
|
|
4
|
+
export declare class GeneratorAnnotations extends Generator {
|
|
5
|
+
constructor();
|
|
6
|
+
protected runInterface(mainData: MetaMainData, names: MetaNames): GeneratorCode | undefined;
|
|
7
|
+
}
|
|
8
|
+
declare const _default: GeneratorAnnotations;
|
|
9
|
+
export default _default;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Collector } from "../generatorCommon/Collector.js";
|
|
2
|
+
import { TargetType } from "../meta/meta.common.js";
|
|
3
|
+
import { MetaFileType, MetaFileExtension } from "../meta/meta.enum.js";
|
|
4
|
+
import { isMetaNameType, metaBasename } from "../meta/meta.names.js";
|
|
5
|
+
import { Generator } from "./Generator.js";
|
|
6
|
+
export class GeneratorAnnotations extends Generator {
|
|
7
|
+
constructor() {
|
|
8
|
+
super(MetaFileType.annotations, MetaFileExtension.ts, TargetType.common, Collector.instance());
|
|
9
|
+
}
|
|
10
|
+
runInterface(mainData, names) {
|
|
11
|
+
const res = {
|
|
12
|
+
code: [],
|
|
13
|
+
imports: [],
|
|
14
|
+
name: mainData.templateName,
|
|
15
|
+
};
|
|
16
|
+
res.code.push(`export interface ${names.doc} {`);
|
|
17
|
+
for (const prop of mainData.properties)
|
|
18
|
+
res.code.push(` ${prop.propertyKey}${prop.optional ? "?" : ""}: `, `${metaBasename(prop.propertyType)}${isMetaNameType(prop.propertyType, MetaFileType.template) ? "Doc" : ""};`);
|
|
19
|
+
res.code.push("}");
|
|
20
|
+
return res;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export default new GeneratorAnnotations();
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { MetaMainData, MetaRoutes } from "../meta/meta.main.js";
|
|
2
|
+
import { MetaNames } from "../meta/meta.types.js";
|
|
3
|
+
import { Generator, GeneratorCode } from "./Generator.js";
|
|
4
|
+
export declare class GeneratorBackend extends Generator {
|
|
5
|
+
constructor();
|
|
6
|
+
protected runInterface(mainData: MetaMainData, names: MetaNames): GeneratorCode | undefined;
|
|
7
|
+
protected buildApi(routes: MetaRoutes, names: MetaNames): string[];
|
|
8
|
+
protected buildFunction(names: MetaNames): string[];
|
|
9
|
+
protected buildStore(names: MetaNames): string[];
|
|
10
|
+
protected buildCreate(names: MetaNames): string[];
|
|
11
|
+
}
|
|
12
|
+
declare const _default: GeneratorBackend;
|
|
13
|
+
export default _default;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { Collector } from "../generatorCommon/Collector.js";
|
|
2
|
+
import { lowerFirstLetter, TargetType } from "../meta/meta.common.js";
|
|
3
|
+
import { MetaFileType, MetaFileExtension, isCrudRoute, hasCrudRoute, } from "../meta/meta.enum.js";
|
|
4
|
+
import { Generator } from "./Generator.js";
|
|
5
|
+
export class GeneratorBackend extends Generator {
|
|
6
|
+
constructor() {
|
|
7
|
+
super(MetaFileType.backend, MetaFileExtension.ts, TargetType.backend, Collector.instance(), true);
|
|
8
|
+
}
|
|
9
|
+
runInterface(mainData, names) {
|
|
10
|
+
if (Object.keys(mainData.routes).length === 0)
|
|
11
|
+
return;
|
|
12
|
+
return {
|
|
13
|
+
code: [
|
|
14
|
+
...this.buildFunction(names),
|
|
15
|
+
"",
|
|
16
|
+
...this.buildApi(mainData.routes, names),
|
|
17
|
+
"",
|
|
18
|
+
...this.buildStore(names),
|
|
19
|
+
"",
|
|
20
|
+
...this.buildCreate(names),
|
|
21
|
+
],
|
|
22
|
+
imports: [
|
|
23
|
+
{
|
|
24
|
+
path: "@mxpicture/gcp-functions-common/types",
|
|
25
|
+
props: ["ApiFromRoutes"],
|
|
26
|
+
isType: true,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
path: "@mxpicture/gcp-functions-backend/store",
|
|
30
|
+
props: ["Store"],
|
|
31
|
+
isType: false,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
path: "@mxpicture/gcp-functions-backend/validation",
|
|
35
|
+
props: ["Validation"],
|
|
36
|
+
isType: false,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
path: "@mxpicture/gcp-functions-backend/api",
|
|
40
|
+
props: ["BackendApi"],
|
|
41
|
+
isType: false,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
path: "firebase-admin/firestore",
|
|
45
|
+
props: ["FirestoreDataConverter"],
|
|
46
|
+
isType: true,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
path: "@picpad/gen-common/routes",
|
|
50
|
+
props: [names.routes],
|
|
51
|
+
isType: true,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
path: "@picpad/gen-common/doc",
|
|
55
|
+
props: [names.doc],
|
|
56
|
+
isType: true,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
path: "@mxpicture/gcp-functions-backend/function",
|
|
60
|
+
props: ["IBackendFunction"],
|
|
61
|
+
isType: false,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
path: "@picpad/gen-common/routes",
|
|
65
|
+
props: [`${names.routes}Name`],
|
|
66
|
+
isType: false,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
path: "@picpad/gen-backend/zod",
|
|
70
|
+
props: [names.shape],
|
|
71
|
+
isType: false,
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
name: mainData.templateName,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
buildApi(routes, names) {
|
|
78
|
+
const code = [];
|
|
79
|
+
const superClassName = hasCrudRoute(Object.keys(routes))
|
|
80
|
+
? "BackendApi"
|
|
81
|
+
: "IBackendApi";
|
|
82
|
+
code.push(`export abstract class I${names.api} extends ${superClassName}<${names.doc}, ${names.store}, Validation<${names.doc}>>`, `implements ApiFromRoutes<${names.routes}>`, "{", ` public constructor() { super("${lowerFirstLetter(names.basename)}"); }`, "");
|
|
83
|
+
for (const [routeName, params] of Object.entries(routes)) {
|
|
84
|
+
if (isCrudRoute(routeName))
|
|
85
|
+
continue; // ignore crud --> provided by super class
|
|
86
|
+
let definition = routeName;
|
|
87
|
+
if (params.requestType !== "never" && params.requestType !== "void")
|
|
88
|
+
definition += `(request: ${params.requestType})`;
|
|
89
|
+
else
|
|
90
|
+
definition += `()`;
|
|
91
|
+
definition += `: Promise<${params.responseType}>`;
|
|
92
|
+
code.push(` public abstract ${definition};`);
|
|
93
|
+
}
|
|
94
|
+
code.push("}");
|
|
95
|
+
return code;
|
|
96
|
+
}
|
|
97
|
+
buildFunction(names) {
|
|
98
|
+
return [
|
|
99
|
+
`export class ${names.func} extends IBackendFunction<${names.doc}, I${names.api}> {`,
|
|
100
|
+
` public constructor() { super("${lowerFirstLetter(names.basename)}"); }`,
|
|
101
|
+
"}",
|
|
102
|
+
];
|
|
103
|
+
}
|
|
104
|
+
buildStore(names) {
|
|
105
|
+
return [
|
|
106
|
+
`export class ${names.store} extends Store<${names.doc}> {`,
|
|
107
|
+
` public constructor(converter?: FirestoreDataConverter<${names.doc}>) { super("${lowerFirstLetter(names.basename)}", converter); }`,
|
|
108
|
+
"}",
|
|
109
|
+
];
|
|
110
|
+
}
|
|
111
|
+
buildCreate(names) {
|
|
112
|
+
return [
|
|
113
|
+
`export const create${names.basename}Function = (api: I${names.api}) => {`,
|
|
114
|
+
` const store = new ${names.store}();`,
|
|
115
|
+
"",
|
|
116
|
+
` const val = new Validation<${names.doc}>("${lowerFirstLetter(names.basename)}");`,
|
|
117
|
+
` val.useShape(${names.shape});`,
|
|
118
|
+
"",
|
|
119
|
+
` const func = new ${names.func}();`,
|
|
120
|
+
" func.useApi(api);",
|
|
121
|
+
` func.useRoutes(Object.keys(${names.routes}Name));`,
|
|
122
|
+
"",
|
|
123
|
+
" api.useStore(store);",
|
|
124
|
+
" api.useValidation(val);",
|
|
125
|
+
"",
|
|
126
|
+
" return func.buildFunction();",
|
|
127
|
+
"};",
|
|
128
|
+
];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
export default new GeneratorBackend();
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { MetaMainData } from "../meta/meta.main.js";
|
|
2
|
+
import { MetaNames } from "../meta/meta.types.js";
|
|
3
|
+
import { Generator, GeneratorCode } from "./Generator.js";
|
|
4
|
+
export declare class GeneratorDoc extends Generator {
|
|
5
|
+
constructor();
|
|
6
|
+
protected runInterface(mainData: MetaMainData, names: MetaNames): GeneratorCode | undefined;
|
|
7
|
+
}
|
|
8
|
+
declare const _default: GeneratorDoc;
|
|
9
|
+
export default _default;
|