@nestia/migrate 12.0.0-dev.20260601.1 → 12.0.0-dev.20260612.2
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/LICENSE +21 -21
- package/README.md +93 -93
- package/lib/NestiaMigrateApplication.js +19 -1
- package/lib/NestiaMigrateApplication.js.map +1 -1
- package/lib/bundles/NEST_TEMPLATE.js +47 -47
- package/lib/bundles/NEST_TEMPLATE.js.map +1 -1
- package/lib/bundles/SDK_TEMPLATE.js +20 -20
- package/lib/bundles/SDK_TEMPLATE.js.map +1 -1
- package/lib/index.mjs +94 -77
- package/lib/index.mjs.map +1 -1
- package/lib/programmers/NestiaMigrateApiFunctionProgrammer.js +6 -2
- package/lib/programmers/NestiaMigrateApiFunctionProgrammer.js.map +1 -1
- package/package.json +5 -5
- package/src/NestiaMigrateApplication.ts +196 -167
- package/src/analyzers/NestiaMigrateControllerAnalyzer.ts +51 -51
- package/src/archivers/NestiaMigrateFileArchiver.ts +28 -28
- package/src/bundles/NEST_TEMPLATE.ts +47 -47
- package/src/bundles/SDK_TEMPLATE.ts +20 -20
- package/src/executable/NestiaMigrateCommander.ts +115 -115
- package/src/executable/NestiaMigrateInquirer.ts +106 -106
- package/src/executable/bundle.js +349 -349
- package/src/executable/migrate.ts +7 -7
- package/src/factories/TypeLiteralFactory.ts +63 -63
- package/src/index.ts +4 -4
- package/src/internal/ts.ts +94 -94
- package/src/module.ts +6 -6
- package/src/programmers/NestiaMigrateApiFileProgrammer.ts +58 -58
- package/src/programmers/NestiaMigrateApiFunctionProgrammer.ts +373 -369
- package/src/programmers/NestiaMigrateApiNamespaceProgrammer.ts +528 -528
- package/src/programmers/NestiaMigrateApiProgrammer.ts +108 -108
- package/src/programmers/NestiaMigrateApiSimulationProgrammer.ts +314 -314
- package/src/programmers/NestiaMigrateApiStartProgrammer.ts +198 -198
- package/src/programmers/NestiaMigrateDtoProgrammer.ts +99 -99
- package/src/programmers/NestiaMigrateE2eFileProgrammer.ts +156 -156
- package/src/programmers/NestiaMigrateE2eProgrammer.ts +48 -48
- package/src/programmers/NestiaMigrateImportProgrammer.ts +119 -119
- package/src/programmers/NestiaMigrateNestControllerProgrammer.ts +70 -70
- package/src/programmers/NestiaMigrateNestMethodProgrammer.ts +414 -414
- package/src/programmers/NestiaMigrateNestModuleProgrammer.ts +66 -66
- package/src/programmers/NestiaMigrateNestProgrammer.ts +89 -89
- package/src/programmers/NestiaMigrateSchemaProgrammer.ts +477 -477
- package/src/programmers/index.ts +15 -15
- package/src/structures/INestiaMigrateConfig.ts +19 -19
- package/src/structures/INestiaMigrateContext.ts +9 -9
- package/src/structures/INestiaMigrateController.ts +8 -8
- package/src/structures/INestiaMigrateFile.ts +5 -5
- package/src/structures/index.ts +4 -4
- package/src/utils/FilePrinter.ts +44 -44
- package/src/utils/MapUtil.ts +13 -13
- package/src/utils/StringUtil.ts +109 -109
package/src/executable/bundle.js
CHANGED
|
@@ -1,349 +1,349 @@
|
|
|
1
|
-
const { version } = require("../../../../package.json");
|
|
2
|
-
const cp = require("child_process");
|
|
3
|
-
const fs = require("fs");
|
|
4
|
-
|
|
5
|
-
const ROOT = `${__dirname}/../..`;
|
|
6
|
-
const ASSETS = `${ROOT}/assets`;
|
|
7
|
-
const TYPIA = require("js-yaml").load(
|
|
8
|
-
fs.readFileSync(`${__dirname}/../../../../pnpm-lock.yaml`, "utf8"),
|
|
9
|
-
).catalogs.samchon;
|
|
10
|
-
|
|
11
|
-
const update = (content, options = {}) => {
|
|
12
|
-
const parsed = JSON.parse(content);
|
|
13
|
-
for (const record of [
|
|
14
|
-
parsed.dependencies ?? {},
|
|
15
|
-
parsed.devDependencies ?? {},
|
|
16
|
-
])
|
|
17
|
-
for (const key of Object.keys(record))
|
|
18
|
-
if (key.startsWith("@nestia/") || key === "nestia")
|
|
19
|
-
record[key] = `^${version}`;
|
|
20
|
-
else if (TYPIA[key]) record[key] = TYPIA[key].specifier;
|
|
21
|
-
migratePackageJson(parsed);
|
|
22
|
-
if (options.sdkAggregate) {
|
|
23
|
-
parsed.devDependencies ??= {};
|
|
24
|
-
parsed.devDependencies["@nestia/core"] = `^${version}`;
|
|
25
|
-
}
|
|
26
|
-
return JSON.stringify(parsed, null, 2);
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const migratePackageJson = (parsed) => {
|
|
30
|
-
if (parsed.scripts)
|
|
31
|
-
for (const [key, value] of Object.entries(parsed.scripts))
|
|
32
|
-
if (typeof value === "string")
|
|
33
|
-
parsed.scripts[key] = normalizeScript(value);
|
|
34
|
-
|
|
35
|
-
if (typeof parsed.scripts?.prepare === "string") {
|
|
36
|
-
const prepare = parsed.scripts.prepare
|
|
37
|
-
.split("&&")
|
|
38
|
-
.map((str) => str.trim())
|
|
39
|
-
.filter((str) => str !== "ts-patch install" && str !== "typia patch")
|
|
40
|
-
.join(" && ");
|
|
41
|
-
if (prepare.length === 0) delete parsed.scripts.prepare;
|
|
42
|
-
else parsed.scripts.prepare = prepare;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const devDependencies = parsed.devDependencies;
|
|
46
|
-
if (devDependencies) {
|
|
47
|
-
const usesTypeScript = typeof devDependencies.typescript === "string";
|
|
48
|
-
delete devDependencies["ts-patch"];
|
|
49
|
-
delete devDependencies["typescript-transform-paths"];
|
|
50
|
-
if (usesTypeScript) devDependencies.ttsc ??= "^0.10.2";
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
const trimTemplateDependencies = (parsed) => {
|
|
55
|
-
if (parsed.dependencies) {
|
|
56
|
-
delete parsed.dependencies.commander;
|
|
57
|
-
delete parsed.dependencies.inquirer;
|
|
58
|
-
}
|
|
59
|
-
if (parsed.devDependencies) {
|
|
60
|
-
delete parsed.devDependencies["@types/inquirer"];
|
|
61
|
-
delete parsed.devDependencies.commander;
|
|
62
|
-
delete parsed.devDependencies.inquirer;
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const normalizeScript = (script) =>
|
|
67
|
-
script.replace(/(^|[^A-Za-z0-9_-])tsc(?=$|[^A-Za-z0-9_-])/g, "$1ttsc");
|
|
68
|
-
|
|
69
|
-
const TYPIA_PLUGIN = `{ "transform": "typia/lib/transform", "enabled": false }`;
|
|
70
|
-
const CORE_PLUGIN = `{ "transform": "@nestia/core/native/transform.cjs" }`;
|
|
71
|
-
|
|
72
|
-
const ARGUMENT_PARSER = `import { createInterface } from "node:readline/promises";
|
|
73
|
-
|
|
74
|
-
export namespace ArgumentParser {
|
|
75
|
-
export interface Command {
|
|
76
|
-
option: (flags: string, description?: string) => Command;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export interface Prompt {
|
|
80
|
-
select: (
|
|
81
|
-
name: string,
|
|
82
|
-
) => (
|
|
83
|
-
message: string,
|
|
84
|
-
) => <Choice extends string>(choices: Choice[]) => Promise<Choice>;
|
|
85
|
-
boolean: (name: string) => (message: string) => Promise<boolean>;
|
|
86
|
-
number: (name: string) => (message: string) => Promise<number>;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export const parse = async <T>(
|
|
90
|
-
inquiry: (
|
|
91
|
-
command: Command,
|
|
92
|
-
prompt: Prompt,
|
|
93
|
-
action: (closure: (options: Partial<T>) => Promise<T>) => Promise<T>,
|
|
94
|
-
) => Promise<T>,
|
|
95
|
-
): Promise<T> => {
|
|
96
|
-
const command: Command = {
|
|
97
|
-
option: (_flags: string, _description?: string): Command => command,
|
|
98
|
-
};
|
|
99
|
-
const action = (closure: (options: Partial<T>) => Promise<T>) =>
|
|
100
|
-
closure(parseArguments() as Partial<T>);
|
|
101
|
-
return inquiry(
|
|
102
|
-
command,
|
|
103
|
-
{ select, boolean, number },
|
|
104
|
-
action,
|
|
105
|
-
);
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
const select =
|
|
109
|
-
(_name: string) =>
|
|
110
|
-
(message: string) =>
|
|
111
|
-
async <Choice extends string>(choices: Choice[]): Promise<Choice> => {
|
|
112
|
-
const answer: string = await ask(\`\${message} (\${choices.join("/")})\`);
|
|
113
|
-
return (choices.find((choice) => choice === answer) ?? choices[0])!;
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
const boolean = (_name: string) => async (message: string) =>
|
|
117
|
-
/^(true|t|yes|y|1)$/i.test(await ask(\`\${message} [y/N]\`));
|
|
118
|
-
|
|
119
|
-
const number = (_name: string) => async (message: string) =>
|
|
120
|
-
Number(await ask(message));
|
|
121
|
-
|
|
122
|
-
const ask = async (message: string): Promise<string> => {
|
|
123
|
-
const reader = createInterface({
|
|
124
|
-
input: process.stdin,
|
|
125
|
-
output: process.stdout,
|
|
126
|
-
});
|
|
127
|
-
try {
|
|
128
|
-
return (await reader.question(\`\${message}: \`)).trim();
|
|
129
|
-
} finally {
|
|
130
|
-
reader.close();
|
|
131
|
-
}
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
const parseArguments = (): Record<string, string | string[] | boolean> => {
|
|
135
|
-
const output: Record<string, string | string[] | boolean> = {};
|
|
136
|
-
const args: string[] = process.argv.slice(2);
|
|
137
|
-
for (let i = 0; i < args.length; ++i) {
|
|
138
|
-
const raw: string = args[i]!;
|
|
139
|
-
if (raw.startsWith("--") === false) continue;
|
|
140
|
-
|
|
141
|
-
const equal: number = raw.indexOf("=");
|
|
142
|
-
const name: string = toCamelCase(
|
|
143
|
-
raw.slice(2, equal === -1 ? undefined : equal),
|
|
144
|
-
);
|
|
145
|
-
if (equal !== -1) {
|
|
146
|
-
assign(output, name, raw.slice(equal + 1));
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const values: string[] = [];
|
|
151
|
-
while (i + 1 < args.length && args[i + 1]!.startsWith("--") === false)
|
|
152
|
-
values.push(args[++i]!);
|
|
153
|
-
assign(
|
|
154
|
-
output,
|
|
155
|
-
name,
|
|
156
|
-
values.length === 0 ? true : values.length === 1 ? values[0]! : values,
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
return output;
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
const assign = (
|
|
163
|
-
output: Record<string, string | string[] | boolean>,
|
|
164
|
-
name: string,
|
|
165
|
-
value: string | string[] | boolean,
|
|
166
|
-
): void => {
|
|
167
|
-
const current: string | string[] | boolean | undefined = output[name];
|
|
168
|
-
if (current === undefined) output[name] = value;
|
|
169
|
-
else
|
|
170
|
-
output[name] = [
|
|
171
|
-
...(Array.isArray(current) ? current : [String(current)]),
|
|
172
|
-
...(Array.isArray(value) ? value : [String(value)]),
|
|
173
|
-
];
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
const toCamelCase = (str: string): string =>
|
|
177
|
-
str.replace(/-([a-z])/g, (_matched, letter: string) =>
|
|
178
|
-
letter.toUpperCase(),
|
|
179
|
-
);
|
|
180
|
-
}
|
|
181
|
-
`;
|
|
182
|
-
|
|
183
|
-
const updateTsConfig = (content, options = {}) => {
|
|
184
|
-
content = content.replace(
|
|
185
|
-
/^\s*\{\s*"transform":\s*"typescript-transform-paths"\s*\},\n/gm,
|
|
186
|
-
"",
|
|
187
|
-
);
|
|
188
|
-
if (options.disableTypia)
|
|
189
|
-
content = content.replace(
|
|
190
|
-
/\{\s*"transform":\s*"typia\/lib\/transform"\s*\}/g,
|
|
191
|
-
TYPIA_PLUGIN,
|
|
192
|
-
);
|
|
193
|
-
if (options.useNestiaAggregate)
|
|
194
|
-
content = content.replace(
|
|
195
|
-
/\{\s*"transform":\s*"@nestia\/core\/lib\/transform"\s*\}/g,
|
|
196
|
-
CORE_PLUGIN,
|
|
197
|
-
);
|
|
198
|
-
content = content.replace(
|
|
199
|
-
/^\s*\{\s*"transform":\s*"@nestia\/sdk\/lib\/transform"\s*\},\n/gm,
|
|
200
|
-
"",
|
|
201
|
-
);
|
|
202
|
-
if (
|
|
203
|
-
options.useNestiaAggregate &&
|
|
204
|
-
content.includes(`"@nestia/core/native/transform.cjs"`) === false
|
|
205
|
-
)
|
|
206
|
-
content = content.replace(
|
|
207
|
-
/\{\s*"transform":\s*"typia\/lib\/transform",\s*"enabled":\s*false\s*\},/g,
|
|
208
|
-
`${TYPIA_PLUGIN},\n ${CORE_PLUGIN},`,
|
|
209
|
-
);
|
|
210
|
-
return content;
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
const bundle = async ({ mode, repository, exceptions, transform }) => {
|
|
214
|
-
const root = `${__dirname}/../..`;
|
|
215
|
-
const assets = `${root}/assets`;
|
|
216
|
-
const template = `${assets}/${mode}`;
|
|
217
|
-
|
|
218
|
-
const clone = async () => {
|
|
219
|
-
// CLONE REPOSITORY
|
|
220
|
-
if (fs.existsSync(template))
|
|
221
|
-
await fs.promises.rm(template, { recursive: true });
|
|
222
|
-
else
|
|
223
|
-
try {
|
|
224
|
-
await fs.promises.mkdir(ASSETS);
|
|
225
|
-
} catch {}
|
|
226
|
-
|
|
227
|
-
cp.execSync(`git clone https://github.com/samchon/${repository} ${mode}`, {
|
|
228
|
-
cwd: ASSETS,
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
// REMOVE VULNERABLE FILES
|
|
232
|
-
for (const location of exceptions ?? [])
|
|
233
|
-
await fs.promises.rm(`${template}/${location}`, { recursive: true });
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
const iterate = (collection) => async (location) => {
|
|
237
|
-
const directory = await fs.promises.readdir(location);
|
|
238
|
-
for (const file of directory) {
|
|
239
|
-
const absolute = location + "/" + file;
|
|
240
|
-
const stats = await fs.promises.stat(absolute);
|
|
241
|
-
if (stats.isDirectory()) await iterate(collection)(absolute);
|
|
242
|
-
else {
|
|
243
|
-
const content = await fs.promises.readFile(absolute, "utf-8");
|
|
244
|
-
collection[
|
|
245
|
-
(() => {
|
|
246
|
-
const str = absolute.replace(template, "");
|
|
247
|
-
return str[0] === "/" ? str.substring(1) : str;
|
|
248
|
-
})()
|
|
249
|
-
] = content;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
const archive = async (collection) => {
|
|
255
|
-
const name = `${mode.toUpperCase()}_TEMPLATE`;
|
|
256
|
-
const body = JSON.stringify(collection, null, 2);
|
|
257
|
-
const content = `export const ${name}: Record<string, string> = ${body}`;
|
|
258
|
-
|
|
259
|
-
try {
|
|
260
|
-
await fs.promises.mkdir(`${ROOT}/src/bundles`);
|
|
261
|
-
} catch {}
|
|
262
|
-
await fs.promises.writeFile(
|
|
263
|
-
`${ROOT}/src/bundles/${name}.ts`,
|
|
264
|
-
content,
|
|
265
|
-
"utf8",
|
|
266
|
-
);
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
const collection = {};
|
|
270
|
-
await clone();
|
|
271
|
-
await iterate(collection)(template);
|
|
272
|
-
if (transform)
|
|
273
|
-
for (const [key, value] of Object.entries(collection))
|
|
274
|
-
collection[key] = await writeTransformedAsset(
|
|
275
|
-
template,
|
|
276
|
-
key,
|
|
277
|
-
transform(key, value),
|
|
278
|
-
);
|
|
279
|
-
await archive(collection);
|
|
280
|
-
};
|
|
281
|
-
|
|
282
|
-
const writeTransformedAsset = async (template, key, value) => {
|
|
283
|
-
await fs.promises.writeFile(`${template}/${key}`, value, "utf8");
|
|
284
|
-
return value;
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
const main = async () => {
|
|
288
|
-
await bundle({
|
|
289
|
-
mode: "nest",
|
|
290
|
-
repository: "nestia-start",
|
|
291
|
-
exceptions: [
|
|
292
|
-
".git",
|
|
293
|
-
".github/dependabot.yml",
|
|
294
|
-
".github/workflows/dependabot-automerge.yml",
|
|
295
|
-
"src/api/functional",
|
|
296
|
-
"src/controllers",
|
|
297
|
-
"src/MyModule.ts",
|
|
298
|
-
"src/providers",
|
|
299
|
-
"test/features",
|
|
300
|
-
],
|
|
301
|
-
transform: (key, value) => {
|
|
302
|
-
if (key.endsWith("package.json")) {
|
|
303
|
-
const parsed = JSON.parse(update(value));
|
|
304
|
-
trimTemplateDependencies(parsed);
|
|
305
|
-
return JSON.stringify(parsed, null, 2);
|
|
306
|
-
}
|
|
307
|
-
if (key === "test/helpers/ArgumentParser.ts") return ARGUMENT_PARSER;
|
|
308
|
-
if (key.endsWith("tsconfig.json"))
|
|
309
|
-
return updateTsConfig(value, {
|
|
310
|
-
disableTypia: true,
|
|
311
|
-
useNestiaAggregate: key === "tsconfig.json",
|
|
312
|
-
});
|
|
313
|
-
return value;
|
|
314
|
-
},
|
|
315
|
-
});
|
|
316
|
-
await bundle({
|
|
317
|
-
mode: "sdk",
|
|
318
|
-
repository: "nestia-sdk-template",
|
|
319
|
-
exceptions: [
|
|
320
|
-
".git",
|
|
321
|
-
".github/dependabot.yml",
|
|
322
|
-
".github/workflows/build.yml",
|
|
323
|
-
".github/workflows/dependabot-automerge.yml",
|
|
324
|
-
"src/functional",
|
|
325
|
-
"src/structures",
|
|
326
|
-
"test/features",
|
|
327
|
-
],
|
|
328
|
-
transform: (key, value) => {
|
|
329
|
-
if (key.endsWith("package.json")) {
|
|
330
|
-
const parsed = JSON.parse(
|
|
331
|
-
update(value, { sdkAggregate: key === "package.json" }),
|
|
332
|
-
);
|
|
333
|
-
trimTemplateDependencies(parsed);
|
|
334
|
-
return JSON.stringify(parsed, null, 2);
|
|
335
|
-
}
|
|
336
|
-
if (key === "test/utils/ArgumentParser.ts") return ARGUMENT_PARSER;
|
|
337
|
-
if (key.endsWith("tsconfig.json"))
|
|
338
|
-
return updateTsConfig(value, {
|
|
339
|
-
disableTypia: true,
|
|
340
|
-
useNestiaAggregate: true,
|
|
341
|
-
});
|
|
342
|
-
return value;
|
|
343
|
-
},
|
|
344
|
-
});
|
|
345
|
-
};
|
|
346
|
-
main().catch((exp) => {
|
|
347
|
-
console.error(exp);
|
|
348
|
-
process.exit(-1);
|
|
349
|
-
});
|
|
1
|
+
const { version } = require("../../../../package.json");
|
|
2
|
+
const cp = require("child_process");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
|
|
5
|
+
const ROOT = `${__dirname}/../..`;
|
|
6
|
+
const ASSETS = `${ROOT}/assets`;
|
|
7
|
+
const TYPIA = require("js-yaml").load(
|
|
8
|
+
fs.readFileSync(`${__dirname}/../../../../pnpm-lock.yaml`, "utf8"),
|
|
9
|
+
).catalogs.samchon;
|
|
10
|
+
|
|
11
|
+
const update = (content, options = {}) => {
|
|
12
|
+
const parsed = JSON.parse(content);
|
|
13
|
+
for (const record of [
|
|
14
|
+
parsed.dependencies ?? {},
|
|
15
|
+
parsed.devDependencies ?? {},
|
|
16
|
+
])
|
|
17
|
+
for (const key of Object.keys(record))
|
|
18
|
+
if (key.startsWith("@nestia/") || key === "nestia")
|
|
19
|
+
record[key] = `^${version}`;
|
|
20
|
+
else if (TYPIA[key]) record[key] = TYPIA[key].specifier;
|
|
21
|
+
migratePackageJson(parsed);
|
|
22
|
+
if (options.sdkAggregate) {
|
|
23
|
+
parsed.devDependencies ??= {};
|
|
24
|
+
parsed.devDependencies["@nestia/core"] = `^${version}`;
|
|
25
|
+
}
|
|
26
|
+
return JSON.stringify(parsed, null, 2);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const migratePackageJson = (parsed) => {
|
|
30
|
+
if (parsed.scripts)
|
|
31
|
+
for (const [key, value] of Object.entries(parsed.scripts))
|
|
32
|
+
if (typeof value === "string")
|
|
33
|
+
parsed.scripts[key] = normalizeScript(value);
|
|
34
|
+
|
|
35
|
+
if (typeof parsed.scripts?.prepare === "string") {
|
|
36
|
+
const prepare = parsed.scripts.prepare
|
|
37
|
+
.split("&&")
|
|
38
|
+
.map((str) => str.trim())
|
|
39
|
+
.filter((str) => str !== "ts-patch install" && str !== "typia patch")
|
|
40
|
+
.join(" && ");
|
|
41
|
+
if (prepare.length === 0) delete parsed.scripts.prepare;
|
|
42
|
+
else parsed.scripts.prepare = prepare;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const devDependencies = parsed.devDependencies;
|
|
46
|
+
if (devDependencies) {
|
|
47
|
+
const usesTypeScript = typeof devDependencies.typescript === "string";
|
|
48
|
+
delete devDependencies["ts-patch"];
|
|
49
|
+
delete devDependencies["typescript-transform-paths"];
|
|
50
|
+
if (usesTypeScript) devDependencies.ttsc ??= "^0.10.2";
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const trimTemplateDependencies = (parsed) => {
|
|
55
|
+
if (parsed.dependencies) {
|
|
56
|
+
delete parsed.dependencies.commander;
|
|
57
|
+
delete parsed.dependencies.inquirer;
|
|
58
|
+
}
|
|
59
|
+
if (parsed.devDependencies) {
|
|
60
|
+
delete parsed.devDependencies["@types/inquirer"];
|
|
61
|
+
delete parsed.devDependencies.commander;
|
|
62
|
+
delete parsed.devDependencies.inquirer;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const normalizeScript = (script) =>
|
|
67
|
+
script.replace(/(^|[^A-Za-z0-9_-])tsc(?=$|[^A-Za-z0-9_-])/g, "$1ttsc");
|
|
68
|
+
|
|
69
|
+
const TYPIA_PLUGIN = `{ "transform": "typia/lib/transform", "enabled": false }`;
|
|
70
|
+
const CORE_PLUGIN = `{ "transform": "@nestia/core/native/transform.cjs" }`;
|
|
71
|
+
|
|
72
|
+
const ARGUMENT_PARSER = `import { createInterface } from "node:readline/promises";
|
|
73
|
+
|
|
74
|
+
export namespace ArgumentParser {
|
|
75
|
+
export interface Command {
|
|
76
|
+
option: (flags: string, description?: string) => Command;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface Prompt {
|
|
80
|
+
select: (
|
|
81
|
+
name: string,
|
|
82
|
+
) => (
|
|
83
|
+
message: string,
|
|
84
|
+
) => <Choice extends string>(choices: Choice[]) => Promise<Choice>;
|
|
85
|
+
boolean: (name: string) => (message: string) => Promise<boolean>;
|
|
86
|
+
number: (name: string) => (message: string) => Promise<number>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export const parse = async <T>(
|
|
90
|
+
inquiry: (
|
|
91
|
+
command: Command,
|
|
92
|
+
prompt: Prompt,
|
|
93
|
+
action: (closure: (options: Partial<T>) => Promise<T>) => Promise<T>,
|
|
94
|
+
) => Promise<T>,
|
|
95
|
+
): Promise<T> => {
|
|
96
|
+
const command: Command = {
|
|
97
|
+
option: (_flags: string, _description?: string): Command => command,
|
|
98
|
+
};
|
|
99
|
+
const action = (closure: (options: Partial<T>) => Promise<T>) =>
|
|
100
|
+
closure(parseArguments() as Partial<T>);
|
|
101
|
+
return inquiry(
|
|
102
|
+
command,
|
|
103
|
+
{ select, boolean, number },
|
|
104
|
+
action,
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const select =
|
|
109
|
+
(_name: string) =>
|
|
110
|
+
(message: string) =>
|
|
111
|
+
async <Choice extends string>(choices: Choice[]): Promise<Choice> => {
|
|
112
|
+
const answer: string = await ask(\`\${message} (\${choices.join("/")})\`);
|
|
113
|
+
return (choices.find((choice) => choice === answer) ?? choices[0])!;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const boolean = (_name: string) => async (message: string) =>
|
|
117
|
+
/^(true|t|yes|y|1)$/i.test(await ask(\`\${message} [y/N]\`));
|
|
118
|
+
|
|
119
|
+
const number = (_name: string) => async (message: string) =>
|
|
120
|
+
Number(await ask(message));
|
|
121
|
+
|
|
122
|
+
const ask = async (message: string): Promise<string> => {
|
|
123
|
+
const reader = createInterface({
|
|
124
|
+
input: process.stdin,
|
|
125
|
+
output: process.stdout,
|
|
126
|
+
});
|
|
127
|
+
try {
|
|
128
|
+
return (await reader.question(\`\${message}: \`)).trim();
|
|
129
|
+
} finally {
|
|
130
|
+
reader.close();
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const parseArguments = (): Record<string, string | string[] | boolean> => {
|
|
135
|
+
const output: Record<string, string | string[] | boolean> = {};
|
|
136
|
+
const args: string[] = process.argv.slice(2);
|
|
137
|
+
for (let i = 0; i < args.length; ++i) {
|
|
138
|
+
const raw: string = args[i]!;
|
|
139
|
+
if (raw.startsWith("--") === false) continue;
|
|
140
|
+
|
|
141
|
+
const equal: number = raw.indexOf("=");
|
|
142
|
+
const name: string = toCamelCase(
|
|
143
|
+
raw.slice(2, equal === -1 ? undefined : equal),
|
|
144
|
+
);
|
|
145
|
+
if (equal !== -1) {
|
|
146
|
+
assign(output, name, raw.slice(equal + 1));
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const values: string[] = [];
|
|
151
|
+
while (i + 1 < args.length && args[i + 1]!.startsWith("--") === false)
|
|
152
|
+
values.push(args[++i]!);
|
|
153
|
+
assign(
|
|
154
|
+
output,
|
|
155
|
+
name,
|
|
156
|
+
values.length === 0 ? true : values.length === 1 ? values[0]! : values,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
return output;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const assign = (
|
|
163
|
+
output: Record<string, string | string[] | boolean>,
|
|
164
|
+
name: string,
|
|
165
|
+
value: string | string[] | boolean,
|
|
166
|
+
): void => {
|
|
167
|
+
const current: string | string[] | boolean | undefined = output[name];
|
|
168
|
+
if (current === undefined) output[name] = value;
|
|
169
|
+
else
|
|
170
|
+
output[name] = [
|
|
171
|
+
...(Array.isArray(current) ? current : [String(current)]),
|
|
172
|
+
...(Array.isArray(value) ? value : [String(value)]),
|
|
173
|
+
];
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const toCamelCase = (str: string): string =>
|
|
177
|
+
str.replace(/-([a-z])/g, (_matched, letter: string) =>
|
|
178
|
+
letter.toUpperCase(),
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
`;
|
|
182
|
+
|
|
183
|
+
const updateTsConfig = (content, options = {}) => {
|
|
184
|
+
content = content.replace(
|
|
185
|
+
/^\s*\{\s*"transform":\s*"typescript-transform-paths"\s*\},\n/gm,
|
|
186
|
+
"",
|
|
187
|
+
);
|
|
188
|
+
if (options.disableTypia)
|
|
189
|
+
content = content.replace(
|
|
190
|
+
/\{\s*"transform":\s*"typia\/lib\/transform"\s*\}/g,
|
|
191
|
+
TYPIA_PLUGIN,
|
|
192
|
+
);
|
|
193
|
+
if (options.useNestiaAggregate)
|
|
194
|
+
content = content.replace(
|
|
195
|
+
/\{\s*"transform":\s*"@nestia\/core\/lib\/transform"\s*\}/g,
|
|
196
|
+
CORE_PLUGIN,
|
|
197
|
+
);
|
|
198
|
+
content = content.replace(
|
|
199
|
+
/^\s*\{\s*"transform":\s*"@nestia\/sdk\/lib\/transform"\s*\},\n/gm,
|
|
200
|
+
"",
|
|
201
|
+
);
|
|
202
|
+
if (
|
|
203
|
+
options.useNestiaAggregate &&
|
|
204
|
+
content.includes(`"@nestia/core/native/transform.cjs"`) === false
|
|
205
|
+
)
|
|
206
|
+
content = content.replace(
|
|
207
|
+
/\{\s*"transform":\s*"typia\/lib\/transform",\s*"enabled":\s*false\s*\},/g,
|
|
208
|
+
`${TYPIA_PLUGIN},\n ${CORE_PLUGIN},`,
|
|
209
|
+
);
|
|
210
|
+
return content;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const bundle = async ({ mode, repository, exceptions, transform }) => {
|
|
214
|
+
const root = `${__dirname}/../..`;
|
|
215
|
+
const assets = `${root}/assets`;
|
|
216
|
+
const template = `${assets}/${mode}`;
|
|
217
|
+
|
|
218
|
+
const clone = async () => {
|
|
219
|
+
// CLONE REPOSITORY
|
|
220
|
+
if (fs.existsSync(template))
|
|
221
|
+
await fs.promises.rm(template, { recursive: true });
|
|
222
|
+
else
|
|
223
|
+
try {
|
|
224
|
+
await fs.promises.mkdir(ASSETS);
|
|
225
|
+
} catch {}
|
|
226
|
+
|
|
227
|
+
cp.execSync(`git clone https://github.com/samchon/${repository} ${mode}`, {
|
|
228
|
+
cwd: ASSETS,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// REMOVE VULNERABLE FILES
|
|
232
|
+
for (const location of exceptions ?? [])
|
|
233
|
+
await fs.promises.rm(`${template}/${location}`, { recursive: true });
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const iterate = (collection) => async (location) => {
|
|
237
|
+
const directory = await fs.promises.readdir(location);
|
|
238
|
+
for (const file of directory) {
|
|
239
|
+
const absolute = location + "/" + file;
|
|
240
|
+
const stats = await fs.promises.stat(absolute);
|
|
241
|
+
if (stats.isDirectory()) await iterate(collection)(absolute);
|
|
242
|
+
else {
|
|
243
|
+
const content = await fs.promises.readFile(absolute, "utf-8");
|
|
244
|
+
collection[
|
|
245
|
+
(() => {
|
|
246
|
+
const str = absolute.replace(template, "");
|
|
247
|
+
return str[0] === "/" ? str.substring(1) : str;
|
|
248
|
+
})()
|
|
249
|
+
] = content;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const archive = async (collection) => {
|
|
255
|
+
const name = `${mode.toUpperCase()}_TEMPLATE`;
|
|
256
|
+
const body = JSON.stringify(collection, null, 2);
|
|
257
|
+
const content = `export const ${name}: Record<string, string> = ${body}`;
|
|
258
|
+
|
|
259
|
+
try {
|
|
260
|
+
await fs.promises.mkdir(`${ROOT}/src/bundles`);
|
|
261
|
+
} catch {}
|
|
262
|
+
await fs.promises.writeFile(
|
|
263
|
+
`${ROOT}/src/bundles/${name}.ts`,
|
|
264
|
+
content,
|
|
265
|
+
"utf8",
|
|
266
|
+
);
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const collection = {};
|
|
270
|
+
await clone();
|
|
271
|
+
await iterate(collection)(template);
|
|
272
|
+
if (transform)
|
|
273
|
+
for (const [key, value] of Object.entries(collection))
|
|
274
|
+
collection[key] = await writeTransformedAsset(
|
|
275
|
+
template,
|
|
276
|
+
key,
|
|
277
|
+
transform(key, value),
|
|
278
|
+
);
|
|
279
|
+
await archive(collection);
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const writeTransformedAsset = async (template, key, value) => {
|
|
283
|
+
await fs.promises.writeFile(`${template}/${key}`, value, "utf8");
|
|
284
|
+
return value;
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
const main = async () => {
|
|
288
|
+
await bundle({
|
|
289
|
+
mode: "nest",
|
|
290
|
+
repository: "nestia-start",
|
|
291
|
+
exceptions: [
|
|
292
|
+
".git",
|
|
293
|
+
".github/dependabot.yml",
|
|
294
|
+
".github/workflows/dependabot-automerge.yml",
|
|
295
|
+
"src/api/functional",
|
|
296
|
+
"src/controllers",
|
|
297
|
+
"src/MyModule.ts",
|
|
298
|
+
"src/providers",
|
|
299
|
+
"test/features",
|
|
300
|
+
],
|
|
301
|
+
transform: (key, value) => {
|
|
302
|
+
if (key.endsWith("package.json")) {
|
|
303
|
+
const parsed = JSON.parse(update(value));
|
|
304
|
+
trimTemplateDependencies(parsed);
|
|
305
|
+
return JSON.stringify(parsed, null, 2);
|
|
306
|
+
}
|
|
307
|
+
if (key === "test/helpers/ArgumentParser.ts") return ARGUMENT_PARSER;
|
|
308
|
+
if (key.endsWith("tsconfig.json"))
|
|
309
|
+
return updateTsConfig(value, {
|
|
310
|
+
disableTypia: true,
|
|
311
|
+
useNestiaAggregate: key === "tsconfig.json",
|
|
312
|
+
});
|
|
313
|
+
return value;
|
|
314
|
+
},
|
|
315
|
+
});
|
|
316
|
+
await bundle({
|
|
317
|
+
mode: "sdk",
|
|
318
|
+
repository: "nestia-sdk-template",
|
|
319
|
+
exceptions: [
|
|
320
|
+
".git",
|
|
321
|
+
".github/dependabot.yml",
|
|
322
|
+
".github/workflows/build.yml",
|
|
323
|
+
".github/workflows/dependabot-automerge.yml",
|
|
324
|
+
"src/functional",
|
|
325
|
+
"src/structures",
|
|
326
|
+
"test/features",
|
|
327
|
+
],
|
|
328
|
+
transform: (key, value) => {
|
|
329
|
+
if (key.endsWith("package.json")) {
|
|
330
|
+
const parsed = JSON.parse(
|
|
331
|
+
update(value, { sdkAggregate: key === "package.json" }),
|
|
332
|
+
);
|
|
333
|
+
trimTemplateDependencies(parsed);
|
|
334
|
+
return JSON.stringify(parsed, null, 2);
|
|
335
|
+
}
|
|
336
|
+
if (key === "test/utils/ArgumentParser.ts") return ARGUMENT_PARSER;
|
|
337
|
+
if (key.endsWith("tsconfig.json"))
|
|
338
|
+
return updateTsConfig(value, {
|
|
339
|
+
disableTypia: true,
|
|
340
|
+
useNestiaAggregate: true,
|
|
341
|
+
});
|
|
342
|
+
return value;
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
};
|
|
346
|
+
main().catch((exp) => {
|
|
347
|
+
console.error(exp);
|
|
348
|
+
process.exit(-1);
|
|
349
|
+
});
|