create-glosc 0.3.0 → 0.4.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/dist/entrytest.zip +0 -0
- package/dist/entrytest2.zip +0 -0
- package/dist/entrytest3.zip +0 -0
- package/dist/entrytest4.zip +0 -0
- package/dist/entrytest5.zip +0 -0
- package/dist/templates.js +189 -59
- package/package.json +1 -1
- package/src/templates.ts +190 -59
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/templates.js
CHANGED
|
@@ -58,6 +58,7 @@ function packagingScriptMjs() {
|
|
|
58
58
|
// Keep this script dependency-free and avoid nested template literals.
|
|
59
59
|
return [
|
|
60
60
|
'import { spawnSync } from "node:child_process";',
|
|
61
|
+
'import * as fs from "node:fs";',
|
|
61
62
|
'import * as fsp from "node:fs/promises";',
|
|
62
63
|
'import * as path from "node:path";',
|
|
63
64
|
"",
|
|
@@ -70,18 +71,61 @@ function packagingScriptMjs() {
|
|
|
70
71
|
" });",
|
|
71
72
|
"}",
|
|
72
73
|
"",
|
|
73
|
-
"function
|
|
74
|
-
|
|
75
|
-
'
|
|
76
|
-
|
|
74
|
+
"function runInherit(cmd, args, opts = {}) {",
|
|
75
|
+
" return spawnSync(cmd, args, {",
|
|
76
|
+
' stdio: "inherit",',
|
|
77
|
+
' encoding: "utf8",',
|
|
78
|
+
" shell: false,",
|
|
79
|
+
" ...opts,",
|
|
80
|
+
" });",
|
|
81
|
+
"}",
|
|
82
|
+
"",
|
|
83
|
+
"function normalizeRelPath(p) {",
|
|
84
|
+
' return String(p || "").replace(/\\\\/g, "/");',
|
|
85
|
+
"}",
|
|
86
|
+
"",
|
|
87
|
+
"function readJsonIfExists(filePath) {",
|
|
88
|
+
" try {",
|
|
89
|
+
" if (!fs.existsSync(filePath)) return undefined;",
|
|
90
|
+
' const raw = fs.readFileSync(filePath, "utf8");',
|
|
91
|
+
" return JSON.parse(raw);",
|
|
92
|
+
" } catch {",
|
|
93
|
+
" return undefined;",
|
|
77
94
|
" }",
|
|
78
|
-
' const check = "command -v " + cmd + " >/dev/null 2>&1";',
|
|
79
|
-
' const r = run("sh", ["-lc", check]);',
|
|
80
|
-
" return r.status === 0;",
|
|
81
95
|
"}",
|
|
82
96
|
"",
|
|
83
|
-
"function
|
|
84
|
-
'
|
|
97
|
+
"function detectTypeScriptProject() {",
|
|
98
|
+
' if (fs.existsSync("tsconfig.json")) return true;',
|
|
99
|
+
' const pkg = readJsonIfExists("package.json");',
|
|
100
|
+
' const buildScript = String(pkg?.scripts?.build || "");',
|
|
101
|
+
' if (buildScript.includes("tsc")) return true;',
|
|
102
|
+
" if (pkg?.devDependencies?.typescript) return true;",
|
|
103
|
+
" return false;",
|
|
104
|
+
"}",
|
|
105
|
+
"",
|
|
106
|
+
"function detectDistEntry() {",
|
|
107
|
+
" // default requested: /dist/index.js",
|
|
108
|
+
' const defaultEntry = "dist/index.js";',
|
|
109
|
+
' const pkg = readJsonIfExists("package.json");',
|
|
110
|
+
' const start = String(pkg?.scripts?.start || "").trim();',
|
|
111
|
+
" const m = /^node\\s+(.+)$/.exec(start);",
|
|
112
|
+
' const inferred = m && m[1] ? String(m[1]).trim() : "";',
|
|
113
|
+
" const entry = inferred || defaultEntry;",
|
|
114
|
+
" return normalizeRelPath(entry);",
|
|
115
|
+
"}",
|
|
116
|
+
"",
|
|
117
|
+
"function ensureGitRepoRoot() {",
|
|
118
|
+
' const r = run("git", ["rev-parse", "--show-toplevel"], { cwd: process.cwd() });',
|
|
119
|
+
" if (r.status !== 0) {",
|
|
120
|
+
' const err = (r.stderr || r.stdout || "").trim();',
|
|
121
|
+
" throw new Error(",
|
|
122
|
+
' "git repo is required for packaging (to respect .gitignore).\\n" + err,',
|
|
123
|
+
" );",
|
|
124
|
+
" }",
|
|
125
|
+
' const top = String(r.stdout || "").trim();',
|
|
126
|
+
' if (!top) throw new Error("Failed to resolve git repo root");',
|
|
127
|
+
" process.chdir(top);",
|
|
128
|
+
" return top;",
|
|
85
129
|
"}",
|
|
86
130
|
"",
|
|
87
131
|
"function gitFileList() {",
|
|
@@ -101,21 +145,100 @@ function packagingScriptMjs() {
|
|
|
101
145
|
" }",
|
|
102
146
|
"",
|
|
103
147
|
' const raw = r.stdout || "";',
|
|
104
|
-
' return raw.split("\\u0000").filter(Boolean);',
|
|
148
|
+
' return raw.split("\\u0000").filter(Boolean).map(normalizeRelPath);',
|
|
149
|
+
"}",
|
|
150
|
+
"",
|
|
151
|
+
"function shouldExcludeFromPackage(relPath) {",
|
|
152
|
+
" const p = normalizeRelPath(relPath);",
|
|
153
|
+
" if (!p) return true;",
|
|
154
|
+
" // 1) scripts/package.mjs does not need to be packaged",
|
|
155
|
+
' if (p === "scripts/package.mjs") return true;',
|
|
156
|
+
" // temp files created by this script",
|
|
157
|
+
' if (p.startsWith(".glosc-tmp/")) return true;',
|
|
158
|
+
" return false;",
|
|
159
|
+
"}",
|
|
160
|
+
"",
|
|
161
|
+
"function uniqStable(list) {",
|
|
162
|
+
" const seen = new Set();",
|
|
163
|
+
" const out = [];",
|
|
164
|
+
" for (const item of list) {",
|
|
165
|
+
" if (seen.has(item)) continue;",
|
|
166
|
+
" seen.add(item);",
|
|
167
|
+
" out.push(item);",
|
|
168
|
+
" }",
|
|
169
|
+
" return out;",
|
|
105
170
|
"}",
|
|
106
171
|
"",
|
|
107
172
|
"async function ensureDir(p) {",
|
|
108
173
|
" await fsp.mkdir(p, { recursive: true });",
|
|
109
174
|
"}",
|
|
110
175
|
"",
|
|
111
|
-
"async function
|
|
176
|
+
"async function prepareTmpDir() {",
|
|
112
177
|
' const tmpDir = path.join(process.cwd(), ".glosc-tmp");',
|
|
178
|
+
" await fsp.rm(tmpDir, { recursive: true, force: true });",
|
|
113
179
|
" await ensureDir(tmpDir);",
|
|
114
|
-
|
|
115
|
-
|
|
180
|
+
" return tmpDir;",
|
|
181
|
+
"}",
|
|
182
|
+
"",
|
|
183
|
+
"async function writeNulSeparatedList(tmpDir, filePaths) {",
|
|
184
|
+
" // Use NUL-separated pathspec so we can pass it to git safely (no quoting issues).",
|
|
185
|
+
' const listPath = path.join(tmpDir, "pathspec.nul");',
|
|
186
|
+
" const normalized = filePaths.map(normalizeRelPath);",
|
|
187
|
+
' const buf = Buffer.from(normalized.join("\u0000") + "\u0000", "utf8");',
|
|
188
|
+
" await fsp.writeFile(listPath, buf);",
|
|
116
189
|
" return listPath;",
|
|
117
190
|
"}",
|
|
118
191
|
"",
|
|
192
|
+
"function gitArchiveZipFromWorkingTreeFiles(outZip, filePaths) {",
|
|
193
|
+
" // Create a temporary index so we can archive *tracked + untracked* files without",
|
|
194
|
+
" // touching the user's real index or requiring a commit.",
|
|
195
|
+
' const tmpIndex = path.join(process.cwd(), ".glosc-tmp", "index");',
|
|
196
|
+
" const env = { ...process.env, GIT_INDEX_FILE: tmpIndex };",
|
|
197
|
+
"",
|
|
198
|
+
" // Add files (force-add so ignored build output like dist/index.js can be included).",
|
|
199
|
+
" const add = run(",
|
|
200
|
+
' "git",',
|
|
201
|
+
" [",
|
|
202
|
+
' "add",',
|
|
203
|
+
' "-f",',
|
|
204
|
+
' "--pathspec-from-file=.glosc-tmp/pathspec.nul",',
|
|
205
|
+
' "--pathspec-file-nul",',
|
|
206
|
+
' "--",',
|
|
207
|
+
" ],",
|
|
208
|
+
" { cwd: process.cwd(), env },",
|
|
209
|
+
" );",
|
|
210
|
+
" if (add.status !== 0) {",
|
|
211
|
+
" // Fallback for older Git versions without --pathspec-from-file.",
|
|
212
|
+
" const chunkSize = 200;",
|
|
213
|
+
" for (let i = 0; i < filePaths.length; i += chunkSize) {",
|
|
214
|
+
" const chunk = filePaths.slice(i, i + chunkSize);",
|
|
215
|
+
' const r = run("git", ["add", "-f", "--", ...chunk], { cwd: process.cwd(), env });',
|
|
216
|
+
" if (r.status !== 0) {",
|
|
217
|
+
' const err = (r.stderr || r.stdout || "").trim();',
|
|
218
|
+
' throw new Error("git add (temp index) failed\\n" + err);',
|
|
219
|
+
" }",
|
|
220
|
+
" }",
|
|
221
|
+
" }",
|
|
222
|
+
"",
|
|
223
|
+
' const wt = run("git", ["write-tree"], { cwd: process.cwd(), env });',
|
|
224
|
+
" if (wt.status !== 0) {",
|
|
225
|
+
' const err = (wt.stderr || wt.stdout || "").trim();',
|
|
226
|
+
' throw new Error("git write-tree failed\\n" + err);',
|
|
227
|
+
" }",
|
|
228
|
+
' const tree = String(wt.stdout || "").trim();',
|
|
229
|
+
' if (!tree) throw new Error("git write-tree produced empty tree hash");',
|
|
230
|
+
"",
|
|
231
|
+
" const ar = run(",
|
|
232
|
+
' "git",',
|
|
233
|
+
' ["archive", "--format=zip", "--output", outZip, tree],',
|
|
234
|
+
" { cwd: process.cwd() },",
|
|
235
|
+
" );",
|
|
236
|
+
" if (ar.status !== 0) {",
|
|
237
|
+
' const err = (ar.stderr || ar.stdout || "").trim();',
|
|
238
|
+
' throw new Error("git archive failed\\n" + err);',
|
|
239
|
+
" }",
|
|
240
|
+
"}",
|
|
241
|
+
"",
|
|
119
242
|
"function timestamp() {",
|
|
120
243
|
" const d = new Date();",
|
|
121
244
|
' const pad = (n) => String(n).padStart(2, "0");',
|
|
@@ -131,7 +254,33 @@ function packagingScriptMjs() {
|
|
|
131
254
|
"}",
|
|
132
255
|
"",
|
|
133
256
|
"async function main() {",
|
|
134
|
-
"
|
|
257
|
+
" ensureGitRepoRoot();",
|
|
258
|
+
" const isTs = detectTypeScriptProject();",
|
|
259
|
+
" const distEntry = detectDistEntry();",
|
|
260
|
+
' const isWin = process.platform === "win32";',
|
|
261
|
+
"",
|
|
262
|
+
" // 2) For TypeScript, run build and package dist entry",
|
|
263
|
+
" if (isTs) {",
|
|
264
|
+
" const r = isWin",
|
|
265
|
+
' ? runInherit("cmd", ["/d", "/s", "/c", "npm run build"], { cwd: process.cwd() })',
|
|
266
|
+
' : runInherit("npm", ["run", "build"], { cwd: process.cwd() });',
|
|
267
|
+
' if (r.error) throw new Error("Failed to run npm: " + String(r.error?.message || r.error));',
|
|
268
|
+
' if (r.status !== 0) throw new Error("npm run build failed");',
|
|
269
|
+
" if (!fs.existsSync(distEntry)) {",
|
|
270
|
+
' throw new Error("Expected build output missing: " + distEntry);',
|
|
271
|
+
" }",
|
|
272
|
+
" }",
|
|
273
|
+
"",
|
|
274
|
+
" let files = gitFileList().filter((p) => !shouldExcludeFromPackage(p));",
|
|
275
|
+
"",
|
|
276
|
+
" if (isTs) {",
|
|
277
|
+
" // dist/ is typically ignored; ensure we still include the built entry",
|
|
278
|
+
' files = files.filter((p) => !p.startsWith("dist/"));',
|
|
279
|
+
" files.push(distEntry);",
|
|
280
|
+
" }",
|
|
281
|
+
"",
|
|
282
|
+
" files = uniqStable(files);",
|
|
283
|
+
"",
|
|
135
284
|
" if (files.length === 0) {",
|
|
136
285
|
" throw new Error(",
|
|
137
286
|
' "No files found to package. If you just created the project, make sure git is initialized and .gitignore exists.",',
|
|
@@ -142,56 +291,23 @@ function packagingScriptMjs() {
|
|
|
142
291
|
" await ensureDir(outDir);",
|
|
143
292
|
' const outZip = path.join(outDir, "glosc-package-" + timestamp() + ".zip");',
|
|
144
293
|
"",
|
|
145
|
-
" const
|
|
146
|
-
"",
|
|
294
|
+
" const tmpDir = await prepareTmpDir();",
|
|
147
295
|
" try {",
|
|
148
|
-
|
|
149
|
-
"
|
|
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
|
-
" }",
|
|
296
|
+
" await writeNulSeparatedList(tmpDir, files);",
|
|
297
|
+
" gitArchiveZipFromWorkingTreeFiles(outZip, files);",
|
|
187
298
|
" } finally {",
|
|
188
299
|
" try {",
|
|
189
|
-
" await fsp.rm(
|
|
300
|
+
" await fsp.rm(tmpDir, { recursive: true, force: true });",
|
|
190
301
|
" } catch {",
|
|
191
302
|
" // ignore",
|
|
192
303
|
" }",
|
|
193
304
|
" }",
|
|
194
305
|
"",
|
|
306
|
+
" const st = await fsp.stat(outZip).catch(() => undefined);",
|
|
307
|
+
" if (!st || st.size <= 0) {",
|
|
308
|
+
' throw new Error("Packaging failed: zip was not created: " + outZip);',
|
|
309
|
+
" }",
|
|
310
|
+
"",
|
|
195
311
|
' console.log("Created: " + outZip);',
|
|
196
312
|
"}",
|
|
197
313
|
"",
|
|
@@ -206,6 +322,14 @@ function entryPath(options) {
|
|
|
206
322
|
? options.mainFileName
|
|
207
323
|
: `src/${options.mainFileName}`;
|
|
208
324
|
}
|
|
325
|
+
function mcpRuntime(options) {
|
|
326
|
+
return options.language === "python" ? "python" : "node";
|
|
327
|
+
}
|
|
328
|
+
function mcpEntry(options) {
|
|
329
|
+
if (options.language === "python")
|
|
330
|
+
return options.mainFileName;
|
|
331
|
+
return `dist/${options.mainFileName.replace(/\.ts$/i, ".js")}`;
|
|
332
|
+
}
|
|
209
333
|
function escapeYamlString(value) {
|
|
210
334
|
const s = String(value ?? "");
|
|
211
335
|
const escaped = s.replace(/"/g, '\\"');
|
|
@@ -223,7 +347,7 @@ function projectReadme(options) {
|
|
|
223
347
|
const runSection = language === "python"
|
|
224
348
|
? `## 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`
|
|
225
349
|
: `## 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`;
|
|
226
|
-
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`;
|
|
350
|
+
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`;
|
|
227
351
|
}
|
|
228
352
|
function packagingPackageJson(options) {
|
|
229
353
|
const { projectName, description, author } = options;
|
|
@@ -244,9 +368,15 @@ function configYml(options) {
|
|
|
244
368
|
return [
|
|
245
369
|
`name: ${escapeYamlString(projectName)}`,
|
|
246
370
|
`description: ${escapeYamlString(description)}`,
|
|
247
|
-
`
|
|
371
|
+
`icon: ${escapeYamlString("")}`,
|
|
248
372
|
`language: ${escapeYamlString(language)}`,
|
|
249
|
-
`
|
|
373
|
+
`author: ${escapeYamlString(author)}`,
|
|
374
|
+
`mcp:`,
|
|
375
|
+
` runtime: ${escapeYamlString(mcpRuntime(options))}`,
|
|
376
|
+
` entry: ${escapeYamlString(mcpEntry(options))}`,
|
|
377
|
+
` cwd: ${escapeYamlString(".")}`,
|
|
378
|
+
` env: {}`,
|
|
379
|
+
` args: []`,
|
|
250
380
|
"",
|
|
251
381
|
].join("\n");
|
|
252
382
|
}
|
package/package.json
CHANGED
package/src/templates.ts
CHANGED
|
@@ -78,6 +78,7 @@ function packagingScriptMjs(): string {
|
|
|
78
78
|
// Keep this script dependency-free and avoid nested template literals.
|
|
79
79
|
return [
|
|
80
80
|
'import { spawnSync } from "node:child_process";',
|
|
81
|
+
'import * as fs from "node:fs";',
|
|
81
82
|
'import * as fsp from "node:fs/promises";',
|
|
82
83
|
'import * as path from "node:path";',
|
|
83
84
|
"",
|
|
@@ -90,18 +91,61 @@ function packagingScriptMjs(): string {
|
|
|
90
91
|
" });",
|
|
91
92
|
"}",
|
|
92
93
|
"",
|
|
93
|
-
"function
|
|
94
|
-
|
|
95
|
-
'
|
|
96
|
-
|
|
94
|
+
"function runInherit(cmd, args, opts = {}) {",
|
|
95
|
+
" return spawnSync(cmd, args, {",
|
|
96
|
+
' stdio: "inherit",',
|
|
97
|
+
' encoding: "utf8",',
|
|
98
|
+
" shell: false,",
|
|
99
|
+
" ...opts,",
|
|
100
|
+
" });",
|
|
101
|
+
"}",
|
|
102
|
+
"",
|
|
103
|
+
"function normalizeRelPath(p) {",
|
|
104
|
+
' return String(p || "").replace(/\\\\/g, "/");',
|
|
105
|
+
"}",
|
|
106
|
+
"",
|
|
107
|
+
"function readJsonIfExists(filePath) {",
|
|
108
|
+
" try {",
|
|
109
|
+
" if (!fs.existsSync(filePath)) return undefined;",
|
|
110
|
+
' const raw = fs.readFileSync(filePath, "utf8");',
|
|
111
|
+
" return JSON.parse(raw);",
|
|
112
|
+
" } catch {",
|
|
113
|
+
" return undefined;",
|
|
97
114
|
" }",
|
|
98
|
-
' const check = "command -v " + cmd + " >/dev/null 2>&1";',
|
|
99
|
-
' const r = run("sh", ["-lc", check]);',
|
|
100
|
-
" return r.status === 0;",
|
|
101
115
|
"}",
|
|
102
116
|
"",
|
|
103
|
-
"function
|
|
104
|
-
'
|
|
117
|
+
"function detectTypeScriptProject() {",
|
|
118
|
+
' if (fs.existsSync("tsconfig.json")) return true;',
|
|
119
|
+
' const pkg = readJsonIfExists("package.json");',
|
|
120
|
+
' const buildScript = String(pkg?.scripts?.build || "");',
|
|
121
|
+
' if (buildScript.includes("tsc")) return true;',
|
|
122
|
+
" if (pkg?.devDependencies?.typescript) return true;",
|
|
123
|
+
" return false;",
|
|
124
|
+
"}",
|
|
125
|
+
"",
|
|
126
|
+
"function detectDistEntry() {",
|
|
127
|
+
" // default requested: /dist/index.js",
|
|
128
|
+
' const defaultEntry = "dist/index.js";',
|
|
129
|
+
' const pkg = readJsonIfExists("package.json");',
|
|
130
|
+
' const start = String(pkg?.scripts?.start || "").trim();',
|
|
131
|
+
" const m = /^node\\s+(.+)$/.exec(start);",
|
|
132
|
+
' const inferred = m && m[1] ? String(m[1]).trim() : "";',
|
|
133
|
+
" const entry = inferred || defaultEntry;",
|
|
134
|
+
" return normalizeRelPath(entry);",
|
|
135
|
+
"}",
|
|
136
|
+
"",
|
|
137
|
+
"function ensureGitRepoRoot() {",
|
|
138
|
+
' const r = run("git", ["rev-parse", "--show-toplevel"], { cwd: process.cwd() });',
|
|
139
|
+
" if (r.status !== 0) {",
|
|
140
|
+
' const err = (r.stderr || r.stdout || "").trim();',
|
|
141
|
+
" throw new Error(",
|
|
142
|
+
' "git repo is required for packaging (to respect .gitignore).\\n" + err,',
|
|
143
|
+
" );",
|
|
144
|
+
" }",
|
|
145
|
+
' const top = String(r.stdout || "").trim();',
|
|
146
|
+
' if (!top) throw new Error("Failed to resolve git repo root");',
|
|
147
|
+
" process.chdir(top);",
|
|
148
|
+
" return top;",
|
|
105
149
|
"}",
|
|
106
150
|
"",
|
|
107
151
|
"function gitFileList() {",
|
|
@@ -121,21 +165,100 @@ function packagingScriptMjs(): string {
|
|
|
121
165
|
" }",
|
|
122
166
|
"",
|
|
123
167
|
' const raw = r.stdout || "";',
|
|
124
|
-
' return raw.split("\\u0000").filter(Boolean);',
|
|
168
|
+
' return raw.split("\\u0000").filter(Boolean).map(normalizeRelPath);',
|
|
169
|
+
"}",
|
|
170
|
+
"",
|
|
171
|
+
"function shouldExcludeFromPackage(relPath) {",
|
|
172
|
+
" const p = normalizeRelPath(relPath);",
|
|
173
|
+
" if (!p) return true;",
|
|
174
|
+
" // 1) scripts/package.mjs does not need to be packaged",
|
|
175
|
+
' if (p === "scripts/package.mjs") return true;',
|
|
176
|
+
" // temp files created by this script",
|
|
177
|
+
' if (p.startsWith(".glosc-tmp/")) return true;',
|
|
178
|
+
" return false;",
|
|
179
|
+
"}",
|
|
180
|
+
"",
|
|
181
|
+
"function uniqStable(list) {",
|
|
182
|
+
" const seen = new Set();",
|
|
183
|
+
" const out = [];",
|
|
184
|
+
" for (const item of list) {",
|
|
185
|
+
" if (seen.has(item)) continue;",
|
|
186
|
+
" seen.add(item);",
|
|
187
|
+
" out.push(item);",
|
|
188
|
+
" }",
|
|
189
|
+
" return out;",
|
|
125
190
|
"}",
|
|
126
191
|
"",
|
|
127
192
|
"async function ensureDir(p) {",
|
|
128
193
|
" await fsp.mkdir(p, { recursive: true });",
|
|
129
194
|
"}",
|
|
130
195
|
"",
|
|
131
|
-
"async function
|
|
196
|
+
"async function prepareTmpDir() {",
|
|
132
197
|
' const tmpDir = path.join(process.cwd(), ".glosc-tmp");',
|
|
198
|
+
" await fsp.rm(tmpDir, { recursive: true, force: true });",
|
|
133
199
|
" await ensureDir(tmpDir);",
|
|
134
|
-
|
|
135
|
-
|
|
200
|
+
" return tmpDir;",
|
|
201
|
+
"}",
|
|
202
|
+
"",
|
|
203
|
+
"async function writeNulSeparatedList(tmpDir, filePaths) {",
|
|
204
|
+
" // Use NUL-separated pathspec so we can pass it to git safely (no quoting issues).",
|
|
205
|
+
' const listPath = path.join(tmpDir, "pathspec.nul");',
|
|
206
|
+
" const normalized = filePaths.map(normalizeRelPath);",
|
|
207
|
+
' const buf = Buffer.from(normalized.join("\u0000") + "\u0000", "utf8");',
|
|
208
|
+
" await fsp.writeFile(listPath, buf);",
|
|
136
209
|
" return listPath;",
|
|
137
210
|
"}",
|
|
138
211
|
"",
|
|
212
|
+
"function gitArchiveZipFromWorkingTreeFiles(outZip, filePaths) {",
|
|
213
|
+
" // Create a temporary index so we can archive *tracked + untracked* files without",
|
|
214
|
+
" // touching the user's real index or requiring a commit.",
|
|
215
|
+
' const tmpIndex = path.join(process.cwd(), ".glosc-tmp", "index");',
|
|
216
|
+
" const env = { ...process.env, GIT_INDEX_FILE: tmpIndex };",
|
|
217
|
+
"",
|
|
218
|
+
" // Add files (force-add so ignored build output like dist/index.js can be included).",
|
|
219
|
+
" const add = run(",
|
|
220
|
+
' "git",',
|
|
221
|
+
" [",
|
|
222
|
+
' "add",',
|
|
223
|
+
' "-f",',
|
|
224
|
+
' "--pathspec-from-file=.glosc-tmp/pathspec.nul",',
|
|
225
|
+
' "--pathspec-file-nul",',
|
|
226
|
+
' "--",',
|
|
227
|
+
" ],",
|
|
228
|
+
" { cwd: process.cwd(), env },",
|
|
229
|
+
" );",
|
|
230
|
+
" if (add.status !== 0) {",
|
|
231
|
+
" // Fallback for older Git versions without --pathspec-from-file.",
|
|
232
|
+
" const chunkSize = 200;",
|
|
233
|
+
" for (let i = 0; i < filePaths.length; i += chunkSize) {",
|
|
234
|
+
" const chunk = filePaths.slice(i, i + chunkSize);",
|
|
235
|
+
' const r = run("git", ["add", "-f", "--", ...chunk], { cwd: process.cwd(), env });',
|
|
236
|
+
" if (r.status !== 0) {",
|
|
237
|
+
' const err = (r.stderr || r.stdout || "").trim();',
|
|
238
|
+
' throw new Error("git add (temp index) failed\\n" + err);',
|
|
239
|
+
" }",
|
|
240
|
+
" }",
|
|
241
|
+
" }",
|
|
242
|
+
"",
|
|
243
|
+
' const wt = run("git", ["write-tree"], { cwd: process.cwd(), env });',
|
|
244
|
+
" if (wt.status !== 0) {",
|
|
245
|
+
' const err = (wt.stderr || wt.stdout || "").trim();',
|
|
246
|
+
' throw new Error("git write-tree failed\\n" + err);',
|
|
247
|
+
" }",
|
|
248
|
+
' const tree = String(wt.stdout || "").trim();',
|
|
249
|
+
' if (!tree) throw new Error("git write-tree produced empty tree hash");',
|
|
250
|
+
"",
|
|
251
|
+
" const ar = run(",
|
|
252
|
+
' "git",',
|
|
253
|
+
' ["archive", "--format=zip", "--output", outZip, tree],',
|
|
254
|
+
" { cwd: process.cwd() },",
|
|
255
|
+
" );",
|
|
256
|
+
" if (ar.status !== 0) {",
|
|
257
|
+
' const err = (ar.stderr || ar.stdout || "").trim();',
|
|
258
|
+
' throw new Error("git archive failed\\n" + err);',
|
|
259
|
+
" }",
|
|
260
|
+
"}",
|
|
261
|
+
"",
|
|
139
262
|
"function timestamp() {",
|
|
140
263
|
" const d = new Date();",
|
|
141
264
|
' const pad = (n) => String(n).padStart(2, "0");',
|
|
@@ -151,7 +274,33 @@ function packagingScriptMjs(): string {
|
|
|
151
274
|
"}",
|
|
152
275
|
"",
|
|
153
276
|
"async function main() {",
|
|
154
|
-
"
|
|
277
|
+
" ensureGitRepoRoot();",
|
|
278
|
+
" const isTs = detectTypeScriptProject();",
|
|
279
|
+
" const distEntry = detectDistEntry();",
|
|
280
|
+
' const isWin = process.platform === "win32";',
|
|
281
|
+
"",
|
|
282
|
+
" // 2) For TypeScript, run build and package dist entry",
|
|
283
|
+
" if (isTs) {",
|
|
284
|
+
" const r = isWin",
|
|
285
|
+
' ? runInherit("cmd", ["/d", "/s", "/c", "npm run build"], { cwd: process.cwd() })',
|
|
286
|
+
' : runInherit("npm", ["run", "build"], { cwd: process.cwd() });',
|
|
287
|
+
' if (r.error) throw new Error("Failed to run npm: " + String(r.error?.message || r.error));',
|
|
288
|
+
' if (r.status !== 0) throw new Error("npm run build failed");',
|
|
289
|
+
" if (!fs.existsSync(distEntry)) {",
|
|
290
|
+
' throw new Error("Expected build output missing: " + distEntry);',
|
|
291
|
+
" }",
|
|
292
|
+
" }",
|
|
293
|
+
"",
|
|
294
|
+
" let files = gitFileList().filter((p) => !shouldExcludeFromPackage(p));",
|
|
295
|
+
"",
|
|
296
|
+
" if (isTs) {",
|
|
297
|
+
" // dist/ is typically ignored; ensure we still include the built entry",
|
|
298
|
+
' files = files.filter((p) => !p.startsWith("dist/"));',
|
|
299
|
+
" files.push(distEntry);",
|
|
300
|
+
" }",
|
|
301
|
+
"",
|
|
302
|
+
" files = uniqStable(files);",
|
|
303
|
+
"",
|
|
155
304
|
" if (files.length === 0) {",
|
|
156
305
|
" throw new Error(",
|
|
157
306
|
' "No files found to package. If you just created the project, make sure git is initialized and .gitignore exists.",',
|
|
@@ -162,56 +311,23 @@ function packagingScriptMjs(): string {
|
|
|
162
311
|
" await ensureDir(outDir);",
|
|
163
312
|
' const outZip = path.join(outDir, "glosc-package-" + timestamp() + ".zip");',
|
|
164
313
|
"",
|
|
165
|
-
" const
|
|
166
|
-
"",
|
|
314
|
+
" const tmpDir = await prepareTmpDir();",
|
|
167
315
|
" try {",
|
|
168
|
-
|
|
169
|
-
"
|
|
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
|
-
" }",
|
|
316
|
+
" await writeNulSeparatedList(tmpDir, files);",
|
|
317
|
+
" gitArchiveZipFromWorkingTreeFiles(outZip, files);",
|
|
207
318
|
" } finally {",
|
|
208
319
|
" try {",
|
|
209
|
-
" await fsp.rm(
|
|
320
|
+
" await fsp.rm(tmpDir, { recursive: true, force: true });",
|
|
210
321
|
" } catch {",
|
|
211
322
|
" // ignore",
|
|
212
323
|
" }",
|
|
213
324
|
" }",
|
|
214
325
|
"",
|
|
326
|
+
" const st = await fsp.stat(outZip).catch(() => undefined);",
|
|
327
|
+
" if (!st || st.size <= 0) {",
|
|
328
|
+
' throw new Error("Packaging failed: zip was not created: " + outZip);',
|
|
329
|
+
" }",
|
|
330
|
+
"",
|
|
215
331
|
' console.log("Created: " + outZip);',
|
|
216
332
|
"}",
|
|
217
333
|
"",
|
|
@@ -228,6 +344,15 @@ function entryPath(options: ProjectOptions): string {
|
|
|
228
344
|
: `src/${options.mainFileName}`;
|
|
229
345
|
}
|
|
230
346
|
|
|
347
|
+
function mcpRuntime(options: ProjectOptions): "python" | "node" {
|
|
348
|
+
return options.language === "python" ? "python" : "node";
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function mcpEntry(options: ProjectOptions): string {
|
|
352
|
+
if (options.language === "python") return options.mainFileName;
|
|
353
|
+
return `dist/${options.mainFileName.replace(/\.ts$/i, ".js")}`;
|
|
354
|
+
}
|
|
355
|
+
|
|
231
356
|
function escapeYamlString(value: unknown): string {
|
|
232
357
|
const s = String(value ?? "");
|
|
233
358
|
const escaped = s.replace(/"/g, '\\"');
|
|
@@ -253,7 +378,7 @@ function projectReadme(options: ProjectOptions): string {
|
|
|
253
378
|
|
|
254
379
|
return `# ${projectName}\n\n${description || ""}\n\n## Author\n\n${
|
|
255
380
|
author || ""
|
|
256
|
-
}\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`;
|
|
381
|
+
}\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`;
|
|
257
382
|
}
|
|
258
383
|
|
|
259
384
|
function packagingPackageJson(options: ProjectOptions): string {
|
|
@@ -278,9 +403,15 @@ function configYml(options: ProjectOptions): string {
|
|
|
278
403
|
return [
|
|
279
404
|
`name: ${escapeYamlString(projectName)}`,
|
|
280
405
|
`description: ${escapeYamlString(description)}`,
|
|
281
|
-
`
|
|
406
|
+
`icon: ${escapeYamlString("")}`,
|
|
282
407
|
`language: ${escapeYamlString(language)}`,
|
|
283
|
-
`
|
|
408
|
+
`author: ${escapeYamlString(author)}`,
|
|
409
|
+
`mcp:`,
|
|
410
|
+
` runtime: ${escapeYamlString(mcpRuntime(options))}`,
|
|
411
|
+
` entry: ${escapeYamlString(mcpEntry(options))}`,
|
|
412
|
+
` cwd: ${escapeYamlString(".")}`,
|
|
413
|
+
` env: {}`,
|
|
414
|
+
` args: []`,
|
|
284
415
|
"",
|
|
285
416
|
].join("\n");
|
|
286
417
|
}
|