create-glosc 0.4.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.
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 hasCommand(cmd) {",
74
- ' if (process.platform === "win32") {',
75
- ' const r = run("where", [cmd]);',
76
- " return r.status === 0;",
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 escapePsSingleQuotes(value) {",
84
- ' return String(value || "").replace(/\'/g, "\'\'");',
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 writeTempFileList(filePaths) {",
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
- ' const listPath = path.join(tmpDir, "zip-file-list.txt");',
115
- ' await fsp.writeFile(listPath, filePaths.join("\\n"), "utf8");',
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
- " const files = gitFileList();",
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 listPath = await writeTempFileList(files);",
146
- "",
294
+ " const tmpDir = await prepareTmpDir();",
147
295
  " 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
- " }",
296
+ " await writeNulSeparatedList(tmpDir, files);",
297
+ " gitArchiveZipFromWorkingTreeFiles(outZip, files);",
187
298
  " } finally {",
188
299
  " try {",
189
- " await fsp.rm(path.dirname(listPath), { recursive: true, force: true });",
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
  "",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-glosc",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Scaffold Glosc projects (Python/TypeScript) via npm create",
5
5
  "author": "glosc-ai",
6
6
  "license": "MIT",
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 hasCommand(cmd) {",
94
- ' if (process.platform === "win32") {',
95
- ' const r = run("where", [cmd]);',
96
- " return r.status === 0;",
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 escapePsSingleQuotes(value) {",
104
- ' return String(value || "").replace(/\'/g, "\'\'");',
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 writeTempFileList(filePaths) {",
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
- ' const listPath = path.join(tmpDir, "zip-file-list.txt");',
135
- ' await fsp.writeFile(listPath, filePaths.join("\\n"), "utf8");',
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
- " const files = gitFileList();",
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 listPath = await writeTempFileList(files);",
166
- "",
314
+ " const tmpDir = await prepareTmpDir();",
167
315
  " 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
- " }",
316
+ " await writeNulSeparatedList(tmpDir, files);",
317
+ " gitArchiveZipFromWorkingTreeFiles(outZip, files);",
207
318
  " } finally {",
208
319
  " try {",
209
- " await fsp.rm(path.dirname(listPath), { recursive: true, force: true });",
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
  "",