create-glosc 0.2.2 → 0.4.0
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 +13 -0
- package/dist/scaffold.js +23 -0
- package/dist/templates.js +244 -3
- package/package.json +1 -1
- package/src/scaffold.ts +25 -0
- package/src/templates.ts +254 -3
package/README.md
CHANGED
|
@@ -69,6 +69,19 @@ npm run build
|
|
|
69
69
|
npm start
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
+
### 发布到 Glosc Store(打包 ZIP)
|
|
73
|
+
|
|
74
|
+
CLI 创建的项目会默认:
|
|
75
|
+
|
|
76
|
+
- 生成 `.gitignore`
|
|
77
|
+
- 尝试自动执行 `git init`(如果本机已安装 Git)
|
|
78
|
+
|
|
79
|
+
打包时会**只包含未被 `.gitignore` 忽略**的文件,输出到 `dist/*.zip`:
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
npm run package
|
|
83
|
+
```
|
|
84
|
+
|
|
72
85
|
### 本地开发(维护此 CLI)
|
|
73
86
|
|
|
74
87
|
```sh
|
package/dist/scaffold.js
CHANGED
|
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.scaffoldProject = scaffoldProject;
|
|
37
37
|
const fs = __importStar(require("node:fs/promises"));
|
|
38
38
|
const path = __importStar(require("node:path"));
|
|
39
|
+
const node_child_process_1 = require("node:child_process");
|
|
39
40
|
const templates_1 = require("./templates");
|
|
40
41
|
async function pathExists(p) {
|
|
41
42
|
try {
|
|
@@ -53,6 +54,27 @@ async function writeFileEnsuringDir(filePath, content) {
|
|
|
53
54
|
await ensureDir(path.dirname(filePath));
|
|
54
55
|
await fs.writeFile(filePath, content, "utf8");
|
|
55
56
|
}
|
|
57
|
+
function tryInitGitRepo(targetRoot) {
|
|
58
|
+
try {
|
|
59
|
+
const version = (0, node_child_process_1.spawnSync)("git", ["--version"], {
|
|
60
|
+
cwd: targetRoot,
|
|
61
|
+
stdio: "ignore",
|
|
62
|
+
shell: false,
|
|
63
|
+
});
|
|
64
|
+
if (version.status !== 0)
|
|
65
|
+
return;
|
|
66
|
+
const init = (0, node_child_process_1.spawnSync)("git", ["init"], {
|
|
67
|
+
cwd: targetRoot,
|
|
68
|
+
stdio: "ignore",
|
|
69
|
+
shell: false,
|
|
70
|
+
});
|
|
71
|
+
if (init.status !== 0)
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// best-effort: do not block scaffolding if git isn't available
|
|
76
|
+
}
|
|
77
|
+
}
|
|
56
78
|
async function scaffoldProject(options) {
|
|
57
79
|
const targetRoot = path.resolve(process.cwd(), options.projectName);
|
|
58
80
|
if (await pathExists(targetRoot)) {
|
|
@@ -64,6 +86,7 @@ async function scaffoldProject(options) {
|
|
|
64
86
|
const absPath = path.join(targetRoot, file.relativePath);
|
|
65
87
|
await writeFileEnsuringDir(absPath, file.content);
|
|
66
88
|
}
|
|
89
|
+
tryInitGitRepo(targetRoot);
|
|
67
90
|
// eslint-disable-next-line no-console
|
|
68
91
|
console.log(`\nCreated project at: ${targetRoot}`);
|
|
69
92
|
}
|
package/dist/templates.js
CHANGED
|
@@ -1,11 +1,219 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getProjectFiles = getProjectFiles;
|
|
4
|
+
function gitignoreContent(language) {
|
|
5
|
+
const common = [
|
|
6
|
+
"# OS",
|
|
7
|
+
".DS_Store",
|
|
8
|
+
"Thumbs.db",
|
|
9
|
+
"Desktop.ini",
|
|
10
|
+
"",
|
|
11
|
+
"# Editors",
|
|
12
|
+
".vscode/",
|
|
13
|
+
".idea/",
|
|
14
|
+
"*.swp",
|
|
15
|
+
"",
|
|
16
|
+
"# Env",
|
|
17
|
+
".env",
|
|
18
|
+
".env.*",
|
|
19
|
+
"",
|
|
20
|
+
"# Logs",
|
|
21
|
+
"*.log",
|
|
22
|
+
"",
|
|
23
|
+
"# Builds / artifacts",
|
|
24
|
+
"dist/",
|
|
25
|
+
"build/",
|
|
26
|
+
"*.zip",
|
|
27
|
+
"",
|
|
28
|
+
];
|
|
29
|
+
const node = [
|
|
30
|
+
"# Node",
|
|
31
|
+
"node_modules/",
|
|
32
|
+
"npm-debug.log*",
|
|
33
|
+
"yarn-debug.log*",
|
|
34
|
+
"yarn-error.log*",
|
|
35
|
+
"pnpm-debug.log*",
|
|
36
|
+
"",
|
|
37
|
+
];
|
|
38
|
+
const python = [
|
|
39
|
+
"# Python",
|
|
40
|
+
"__pycache__/",
|
|
41
|
+
"*.py[cod]",
|
|
42
|
+
"*.pyd",
|
|
43
|
+
".Python",
|
|
44
|
+
".venv/",
|
|
45
|
+
"venv/",
|
|
46
|
+
"ENV/",
|
|
47
|
+
"env/",
|
|
48
|
+
".pytest_cache/",
|
|
49
|
+
".mypy_cache/",
|
|
50
|
+
".ruff_cache/",
|
|
51
|
+
"*.egg-info/",
|
|
52
|
+
"",
|
|
53
|
+
];
|
|
54
|
+
return (common.join("\n") +
|
|
55
|
+
(language === "typescript" ? node.join("\n") : python.join("\n")));
|
|
56
|
+
}
|
|
57
|
+
function packagingScriptMjs() {
|
|
58
|
+
// Keep this script dependency-free and avoid nested template literals.
|
|
59
|
+
return [
|
|
60
|
+
'import { spawnSync } from "node:child_process";',
|
|
61
|
+
'import * as fsp from "node:fs/promises";',
|
|
62
|
+
'import * as path from "node:path";',
|
|
63
|
+
"",
|
|
64
|
+
"function run(cmd, args, opts = {}) {",
|
|
65
|
+
" return spawnSync(cmd, args, {",
|
|
66
|
+
' stdio: ["ignore", "pipe", "pipe"],',
|
|
67
|
+
' encoding: "utf8",',
|
|
68
|
+
" shell: false,",
|
|
69
|
+
" ...opts,",
|
|
70
|
+
" });",
|
|
71
|
+
"}",
|
|
72
|
+
"",
|
|
73
|
+
"function hasCommand(cmd) {",
|
|
74
|
+
' if (process.platform === "win32") {',
|
|
75
|
+
' const r = run("where", [cmd]);',
|
|
76
|
+
" return r.status === 0;",
|
|
77
|
+
" }",
|
|
78
|
+
' const check = "command -v " + cmd + " >/dev/null 2>&1";',
|
|
79
|
+
' const r = run("sh", ["-lc", check]);',
|
|
80
|
+
" return r.status === 0;",
|
|
81
|
+
"}",
|
|
82
|
+
"",
|
|
83
|
+
"function escapePsSingleQuotes(value) {",
|
|
84
|
+
' return String(value || "").replace(/\'/g, "\'\'");',
|
|
85
|
+
"}",
|
|
86
|
+
"",
|
|
87
|
+
"function gitFileList() {",
|
|
88
|
+
' const r = run("git", [',
|
|
89
|
+
' "ls-files",',
|
|
90
|
+
' "-z",',
|
|
91
|
+
' "--cached",',
|
|
92
|
+
' "--others",',
|
|
93
|
+
' "--exclude-standard",',
|
|
94
|
+
" ]);",
|
|
95
|
+
"",
|
|
96
|
+
" if (r.status !== 0) {",
|
|
97
|
+
' const err = (r.stderr || r.stdout || "").trim();',
|
|
98
|
+
" throw new Error(",
|
|
99
|
+
' "git is required for packaging (and to respect .gitignore).\\n" + err,',
|
|
100
|
+
" );",
|
|
101
|
+
" }",
|
|
102
|
+
"",
|
|
103
|
+
' const raw = r.stdout || "";',
|
|
104
|
+
' return raw.split("\\u0000").filter(Boolean);',
|
|
105
|
+
"}",
|
|
106
|
+
"",
|
|
107
|
+
"async function ensureDir(p) {",
|
|
108
|
+
" await fsp.mkdir(p, { recursive: true });",
|
|
109
|
+
"}",
|
|
110
|
+
"",
|
|
111
|
+
"async function writeTempFileList(filePaths) {",
|
|
112
|
+
' const tmpDir = path.join(process.cwd(), ".glosc-tmp");',
|
|
113
|
+
" await ensureDir(tmpDir);",
|
|
114
|
+
' const listPath = path.join(tmpDir, "zip-file-list.txt");',
|
|
115
|
+
' await fsp.writeFile(listPath, filePaths.join("\\n"), "utf8");',
|
|
116
|
+
" return listPath;",
|
|
117
|
+
"}",
|
|
118
|
+
"",
|
|
119
|
+
"function timestamp() {",
|
|
120
|
+
" const d = new Date();",
|
|
121
|
+
' const pad = (n) => String(n).padStart(2, "0");',
|
|
122
|
+
" return (",
|
|
123
|
+
" String(d.getFullYear()) +",
|
|
124
|
+
" pad(d.getMonth() + 1) +",
|
|
125
|
+
" pad(d.getDate()) +",
|
|
126
|
+
' "-" +',
|
|
127
|
+
" pad(d.getHours()) +",
|
|
128
|
+
" pad(d.getMinutes()) +",
|
|
129
|
+
" pad(d.getSeconds())",
|
|
130
|
+
" );",
|
|
131
|
+
"}",
|
|
132
|
+
"",
|
|
133
|
+
"async function main() {",
|
|
134
|
+
" const files = gitFileList();",
|
|
135
|
+
" if (files.length === 0) {",
|
|
136
|
+
" throw new Error(",
|
|
137
|
+
' "No files found to package. If you just created the project, make sure git is initialized and .gitignore exists.",',
|
|
138
|
+
" );",
|
|
139
|
+
" }",
|
|
140
|
+
"",
|
|
141
|
+
' const outDir = path.join(process.cwd(), "dist");',
|
|
142
|
+
" await ensureDir(outDir);",
|
|
143
|
+
' const outZip = path.join(outDir, "glosc-package-" + timestamp() + ".zip");',
|
|
144
|
+
"",
|
|
145
|
+
" const listPath = await writeTempFileList(files);",
|
|
146
|
+
"",
|
|
147
|
+
" try {",
|
|
148
|
+
' if (hasCommand("powershell")) {',
|
|
149
|
+
" const listEsc = escapePsSingleQuotes(listPath);",
|
|
150
|
+
" const outEsc = escapePsSingleQuotes(outZip);",
|
|
151
|
+
" const psCommand = [",
|
|
152
|
+
' "$paths = Get-Content -LiteralPath \\\'" + listEsc + "\\\';",',
|
|
153
|
+
' "Compress-Archive -LiteralPath $paths -DestinationPath \\\'" + outEsc + "\\\' -Force -CompressionLevel Optimal;",',
|
|
154
|
+
' ].join(" ");',
|
|
155
|
+
" const psArgs = [",
|
|
156
|
+
' "-NoProfile",',
|
|
157
|
+
' "-ExecutionPolicy",',
|
|
158
|
+
' "Bypass",',
|
|
159
|
+
' "-Command",',
|
|
160
|
+
" psCommand,",
|
|
161
|
+
" ];",
|
|
162
|
+
' const r = run("powershell", psArgs, { cwd: process.cwd() });',
|
|
163
|
+
' if (r.status !== 0) throw new Error((r.stderr || r.stdout || "").trim());',
|
|
164
|
+
' } else if (hasCommand("pwsh")) {',
|
|
165
|
+
" const listEsc = escapePsSingleQuotes(listPath);",
|
|
166
|
+
" const outEsc = escapePsSingleQuotes(outZip);",
|
|
167
|
+
" const psCommand = [",
|
|
168
|
+
' "$paths = Get-Content -LiteralPath \\\'" + listEsc + "\\\';",',
|
|
169
|
+
' "Compress-Archive -LiteralPath $paths -DestinationPath \\\'" + outEsc + "\\\' -Force -CompressionLevel Optimal;",',
|
|
170
|
+
' ].join(" ");',
|
|
171
|
+
' const psArgs = ["-NoProfile", "-Command", psCommand];',
|
|
172
|
+
' const r = run("pwsh", psArgs, { cwd: process.cwd() });',
|
|
173
|
+
' if (r.status !== 0) throw new Error((r.stderr || r.stdout || "").trim());',
|
|
174
|
+
' } else if (hasCommand("zip")) {',
|
|
175
|
+
' const r = spawnSync("zip", ["-q", "-r", outZip, "-@"], {',
|
|
176
|
+
" cwd: process.cwd(),",
|
|
177
|
+
' input: files.join("\\n"),',
|
|
178
|
+
' stdio: ["pipe", "pipe", "pipe"],',
|
|
179
|
+
' encoding: "utf8",',
|
|
180
|
+
" });",
|
|
181
|
+
' if (r.status !== 0) throw new Error((r.stderr || r.stdout || "").trim());',
|
|
182
|
+
" } else {",
|
|
183
|
+
" throw new Error(",
|
|
184
|
+
' "No zip backend found. Install Git + PowerShell (Windows) or `zip` (macOS/Linux), then rerun.",',
|
|
185
|
+
" );",
|
|
186
|
+
" }",
|
|
187
|
+
" } finally {",
|
|
188
|
+
" try {",
|
|
189
|
+
" await fsp.rm(path.dirname(listPath), { recursive: true, force: true });",
|
|
190
|
+
" } catch {",
|
|
191
|
+
" // ignore",
|
|
192
|
+
" }",
|
|
193
|
+
" }",
|
|
194
|
+
"",
|
|
195
|
+
' console.log("Created: " + outZip);',
|
|
196
|
+
"}",
|
|
197
|
+
"",
|
|
198
|
+
"main().catch((err) => {",
|
|
199
|
+
" console.error(String(err?.message || err));",
|
|
200
|
+
" process.exit(1);",
|
|
201
|
+
"});",
|
|
202
|
+
].join("\n");
|
|
203
|
+
}
|
|
4
204
|
function entryPath(options) {
|
|
5
205
|
return options.language === "python"
|
|
6
206
|
? options.mainFileName
|
|
7
207
|
: `src/${options.mainFileName}`;
|
|
8
208
|
}
|
|
209
|
+
function mcpRuntime(options) {
|
|
210
|
+
return options.language === "python" ? "python" : "node";
|
|
211
|
+
}
|
|
212
|
+
function mcpEntry(options) {
|
|
213
|
+
if (options.language === "python")
|
|
214
|
+
return options.mainFileName;
|
|
215
|
+
return `dist/${options.mainFileName.replace(/\.ts$/i, ".js")}`;
|
|
216
|
+
}
|
|
9
217
|
function escapeYamlString(value) {
|
|
10
218
|
const s = String(value ?? "");
|
|
11
219
|
const escaped = s.replace(/"/g, '\\"');
|
|
@@ -23,16 +231,36 @@ function projectReadme(options) {
|
|
|
23
231
|
const runSection = language === "python"
|
|
24
232
|
? `## Run (Python)\n\n\n\n1) Install deps\n\n\n\n\`\`\`sh\npython -m pip install -r requirements.txt\n\`\`\`\n\n\n\n2) Run the MCP server (stdio)\n\n\n\n\`\`\`sh\npython ${entry}\n\`\`\`\n\n\n\nThis server speaks MCP over stdio. Connect using an MCP client (e.g. an editor integration).\n`
|
|
25
233
|
: `## Run (TypeScript)\n\n\n\n1) Install deps\n\n\n\n\`\`\`sh\nnpm install\n\`\`\`\n\n\n\n2) Build\n\n\n\n\`\`\`sh\nnpm run build\n\`\`\`\n\n\n\n3) Run the MCP server (stdio)\n\n\n\n\`\`\`sh\nnpm start\n\`\`\`\n\n\n\nThis server speaks MCP over stdio. Connect using an MCP client (e.g. an editor integration).\n`;
|
|
26
|
-
return `# ${projectName}\n\n${description || ""}\n\n## Author\n\n${author || ""}\n\n## Language\n\n${langLabel}\n\n## Entry\n\n- ${entry}\n\n## MCP Tools\n\n- get_current_time: Returns the current time (UTC, ISO 8601)\n\n${runSection}\n\n## Config\n\n- config.yml\n`;
|
|
234
|
+
return `# ${projectName}\n\n${description || ""}\n\n## Author\n\n${author || ""}\n\n## Language\n\n${langLabel}\n\n## Entry\n\n- ${entry}\n\n## MCP Tools\n\n- get_current_time: Returns the current time (UTC, ISO 8601)\n\n${runSection}\n\n## Config\n\n- config.yml (see: mcp.runtime / mcp.entry)\n`;
|
|
235
|
+
}
|
|
236
|
+
function packagingPackageJson(options) {
|
|
237
|
+
const { projectName, description, author } = options;
|
|
238
|
+
const pkg = {
|
|
239
|
+
name: projectName,
|
|
240
|
+
version: "0.1.0",
|
|
241
|
+
description: description || "",
|
|
242
|
+
author: author || "",
|
|
243
|
+
private: true,
|
|
244
|
+
scripts: {
|
|
245
|
+
package: "node scripts/package.mjs",
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
return JSON.stringify(pkg, null, 2) + "\n";
|
|
27
249
|
}
|
|
28
250
|
function configYml(options) {
|
|
29
251
|
const { projectName, description, author, language, mainFileName } = options;
|
|
30
252
|
return [
|
|
31
253
|
`name: ${escapeYamlString(projectName)}`,
|
|
32
254
|
`description: ${escapeYamlString(description)}`,
|
|
33
|
-
`
|
|
255
|
+
`icon: ${escapeYamlString("")}`,
|
|
34
256
|
`language: ${escapeYamlString(language)}`,
|
|
35
|
-
`
|
|
257
|
+
`author: ${escapeYamlString(author)}`,
|
|
258
|
+
`mcp:`,
|
|
259
|
+
` runtime: ${escapeYamlString(mcpRuntime(options))}`,
|
|
260
|
+
` entry: ${escapeYamlString(mcpEntry(options))}`,
|
|
261
|
+
` cwd: ${escapeYamlString(".")}`,
|
|
262
|
+
` env: {}`,
|
|
263
|
+
` args: []`,
|
|
36
264
|
"",
|
|
37
265
|
].join("\n");
|
|
38
266
|
}
|
|
@@ -66,6 +294,7 @@ function nodePackageJson(options) {
|
|
|
66
294
|
scripts: {
|
|
67
295
|
build: "tsc -p .",
|
|
68
296
|
start: `node ${distEntry}`,
|
|
297
|
+
package: "node scripts/package.mjs",
|
|
69
298
|
},
|
|
70
299
|
dependencies: {
|
|
71
300
|
"@modelcontextprotocol/sdk": "^1.24.3",
|
|
@@ -100,6 +329,14 @@ function tsMain({ projectName }) {
|
|
|
100
329
|
}
|
|
101
330
|
function getProjectFiles(options) {
|
|
102
331
|
const files = [];
|
|
332
|
+
files.push({
|
|
333
|
+
relativePath: ".gitignore",
|
|
334
|
+
content: gitignoreContent(options.language),
|
|
335
|
+
});
|
|
336
|
+
files.push({
|
|
337
|
+
relativePath: "scripts/package.mjs",
|
|
338
|
+
content: packagingScriptMjs(),
|
|
339
|
+
});
|
|
103
340
|
files.push({ relativePath: "config.yml", content: configYml(options) });
|
|
104
341
|
if (options.readme) {
|
|
105
342
|
files.push({
|
|
@@ -126,6 +363,10 @@ function getProjectFiles(options) {
|
|
|
126
363
|
relativePath: "pyproject.toml",
|
|
127
364
|
content: pythonPyproject(options),
|
|
128
365
|
});
|
|
366
|
+
files.push({
|
|
367
|
+
relativePath: "package.json",
|
|
368
|
+
content: packagingPackageJson(options),
|
|
369
|
+
});
|
|
129
370
|
}
|
|
130
371
|
if (options.language === "typescript") {
|
|
131
372
|
files.push({
|
package/package.json
CHANGED
package/src/scaffold.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
3
4
|
import { getProjectFiles, type ProjectOptions } from "./templates";
|
|
4
5
|
|
|
5
6
|
async function pathExists(p: string): Promise<boolean> {
|
|
@@ -23,6 +24,28 @@ async function writeFileEnsuringDir(
|
|
|
23
24
|
await fs.writeFile(filePath, content, "utf8");
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
function tryInitGitRepo(targetRoot: string): void {
|
|
28
|
+
try {
|
|
29
|
+
const version = spawnSync("git", ["--version"], {
|
|
30
|
+
cwd: targetRoot,
|
|
31
|
+
stdio: "ignore",
|
|
32
|
+
shell: false,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (version.status !== 0) return;
|
|
36
|
+
|
|
37
|
+
const init = spawnSync("git", ["init"], {
|
|
38
|
+
cwd: targetRoot,
|
|
39
|
+
stdio: "ignore",
|
|
40
|
+
shell: false,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (init.status !== 0) return;
|
|
44
|
+
} catch {
|
|
45
|
+
// best-effort: do not block scaffolding if git isn't available
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
26
49
|
export async function scaffoldProject(options: ProjectOptions): Promise<void> {
|
|
27
50
|
const targetRoot = path.resolve(process.cwd(), options.projectName);
|
|
28
51
|
|
|
@@ -39,6 +62,8 @@ export async function scaffoldProject(options: ProjectOptions): Promise<void> {
|
|
|
39
62
|
await writeFileEnsuringDir(absPath, file.content);
|
|
40
63
|
}
|
|
41
64
|
|
|
65
|
+
tryInitGitRepo(targetRoot);
|
|
66
|
+
|
|
42
67
|
// eslint-disable-next-line no-console
|
|
43
68
|
console.log(`\nCreated project at: ${targetRoot}`);
|
|
44
69
|
}
|
package/src/templates.ts
CHANGED
|
@@ -15,12 +15,228 @@ export type ProjectFile = {
|
|
|
15
15
|
content: string;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
function gitignoreContent(language: Language): string {
|
|
19
|
+
const common = [
|
|
20
|
+
"# OS",
|
|
21
|
+
".DS_Store",
|
|
22
|
+
"Thumbs.db",
|
|
23
|
+
"Desktop.ini",
|
|
24
|
+
"",
|
|
25
|
+
"# Editors",
|
|
26
|
+
".vscode/",
|
|
27
|
+
".idea/",
|
|
28
|
+
"*.swp",
|
|
29
|
+
"",
|
|
30
|
+
"# Env",
|
|
31
|
+
".env",
|
|
32
|
+
".env.*",
|
|
33
|
+
"",
|
|
34
|
+
"# Logs",
|
|
35
|
+
"*.log",
|
|
36
|
+
"",
|
|
37
|
+
"# Builds / artifacts",
|
|
38
|
+
"dist/",
|
|
39
|
+
"build/",
|
|
40
|
+
"*.zip",
|
|
41
|
+
"",
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
const node = [
|
|
45
|
+
"# Node",
|
|
46
|
+
"node_modules/",
|
|
47
|
+
"npm-debug.log*",
|
|
48
|
+
"yarn-debug.log*",
|
|
49
|
+
"yarn-error.log*",
|
|
50
|
+
"pnpm-debug.log*",
|
|
51
|
+
"",
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
const python = [
|
|
55
|
+
"# Python",
|
|
56
|
+
"__pycache__/",
|
|
57
|
+
"*.py[cod]",
|
|
58
|
+
"*.pyd",
|
|
59
|
+
".Python",
|
|
60
|
+
".venv/",
|
|
61
|
+
"venv/",
|
|
62
|
+
"ENV/",
|
|
63
|
+
"env/",
|
|
64
|
+
".pytest_cache/",
|
|
65
|
+
".mypy_cache/",
|
|
66
|
+
".ruff_cache/",
|
|
67
|
+
"*.egg-info/",
|
|
68
|
+
"",
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
common.join("\n") +
|
|
73
|
+
(language === "typescript" ? node.join("\n") : python.join("\n"))
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function packagingScriptMjs(): string {
|
|
78
|
+
// Keep this script dependency-free and avoid nested template literals.
|
|
79
|
+
return [
|
|
80
|
+
'import { spawnSync } from "node:child_process";',
|
|
81
|
+
'import * as fsp from "node:fs/promises";',
|
|
82
|
+
'import * as path from "node:path";',
|
|
83
|
+
"",
|
|
84
|
+
"function run(cmd, args, opts = {}) {",
|
|
85
|
+
" return spawnSync(cmd, args, {",
|
|
86
|
+
' stdio: ["ignore", "pipe", "pipe"],',
|
|
87
|
+
' encoding: "utf8",',
|
|
88
|
+
" shell: false,",
|
|
89
|
+
" ...opts,",
|
|
90
|
+
" });",
|
|
91
|
+
"}",
|
|
92
|
+
"",
|
|
93
|
+
"function hasCommand(cmd) {",
|
|
94
|
+
' if (process.platform === "win32") {',
|
|
95
|
+
' const r = run("where", [cmd]);',
|
|
96
|
+
" return r.status === 0;",
|
|
97
|
+
" }",
|
|
98
|
+
' const check = "command -v " + cmd + " >/dev/null 2>&1";',
|
|
99
|
+
' const r = run("sh", ["-lc", check]);',
|
|
100
|
+
" return r.status === 0;",
|
|
101
|
+
"}",
|
|
102
|
+
"",
|
|
103
|
+
"function escapePsSingleQuotes(value) {",
|
|
104
|
+
' return String(value || "").replace(/\'/g, "\'\'");',
|
|
105
|
+
"}",
|
|
106
|
+
"",
|
|
107
|
+
"function gitFileList() {",
|
|
108
|
+
' const r = run("git", [',
|
|
109
|
+
' "ls-files",',
|
|
110
|
+
' "-z",',
|
|
111
|
+
' "--cached",',
|
|
112
|
+
' "--others",',
|
|
113
|
+
' "--exclude-standard",',
|
|
114
|
+
" ]);",
|
|
115
|
+
"",
|
|
116
|
+
" if (r.status !== 0) {",
|
|
117
|
+
' const err = (r.stderr || r.stdout || "").trim();',
|
|
118
|
+
" throw new Error(",
|
|
119
|
+
' "git is required for packaging (and to respect .gitignore).\\n" + err,',
|
|
120
|
+
" );",
|
|
121
|
+
" }",
|
|
122
|
+
"",
|
|
123
|
+
' const raw = r.stdout || "";',
|
|
124
|
+
' return raw.split("\\u0000").filter(Boolean);',
|
|
125
|
+
"}",
|
|
126
|
+
"",
|
|
127
|
+
"async function ensureDir(p) {",
|
|
128
|
+
" await fsp.mkdir(p, { recursive: true });",
|
|
129
|
+
"}",
|
|
130
|
+
"",
|
|
131
|
+
"async function writeTempFileList(filePaths) {",
|
|
132
|
+
' const tmpDir = path.join(process.cwd(), ".glosc-tmp");',
|
|
133
|
+
" await ensureDir(tmpDir);",
|
|
134
|
+
' const listPath = path.join(tmpDir, "zip-file-list.txt");',
|
|
135
|
+
' await fsp.writeFile(listPath, filePaths.join("\\n"), "utf8");',
|
|
136
|
+
" return listPath;",
|
|
137
|
+
"}",
|
|
138
|
+
"",
|
|
139
|
+
"function timestamp() {",
|
|
140
|
+
" const d = new Date();",
|
|
141
|
+
' const pad = (n) => String(n).padStart(2, "0");',
|
|
142
|
+
" return (",
|
|
143
|
+
" String(d.getFullYear()) +",
|
|
144
|
+
" pad(d.getMonth() + 1) +",
|
|
145
|
+
" pad(d.getDate()) +",
|
|
146
|
+
' "-" +',
|
|
147
|
+
" pad(d.getHours()) +",
|
|
148
|
+
" pad(d.getMinutes()) +",
|
|
149
|
+
" pad(d.getSeconds())",
|
|
150
|
+
" );",
|
|
151
|
+
"}",
|
|
152
|
+
"",
|
|
153
|
+
"async function main() {",
|
|
154
|
+
" const files = gitFileList();",
|
|
155
|
+
" if (files.length === 0) {",
|
|
156
|
+
" throw new Error(",
|
|
157
|
+
' "No files found to package. If you just created the project, make sure git is initialized and .gitignore exists.",',
|
|
158
|
+
" );",
|
|
159
|
+
" }",
|
|
160
|
+
"",
|
|
161
|
+
' const outDir = path.join(process.cwd(), "dist");',
|
|
162
|
+
" await ensureDir(outDir);",
|
|
163
|
+
' const outZip = path.join(outDir, "glosc-package-" + timestamp() + ".zip");',
|
|
164
|
+
"",
|
|
165
|
+
" const listPath = await writeTempFileList(files);",
|
|
166
|
+
"",
|
|
167
|
+
" try {",
|
|
168
|
+
' if (hasCommand("powershell")) {',
|
|
169
|
+
" const listEsc = escapePsSingleQuotes(listPath);",
|
|
170
|
+
" const outEsc = escapePsSingleQuotes(outZip);",
|
|
171
|
+
" const psCommand = [",
|
|
172
|
+
' "$paths = Get-Content -LiteralPath \\\'" + listEsc + "\\\';",',
|
|
173
|
+
' "Compress-Archive -LiteralPath $paths -DestinationPath \\\'" + outEsc + "\\\' -Force -CompressionLevel Optimal;",',
|
|
174
|
+
' ].join(" ");',
|
|
175
|
+
" const psArgs = [",
|
|
176
|
+
' "-NoProfile",',
|
|
177
|
+
' "-ExecutionPolicy",',
|
|
178
|
+
' "Bypass",',
|
|
179
|
+
' "-Command",',
|
|
180
|
+
" psCommand,",
|
|
181
|
+
" ];",
|
|
182
|
+
' const r = run("powershell", psArgs, { cwd: process.cwd() });',
|
|
183
|
+
' if (r.status !== 0) throw new Error((r.stderr || r.stdout || "").trim());',
|
|
184
|
+
' } else if (hasCommand("pwsh")) {',
|
|
185
|
+
" const listEsc = escapePsSingleQuotes(listPath);",
|
|
186
|
+
" const outEsc = escapePsSingleQuotes(outZip);",
|
|
187
|
+
" const psCommand = [",
|
|
188
|
+
' "$paths = Get-Content -LiteralPath \\\'" + listEsc + "\\\';",',
|
|
189
|
+
' "Compress-Archive -LiteralPath $paths -DestinationPath \\\'" + outEsc + "\\\' -Force -CompressionLevel Optimal;",',
|
|
190
|
+
' ].join(" ");',
|
|
191
|
+
' const psArgs = ["-NoProfile", "-Command", psCommand];',
|
|
192
|
+
' const r = run("pwsh", psArgs, { cwd: process.cwd() });',
|
|
193
|
+
' if (r.status !== 0) throw new Error((r.stderr || r.stdout || "").trim());',
|
|
194
|
+
' } else if (hasCommand("zip")) {',
|
|
195
|
+
' const r = spawnSync("zip", ["-q", "-r", outZip, "-@"], {',
|
|
196
|
+
" cwd: process.cwd(),",
|
|
197
|
+
' input: files.join("\\n"),',
|
|
198
|
+
' stdio: ["pipe", "pipe", "pipe"],',
|
|
199
|
+
' encoding: "utf8",',
|
|
200
|
+
" });",
|
|
201
|
+
' if (r.status !== 0) throw new Error((r.stderr || r.stdout || "").trim());',
|
|
202
|
+
" } else {",
|
|
203
|
+
" throw new Error(",
|
|
204
|
+
' "No zip backend found. Install Git + PowerShell (Windows) or `zip` (macOS/Linux), then rerun.",',
|
|
205
|
+
" );",
|
|
206
|
+
" }",
|
|
207
|
+
" } finally {",
|
|
208
|
+
" try {",
|
|
209
|
+
" await fsp.rm(path.dirname(listPath), { recursive: true, force: true });",
|
|
210
|
+
" } catch {",
|
|
211
|
+
" // ignore",
|
|
212
|
+
" }",
|
|
213
|
+
" }",
|
|
214
|
+
"",
|
|
215
|
+
' console.log("Created: " + outZip);',
|
|
216
|
+
"}",
|
|
217
|
+
"",
|
|
218
|
+
"main().catch((err) => {",
|
|
219
|
+
" console.error(String(err?.message || err));",
|
|
220
|
+
" process.exit(1);",
|
|
221
|
+
"});",
|
|
222
|
+
].join("\n");
|
|
223
|
+
}
|
|
224
|
+
|
|
18
225
|
function entryPath(options: ProjectOptions): string {
|
|
19
226
|
return options.language === "python"
|
|
20
227
|
? options.mainFileName
|
|
21
228
|
: `src/${options.mainFileName}`;
|
|
22
229
|
}
|
|
23
230
|
|
|
231
|
+
function mcpRuntime(options: ProjectOptions): "python" | "node" {
|
|
232
|
+
return options.language === "python" ? "python" : "node";
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function mcpEntry(options: ProjectOptions): string {
|
|
236
|
+
if (options.language === "python") return options.mainFileName;
|
|
237
|
+
return `dist/${options.mainFileName.replace(/\.ts$/i, ".js")}`;
|
|
238
|
+
}
|
|
239
|
+
|
|
24
240
|
function escapeYamlString(value: unknown): string {
|
|
25
241
|
const s = String(value ?? "");
|
|
26
242
|
const escaped = s.replace(/"/g, '\\"');
|
|
@@ -46,7 +262,23 @@ function projectReadme(options: ProjectOptions): string {
|
|
|
46
262
|
|
|
47
263
|
return `# ${projectName}\n\n${description || ""}\n\n## Author\n\n${
|
|
48
264
|
author || ""
|
|
49
|
-
}\n\n## Language\n\n${langLabel}\n\n## Entry\n\n- ${entry}\n\n## MCP Tools\n\n- get_current_time: Returns the current time (UTC, ISO 8601)\n\n${runSection}\n\n## Config\n\n- config.yml\n`;
|
|
265
|
+
}\n\n## Language\n\n${langLabel}\n\n## Entry\n\n- ${entry}\n\n## MCP Tools\n\n- get_current_time: Returns the current time (UTC, ISO 8601)\n\n${runSection}\n\n## Config\n\n- config.yml (see: mcp.runtime / mcp.entry)\n`;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function packagingPackageJson(options: ProjectOptions): string {
|
|
269
|
+
const { projectName, description, author } = options;
|
|
270
|
+
const pkg: Record<string, unknown> = {
|
|
271
|
+
name: projectName,
|
|
272
|
+
version: "0.1.0",
|
|
273
|
+
description: description || "",
|
|
274
|
+
author: author || "",
|
|
275
|
+
private: true,
|
|
276
|
+
scripts: {
|
|
277
|
+
package: "node scripts/package.mjs",
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
return JSON.stringify(pkg, null, 2) + "\n";
|
|
50
282
|
}
|
|
51
283
|
|
|
52
284
|
function configYml(options: ProjectOptions): string {
|
|
@@ -55,9 +287,15 @@ function configYml(options: ProjectOptions): string {
|
|
|
55
287
|
return [
|
|
56
288
|
`name: ${escapeYamlString(projectName)}`,
|
|
57
289
|
`description: ${escapeYamlString(description)}`,
|
|
58
|
-
`
|
|
290
|
+
`icon: ${escapeYamlString("")}`,
|
|
59
291
|
`language: ${escapeYamlString(language)}`,
|
|
60
|
-
`
|
|
292
|
+
`author: ${escapeYamlString(author)}`,
|
|
293
|
+
`mcp:`,
|
|
294
|
+
` runtime: ${escapeYamlString(mcpRuntime(options))}`,
|
|
295
|
+
` entry: ${escapeYamlString(mcpEntry(options))}`,
|
|
296
|
+
` cwd: ${escapeYamlString(".")}`,
|
|
297
|
+
` env: {}`,
|
|
298
|
+
` args: []`,
|
|
61
299
|
"",
|
|
62
300
|
].join("\n");
|
|
63
301
|
}
|
|
@@ -105,6 +343,7 @@ function nodePackageJson(options: ProjectOptions): string {
|
|
|
105
343
|
scripts: {
|
|
106
344
|
build: "tsc -p .",
|
|
107
345
|
start: `node ${distEntry}`,
|
|
346
|
+
package: "node scripts/package.mjs",
|
|
108
347
|
},
|
|
109
348
|
dependencies: {
|
|
110
349
|
"@modelcontextprotocol/sdk": "^1.24.3",
|
|
@@ -151,6 +390,14 @@ function tsMain({ projectName }: Pick<ProjectOptions, "projectName">): string {
|
|
|
151
390
|
export function getProjectFiles(options: ProjectOptions): ProjectFile[] {
|
|
152
391
|
const files: ProjectFile[] = [];
|
|
153
392
|
|
|
393
|
+
files.push({
|
|
394
|
+
relativePath: ".gitignore",
|
|
395
|
+
content: gitignoreContent(options.language),
|
|
396
|
+
});
|
|
397
|
+
files.push({
|
|
398
|
+
relativePath: "scripts/package.mjs",
|
|
399
|
+
content: packagingScriptMjs(),
|
|
400
|
+
});
|
|
154
401
|
files.push({ relativePath: "config.yml", content: configYml(options) });
|
|
155
402
|
|
|
156
403
|
if (options.readme) {
|
|
@@ -180,6 +427,10 @@ export function getProjectFiles(options: ProjectOptions): ProjectFile[] {
|
|
|
180
427
|
relativePath: "pyproject.toml",
|
|
181
428
|
content: pythonPyproject(options),
|
|
182
429
|
});
|
|
430
|
+
files.push({
|
|
431
|
+
relativePath: "package.json",
|
|
432
|
+
content: packagingPackageJson(options),
|
|
433
|
+
});
|
|
183
434
|
}
|
|
184
435
|
|
|
185
436
|
if (options.language === "typescript") {
|