create-caspian-app 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +418 -0
- package/dist/.prettierrc +10 -0
- package/dist/app-gitignore +28 -0
- package/dist/caspian.js +2 -0
- package/dist/index.js +2 -0
- package/dist/main.py +525 -0
- package/dist/postcss.config.js +6 -0
- package/dist/public/css/styles.css +1 -0
- package/dist/public/favicon.ico +0 -0
- package/dist/public/js/main.js +1 -0
- package/dist/public/js/pp-reactive-v1.js +1 -0
- package/dist/pyproject.toml +9 -0
- package/dist/settings/bs-config.json +7 -0
- package/dist/settings/bs-config.ts +291 -0
- package/dist/settings/build.ts +19 -0
- package/dist/settings/component-map.json +1361 -0
- package/dist/settings/component-map.ts +381 -0
- package/dist/settings/files-list.json +36 -0
- package/dist/settings/files-list.ts +49 -0
- package/dist/settings/project-name.ts +119 -0
- package/dist/settings/python-server.ts +173 -0
- package/dist/settings/utils.ts +239 -0
- package/dist/settings/vite-plugins/generate-global-types.ts +246 -0
- package/dist/src/app/error.html +130 -0
- package/dist/src/app/globals.css +24 -0
- package/dist/src/app/index.html +159 -0
- package/dist/src/app/layout.html +22 -0
- package/dist/src/app/not-found.html +18 -0
- package/dist/ts/global-functions.ts +35 -0
- package/dist/ts/main.ts +5 -0
- package/dist/tsconfig.json +111 -0
- package/dist/vite.config.ts +61 -0
- package/package.json +42 -0
- package/tsconfig.json +49 -0
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import Parser from "tree-sitter";
|
|
4
|
+
import Python from "tree-sitter-python";
|
|
5
|
+
import { getFileMeta } from "./utils";
|
|
6
|
+
|
|
7
|
+
const { __dirname } = getFileMeta();
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* ---------------------------------------------------------
|
|
11
|
+
* Configuration & Interfaces
|
|
12
|
+
* ---------------------------------------------------------
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
interface CaspianConfig {
|
|
16
|
+
projectName: string;
|
|
17
|
+
projectRootPath: string;
|
|
18
|
+
componentScanDirs: string[];
|
|
19
|
+
excludeFiles?: string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface ComponentProp {
|
|
23
|
+
name: string;
|
|
24
|
+
type: string;
|
|
25
|
+
hasDefault: boolean;
|
|
26
|
+
defaultValue?: string;
|
|
27
|
+
options?: string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface ComponentMetadata {
|
|
31
|
+
componentName: string;
|
|
32
|
+
filePath: string;
|
|
33
|
+
relativePath: string;
|
|
34
|
+
importRoute: string;
|
|
35
|
+
acceptsArbitraryProps: boolean;
|
|
36
|
+
props: ComponentProp[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const CONFIG_FILENAME = "caspian.config.json";
|
|
40
|
+
const PROJECT_ROOT = path.resolve(__dirname, "..");
|
|
41
|
+
const CONFIG_PATH = path.join(PROJECT_ROOT, CONFIG_FILENAME);
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* ---------------------------------------------------------
|
|
45
|
+
* AST Helpers
|
|
46
|
+
* ---------------------------------------------------------
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
const parser = new Parser();
|
|
50
|
+
parser.setLanguage(Python as any);
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Extracts string values from a Dictionary node
|
|
54
|
+
*/
|
|
55
|
+
function extractDictKeysFromNode(node: Parser.SyntaxNode): string[] {
|
|
56
|
+
const keys: string[] = [];
|
|
57
|
+
for (const child of node.children) {
|
|
58
|
+
if (child.type === "dictionary_splat") continue;
|
|
59
|
+
|
|
60
|
+
if (child.type === "pair") {
|
|
61
|
+
const keyNode = child.childForFieldName("key");
|
|
62
|
+
if (
|
|
63
|
+
keyNode &&
|
|
64
|
+
(keyNode.type === "string" || keyNode.type === "identifier")
|
|
65
|
+
) {
|
|
66
|
+
keys.push(keyNode.text.replace(/^['"]|['"]$/g, ""));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return keys;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Extracts values from a Literal[...] type node
|
|
75
|
+
*/
|
|
76
|
+
function extractLiteralValues(node: Parser.SyntaxNode): string[] {
|
|
77
|
+
const values: string[] = [];
|
|
78
|
+
|
|
79
|
+
if (node.type !== "subscript" && node.type !== "generic_type") return values;
|
|
80
|
+
|
|
81
|
+
let typeName: string | undefined;
|
|
82
|
+
if (node.type === "subscript") {
|
|
83
|
+
typeName = node.childForFieldName("value")?.text;
|
|
84
|
+
} else if (node.type === "generic_type") {
|
|
85
|
+
typeName = node.children.find((c) => c.type === "identifier")?.text;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (typeName !== "Literal") return values;
|
|
89
|
+
|
|
90
|
+
const findStrings = (n: Parser.SyntaxNode) => {
|
|
91
|
+
if (n.type === "string") {
|
|
92
|
+
values.push(n.text.replace(/^['"]|['"]$/g, ""));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
for (const child of n.children) {
|
|
96
|
+
findStrings(child);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
findStrings(node);
|
|
101
|
+
return values;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Collects module-level type aliases
|
|
106
|
+
*/
|
|
107
|
+
function collectTypeAliases(
|
|
108
|
+
rootNode: Parser.SyntaxNode
|
|
109
|
+
): Map<string, string[]> {
|
|
110
|
+
const aliases = new Map<string, string[]>();
|
|
111
|
+
|
|
112
|
+
for (const child of rootNode.children) {
|
|
113
|
+
if (child.type === "expression_statement") {
|
|
114
|
+
const assignment = child.firstChild;
|
|
115
|
+
if (assignment?.type === "assignment") {
|
|
116
|
+
const left = assignment.childForFieldName("left");
|
|
117
|
+
const right = assignment.childForFieldName("right");
|
|
118
|
+
|
|
119
|
+
if (left?.type === "identifier" && right) {
|
|
120
|
+
const values = extractLiteralValues(right);
|
|
121
|
+
if (values.length > 0) {
|
|
122
|
+
aliases.set(left.text, values);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return aliases;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* ---------------------------------------------------------
|
|
133
|
+
* Core Analysis Logic (AST Based)
|
|
134
|
+
* ---------------------------------------------------------
|
|
135
|
+
*/
|
|
136
|
+
|
|
137
|
+
function analyzeFile(filePath: string, rootDir: string): ComponentMetadata[] {
|
|
138
|
+
const fileContent = fs.readFileSync(filePath, "utf-8");
|
|
139
|
+
const tree = parser.parse(fileContent);
|
|
140
|
+
const components: ComponentMetadata[] = [];
|
|
141
|
+
const typeAliases = collectTypeAliases(tree.rootNode);
|
|
142
|
+
|
|
143
|
+
const query = new Parser.Query(
|
|
144
|
+
Python as any,
|
|
145
|
+
`(decorated_definition
|
|
146
|
+
(decorator) @dec
|
|
147
|
+
(function_definition) @func
|
|
148
|
+
)`
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const matches = query.matches(tree.rootNode);
|
|
152
|
+
|
|
153
|
+
for (const match of matches) {
|
|
154
|
+
const decoratorNode = match.captures.find((c) => c.name === "dec")?.node;
|
|
155
|
+
if (decoratorNode?.text.trim() !== "@component") continue;
|
|
156
|
+
|
|
157
|
+
const funcNode = match.captures.find((c) => c.name === "func")?.node;
|
|
158
|
+
if (!funcNode) continue;
|
|
159
|
+
|
|
160
|
+
const nameNode = funcNode.childForFieldName("name");
|
|
161
|
+
const componentName = nameNode?.text || "Unknown";
|
|
162
|
+
const paramsNode = funcNode.childForFieldName("parameters");
|
|
163
|
+
const bodyNode = funcNode.childForFieldName("body");
|
|
164
|
+
|
|
165
|
+
// 1. Parse Props (Parameters)
|
|
166
|
+
const props: ComponentProp[] = [];
|
|
167
|
+
const propMap = new Map<string, ComponentProp>();
|
|
168
|
+
let acceptsArbitraryProps = false;
|
|
169
|
+
|
|
170
|
+
if (paramsNode) {
|
|
171
|
+
for (const param of paramsNode.children) {
|
|
172
|
+
// --- Detect **props (dictionary_splat_pattern) ---
|
|
173
|
+
if (param.type === "dictionary_splat_pattern") {
|
|
174
|
+
acceptsArbitraryProps = true;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Skip punctuation and list splats (*args)
|
|
179
|
+
if (["(", ")", ",", "list_splat_pattern"].includes(param.type))
|
|
180
|
+
continue;
|
|
181
|
+
|
|
182
|
+
let name = "";
|
|
183
|
+
let defaultValue: string | undefined = undefined;
|
|
184
|
+
let typeNode: Parser.SyntaxNode | null = null;
|
|
185
|
+
|
|
186
|
+
if (param.type === "identifier") {
|
|
187
|
+
name = param.text;
|
|
188
|
+
} else if (param.type === "default_parameter") {
|
|
189
|
+
name = param.childForFieldName("name")?.text || "";
|
|
190
|
+
defaultValue = param.childForFieldName("value")?.text;
|
|
191
|
+
} else if (param.type === "typed_parameter") {
|
|
192
|
+
name = param.childForFieldName("name")?.text || "";
|
|
193
|
+
typeNode = param.childForFieldName("type");
|
|
194
|
+
} else if (param.type === "typed_default_parameter") {
|
|
195
|
+
name = param.childForFieldName("name")?.text || "";
|
|
196
|
+
defaultValue = param.childForFieldName("value")?.text;
|
|
197
|
+
typeNode = param.childForFieldName("type");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Clean up quotes from default value
|
|
201
|
+
if (defaultValue)
|
|
202
|
+
defaultValue = defaultValue.replace(/^['"]|['"]$/g, "");
|
|
203
|
+
|
|
204
|
+
// Exclude standard python args
|
|
205
|
+
if (name === "self" || name === "cls") continue;
|
|
206
|
+
|
|
207
|
+
// --- Extract Type String ---
|
|
208
|
+
let propType = "Any";
|
|
209
|
+
if (typeNode) {
|
|
210
|
+
propType = typeNode.text;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// --- Extract Options ---
|
|
214
|
+
let options: string[] = [];
|
|
215
|
+
if (typeNode) {
|
|
216
|
+
let actualType: Parser.SyntaxNode | null = typeNode;
|
|
217
|
+
if (typeNode.type === "type") {
|
|
218
|
+
actualType = typeNode.firstChild;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (actualType?.type === "identifier") {
|
|
222
|
+
options = typeAliases.get(actualType.text) || [];
|
|
223
|
+
} else if (
|
|
224
|
+
actualType?.type === "subscript" ||
|
|
225
|
+
actualType?.type === "generic_type"
|
|
226
|
+
) {
|
|
227
|
+
options = extractLiteralValues(actualType);
|
|
228
|
+
} else if (actualType) {
|
|
229
|
+
const findSubscript = (
|
|
230
|
+
n: Parser.SyntaxNode
|
|
231
|
+
): Parser.SyntaxNode | null => {
|
|
232
|
+
if (n.type === "subscript") return n;
|
|
233
|
+
for (const c of n.children) {
|
|
234
|
+
const found = findSubscript(c);
|
|
235
|
+
if (found) return found;
|
|
236
|
+
}
|
|
237
|
+
return null;
|
|
238
|
+
};
|
|
239
|
+
const subscript = findSubscript(actualType);
|
|
240
|
+
if (subscript) {
|
|
241
|
+
options = extractLiteralValues(subscript);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const propObj: ComponentProp = {
|
|
247
|
+
name,
|
|
248
|
+
type: propType,
|
|
249
|
+
hasDefault: defaultValue !== undefined,
|
|
250
|
+
defaultValue,
|
|
251
|
+
options: options.length > 0 ? options : undefined,
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
props.push(propObj);
|
|
255
|
+
propMap.set(name, propObj);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// 2. Parse Body for Dictionaries
|
|
260
|
+
if (bodyNode) {
|
|
261
|
+
for (const statement of bodyNode.children) {
|
|
262
|
+
if (statement.type === "expression_statement") {
|
|
263
|
+
const assignment = statement.firstChild;
|
|
264
|
+
if (assignment?.type === "assignment") {
|
|
265
|
+
const left = assignment.childForFieldName("left");
|
|
266
|
+
const right = assignment.childForFieldName("right");
|
|
267
|
+
|
|
268
|
+
if (left?.type === "identifier" && right?.type === "dictionary") {
|
|
269
|
+
const varName = left.text;
|
|
270
|
+
if (varName.endsWith("s")) {
|
|
271
|
+
const propName = varName.slice(0, -1);
|
|
272
|
+
const relatedProp = propMap.get(propName);
|
|
273
|
+
|
|
274
|
+
if (relatedProp) {
|
|
275
|
+
if (!relatedProp.options) {
|
|
276
|
+
relatedProp.options = extractDictKeysFromNode(right);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Generate Metadata Paths
|
|
287
|
+
const relativePath = path.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
288
|
+
const pathNoExt = relativePath.replace(/\.py$/, "");
|
|
289
|
+
const importRoute = pathNoExt.replace(/\//g, ".");
|
|
290
|
+
|
|
291
|
+
components.push({
|
|
292
|
+
componentName,
|
|
293
|
+
filePath,
|
|
294
|
+
relativePath,
|
|
295
|
+
importRoute,
|
|
296
|
+
acceptsArbitraryProps,
|
|
297
|
+
props,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return components;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* ---------------------------------------------------------
|
|
306
|
+
* File System Helpers
|
|
307
|
+
* ---------------------------------------------------------
|
|
308
|
+
*/
|
|
309
|
+
function loadConfig(): CaspianConfig {
|
|
310
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
311
|
+
console.error(`❌ Configuration file not found at: ${CONFIG_PATH}`);
|
|
312
|
+
process.exit(1);
|
|
313
|
+
}
|
|
314
|
+
return JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8"));
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function walkDirectory(
|
|
318
|
+
dir: string,
|
|
319
|
+
fileList: string[] = [],
|
|
320
|
+
excludeList: string[] = []
|
|
321
|
+
): string[] {
|
|
322
|
+
if (!fs.existsSync(dir)) return fileList;
|
|
323
|
+
const files = fs.readdirSync(dir);
|
|
324
|
+
files.forEach((file) => {
|
|
325
|
+
const fullPath = path.join(dir, file);
|
|
326
|
+
const stat = fs.statSync(fullPath);
|
|
327
|
+
const relativeCheck = path
|
|
328
|
+
.relative(PROJECT_ROOT, fullPath)
|
|
329
|
+
.replace(/\\/g, "/");
|
|
330
|
+
const isExcluded = excludeList.some((ex) =>
|
|
331
|
+
relativeCheck.includes(ex.replace(/^\.\//, ""))
|
|
332
|
+
);
|
|
333
|
+
if (isExcluded) return;
|
|
334
|
+
|
|
335
|
+
if (stat.isDirectory()) {
|
|
336
|
+
walkDirectory(fullPath, fileList, excludeList);
|
|
337
|
+
} else if (path.extname(file) === ".py") {
|
|
338
|
+
fileList.push(fullPath);
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
return fileList;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* ---------------------------------------------------------
|
|
346
|
+
* Execution Entry Point
|
|
347
|
+
* ---------------------------------------------------------
|
|
348
|
+
*/
|
|
349
|
+
export async function componentMap() {
|
|
350
|
+
console.log(`🔍 Starting Component Analysis (AST Powered)...`);
|
|
351
|
+
const config = loadConfig();
|
|
352
|
+
let allFiles: string[] = [];
|
|
353
|
+
|
|
354
|
+
config.componentScanDirs.forEach((scanDir) => {
|
|
355
|
+
allFiles = walkDirectory(
|
|
356
|
+
path.join(PROJECT_ROOT, scanDir),
|
|
357
|
+
allFiles,
|
|
358
|
+
config.excludeFiles || []
|
|
359
|
+
);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
console.log(`📂 Found ${allFiles.length} Python files.`);
|
|
363
|
+
const componentRegistry: ComponentMetadata[] = [];
|
|
364
|
+
|
|
365
|
+
allFiles.forEach((file) => {
|
|
366
|
+
try {
|
|
367
|
+
const foundComponents = analyzeFile(file, PROJECT_ROOT);
|
|
368
|
+
componentRegistry.push(...foundComponents);
|
|
369
|
+
} catch (e) {
|
|
370
|
+
console.warn(`⚠️ Failed to parse ${file}:`, e);
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
console.log(`✅ Discovered ${componentRegistry.length} Components.`);
|
|
375
|
+
|
|
376
|
+
const outputPath = path.join(__dirname, "component-map.json");
|
|
377
|
+
if (componentRegistry.length > 0 || !fs.existsSync(outputPath)) {
|
|
378
|
+
fs.writeFileSync(outputPath, JSON.stringify(componentRegistry, null, 2));
|
|
379
|
+
console.log(`📝 Component map written to: ${outputPath}`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[
|
|
2
|
+
"./src/app/docs/auth/index.html",
|
|
3
|
+
"./src/app/docs/cli/index.html",
|
|
4
|
+
"./src/app/docs/components/index.html",
|
|
5
|
+
"./src/app/docs/database/index.html",
|
|
6
|
+
"./src/app/docs/files/error/index.html",
|
|
7
|
+
"./src/app/docs/files/index/index.html",
|
|
8
|
+
"./src/app/docs/files/layout/index.html",
|
|
9
|
+
"./src/app/docs/files/loading/index.html",
|
|
10
|
+
"./src/app/docs/files/not-found/index.html",
|
|
11
|
+
"./src/app/docs/index.html",
|
|
12
|
+
"./src/app/docs/index.py",
|
|
13
|
+
"./src/app/docs/installation/index.html",
|
|
14
|
+
"./src/app/docs/layout.html",
|
|
15
|
+
"./src/app/docs/layout.py",
|
|
16
|
+
"./src/app/docs/loading.html",
|
|
17
|
+
"./src/app/docs/reactivity/index.html",
|
|
18
|
+
"./src/app/docs/routing/index.html",
|
|
19
|
+
"./src/app/docs/rpc/index.html",
|
|
20
|
+
"./src/app/docs/structure/index.html",
|
|
21
|
+
"./src/app/docs/__pycache__/index.cpython-314.pyc",
|
|
22
|
+
"./src/app/docs/__pycache__/layout.cpython-314.pyc",
|
|
23
|
+
"./src/app/error.html",
|
|
24
|
+
"./src/app/globals.css",
|
|
25
|
+
"./src/app/index.html",
|
|
26
|
+
"./src/app/index.py",
|
|
27
|
+
"./src/app/layout.html",
|
|
28
|
+
"./src/app/not-found.html",
|
|
29
|
+
"./src/app/__pycache__/index.cpython-314.pyc",
|
|
30
|
+
"./src/app/__pycache__/layout.cpython-314.pyc",
|
|
31
|
+
"./public/css/styles.css",
|
|
32
|
+
"./public/favicon.ico",
|
|
33
|
+
"./public/js/global-functions.js",
|
|
34
|
+
"./public/js/main.js",
|
|
35
|
+
"./public/js/pp-reactive-v1.js"
|
|
36
|
+
]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { existsSync, readdirSync, statSync, writeFileSync } from "fs";
|
|
2
|
+
import { join, sep, relative } from "path";
|
|
3
|
+
import { getFileMeta } from "./utils.js";
|
|
4
|
+
import { PUBLIC_DIR, APP_DIR } from "../settings/utils.js";
|
|
5
|
+
|
|
6
|
+
const { __dirname } = getFileMeta();
|
|
7
|
+
|
|
8
|
+
const jsonFilePath = "settings/files-list.json";
|
|
9
|
+
|
|
10
|
+
const getAllFiles = (dirPath: string): string[] => {
|
|
11
|
+
const files: string[] = [];
|
|
12
|
+
|
|
13
|
+
if (!existsSync(dirPath)) {
|
|
14
|
+
console.error(`Directory not found: ${dirPath}`);
|
|
15
|
+
return files;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const items = readdirSync(dirPath);
|
|
19
|
+
items.forEach((item) => {
|
|
20
|
+
const fullPath = join(dirPath, item);
|
|
21
|
+
if (statSync(fullPath).isDirectory()) {
|
|
22
|
+
files.push(...getAllFiles(fullPath));
|
|
23
|
+
} else {
|
|
24
|
+
const relativePath = `.${sep}${relative(
|
|
25
|
+
join(__dirname, ".."),
|
|
26
|
+
fullPath
|
|
27
|
+
)}`;
|
|
28
|
+
files.push(relativePath.replace(/\\/g, "/").replace(/^\.\.\//, ""));
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return files;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const generateFileListJson = async (): Promise<void> => {
|
|
36
|
+
const appFiles = getAllFiles(APP_DIR);
|
|
37
|
+
const publicFiles = getAllFiles(PUBLIC_DIR);
|
|
38
|
+
|
|
39
|
+
const allFiles = [...appFiles, ...publicFiles];
|
|
40
|
+
|
|
41
|
+
if (allFiles.length > 0) {
|
|
42
|
+
writeFileSync(jsonFilePath, JSON.stringify(allFiles, null, 2));
|
|
43
|
+
console.log(
|
|
44
|
+
`File list generated: ${appFiles.length} app files, ${publicFiles.length} public files`
|
|
45
|
+
);
|
|
46
|
+
} else {
|
|
47
|
+
console.error("No files found to save in the JSON file.");
|
|
48
|
+
}
|
|
49
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { writeFile } from "fs";
|
|
2
|
+
import { join, basename, normalize, relative, sep } from "path";
|
|
3
|
+
import caspianConfigJson from "../caspian.config.json";
|
|
4
|
+
import { promises as fsPromises } from "fs";
|
|
5
|
+
import { generateFileListJson } from "./files-list";
|
|
6
|
+
import { componentMap } from "./component-map";
|
|
7
|
+
|
|
8
|
+
const currentProjectRoot = process.cwd();
|
|
9
|
+
const newProjectName = basename(currentProjectRoot);
|
|
10
|
+
const configFilePath = join(currentProjectRoot, "caspian.config.json");
|
|
11
|
+
|
|
12
|
+
function updateProjectNameInConfig(
|
|
13
|
+
filePath: string,
|
|
14
|
+
newProjectName: string,
|
|
15
|
+
currentRoot: string
|
|
16
|
+
): void {
|
|
17
|
+
const newWebPath = calculateDynamicWebPath(
|
|
18
|
+
currentRoot,
|
|
19
|
+
caspianConfigJson.projectRootPath,
|
|
20
|
+
caspianConfigJson.bsPathRewrite?.["^/"]
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
caspianConfigJson.projectName = newProjectName;
|
|
24
|
+
caspianConfigJson.projectRootPath = currentRoot;
|
|
25
|
+
caspianConfigJson.bsTarget = `http://localhost${newWebPath}`;
|
|
26
|
+
|
|
27
|
+
if (!caspianConfigJson.bsPathRewrite) {
|
|
28
|
+
(caspianConfigJson as any).bsPathRewrite = {};
|
|
29
|
+
}
|
|
30
|
+
caspianConfigJson.bsPathRewrite["^/"] = newWebPath;
|
|
31
|
+
|
|
32
|
+
writeFile(
|
|
33
|
+
filePath,
|
|
34
|
+
JSON.stringify(caspianConfigJson, null, 2),
|
|
35
|
+
"utf8",
|
|
36
|
+
(err) => {
|
|
37
|
+
if (err) {
|
|
38
|
+
console.error("Error writing the updated JSON file:", err);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
console.log(
|
|
42
|
+
`Configuration updated.\nProject: ${newProjectName}\nURL: http://localhost${newWebPath}`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function calculateDynamicWebPath(
|
|
49
|
+
currentPath: string,
|
|
50
|
+
oldPath?: string,
|
|
51
|
+
oldUrl?: string
|
|
52
|
+
): string {
|
|
53
|
+
let webRoot: string | null = null;
|
|
54
|
+
|
|
55
|
+
if (oldPath && oldUrl) {
|
|
56
|
+
try {
|
|
57
|
+
const normOldPath = normalize(oldPath);
|
|
58
|
+
const normOldUrl = normalize(oldUrl).replace(/^[\\\/]|[\\\/]$/g, "");
|
|
59
|
+
|
|
60
|
+
if (normOldPath.endsWith(normOldUrl)) {
|
|
61
|
+
webRoot = normOldPath.slice(0, -normOldUrl.length);
|
|
62
|
+
if (webRoot.endsWith(sep)) webRoot = webRoot.slice(0, -1);
|
|
63
|
+
}
|
|
64
|
+
} catch (e) {}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (webRoot) {
|
|
68
|
+
const relPath = relative(webRoot, currentPath);
|
|
69
|
+
const urlPath = relPath.split(sep).join("/");
|
|
70
|
+
return `/${urlPath}/`.replace(/\/+/g, "/");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return "/";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
updateProjectNameInConfig(configFilePath, newProjectName, currentProjectRoot);
|
|
77
|
+
|
|
78
|
+
export const deleteFilesIfExist = async (
|
|
79
|
+
filePaths: string[]
|
|
80
|
+
): Promise<void> => {
|
|
81
|
+
for (const filePath of filePaths) {
|
|
82
|
+
try {
|
|
83
|
+
await fsPromises.unlink(filePath);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
86
|
+
console.error(`Error deleting ${filePath}:`, error);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export async function deleteDirectoriesIfExist(
|
|
93
|
+
dirPaths: string[]
|
|
94
|
+
): Promise<void> {
|
|
95
|
+
for (const dirPath of dirPaths) {
|
|
96
|
+
try {
|
|
97
|
+
await fsPromises.rm(dirPath, { recursive: true, force: true });
|
|
98
|
+
console.log(`Deleted directory: ${dirPath}`);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
101
|
+
console.error(`Error deleting directory (${dirPath}):`, error);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export const filesToDelete = [
|
|
108
|
+
join(currentProjectRoot, "settings", "component-map.json"),
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
export const dirsToDelete = [
|
|
112
|
+
join(currentProjectRoot, "caches"),
|
|
113
|
+
join(currentProjectRoot, ".casp"),
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
await deleteFilesIfExist(filesToDelete);
|
|
117
|
+
await deleteDirectoriesIfExist(dirsToDelete);
|
|
118
|
+
await generateFileListJson();
|
|
119
|
+
await componentMap();
|