craft-native 0.0.37 → 0.0.49
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/android/src/index.js +268 -0
- package/dist/android/src/index.js.map +10 -0
- package/dist/android/templates/AndroidManifest.xml.template +69 -0
- package/dist/android/templates/CraftBridge.kt.template +3748 -0
- package/dist/android/templates/CraftBridgeExtensions.kt.template +383 -0
- package/dist/android/templates/CraftWidgetProvider.kt.template +246 -0
- package/dist/android/templates/MainActivity.kt.template +109 -0
- package/dist/android/templates/build.gradle.kts.app.template +80 -0
- package/dist/android/templates/build.gradle.kts.project.template +5 -0
- package/dist/android/templates/fastlane/Appfile +25 -0
- package/dist/android/templates/fastlane/Fastfile +192 -0
- package/dist/android/templates/fastlane/Gemfile +7 -0
- package/dist/android/templates/github-workflow-android.yml +248 -0
- package/dist/android/templates/github-workflow-test.yml +329 -0
- package/dist/android/templates/settings.gradle.kts.template +18 -0
- package/dist/android/templates/test-bridges.html +1463 -0
- package/dist/api/window.d.ts +18 -0
- package/dist/api/window.d.ts.map +1 -1
- package/dist/assets/index.d.ts.map +1 -1
- package/dist/bridge/core.d.ts +2 -0
- package/dist/bridge/core.d.ts.map +1 -1
- package/dist/cli.js +4168 -0
- package/dist/components/sidebar.d.ts +5 -1
- package/dist/components/sidebar.d.ts.map +1 -1
- package/dist/dev/devtools.d.ts +1 -1
- package/dist/dev/devtools.d.ts.map +1 -1
- package/dist/dev/hot-reload.d.ts +12 -3
- package/dist/dev/hot-reload.d.ts.map +1 -1
- package/dist/index.cjs +573 -208
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +585 -216
- package/dist/index.js.map +45 -0
- package/dist/ios/src/index.js +186 -0
- package/dist/ios/src/index.js.map +10 -0
- package/dist/ios/templates/CraftApp.swift +4241 -0
- package/dist/ios/templates/CraftAppExtensions.swift +361 -0
- package/dist/ios/templates/CraftWidget.swift +247 -0
- package/dist/ios/templates/Info.plist.template +65 -0
- package/dist/ios/templates/fastlane/Appfile +45 -0
- package/dist/ios/templates/fastlane/Fastfile +189 -0
- package/dist/ios/templates/fastlane/Gemfile +7 -0
- package/dist/ios/templates/fastlane/Matchfile +58 -0
- package/dist/ios/templates/github-workflow-ios.yml +192 -0
- package/dist/ios/templates/github-workflow-release.yml +175 -0
- package/dist/ios/templates/github-workflow-test.yml +272 -0
- package/dist/ios/templates/project.yml.template +25 -0
- package/dist/ios/templates/test-bridges.html +1463 -0
- package/dist/package.d.ts +106 -2
- package/dist/package.d.ts.map +1 -1
- package/dist/package.test.d.ts +2 -0
- package/dist/package.test.d.ts.map +1 -0
- package/dist/styles/sidebar-templates.d.ts.map +1 -1
- package/dist/styles/sidebars.d.ts +12 -8
- package/dist/styles/sidebars.d.ts.map +1 -1
- package/dist/templates/projects/blank/craft.config.ts +19 -0
- package/dist/templates/projects/blank/index.html +27 -0
- package/dist/templates/projects/blank/package.json +19 -0
- package/dist/templates/projects/blank/src/main.ts +26 -0
- package/dist/templates/projects/dashboard/craft.config.ts +42 -0
- package/dist/templates/projects/dashboard/index.html +13 -0
- package/dist/templates/projects/dashboard/package.json +20 -0
- package/dist/templates/projects/dashboard/src/main.ts +163 -0
- package/dist/templates/projects/dashboard/src/styles.css +280 -0
- package/dist/templates/projects/drawer/craft.config.ts +47 -0
- package/dist/templates/projects/drawer/index.html +13 -0
- package/dist/templates/projects/drawer/package.json +21 -0
- package/dist/templates/projects/drawer/src/main.ts +406 -0
- package/dist/templates/projects/drawer/src/styles.css +468 -0
- package/dist/templates/projects/ecommerce/craft.config.ts +32 -0
- package/dist/templates/projects/ecommerce/index.html +13 -0
- package/dist/templates/projects/ecommerce/package.json +19 -0
- package/dist/templates/projects/ecommerce/src/main.ts +194 -0
- package/dist/templates/projects/ecommerce/src/styles.css +169 -0
- package/dist/templates/projects/social/craft.config.ts +32 -0
- package/dist/templates/projects/social/index.html +13 -0
- package/dist/templates/projects/social/package.json +19 -0
- package/dist/templates/projects/social/src/main.ts +270 -0
- package/dist/templates/projects/social/src/styles.css +216 -0
- package/dist/templates/projects/tabs/craft.config.ts +47 -0
- package/dist/templates/projects/tabs/index.html +13 -0
- package/dist/templates/projects/tabs/package.json +21 -0
- package/dist/templates/projects/tabs/src/main.ts +177 -0
- package/dist/templates/projects/tabs/src/styles.css +163 -0
- package/dist/types.d.ts +29 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/updater/index.d.ts +1 -0
- package/dist/updater/index.d.ts.map +1 -1
- package/package.json +5 -12
- package/bin/cli.ts +0 -937
package/dist/cli.js
ADDED
|
@@ -0,0 +1,4168 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __returnValue = (v) => v;
|
|
5
|
+
function __exportSetter(name, newValue) {
|
|
6
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
7
|
+
}
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, {
|
|
11
|
+
get: all[name],
|
|
12
|
+
enumerable: true,
|
|
13
|
+
configurable: true,
|
|
14
|
+
set: __exportSetter.bind(all, name)
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
18
|
+
var __require = import.meta.require;
|
|
19
|
+
|
|
20
|
+
// src/package.ts
|
|
21
|
+
var exports_package = {};
|
|
22
|
+
__export(exports_package, {
|
|
23
|
+
windowsExecutableName: () => windowsExecutableName,
|
|
24
|
+
windowsArchitecture: () => windowsArchitecture,
|
|
25
|
+
shouldRetryHdiutil: () => shouldRetryHdiutil,
|
|
26
|
+
renderWixSource: () => renderWixSource,
|
|
27
|
+
productbuildArguments: () => productbuildArguments,
|
|
28
|
+
packageApp: () => packageApp,
|
|
29
|
+
pack: () => pack,
|
|
30
|
+
notarytoolArguments: () => notarytoolArguments,
|
|
31
|
+
macOSInfoPlist: () => macOSInfoPlist,
|
|
32
|
+
formatPackagingCommandError: () => formatPackagingCommandError,
|
|
33
|
+
dmgCreateArguments: () => dmgCreateArguments,
|
|
34
|
+
dmgCapacityMegabytes: () => dmgCapacityMegabytes,
|
|
35
|
+
codesignArguments: () => codesignArguments
|
|
36
|
+
});
|
|
37
|
+
import { spawn } from "child_process";
|
|
38
|
+
import { createHash } from "crypto";
|
|
39
|
+
import {
|
|
40
|
+
chmodSync,
|
|
41
|
+
copyFileSync,
|
|
42
|
+
cpSync,
|
|
43
|
+
existsSync as existsSync3,
|
|
44
|
+
lstatSync,
|
|
45
|
+
mkdirSync,
|
|
46
|
+
mkdtempSync,
|
|
47
|
+
readFileSync,
|
|
48
|
+
readdirSync,
|
|
49
|
+
rmSync,
|
|
50
|
+
writeFileSync
|
|
51
|
+
} from "fs";
|
|
52
|
+
import { homedir, tmpdir } from "os";
|
|
53
|
+
import { basename, join } from "path";
|
|
54
|
+
import { deflateRawSync } from "zlib";
|
|
55
|
+
function crc32(data) {
|
|
56
|
+
let c = 4294967295;
|
|
57
|
+
for (let i = 0;i < data.length; i++)
|
|
58
|
+
c = CRC32_TABLE[(c ^ data[i]) & 255] ^ c >>> 8;
|
|
59
|
+
return (c ^ 4294967295) >>> 0;
|
|
60
|
+
}
|
|
61
|
+
function dosDateTime(d) {
|
|
62
|
+
const time = d.getHours() << 11 | d.getMinutes() << 5 | d.getSeconds() >> 1;
|
|
63
|
+
const date = d.getFullYear() - 1980 << 9 | d.getMonth() + 1 << 5 | d.getDate();
|
|
64
|
+
return { time: time & 65535, date: date & 65535 };
|
|
65
|
+
}
|
|
66
|
+
function buildZip(entries) {
|
|
67
|
+
const { time, date } = dosDateTime(new Date);
|
|
68
|
+
const locals = [];
|
|
69
|
+
const central = [];
|
|
70
|
+
let offset = 0;
|
|
71
|
+
for (const entry of entries) {
|
|
72
|
+
const nameBytes = Buffer.from(entry.name, "utf8");
|
|
73
|
+
const compressed = deflateRawSync(entry.data, { level: 9 });
|
|
74
|
+
const crc = crc32(entry.data);
|
|
75
|
+
const lfh = Buffer.alloc(30);
|
|
76
|
+
lfh.writeUInt32LE(67324752, 0);
|
|
77
|
+
lfh.writeUInt16LE(20, 4);
|
|
78
|
+
lfh.writeUInt16LE(0, 6);
|
|
79
|
+
lfh.writeUInt16LE(8, 8);
|
|
80
|
+
lfh.writeUInt16LE(time, 10);
|
|
81
|
+
lfh.writeUInt16LE(date, 12);
|
|
82
|
+
lfh.writeUInt32LE(crc, 14);
|
|
83
|
+
lfh.writeUInt32LE(compressed.length, 18);
|
|
84
|
+
lfh.writeUInt32LE(entry.data.length, 22);
|
|
85
|
+
lfh.writeUInt16LE(nameBytes.length, 26);
|
|
86
|
+
lfh.writeUInt16LE(0, 28);
|
|
87
|
+
locals.push(lfh, nameBytes, compressed);
|
|
88
|
+
const cdh = Buffer.alloc(46);
|
|
89
|
+
cdh.writeUInt32LE(33639248, 0);
|
|
90
|
+
cdh.writeUInt16LE(20, 4);
|
|
91
|
+
cdh.writeUInt16LE(20, 6);
|
|
92
|
+
cdh.writeUInt16LE(0, 8);
|
|
93
|
+
cdh.writeUInt16LE(8, 10);
|
|
94
|
+
cdh.writeUInt16LE(time, 12);
|
|
95
|
+
cdh.writeUInt16LE(date, 14);
|
|
96
|
+
cdh.writeUInt32LE(crc, 16);
|
|
97
|
+
cdh.writeUInt32LE(compressed.length, 20);
|
|
98
|
+
cdh.writeUInt32LE(entry.data.length, 24);
|
|
99
|
+
cdh.writeUInt16LE(nameBytes.length, 28);
|
|
100
|
+
cdh.writeUInt16LE(0, 30);
|
|
101
|
+
cdh.writeUInt16LE(0, 32);
|
|
102
|
+
cdh.writeUInt16LE(0, 34);
|
|
103
|
+
cdh.writeUInt16LE(0, 36);
|
|
104
|
+
cdh.writeUInt32LE(0, 38);
|
|
105
|
+
cdh.writeUInt32LE(offset, 42);
|
|
106
|
+
central.push(cdh, nameBytes);
|
|
107
|
+
offset += lfh.length + nameBytes.length + compressed.length;
|
|
108
|
+
}
|
|
109
|
+
const centralBuf = Buffer.concat(central);
|
|
110
|
+
const eocd = Buffer.alloc(22);
|
|
111
|
+
eocd.writeUInt32LE(101010256, 0);
|
|
112
|
+
eocd.writeUInt16LE(0, 4);
|
|
113
|
+
eocd.writeUInt16LE(0, 6);
|
|
114
|
+
eocd.writeUInt16LE(entries.length, 8);
|
|
115
|
+
eocd.writeUInt16LE(entries.length, 10);
|
|
116
|
+
eocd.writeUInt32LE(centralBuf.length, 12);
|
|
117
|
+
eocd.writeUInt32LE(offset, 16);
|
|
118
|
+
eocd.writeUInt16LE(0, 20);
|
|
119
|
+
return Buffer.concat([...locals, centralBuf, eocd]);
|
|
120
|
+
}
|
|
121
|
+
function formatPackagingCommandError(tool, code, stdout3, stderr) {
|
|
122
|
+
const detail = `${stdout3}${stderr}`.trim();
|
|
123
|
+
return `${tool} exited with code ${code}${detail ? `: ${detail}` : ""}`;
|
|
124
|
+
}
|
|
125
|
+
async function packageApp(config) {
|
|
126
|
+
const results = [];
|
|
127
|
+
if (!config.binaryPath || !existsSync3(config.binaryPath)) {
|
|
128
|
+
throw new Error(`Binary not found: ${config.binaryPath}`);
|
|
129
|
+
}
|
|
130
|
+
const platforms = config.platforms || [detectPlatform()];
|
|
131
|
+
const outDir = config.outDir || join(process.cwd(), "dist");
|
|
132
|
+
if (!existsSync3(outDir)) {
|
|
133
|
+
mkdirSync(outDir, { recursive: true });
|
|
134
|
+
}
|
|
135
|
+
for (const platform of platforms) {
|
|
136
|
+
switch (platform) {
|
|
137
|
+
case "macos":
|
|
138
|
+
results.push(...await packageMacOS(config, outDir));
|
|
139
|
+
break;
|
|
140
|
+
case "windows":
|
|
141
|
+
results.push(...await packageWindows(config, outDir));
|
|
142
|
+
break;
|
|
143
|
+
case "linux":
|
|
144
|
+
results.push(...await packageLinux(config, outDir));
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return results;
|
|
149
|
+
}
|
|
150
|
+
async function packageMacOS(config, outDir) {
|
|
151
|
+
const results = [];
|
|
152
|
+
const { name, version: version2, bundleId = `com.myapp.${name.toLowerCase()}` } = config;
|
|
153
|
+
const opts = config.macos || {};
|
|
154
|
+
const hardenedRuntime = !opts.appStore;
|
|
155
|
+
const appBundlePath = join(outDir, `${name}.app`);
|
|
156
|
+
const appBundle = createMacOSAppBundle({
|
|
157
|
+
name,
|
|
158
|
+
version: version2,
|
|
159
|
+
bundleId,
|
|
160
|
+
buildNumber: opts.buildNumber,
|
|
161
|
+
category: opts.category,
|
|
162
|
+
minimumSystemVersion: opts.minimumSystemVersion,
|
|
163
|
+
menuBarOnly: opts.menuBarOnly,
|
|
164
|
+
binaryPath: config.binaryPath,
|
|
165
|
+
iconPath: config.iconPath,
|
|
166
|
+
provisioningProfile: opts.provisioningProfile,
|
|
167
|
+
additionalExecutables: opts.additionalExecutables,
|
|
168
|
+
outputPath: appBundlePath
|
|
169
|
+
});
|
|
170
|
+
if (!appBundle.success) {
|
|
171
|
+
results.push({
|
|
172
|
+
success: false,
|
|
173
|
+
platform: "macos",
|
|
174
|
+
format: "app",
|
|
175
|
+
error: appBundle.error
|
|
176
|
+
});
|
|
177
|
+
return results;
|
|
178
|
+
}
|
|
179
|
+
if (opts.signIdentity) {
|
|
180
|
+
const signed = await runTool("codesign", codesignArguments({
|
|
181
|
+
path: appBundlePath,
|
|
182
|
+
identity: opts.signIdentity,
|
|
183
|
+
entitlements: opts.entitlements,
|
|
184
|
+
hardenedRuntime
|
|
185
|
+
}));
|
|
186
|
+
if (!signed.success) {
|
|
187
|
+
results.push({ success: false, platform: "macos", format: "app", error: signed.error });
|
|
188
|
+
return results;
|
|
189
|
+
}
|
|
190
|
+
} else if (opts.appStore) {
|
|
191
|
+
results.push({
|
|
192
|
+
success: false,
|
|
193
|
+
platform: "macos",
|
|
194
|
+
format: "pkg",
|
|
195
|
+
error: 'App Store packaging requires macos.signIdentity (a "3rd Party Mac Developer Application" identity)'
|
|
196
|
+
});
|
|
197
|
+
return results;
|
|
198
|
+
}
|
|
199
|
+
results.push({ success: true, platform: "macos", format: "app", outputPath: appBundlePath });
|
|
200
|
+
if (opts.dmg !== false) {
|
|
201
|
+
const outputPath = join(outDir, `${name}-${version2}.dmg`);
|
|
202
|
+
const dmgResult = await createDMG({ appBundlePath, outputPath, volumeName: name });
|
|
203
|
+
results.push({
|
|
204
|
+
success: dmgResult.success,
|
|
205
|
+
platform: "macos",
|
|
206
|
+
format: "dmg",
|
|
207
|
+
outputPath: dmgResult.outputPath,
|
|
208
|
+
error: dmgResult.error
|
|
209
|
+
});
|
|
210
|
+
if (dmgResult.success)
|
|
211
|
+
results.push(...await notarizeIfRequested(opts, outputPath, "dmg"));
|
|
212
|
+
}
|
|
213
|
+
if (opts.pkg || opts.appStore) {
|
|
214
|
+
const outputPath = join(outDir, `${name}-${version2}.pkg`);
|
|
215
|
+
const pkgResult = await createPKG({
|
|
216
|
+
appBundlePath,
|
|
217
|
+
outputPath,
|
|
218
|
+
identifier: bundleId,
|
|
219
|
+
version: version2,
|
|
220
|
+
appStore: opts.appStore === true,
|
|
221
|
+
installerIdentity: opts.installerIdentity
|
|
222
|
+
});
|
|
223
|
+
results.push({
|
|
224
|
+
success: pkgResult.success,
|
|
225
|
+
platform: "macos",
|
|
226
|
+
format: opts.appStore ? "pkg (app store)" : "pkg",
|
|
227
|
+
outputPath: pkgResult.outputPath,
|
|
228
|
+
error: pkgResult.error
|
|
229
|
+
});
|
|
230
|
+
if (pkgResult.success && !opts.appStore)
|
|
231
|
+
results.push(...await notarizeIfRequested(opts, outputPath, "pkg"));
|
|
232
|
+
}
|
|
233
|
+
return results;
|
|
234
|
+
}
|
|
235
|
+
async function notarizeIfRequested(opts, artifactPath, format) {
|
|
236
|
+
if (!opts.notarize)
|
|
237
|
+
return [];
|
|
238
|
+
if (!opts.appleId || !opts.applePassword || !opts.teamId) {
|
|
239
|
+
return [{
|
|
240
|
+
success: false,
|
|
241
|
+
platform: "macos",
|
|
242
|
+
format: `${format} (notarize)`,
|
|
243
|
+
error: "Notarization requires macos.appleId, macos.applePassword and macos.teamId"
|
|
244
|
+
}];
|
|
245
|
+
}
|
|
246
|
+
const submitted = await runTool("xcrun", notarytoolArguments({
|
|
247
|
+
artifactPath,
|
|
248
|
+
appleId: opts.appleId,
|
|
249
|
+
applePassword: opts.applePassword,
|
|
250
|
+
teamId: opts.teamId
|
|
251
|
+
}));
|
|
252
|
+
if (!submitted.success)
|
|
253
|
+
return [{ success: false, platform: "macos", format: `${format} (notarize)`, error: submitted.error }];
|
|
254
|
+
const stapled = await runTool("xcrun", ["stapler", "staple", artifactPath]);
|
|
255
|
+
return [{
|
|
256
|
+
success: stapled.success,
|
|
257
|
+
platform: "macos",
|
|
258
|
+
format: `${format} (notarize)`,
|
|
259
|
+
outputPath: stapled.success ? artifactPath : undefined,
|
|
260
|
+
error: stapled.success ? undefined : stapled.error
|
|
261
|
+
}];
|
|
262
|
+
}
|
|
263
|
+
async function packageWindows(config, outDir) {
|
|
264
|
+
const results = [];
|
|
265
|
+
const { name, version: version2 } = config;
|
|
266
|
+
const opts = config.windows || {};
|
|
267
|
+
if (opts.msi !== false) {
|
|
268
|
+
const msiResult = await createMSI({
|
|
269
|
+
name,
|
|
270
|
+
version: version2,
|
|
271
|
+
binaryPath: config.binaryPath,
|
|
272
|
+
outputPath: join(outDir, `${name}-${version2}.msi`),
|
|
273
|
+
manufacturer: config.author || "Unknown",
|
|
274
|
+
architecture: opts.architecture || windowsArchitecture(process.arch),
|
|
275
|
+
certificatePath: opts.certificatePath,
|
|
276
|
+
certificatePassword: opts.certificatePassword
|
|
277
|
+
});
|
|
278
|
+
results.push({
|
|
279
|
+
success: msiResult.success,
|
|
280
|
+
platform: "windows",
|
|
281
|
+
format: "msi",
|
|
282
|
+
outputPath: msiResult.outputPath,
|
|
283
|
+
error: msiResult.error
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
if (opts.zip || opts.msi === false) {
|
|
287
|
+
const zipResult = await createZIP({
|
|
288
|
+
name,
|
|
289
|
+
version: version2,
|
|
290
|
+
binaryPath: config.binaryPath,
|
|
291
|
+
outputPath: join(outDir, `${name}-${version2}-windows.zip`)
|
|
292
|
+
});
|
|
293
|
+
results.push({
|
|
294
|
+
success: zipResult.success,
|
|
295
|
+
platform: "windows",
|
|
296
|
+
format: "zip",
|
|
297
|
+
outputPath: zipResult.outputPath,
|
|
298
|
+
error: zipResult.error
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
return results;
|
|
302
|
+
}
|
|
303
|
+
async function packageLinux(config, outDir) {
|
|
304
|
+
const results = [];
|
|
305
|
+
const { name, version: version2 } = config;
|
|
306
|
+
const opts = config.linux || {};
|
|
307
|
+
if (opts.deb !== false) {
|
|
308
|
+
const debResult = await createDEB({
|
|
309
|
+
name,
|
|
310
|
+
version: version2,
|
|
311
|
+
binaryPath: config.binaryPath,
|
|
312
|
+
outputPath: join(outDir, `${name}_${version2}_amd64.deb`),
|
|
313
|
+
description: config.description || "",
|
|
314
|
+
maintainer: config.author || "Unknown",
|
|
315
|
+
dependencies: opts.debDependencies || ["libgtk-3-0", "libwebkit2gtk-4.1-37"]
|
|
316
|
+
});
|
|
317
|
+
results.push({
|
|
318
|
+
success: debResult.success,
|
|
319
|
+
platform: "linux",
|
|
320
|
+
format: "deb",
|
|
321
|
+
outputPath: debResult.outputPath,
|
|
322
|
+
error: debResult.error
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
if (opts.rpm) {
|
|
326
|
+
const rpmResult = await createRPM({
|
|
327
|
+
name,
|
|
328
|
+
version: version2,
|
|
329
|
+
binaryPath: config.binaryPath,
|
|
330
|
+
outputPath: join(outDir, `${name}-${version2}-1.x86_64.rpm`),
|
|
331
|
+
description: config.description || "",
|
|
332
|
+
requires: opts.rpmDependencies || ["gtk3", "webkit2gtk3"]
|
|
333
|
+
});
|
|
334
|
+
results.push({
|
|
335
|
+
success: rpmResult.success,
|
|
336
|
+
platform: "linux",
|
|
337
|
+
format: "rpm",
|
|
338
|
+
outputPath: rpmResult.outputPath,
|
|
339
|
+
error: rpmResult.error
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
if (opts.appImage) {
|
|
343
|
+
const appImageResult = await createAppImage({
|
|
344
|
+
name,
|
|
345
|
+
version: version2,
|
|
346
|
+
binaryPath: config.binaryPath,
|
|
347
|
+
outputPath: join(outDir, `${name}-${version2}-x86_64.AppImage`),
|
|
348
|
+
iconPath: config.iconPath
|
|
349
|
+
});
|
|
350
|
+
results.push({
|
|
351
|
+
success: appImageResult.success,
|
|
352
|
+
platform: "linux",
|
|
353
|
+
format: "appimage",
|
|
354
|
+
outputPath: appImageResult.outputPath,
|
|
355
|
+
error: appImageResult.error
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
return results;
|
|
359
|
+
}
|
|
360
|
+
function macOSInfoPlist(metadata) {
|
|
361
|
+
const entries = [
|
|
362
|
+
["CFBundleExecutable", metadata.name],
|
|
363
|
+
["CFBundleIdentifier", metadata.bundleId],
|
|
364
|
+
["CFBundleName", metadata.name],
|
|
365
|
+
["CFBundleShortVersionString", metadata.version],
|
|
366
|
+
["CFBundleVersion", metadata.buildNumber || metadata.version],
|
|
367
|
+
["CFBundlePackageType", "APPL"],
|
|
368
|
+
["NSHighResolutionCapable", true]
|
|
369
|
+
];
|
|
370
|
+
if (metadata.iconName)
|
|
371
|
+
entries.push(["CFBundleIconFile", metadata.iconName]);
|
|
372
|
+
if (metadata.category)
|
|
373
|
+
entries.push(["LSApplicationCategoryType", metadata.category]);
|
|
374
|
+
if (metadata.minimumSystemVersion)
|
|
375
|
+
entries.push(["LSMinimumSystemVersion", metadata.minimumSystemVersion]);
|
|
376
|
+
if (metadata.menuBarOnly)
|
|
377
|
+
entries.push(["LSUIElement", true]);
|
|
378
|
+
const body = entries.map(([key, value]) => ` <key>${xml(key)}</key>
|
|
379
|
+
${value === true ? " <true/>" : ` <string>${xml(value)}</string>`}`).join(`
|
|
380
|
+
`);
|
|
381
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
382
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
383
|
+
<plist version="1.0">
|
|
384
|
+
<dict>
|
|
385
|
+
${body}
|
|
386
|
+
</dict>
|
|
387
|
+
</plist>`;
|
|
388
|
+
}
|
|
389
|
+
function codesignArguments(opts) {
|
|
390
|
+
return [
|
|
391
|
+
"--force",
|
|
392
|
+
"--sign",
|
|
393
|
+
opts.identity,
|
|
394
|
+
"--timestamp",
|
|
395
|
+
...opts.hardenedRuntime ? ["--options", "runtime"] : [],
|
|
396
|
+
...opts.entitlements ? ["--entitlements", opts.entitlements] : [],
|
|
397
|
+
"--deep",
|
|
398
|
+
opts.path
|
|
399
|
+
];
|
|
400
|
+
}
|
|
401
|
+
function productbuildArguments(opts) {
|
|
402
|
+
return [
|
|
403
|
+
"--component",
|
|
404
|
+
opts.appBundlePath,
|
|
405
|
+
"/Applications",
|
|
406
|
+
"--sign",
|
|
407
|
+
opts.identity,
|
|
408
|
+
opts.outputPath
|
|
409
|
+
];
|
|
410
|
+
}
|
|
411
|
+
function notarytoolArguments(opts) {
|
|
412
|
+
return [
|
|
413
|
+
"notarytool",
|
|
414
|
+
"submit",
|
|
415
|
+
opts.artifactPath,
|
|
416
|
+
"--apple-id",
|
|
417
|
+
opts.appleId,
|
|
418
|
+
"--password",
|
|
419
|
+
opts.applePassword,
|
|
420
|
+
"--team-id",
|
|
421
|
+
opts.teamId,
|
|
422
|
+
"--wait"
|
|
423
|
+
];
|
|
424
|
+
}
|
|
425
|
+
function createMacOSAppBundle(opts) {
|
|
426
|
+
try {
|
|
427
|
+
const { name, binaryPath, iconPath, provisioningProfile, additionalExecutables, outputPath } = opts;
|
|
428
|
+
const contents = join(outputPath, "Contents");
|
|
429
|
+
const macOS = join(contents, "MacOS");
|
|
430
|
+
mkdirSync(macOS, { recursive: true });
|
|
431
|
+
mkdirSync(join(contents, "Resources"), { recursive: true });
|
|
432
|
+
copyFileSync(binaryPath, join(macOS, name));
|
|
433
|
+
chmodSync(join(macOS, name), 493);
|
|
434
|
+
for (const executable of additionalExecutables || []) {
|
|
435
|
+
if (!existsSync3(executable))
|
|
436
|
+
return { success: false, error: `Additional executable not found: ${executable}` };
|
|
437
|
+
const destination = join(macOS, basename(executable));
|
|
438
|
+
copyFileSync(executable, destination);
|
|
439
|
+
chmodSync(destination, 493);
|
|
440
|
+
}
|
|
441
|
+
let iconName;
|
|
442
|
+
if (iconPath) {
|
|
443
|
+
if (!existsSync3(iconPath))
|
|
444
|
+
return { success: false, error: `Icon not found: ${iconPath}` };
|
|
445
|
+
iconName = basename(iconPath).replace(/\.icns$/i, "");
|
|
446
|
+
copyFileSync(iconPath, join(contents, "Resources", `${iconName}.icns`));
|
|
447
|
+
}
|
|
448
|
+
if (provisioningProfile) {
|
|
449
|
+
if (!existsSync3(provisioningProfile))
|
|
450
|
+
return { success: false, error: `Provisioning profile not found: ${provisioningProfile}` };
|
|
451
|
+
copyFileSync(provisioningProfile, join(contents, "embedded.provisionprofile"));
|
|
452
|
+
}
|
|
453
|
+
writeFileSync(join(contents, "Info.plist"), macOSInfoPlist({ ...opts, iconName }));
|
|
454
|
+
return { success: true };
|
|
455
|
+
} catch (error) {
|
|
456
|
+
return { success: false, error: error.message };
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
function pathContentBytes(path2) {
|
|
460
|
+
const stat = lstatSync(path2);
|
|
461
|
+
if (!stat.isDirectory())
|
|
462
|
+
return stat.size;
|
|
463
|
+
return readdirSync(path2).reduce((total, entry) => total + pathContentBytes(join(path2, entry)), 0);
|
|
464
|
+
}
|
|
465
|
+
function dmgCapacityMegabytes(contentBytes) {
|
|
466
|
+
if (!Number.isSafeInteger(contentBytes) || contentBytes < 0)
|
|
467
|
+
throw new Error(`DMG content size must be a non-negative safe integer: ${contentBytes}`);
|
|
468
|
+
const contentMegabytes = Math.ceil(contentBytes / MEBIBYTE);
|
|
469
|
+
return Math.max(64, Math.ceil(contentMegabytes * 1.25) + 32);
|
|
470
|
+
}
|
|
471
|
+
function dmgCreateArguments(opts, contentBytes) {
|
|
472
|
+
return [
|
|
473
|
+
"create",
|
|
474
|
+
"-volname",
|
|
475
|
+
opts.volumeName,
|
|
476
|
+
"-srcfolder",
|
|
477
|
+
opts.appBundlePath,
|
|
478
|
+
"-size",
|
|
479
|
+
`${dmgCapacityMegabytes(contentBytes)}m`,
|
|
480
|
+
"-ov",
|
|
481
|
+
"-format",
|
|
482
|
+
"UDZO",
|
|
483
|
+
opts.outputPath
|
|
484
|
+
];
|
|
485
|
+
}
|
|
486
|
+
function shouldRetryHdiutil(error, attempt, maxAttempts = HDIUTIL_MAX_ATTEMPTS) {
|
|
487
|
+
if (!Number.isInteger(attempt) || attempt < 1 || !Number.isInteger(maxAttempts) || maxAttempts < 1)
|
|
488
|
+
throw new Error("hdiutil retry attempts must be positive integers");
|
|
489
|
+
return attempt < maxAttempts && /(?:resource busy|resource temporarily unavailable)/i.test(error);
|
|
490
|
+
}
|
|
491
|
+
function runTool(tool, args) {
|
|
492
|
+
return new Promise((resolve) => {
|
|
493
|
+
const proc = spawn(tool, args);
|
|
494
|
+
let stdout3 = "";
|
|
495
|
+
let stderr = "";
|
|
496
|
+
proc.stdout?.on("data", (chunk) => {
|
|
497
|
+
stdout3 += chunk.toString();
|
|
498
|
+
});
|
|
499
|
+
proc.stderr?.on("data", (chunk) => {
|
|
500
|
+
stderr += chunk.toString();
|
|
501
|
+
});
|
|
502
|
+
proc.on("close", (code) => {
|
|
503
|
+
if (code === 0)
|
|
504
|
+
resolve({ success: true });
|
|
505
|
+
else
|
|
506
|
+
resolve({ success: false, error: formatPackagingCommandError(tool, code, stdout3, stderr) });
|
|
507
|
+
});
|
|
508
|
+
proc.on("error", (err) => resolve({ success: false, error: err.message }));
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
async function createDMG(opts) {
|
|
512
|
+
if (!/^[^/:\n]{1,27}$/.test(opts.volumeName))
|
|
513
|
+
return { success: false, error: `Invalid DMG volume name "${opts.volumeName}"; must be 1..27 chars without /, :, or newline` };
|
|
514
|
+
const args = dmgCreateArguments(opts, pathContentBytes(opts.appBundlePath));
|
|
515
|
+
const failures = [];
|
|
516
|
+
for (let attempt = 1;attempt <= HDIUTIL_MAX_ATTEMPTS; attempt++) {
|
|
517
|
+
const result = await runHdiutil(args);
|
|
518
|
+
if (result.success)
|
|
519
|
+
return { success: true, outputPath: opts.outputPath };
|
|
520
|
+
failures.push(`attempt ${attempt}: ${result.error}`);
|
|
521
|
+
if (!shouldRetryHdiutil(result.error, attempt))
|
|
522
|
+
return { success: false, error: failures.join(`
|
|
523
|
+
`) };
|
|
524
|
+
rmSync(opts.outputPath, { force: true });
|
|
525
|
+
await new Promise((resolve) => setTimeout(resolve, attempt * 2000));
|
|
526
|
+
}
|
|
527
|
+
return { success: false, error: failures.join(`
|
|
528
|
+
`) };
|
|
529
|
+
}
|
|
530
|
+
async function createPKG(opts) {
|
|
531
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(opts.identifier) || !opts.identifier.includes("."))
|
|
532
|
+
return { success: false, error: `Invalid pkg identifier "${opts.identifier}"; expected reverse-DNS like com.example.app` };
|
|
533
|
+
if (!/^[A-Za-z0-9._+-]+$/.test(opts.version))
|
|
534
|
+
return { success: false, error: `Invalid pkg version "${opts.version}"` };
|
|
535
|
+
if (opts.appStore) {
|
|
536
|
+
if (!opts.installerIdentity)
|
|
537
|
+
return { success: false, error: 'App Store packaging requires macos.installerIdentity (a "3rd Party Mac Developer Installer" identity)' };
|
|
538
|
+
const built = await runTool("productbuild", productbuildArguments({
|
|
539
|
+
appBundlePath: opts.appBundlePath,
|
|
540
|
+
outputPath: opts.outputPath,
|
|
541
|
+
identity: opts.installerIdentity
|
|
542
|
+
}));
|
|
543
|
+
return built.success ? { success: true, outputPath: opts.outputPath } : { success: false, error: built.error };
|
|
544
|
+
}
|
|
545
|
+
const tempDir = mkdtempSync(join(tmpdir(), "craft-pkg-"));
|
|
546
|
+
try {
|
|
547
|
+
const appsDir = join(tempDir, "Applications");
|
|
548
|
+
mkdirSync(appsDir, { recursive: true });
|
|
549
|
+
cpSync(opts.appBundlePath, join(appsDir, basename(opts.appBundlePath)), { recursive: true });
|
|
550
|
+
const built = await runTool("pkgbuild", [
|
|
551
|
+
"--root",
|
|
552
|
+
tempDir,
|
|
553
|
+
"--identifier",
|
|
554
|
+
opts.identifier,
|
|
555
|
+
"--version",
|
|
556
|
+
opts.version,
|
|
557
|
+
"--install-location",
|
|
558
|
+
"/",
|
|
559
|
+
...opts.installerIdentity ? ["--sign", opts.installerIdentity] : [],
|
|
560
|
+
opts.outputPath
|
|
561
|
+
]);
|
|
562
|
+
return built.success ? { success: true, outputPath: opts.outputPath } : { success: false, error: built.error };
|
|
563
|
+
} finally {
|
|
564
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
function xml(value) {
|
|
568
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll(`'`, "'");
|
|
569
|
+
}
|
|
570
|
+
function wixIdentifier(value) {
|
|
571
|
+
const sanitized = value.replace(/[^A-Za-z0-9_.]/g, "_");
|
|
572
|
+
return /^[A-Za-z_]/.test(sanitized) ? sanitized : `_${sanitized}`;
|
|
573
|
+
}
|
|
574
|
+
function deterministicGuid(value) {
|
|
575
|
+
const digest = createHash("sha256").update(value).digest("hex").slice(0, 32).split("");
|
|
576
|
+
digest[12] = "4";
|
|
577
|
+
digest[16] = (Number.parseInt(digest[16], 16) & 3 | 8).toString(16);
|
|
578
|
+
const hex = digest.join("").toUpperCase();
|
|
579
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
580
|
+
}
|
|
581
|
+
function windowsArchitecture(architecture) {
|
|
582
|
+
if (architecture === "ia32" || architecture === "x86")
|
|
583
|
+
return "x86";
|
|
584
|
+
if (architecture === "x64" || architecture === "arm64")
|
|
585
|
+
return architecture;
|
|
586
|
+
throw new Error(`Unsupported Windows package architecture: ${architecture}`);
|
|
587
|
+
}
|
|
588
|
+
function renderWixSource(opts, sourceName) {
|
|
589
|
+
if (!/^[0-9]+\.[0-9]+\.[0-9]+(?:\.[0-9]+)?$/.test(opts.version))
|
|
590
|
+
throw new Error(`MSI version must have 3 or 4 numeric parts: ${opts.version}`);
|
|
591
|
+
const id = wixIdentifier(opts.name);
|
|
592
|
+
const manufacturer = opts.manufacturer.trim() || "Unknown";
|
|
593
|
+
const upgradeCode = deterministicGuid(`${manufacturer}/${opts.name}`);
|
|
594
|
+
const programFilesFolder = opts.architecture === "x86" ? "ProgramFilesFolder" : "ProgramFiles64Folder";
|
|
595
|
+
const win64 = opts.architecture === "x86" ? "no" : "yes";
|
|
596
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
597
|
+
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
|
598
|
+
<Product Id="*" Name="${xml(opts.name)}" Language="1033" Version="${opts.version}" Manufacturer="${xml(manufacturer)}" UpgradeCode="${upgradeCode}">
|
|
599
|
+
<Package InstallerVersion="500" Compressed="yes" InstallScope="perMachine" Platform="${opts.architecture}" />
|
|
600
|
+
<MajorUpgrade AllowDowngrades="yes" />
|
|
601
|
+
<MediaTemplate EmbedCab="yes" />
|
|
602
|
+
<Directory Id="TARGETDIR" Name="SourceDir">
|
|
603
|
+
<Directory Id="${programFilesFolder}">
|
|
604
|
+
<Directory Id="INSTALLFOLDER" Name="${xml(opts.name)}">
|
|
605
|
+
<Component Id="${id}Executable" Guid="*" Win64="${win64}">
|
|
606
|
+
<File Id="${id}File" Source="${xml(sourceName)}" KeyPath="yes" />
|
|
607
|
+
</Component>
|
|
608
|
+
</Directory>
|
|
609
|
+
</Directory>
|
|
610
|
+
</Directory>
|
|
611
|
+
<Feature Id="ProductFeature" Title="${xml(opts.name)}" Level="1">
|
|
612
|
+
<ComponentRef Id="${id}Executable" />
|
|
613
|
+
</Feature>
|
|
614
|
+
</Product>
|
|
615
|
+
</Wix>
|
|
616
|
+
`;
|
|
617
|
+
}
|
|
618
|
+
function windowsExecutableName(name) {
|
|
619
|
+
if (name.length === 0 || name.trim() !== name || /[<>:"/\\|?*\u0000-\u001F]/.test(name) || /[. ]$/.test(name))
|
|
620
|
+
throw new Error(`Invalid Windows application name: ${JSON.stringify(name)}`);
|
|
621
|
+
if (/^(?:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(?:\.|$)/i.test(name))
|
|
622
|
+
throw new Error(`Reserved Windows application name: ${JSON.stringify(name)}`);
|
|
623
|
+
return `${name}.exe`;
|
|
624
|
+
}
|
|
625
|
+
function runCommand(command, args, cwd) {
|
|
626
|
+
return new Promise((resolve, reject) => {
|
|
627
|
+
const child = spawn(command, args, { cwd, stdio: "inherit", windowsHide: true });
|
|
628
|
+
child.once("error", reject);
|
|
629
|
+
child.once("close", (code) => code === 0 ? resolve() : reject(new Error(`${command} exited with code ${code}`)));
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
async function createMSI(opts) {
|
|
633
|
+
const tempDir = mkdtempSync(join(tmpdir(), "craft-msi-"));
|
|
634
|
+
try {
|
|
635
|
+
const binaryName = windowsExecutableName(opts.name);
|
|
636
|
+
const sourcePath = join(tempDir, binaryName);
|
|
637
|
+
const wxsPath = join(tempDir, "installer.wxs");
|
|
638
|
+
const wixobjPath = join(tempDir, "installer.wixobj");
|
|
639
|
+
copyFileSync(opts.binaryPath, sourcePath);
|
|
640
|
+
writeFileSync(wxsPath, renderWixSource(opts, binaryName));
|
|
641
|
+
await runCommand("candle.exe", ["-nologo", "-out", wixobjPath, wxsPath], tempDir);
|
|
642
|
+
await runCommand("light.exe", ["-nologo", "-sval", "-out", opts.outputPath, wixobjPath], tempDir);
|
|
643
|
+
if (opts.certificatePath) {
|
|
644
|
+
const signArgs = ["sign", "/fd", "sha256", "/tr", "https://timestamp.digicert.com", "/td", "sha256", "/f", opts.certificatePath];
|
|
645
|
+
if (opts.certificatePassword)
|
|
646
|
+
signArgs.push("/p", opts.certificatePassword);
|
|
647
|
+
signArgs.push(opts.outputPath);
|
|
648
|
+
await runCommand("signtool.exe", signArgs);
|
|
649
|
+
await runCommand("signtool.exe", ["verify", "/pa", "/all", opts.outputPath]);
|
|
650
|
+
}
|
|
651
|
+
return { success: true, outputPath: opts.outputPath };
|
|
652
|
+
} catch (error) {
|
|
653
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
654
|
+
const missingTool = /ENOENT|not found/i.test(message);
|
|
655
|
+
return { success: false, error: missingTool ? `WiX Toolset not found: ${message}` : message };
|
|
656
|
+
} finally {
|
|
657
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
async function createZIP(opts) {
|
|
661
|
+
try {
|
|
662
|
+
const data = new Uint8Array(readFileSync(opts.binaryPath));
|
|
663
|
+
const zip = buildZip([{ name: `${opts.name}.exe`, data }]);
|
|
664
|
+
writeFileSync(opts.outputPath, zip);
|
|
665
|
+
return { success: true, outputPath: opts.outputPath };
|
|
666
|
+
} catch (err) {
|
|
667
|
+
return { success: false, error: `Failed to write ZIP: ${err.message}` };
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
async function createDEB(opts) {
|
|
671
|
+
return new Promise((resolve) => {
|
|
672
|
+
try {
|
|
673
|
+
const sanitizedName = opts.name.toLowerCase();
|
|
674
|
+
const safeDebName = /^[a-z0-9._+-]+$/;
|
|
675
|
+
const safeVersion = /^[A-Za-z0-9._+-]+$/;
|
|
676
|
+
if (!safeDebName.test(sanitizedName)) {
|
|
677
|
+
resolve({ success: false, error: `DEB package name must match ${safeDebName} (got: ${sanitizedName})` });
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
if (!safeVersion.test(opts.version)) {
|
|
681
|
+
resolve({ success: false, error: `DEB version must match ${safeVersion} (got: ${opts.version})` });
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
const cleanLine = (s) => s.replace(/[\r\n]+/g, " ").trim();
|
|
685
|
+
const description = cleanLine(opts.description);
|
|
686
|
+
const maintainer = cleanLine(opts.maintainer);
|
|
687
|
+
for (const dep of opts.dependencies) {
|
|
688
|
+
if (!/^[a-z0-9._+-]+(?:\s*\([^)\n,]+\))?$/i.test(dep)) {
|
|
689
|
+
resolve({ success: false, error: `Invalid DEB dependency "${dep}"` });
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
const tempDir = mkdtempSync(join(tmpdir(), "craft-deb-"));
|
|
694
|
+
const debianDir = join(tempDir, "DEBIAN");
|
|
695
|
+
const binDir = join(tempDir, "usr", "bin");
|
|
696
|
+
const applicationsDir = join(tempDir, "usr", "share", "applications");
|
|
697
|
+
mkdirSync(debianDir, { recursive: true });
|
|
698
|
+
mkdirSync(binDir, { recursive: true });
|
|
699
|
+
mkdirSync(applicationsDir, { recursive: true });
|
|
700
|
+
const binaryName = sanitizedName;
|
|
701
|
+
copyFileSync(opts.binaryPath, join(binDir, binaryName));
|
|
702
|
+
chmodSync(join(binDir, binaryName), 493);
|
|
703
|
+
const controlContent = `Package: ${sanitizedName}
|
|
704
|
+
Version: ${opts.version}
|
|
705
|
+
Section: utils
|
|
706
|
+
Priority: optional
|
|
707
|
+
Architecture: amd64
|
|
708
|
+
Depends: ${opts.dependencies.join(", ")}
|
|
709
|
+
Maintainer: ${maintainer}
|
|
710
|
+
Description: ${description || opts.name}
|
|
711
|
+
`;
|
|
712
|
+
writeFileSync(join(debianDir, "control"), controlContent);
|
|
713
|
+
const desktopContent = `[Desktop Entry]
|
|
714
|
+
Type=Application
|
|
715
|
+
Name=${opts.name}
|
|
716
|
+
Exec=/usr/bin/${binaryName}
|
|
717
|
+
Terminal=false
|
|
718
|
+
Categories=Utility;
|
|
719
|
+
`;
|
|
720
|
+
writeFileSync(join(applicationsDir, `${binaryName}.desktop`), desktopContent);
|
|
721
|
+
const proc = spawn("dpkg-deb", ["--build", tempDir, opts.outputPath]);
|
|
722
|
+
proc.on("close", (code) => {
|
|
723
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
724
|
+
if (code === 0) {
|
|
725
|
+
resolve({ success: true, outputPath: opts.outputPath });
|
|
726
|
+
} else {
|
|
727
|
+
resolve({ success: false, error: `dpkg-deb exited with code ${code}` });
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
proc.on("error", (err) => {
|
|
731
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
732
|
+
resolve({ success: false, error: err.message });
|
|
733
|
+
});
|
|
734
|
+
} catch (error) {
|
|
735
|
+
resolve({ success: false, error: error.message });
|
|
736
|
+
}
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
async function createRPM(opts) {
|
|
740
|
+
return new Promise((resolve) => {
|
|
741
|
+
try {
|
|
742
|
+
const buildRoot = join(homedir(), "rpmbuild");
|
|
743
|
+
const specDir = join(buildRoot, "SPECS");
|
|
744
|
+
const sourcesDir = join(buildRoot, "SOURCES");
|
|
745
|
+
const buildDir = join(buildRoot, "BUILD");
|
|
746
|
+
const rpmsDir = join(buildRoot, "RPMS");
|
|
747
|
+
mkdirSync(specDir, { recursive: true });
|
|
748
|
+
mkdirSync(sourcesDir, { recursive: true });
|
|
749
|
+
mkdirSync(buildDir, { recursive: true });
|
|
750
|
+
mkdirSync(rpmsDir, { recursive: true });
|
|
751
|
+
const safePackageName = /^[a-z0-9._+-]+$/i;
|
|
752
|
+
const safeVersion = /^[A-Za-z0-9._+-]+$/;
|
|
753
|
+
const sanitizedName = opts.name.toLowerCase();
|
|
754
|
+
if (!safePackageName.test(sanitizedName)) {
|
|
755
|
+
return resolve({ success: false, error: `RPM package name must match ${safePackageName} (got: ${sanitizedName})` });
|
|
756
|
+
}
|
|
757
|
+
if (!safeVersion.test(opts.version)) {
|
|
758
|
+
return resolve({ success: false, error: `RPM version must match ${safeVersion} (got: ${opts.version})` });
|
|
759
|
+
}
|
|
760
|
+
const cleanLine = (s) => s.replace(/[\r\n]+/g, " ").replace(/%/g, "%%");
|
|
761
|
+
const summary = cleanLine(opts.description || opts.name);
|
|
762
|
+
const description = (opts.description || opts.name).split(/\r?\n/).map((line) => line.replace(/%/g, "%%")).join(`
|
|
763
|
+
`);
|
|
764
|
+
const requires = opts.requires.map((r) => {
|
|
765
|
+
if (!safePackageName.test(r)) {
|
|
766
|
+
throw new Error(`RPM Requires entry must match ${safePackageName} (got: ${r})`);
|
|
767
|
+
}
|
|
768
|
+
return r;
|
|
769
|
+
}).join(", ");
|
|
770
|
+
copyFileSync(opts.binaryPath, join(sourcesDir, sanitizedName));
|
|
771
|
+
const specContent = `Name: ${sanitizedName}
|
|
772
|
+
Version: ${opts.version}
|
|
773
|
+
Release: 1%{?dist}
|
|
774
|
+
Summary: ${summary}
|
|
775
|
+
License: MIT
|
|
776
|
+
Requires: ${requires}
|
|
777
|
+
|
|
778
|
+
%description
|
|
779
|
+
${description}
|
|
780
|
+
|
|
781
|
+
%install
|
|
782
|
+
mkdir -p %{buildroot}/usr/bin
|
|
783
|
+
install -m 755 %{SOURCE0} %{buildroot}/usr/bin/${sanitizedName}
|
|
784
|
+
|
|
785
|
+
%files
|
|
786
|
+
/usr/bin/${sanitizedName}
|
|
787
|
+
`;
|
|
788
|
+
const specPath = join(specDir, `${opts.name.toLowerCase()}.spec`);
|
|
789
|
+
writeFileSync(specPath, specContent);
|
|
790
|
+
const proc = spawn("rpmbuild", ["-bb", specPath]);
|
|
791
|
+
proc.on("close", (code) => {
|
|
792
|
+
if (code === 0) {
|
|
793
|
+
const rpmName = `${opts.name.toLowerCase()}-${opts.version}-1.x86_64.rpm`;
|
|
794
|
+
const builtRpmPath = join(rpmsDir, "x86_64", rpmName);
|
|
795
|
+
try {
|
|
796
|
+
copyFileSync(builtRpmPath, opts.outputPath);
|
|
797
|
+
resolve({ success: true, outputPath: opts.outputPath });
|
|
798
|
+
} catch {
|
|
799
|
+
resolve({ success: false, error: "Failed to copy built RPM" });
|
|
800
|
+
}
|
|
801
|
+
} else {
|
|
802
|
+
resolve({ success: false, error: `rpmbuild exited with code ${code}` });
|
|
803
|
+
}
|
|
804
|
+
});
|
|
805
|
+
proc.on("error", (err) => {
|
|
806
|
+
resolve({ success: false, error: err.message });
|
|
807
|
+
});
|
|
808
|
+
} catch (error) {
|
|
809
|
+
resolve({ success: false, error: error.message });
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
async function createAppImage(opts) {
|
|
814
|
+
return new Promise((resolve) => {
|
|
815
|
+
try {
|
|
816
|
+
const appDir = mkdtempSync(join(tmpdir(), "craft-appimage-"));
|
|
817
|
+
const appDirPath = join(appDir, `${opts.name}.AppDir`);
|
|
818
|
+
const binDir = join(appDirPath, "usr", "bin");
|
|
819
|
+
const shareDir = join(appDirPath, "usr", "share");
|
|
820
|
+
mkdirSync(binDir, { recursive: true });
|
|
821
|
+
mkdirSync(shareDir, { recursive: true });
|
|
822
|
+
const binaryName = opts.name.toLowerCase();
|
|
823
|
+
copyFileSync(opts.binaryPath, join(binDir, binaryName));
|
|
824
|
+
chmodSync(join(binDir, binaryName), 493);
|
|
825
|
+
const appRunContent = `#!/bin/bash
|
|
826
|
+
SELF=$(readlink -f "$0")
|
|
827
|
+
HERE=\${SELF%/*}
|
|
828
|
+
export PATH="\${HERE}/usr/bin/:\${PATH}"
|
|
829
|
+
exec "\${HERE}/usr/bin/${binaryName}" "$@"
|
|
830
|
+
`;
|
|
831
|
+
writeFileSync(join(appDirPath, "AppRun"), appRunContent);
|
|
832
|
+
chmodSync(join(appDirPath, "AppRun"), 493);
|
|
833
|
+
const desktopContent = `[Desktop Entry]
|
|
834
|
+
Type=Application
|
|
835
|
+
Name=${opts.name}
|
|
836
|
+
Exec=${binaryName}
|
|
837
|
+
Terminal=false
|
|
838
|
+
Categories=Utility;
|
|
839
|
+
Icon=${binaryName}
|
|
840
|
+
`;
|
|
841
|
+
writeFileSync(join(appDirPath, `${binaryName}.desktop`), desktopContent);
|
|
842
|
+
if (opts.iconPath && existsSync3(opts.iconPath)) {
|
|
843
|
+
copyFileSync(opts.iconPath, join(appDirPath, `${binaryName}.png`));
|
|
844
|
+
} else {
|
|
845
|
+
const placeholderPng = Buffer.from([
|
|
846
|
+
137,
|
|
847
|
+
80,
|
|
848
|
+
78,
|
|
849
|
+
71,
|
|
850
|
+
13,
|
|
851
|
+
10,
|
|
852
|
+
26,
|
|
853
|
+
10,
|
|
854
|
+
0,
|
|
855
|
+
0,
|
|
856
|
+
0,
|
|
857
|
+
13,
|
|
858
|
+
73,
|
|
859
|
+
72,
|
|
860
|
+
68,
|
|
861
|
+
82,
|
|
862
|
+
0,
|
|
863
|
+
0,
|
|
864
|
+
0,
|
|
865
|
+
1,
|
|
866
|
+
0,
|
|
867
|
+
0,
|
|
868
|
+
0,
|
|
869
|
+
1,
|
|
870
|
+
8,
|
|
871
|
+
2,
|
|
872
|
+
0,
|
|
873
|
+
0,
|
|
874
|
+
0,
|
|
875
|
+
144,
|
|
876
|
+
119,
|
|
877
|
+
83,
|
|
878
|
+
222,
|
|
879
|
+
0,
|
|
880
|
+
0,
|
|
881
|
+
0,
|
|
882
|
+
12,
|
|
883
|
+
73,
|
|
884
|
+
68,
|
|
885
|
+
65,
|
|
886
|
+
84,
|
|
887
|
+
8,
|
|
888
|
+
215,
|
|
889
|
+
99,
|
|
890
|
+
248,
|
|
891
|
+
255,
|
|
892
|
+
255,
|
|
893
|
+
63,
|
|
894
|
+
0,
|
|
895
|
+
5,
|
|
896
|
+
254,
|
|
897
|
+
2,
|
|
898
|
+
254,
|
|
899
|
+
220,
|
|
900
|
+
204,
|
|
901
|
+
89,
|
|
902
|
+
231,
|
|
903
|
+
0,
|
|
904
|
+
0,
|
|
905
|
+
0,
|
|
906
|
+
0,
|
|
907
|
+
73,
|
|
908
|
+
69,
|
|
909
|
+
78,
|
|
910
|
+
68,
|
|
911
|
+
174,
|
|
912
|
+
66,
|
|
913
|
+
96,
|
|
914
|
+
130
|
|
915
|
+
]);
|
|
916
|
+
writeFileSync(join(appDirPath, `${binaryName}.png`), placeholderPng);
|
|
917
|
+
}
|
|
918
|
+
const proc = spawn("appimagetool", [appDirPath, opts.outputPath], {
|
|
919
|
+
env: { ...process.env, ARCH: "x86_64" }
|
|
920
|
+
});
|
|
921
|
+
proc.on("close", (code) => {
|
|
922
|
+
rmSync(appDir, { recursive: true, force: true });
|
|
923
|
+
if (code === 0) {
|
|
924
|
+
chmodSync(opts.outputPath, 493);
|
|
925
|
+
resolve({ success: true, outputPath: opts.outputPath });
|
|
926
|
+
} else {
|
|
927
|
+
resolve({ success: false, error: `appimagetool exited with code ${code}. Install from https://appimage.github.io/appimagetool/` });
|
|
928
|
+
}
|
|
929
|
+
});
|
|
930
|
+
proc.on("error", (err) => {
|
|
931
|
+
rmSync(appDir, { recursive: true, force: true });
|
|
932
|
+
resolve({ success: false, error: `appimagetool not found: ${err.message}` });
|
|
933
|
+
});
|
|
934
|
+
} catch (error) {
|
|
935
|
+
resolve({ success: false, error: error.message });
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
function detectPlatform() {
|
|
940
|
+
switch (process.platform) {
|
|
941
|
+
case "darwin":
|
|
942
|
+
return "macos";
|
|
943
|
+
case "win32":
|
|
944
|
+
return "windows";
|
|
945
|
+
default:
|
|
946
|
+
return "linux";
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
async function pack(options) {
|
|
950
|
+
return packageApp({
|
|
951
|
+
...options,
|
|
952
|
+
platforms: [detectPlatform()]
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
var CRC32_TABLE, MEBIBYTE, HDIUTIL_MAX_ATTEMPTS = 3, runHdiutil = (args) => runTool("hdiutil", args);
|
|
956
|
+
var init_package = __esm(() => {
|
|
957
|
+
CRC32_TABLE = (() => {
|
|
958
|
+
const table = new Uint32Array(256);
|
|
959
|
+
for (let n = 0;n < 256; n++) {
|
|
960
|
+
let c = n;
|
|
961
|
+
for (let k = 0;k < 8; k++)
|
|
962
|
+
c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
963
|
+
table[n] = c >>> 0;
|
|
964
|
+
}
|
|
965
|
+
return table;
|
|
966
|
+
})();
|
|
967
|
+
MEBIBYTE = 1024 * 1024;
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
// dist/ios/src/index.js
|
|
971
|
+
var exports_src = {};
|
|
972
|
+
__export(exports_src, {
|
|
973
|
+
run: () => run,
|
|
974
|
+
open: () => open,
|
|
975
|
+
init: () => init,
|
|
976
|
+
build: () => build
|
|
977
|
+
});
|
|
978
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync4, readdirSync as readdirSync2 } from "fs";
|
|
979
|
+
import { join as join2, dirname } from "path";
|
|
980
|
+
async function init(options) {
|
|
981
|
+
const { name, bundleId, teamId, output } = options;
|
|
982
|
+
console.log(`
|
|
983
|
+
\u26A1 Initializing Craft iOS project: ${name}`);
|
|
984
|
+
console.log(` Output: ${output}
|
|
985
|
+
`);
|
|
986
|
+
const dirs = [output, join2(output, "Sources"), join2(output, "dist")];
|
|
987
|
+
for (const dir of dirs) {
|
|
988
|
+
if (!existsSync4(dir)) {
|
|
989
|
+
mkdirSync2(dir, { recursive: true });
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
const finalBundleId = bundleId || `com.craft.${name.toLowerCase().replace(/[^a-z0-9]/g, "")}`;
|
|
993
|
+
const bundleIdPrefix = finalBundleId.split(".").slice(0, -1).join(".");
|
|
994
|
+
const config = {
|
|
995
|
+
appName: name,
|
|
996
|
+
bundleId: finalBundleId,
|
|
997
|
+
version: "1.0.0",
|
|
998
|
+
buildNumber: "1",
|
|
999
|
+
darkMode: true,
|
|
1000
|
+
backgroundColor: "#1a1a2e",
|
|
1001
|
+
enableSpeechRecognition: true,
|
|
1002
|
+
enableHaptics: true,
|
|
1003
|
+
enableShare: true,
|
|
1004
|
+
iosVersion: "15.0",
|
|
1005
|
+
teamId: teamId || ""
|
|
1006
|
+
};
|
|
1007
|
+
writeFileSync2(join2(output, "craft.config.json"), JSON.stringify(config, null, 2));
|
|
1008
|
+
const swiftTemplate = readFileSync2(join2(TEMPLATES_DIR, "CraftApp.swift"), "utf-8");
|
|
1009
|
+
writeFileSync2(join2(output, "Sources", `${name}App.swift`), swiftTemplate.replace(/CraftApp/g, `${name}App`));
|
|
1010
|
+
const infoPlistTemplate = readFileSync2(join2(TEMPLATES_DIR, "Info.plist.template"), "utf-8");
|
|
1011
|
+
const infoPlist = infoPlistTemplate.replace(/\{\{APP_NAME\}\}/g, name).replace(/\{\{BUNDLE_ID\}\}/g, finalBundleId).replace(/\{\{VERSION\}\}/g, config.version || "1.0.0").replace(/\{\{BUILD_NUMBER\}\}/g, config.buildNumber || "1").replace(/\{\{UI_STYLE\}\}/g, config.darkMode ? "Dark" : "Light");
|
|
1012
|
+
writeFileSync2(join2(output, "Info.plist"), infoPlist);
|
|
1013
|
+
const projectYmlTemplate = readFileSync2(join2(TEMPLATES_DIR, "project.yml.template"), "utf-8");
|
|
1014
|
+
const projectYml = projectYmlTemplate.replace(/\{\{APP_NAME\}\}/g, name).replace(/\{\{BUNDLE_ID\}\}/g, finalBundleId).replace(/\{\{BUNDLE_ID_PREFIX\}\}/g, bundleIdPrefix).replace(/\{\{VERSION\}\}/g, config.version || "1.0.0").replace(/\{\{BUILD_NUMBER\}\}/g, config.buildNumber || "1").replace(/\{\{IOS_VERSION\}\}/g, config.iosVersion || "15.0").replace(/\{\{TEAM_ID\}\}/g, teamId || "");
|
|
1015
|
+
writeFileSync2(join2(output, "project.yml"), projectYml);
|
|
1016
|
+
const placeholderHtml = `<!DOCTYPE html>
|
|
1017
|
+
<html>
|
|
1018
|
+
<head>
|
|
1019
|
+
<meta charset="UTF-8">
|
|
1020
|
+
<meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
1021
|
+
<title>${name}</title>
|
|
1022
|
+
<style>
|
|
1023
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
1024
|
+
body {
|
|
1025
|
+
font-family: -apple-system, system-ui, sans-serif;
|
|
1026
|
+
background: ${config.backgroundColor};
|
|
1027
|
+
color: white;
|
|
1028
|
+
min-height: 100vh;
|
|
1029
|
+
display: flex;
|
|
1030
|
+
justify-content: center;
|
|
1031
|
+
align-items: center;
|
|
1032
|
+
padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
|
|
1033
|
+
}
|
|
1034
|
+
.container { text-align: center; padding: 2rem; }
|
|
1035
|
+
h1 { font-size: 2.5rem; margin-bottom: 1rem; }
|
|
1036
|
+
p { opacity: 0.7; }
|
|
1037
|
+
.ready { color: #4ade80; font-size: 0.9rem; margin-top: 2rem; }
|
|
1038
|
+
</style>
|
|
1039
|
+
</head>
|
|
1040
|
+
<body>
|
|
1041
|
+
<div class="container">
|
|
1042
|
+
<h1>\u26A1 ${name}</h1>
|
|
1043
|
+
<p>Built with Craft iOS</p>
|
|
1044
|
+
<p class="ready" id="status">Waiting for Craft bridge...</p>
|
|
1045
|
+
</div>
|
|
1046
|
+
<script>
|
|
1047
|
+
window.addEventListener('craftReady', (e) => {
|
|
1048
|
+
document.getElementById('status').textContent = \`\u2713 Craft bridge ready (platform: \${e.detail.platform})\`;
|
|
1049
|
+
console.log('Craft capabilities:', e.detail.capabilities);
|
|
1050
|
+
});
|
|
1051
|
+
</script>
|
|
1052
|
+
</body>
|
|
1053
|
+
</html>`;
|
|
1054
|
+
writeFileSync2(join2(output, "dist", "index.html"), placeholderHtml);
|
|
1055
|
+
console.log("\u2705 Project initialized");
|
|
1056
|
+
console.log("");
|
|
1057
|
+
console.log("Next steps:");
|
|
1058
|
+
console.log(` 1. cd ${output}`);
|
|
1059
|
+
console.log(" 2. Add your web content to dist/index.html");
|
|
1060
|
+
console.log(" 3. Run: craft ios build");
|
|
1061
|
+
console.log(" 4. Run: craft ios open");
|
|
1062
|
+
console.log("");
|
|
1063
|
+
}
|
|
1064
|
+
async function build(options) {
|
|
1065
|
+
const { htmlPath, devServer, output } = options;
|
|
1066
|
+
console.log(`
|
|
1067
|
+
\uD83D\uDCE6 Building Craft iOS project...`);
|
|
1068
|
+
const configPath = join2(output, "craft.config.json");
|
|
1069
|
+
if (!existsSync4(configPath)) {
|
|
1070
|
+
throw new Error(`No craft.config.json found in ${output}. Run 'craft ios init' first.`);
|
|
1071
|
+
}
|
|
1072
|
+
const config = JSON.parse(readFileSync2(configPath, "utf-8"));
|
|
1073
|
+
if (devServer) {
|
|
1074
|
+
config.devServerURL = devServer;
|
|
1075
|
+
writeFileSync2(configPath, JSON.stringify(config, null, 2));
|
|
1076
|
+
console.log(` Dev server: ${devServer}`);
|
|
1077
|
+
}
|
|
1078
|
+
if (htmlPath) {
|
|
1079
|
+
const distDir = join2(output, "dist");
|
|
1080
|
+
if (!existsSync4(distDir)) {
|
|
1081
|
+
mkdirSync2(distDir, { recursive: true });
|
|
1082
|
+
}
|
|
1083
|
+
if (existsSync4(htmlPath)) {
|
|
1084
|
+
const stat = await Bun.file(htmlPath).exists();
|
|
1085
|
+
if (stat) {
|
|
1086
|
+
const html = readFileSync2(htmlPath, "utf-8");
|
|
1087
|
+
writeFileSync2(join2(distDir, "index.html"), html);
|
|
1088
|
+
console.log(` Copied: ${htmlPath} \u2192 dist/index.html`);
|
|
1089
|
+
}
|
|
1090
|
+
} else {
|
|
1091
|
+
throw new Error(`HTML path not found: ${htmlPath}`);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
try {
|
|
1095
|
+
const result = await $`which xcodegen`.quiet();
|
|
1096
|
+
if (result.exitCode === 0) {
|
|
1097
|
+
console.log(" Running xcodegen...");
|
|
1098
|
+
await $`cd ${output} && xcodegen generate`.quiet();
|
|
1099
|
+
console.log(`\u2705 Xcode project created: ${config.appName}.xcodeproj`);
|
|
1100
|
+
} else {
|
|
1101
|
+
throw new Error("xcodegen not found");
|
|
1102
|
+
}
|
|
1103
|
+
} catch {
|
|
1104
|
+
console.log("\u26A0\uFE0F xcodegen not found. Install with: brew install xcodegen");
|
|
1105
|
+
console.log(" Then run: craft ios build");
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
console.log("");
|
|
1109
|
+
}
|
|
1110
|
+
async function open(options) {
|
|
1111
|
+
const { output } = options;
|
|
1112
|
+
const files = readdirSync2(output);
|
|
1113
|
+
const xcodeproj = files.find((f) => f.endsWith(".xcodeproj"));
|
|
1114
|
+
if (!xcodeproj) {
|
|
1115
|
+
throw new Error(`No Xcode project found in ${output}. Run 'craft ios build' first.`);
|
|
1116
|
+
}
|
|
1117
|
+
const projectPath = join2(output, xcodeproj);
|
|
1118
|
+
console.log(`\uD83D\uDE80 Opening ${xcodeproj}...`);
|
|
1119
|
+
await $`open ${projectPath}`;
|
|
1120
|
+
}
|
|
1121
|
+
async function run(options) {
|
|
1122
|
+
const { simulator, output } = options;
|
|
1123
|
+
await build({ output });
|
|
1124
|
+
const files = readdirSync2(output);
|
|
1125
|
+
const xcodeproj = files.find((f) => f.endsWith(".xcodeproj"));
|
|
1126
|
+
if (!xcodeproj) {
|
|
1127
|
+
throw new Error(`No Xcode project found in ${output}`);
|
|
1128
|
+
}
|
|
1129
|
+
const projectPath = join2(output, xcodeproj);
|
|
1130
|
+
const appName = xcodeproj.replace(".xcodeproj", "");
|
|
1131
|
+
if (simulator) {
|
|
1132
|
+
console.log("\uD83D\uDCF1 Building and running on simulator...");
|
|
1133
|
+
try {
|
|
1134
|
+
await $`xcodebuild -project ${projectPath} -scheme ${appName} -destination 'platform=iOS Simulator,name=iPhone 15' build`;
|
|
1135
|
+
await $`xcrun simctl boot "iPhone 15"`.quiet().nothrow();
|
|
1136
|
+
await $`open -a Simulator`;
|
|
1137
|
+
console.log("\u2705 App deployed to simulator");
|
|
1138
|
+
} catch (error) {
|
|
1139
|
+
console.error("Build failed. Open in Xcode for details:", projectPath);
|
|
1140
|
+
}
|
|
1141
|
+
} else {
|
|
1142
|
+
console.log("\uD83D\uDCF1 Opening Xcode for device deployment...");
|
|
1143
|
+
await $`open ${projectPath}`;
|
|
1144
|
+
console.log("");
|
|
1145
|
+
console.log("In Xcode:");
|
|
1146
|
+
console.log(" 1. Select your Team in Signing & Capabilities");
|
|
1147
|
+
console.log(" 2. Connect your iPhone");
|
|
1148
|
+
console.log(" 3. Select your device");
|
|
1149
|
+
console.log(" 4. Click Run (\u25B6\uFE0F)");
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
var $, TEMPLATES_DIR;
|
|
1153
|
+
var init_src = __esm(() => {
|
|
1154
|
+
({ $ } = globalThis.Bun);
|
|
1155
|
+
TEMPLATES_DIR = join2(dirname(import.meta.dir), "templates");
|
|
1156
|
+
});
|
|
1157
|
+
|
|
1158
|
+
// dist/android/src/index.js
|
|
1159
|
+
var exports_src2 = {};
|
|
1160
|
+
__export(exports_src2, {
|
|
1161
|
+
run: () => run2,
|
|
1162
|
+
open: () => open2,
|
|
1163
|
+
init: () => init2,
|
|
1164
|
+
build: () => build2
|
|
1165
|
+
});
|
|
1166
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync5, statSync } from "fs";
|
|
1167
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
1168
|
+
async function init2(options) {
|
|
1169
|
+
const { name, packageName, output } = options;
|
|
1170
|
+
console.log(`
|
|
1171
|
+
\u26A1 Initializing Craft Android project: ${name}`);
|
|
1172
|
+
console.log(` Output: ${output}
|
|
1173
|
+
`);
|
|
1174
|
+
const finalPackageName = packageName || `com.craft.${name.toLowerCase().replace(/[^a-z0-9]/g, "")}`;
|
|
1175
|
+
const packagePath = finalPackageName.replace(/\./g, "/");
|
|
1176
|
+
const dirs = [
|
|
1177
|
+
output,
|
|
1178
|
+
join3(output, "app/src/main/java", packagePath),
|
|
1179
|
+
join3(output, "app/src/main/res/layout"),
|
|
1180
|
+
join3(output, "app/src/main/res/values"),
|
|
1181
|
+
join3(output, "app/src/main/res/drawable"),
|
|
1182
|
+
join3(output, "app/src/main/assets"),
|
|
1183
|
+
join3(output, "gradle/wrapper")
|
|
1184
|
+
];
|
|
1185
|
+
for (const dir of dirs) {
|
|
1186
|
+
if (!existsSync5(dir)) {
|
|
1187
|
+
mkdirSync3(dir, { recursive: true });
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
const config = {
|
|
1191
|
+
appName: name,
|
|
1192
|
+
packageName: finalPackageName,
|
|
1193
|
+
version: "1.0.0",
|
|
1194
|
+
versionCode: 1,
|
|
1195
|
+
darkMode: true,
|
|
1196
|
+
backgroundColor: "#1a1a2e",
|
|
1197
|
+
enableSpeechRecognition: true,
|
|
1198
|
+
enableHaptics: true,
|
|
1199
|
+
enableShare: true,
|
|
1200
|
+
enableCamera: true,
|
|
1201
|
+
enableBiometric: true,
|
|
1202
|
+
enablePushNotifications: false,
|
|
1203
|
+
minSdk: 24,
|
|
1204
|
+
targetSdk: 34
|
|
1205
|
+
};
|
|
1206
|
+
writeFileSync3(join3(output, "craft.config.json"), JSON.stringify(config, null, 2));
|
|
1207
|
+
const mainActivityTemplate = readFileSync3(join3(TEMPLATES_DIR2, "MainActivity.kt.template"), "utf-8");
|
|
1208
|
+
const mainActivity = mainActivityTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{APP_NAME\}\}/g, name);
|
|
1209
|
+
writeFileSync3(join3(output, "app/src/main/java", packagePath, "MainActivity.kt"), mainActivity);
|
|
1210
|
+
const craftBridgeTemplate = readFileSync3(join3(TEMPLATES_DIR2, "CraftBridge.kt.template"), "utf-8");
|
|
1211
|
+
const craftBridge = craftBridgeTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName);
|
|
1212
|
+
writeFileSync3(join3(output, "app/src/main/java", packagePath, "CraftBridge.kt"), craftBridge);
|
|
1213
|
+
const manifestTemplate = readFileSync3(join3(TEMPLATES_DIR2, "AndroidManifest.xml.template"), "utf-8");
|
|
1214
|
+
const manifest = manifestTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{APP_NAME\}\}/g, name);
|
|
1215
|
+
writeFileSync3(join3(output, "app/src/main/AndroidManifest.xml"), manifest);
|
|
1216
|
+
const projectGradleTemplate = readFileSync3(join3(TEMPLATES_DIR2, "build.gradle.kts.project.template"), "utf-8");
|
|
1217
|
+
writeFileSync3(join3(output, "build.gradle.kts"), projectGradleTemplate);
|
|
1218
|
+
const appGradleTemplate = readFileSync3(join3(TEMPLATES_DIR2, "build.gradle.kts.app.template"), "utf-8");
|
|
1219
|
+
const appGradle = appGradleTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{VERSION_NAME\}\}/g, config.version || "1.0.0").replace(/\{\{VERSION_CODE\}\}/g, String(config.versionCode || 1)).replace(/\{\{MIN_SDK\}\}/g, String(config.minSdk || 24)).replace(/\{\{TARGET_SDK\}\}/g, String(config.targetSdk || 34));
|
|
1220
|
+
writeFileSync3(join3(output, "app/build.gradle.kts"), appGradle);
|
|
1221
|
+
const settingsTemplate = readFileSync3(join3(TEMPLATES_DIR2, "settings.gradle.kts.template"), "utf-8");
|
|
1222
|
+
const settings2 = settingsTemplate.replace(/\{\{APP_NAME\}\}/g, name);
|
|
1223
|
+
writeFileSync3(join3(output, "settings.gradle.kts"), settings2);
|
|
1224
|
+
const gradleProperties = `org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
|
1225
|
+
android.useAndroidX=true
|
|
1226
|
+
kotlin.code.style=official
|
|
1227
|
+
android.nonTransitiveRClass=true
|
|
1228
|
+
`;
|
|
1229
|
+
writeFileSync3(join3(output, "gradle.properties"), gradleProperties);
|
|
1230
|
+
writeFileSync3(join3(output, "local.properties"), `# SDK location will be set by Android Studio
|
|
1231
|
+
`);
|
|
1232
|
+
const gradleWrapperProps = `distributionBase=GRADLE_USER_HOME
|
|
1233
|
+
distributionPath=wrapper/dists
|
|
1234
|
+
distributionUrl=https\\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
|
1235
|
+
networkTimeout=10000
|
|
1236
|
+
validateDistributionUrl=true
|
|
1237
|
+
zipStoreBase=GRADLE_USER_HOME
|
|
1238
|
+
zipStorePath=wrapper/dists
|
|
1239
|
+
`;
|
|
1240
|
+
writeFileSync3(join3(output, "gradle/wrapper/gradle-wrapper.properties"), gradleWrapperProps);
|
|
1241
|
+
const stringsXml = `<?xml version="1.0" encoding="utf-8"?>
|
|
1242
|
+
<resources>
|
|
1243
|
+
<string name="app_name">${name}</string>
|
|
1244
|
+
</resources>
|
|
1245
|
+
`;
|
|
1246
|
+
writeFileSync3(join3(output, "app/src/main/res/values/strings.xml"), stringsXml);
|
|
1247
|
+
const colorsXml = `<?xml version="1.0" encoding="utf-8"?>
|
|
1248
|
+
<resources>
|
|
1249
|
+
<color name="primary">#1a1a2e</color>
|
|
1250
|
+
<color name="primary_dark">#16162b</color>
|
|
1251
|
+
<color name="accent">#4ade80</color>
|
|
1252
|
+
<color name="background">${config.backgroundColor}</color>
|
|
1253
|
+
</resources>
|
|
1254
|
+
`;
|
|
1255
|
+
writeFileSync3(join3(output, "app/src/main/res/values/colors.xml"), colorsXml);
|
|
1256
|
+
const themesXml = `<?xml version="1.0" encoding="utf-8"?>
|
|
1257
|
+
<resources>
|
|
1258
|
+
<style name="Theme.CraftApp" parent="Theme.Material3.DayNight.NoActionBar">
|
|
1259
|
+
<item name="android:statusBarColor">@color/primary_dark</item>
|
|
1260
|
+
<item name="android:navigationBarColor">@color/primary</item>
|
|
1261
|
+
<item name="android:windowBackground">@color/background</item>
|
|
1262
|
+
</style>
|
|
1263
|
+
</resources>
|
|
1264
|
+
`;
|
|
1265
|
+
writeFileSync3(join3(output, "app/src/main/res/values/themes.xml"), themesXml);
|
|
1266
|
+
const activityMainXml = `<?xml version="1.0" encoding="utf-8"?>
|
|
1267
|
+
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
1268
|
+
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
1269
|
+
android:layout_width="match_parent"
|
|
1270
|
+
android:layout_height="match_parent"
|
|
1271
|
+
android:fitsSystemWindows="true">
|
|
1272
|
+
|
|
1273
|
+
<WebView
|
|
1274
|
+
android:id="@+id/webview"
|
|
1275
|
+
android:layout_width="match_parent"
|
|
1276
|
+
android:layout_height="match_parent" />
|
|
1277
|
+
|
|
1278
|
+
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
1279
|
+
`;
|
|
1280
|
+
writeFileSync3(join3(output, "app/src/main/res/layout/activity_main.xml"), activityMainXml);
|
|
1281
|
+
const placeholderHtml = `<!DOCTYPE html>
|
|
1282
|
+
<html>
|
|
1283
|
+
<head>
|
|
1284
|
+
<meta charset="UTF-8">
|
|
1285
|
+
<meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
1286
|
+
<title>${name}</title>
|
|
1287
|
+
<style>
|
|
1288
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
1289
|
+
body {
|
|
1290
|
+
font-family: 'Roboto', sans-serif;
|
|
1291
|
+
background: ${config.backgroundColor};
|
|
1292
|
+
color: white;
|
|
1293
|
+
min-height: 100vh;
|
|
1294
|
+
display: flex;
|
|
1295
|
+
justify-content: center;
|
|
1296
|
+
align-items: center;
|
|
1297
|
+
}
|
|
1298
|
+
.container { text-align: center; padding: 2rem; }
|
|
1299
|
+
h1 { font-size: 2.5rem; margin-bottom: 1rem; }
|
|
1300
|
+
p { opacity: 0.7; }
|
|
1301
|
+
.ready { color: #4ade80; font-size: 0.9rem; margin-top: 2rem; }
|
|
1302
|
+
</style>
|
|
1303
|
+
</head>
|
|
1304
|
+
<body>
|
|
1305
|
+
<div class="container">
|
|
1306
|
+
<h1>\u26A1 ${name}</h1>
|
|
1307
|
+
<p>Built with Craft Android</p>
|
|
1308
|
+
<p class="ready" id="status">Waiting for Craft bridge...</p>
|
|
1309
|
+
</div>
|
|
1310
|
+
<script>
|
|
1311
|
+
window.addEventListener('craftReady', (e) => {
|
|
1312
|
+
document.getElementById('status').textContent = \`\u2713 Craft bridge ready (platform: \${e.detail.platform})\`;
|
|
1313
|
+
console.log('Craft capabilities:', e.detail.capabilities);
|
|
1314
|
+
});
|
|
1315
|
+
</script>
|
|
1316
|
+
</body>
|
|
1317
|
+
</html>`;
|
|
1318
|
+
writeFileSync3(join3(output, "app/src/main/assets/index.html"), placeholderHtml);
|
|
1319
|
+
console.log("\u2705 Project initialized");
|
|
1320
|
+
console.log("");
|
|
1321
|
+
console.log("Next steps:");
|
|
1322
|
+
console.log(` 1. cd ${output}`);
|
|
1323
|
+
console.log(" 2. Add your web content to app/src/main/assets/index.html");
|
|
1324
|
+
console.log(" 3. Run: craft android build");
|
|
1325
|
+
console.log(" 4. Run: craft android open");
|
|
1326
|
+
console.log("");
|
|
1327
|
+
}
|
|
1328
|
+
async function build2(options) {
|
|
1329
|
+
const { htmlPath, devServer, output, release } = options;
|
|
1330
|
+
console.log(`
|
|
1331
|
+
\uD83D\uDCE6 Building Craft Android project...`);
|
|
1332
|
+
const configPath = join3(output, "craft.config.json");
|
|
1333
|
+
if (!existsSync5(configPath)) {
|
|
1334
|
+
throw new Error(`No craft.config.json found in ${output}. Run 'craft android init' first.`);
|
|
1335
|
+
}
|
|
1336
|
+
const config = JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
1337
|
+
if (devServer) {
|
|
1338
|
+
config.devServerURL = devServer;
|
|
1339
|
+
writeFileSync3(configPath, JSON.stringify(config, null, 2));
|
|
1340
|
+
console.log(` Dev server: ${devServer}`);
|
|
1341
|
+
}
|
|
1342
|
+
if (htmlPath) {
|
|
1343
|
+
const assetsDir = join3(output, "app/src/main/assets");
|
|
1344
|
+
if (!existsSync5(assetsDir)) {
|
|
1345
|
+
mkdirSync3(assetsDir, { recursive: true });
|
|
1346
|
+
}
|
|
1347
|
+
if (existsSync5(htmlPath) && statSync(htmlPath).isFile()) {
|
|
1348
|
+
const html = readFileSync3(htmlPath, "utf-8");
|
|
1349
|
+
writeFileSync3(join3(assetsDir, "index.html"), html);
|
|
1350
|
+
console.log(` Copied: ${htmlPath} \u2192 assets/index.html`);
|
|
1351
|
+
} else {
|
|
1352
|
+
throw new Error(`HTML file not found or is a directory: ${htmlPath}`);
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
const gradlewPath = join3(output, "gradlew");
|
|
1356
|
+
if (!existsSync5(gradlewPath)) {
|
|
1357
|
+
console.log(" Generating Gradle wrapper...");
|
|
1358
|
+
try {
|
|
1359
|
+
await $2`cd ${output} && gradle wrapper`.quiet();
|
|
1360
|
+
} catch {
|
|
1361
|
+
console.log("\u26A0\uFE0F Gradle not found. Please install Android Studio or Gradle.");
|
|
1362
|
+
console.log(" Download: https://developer.android.com/studio");
|
|
1363
|
+
return;
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
const buildType = release ? "assembleRelease" : "assembleDebug";
|
|
1367
|
+
console.log(` Building ${release ? "release" : "debug"} APK...`);
|
|
1368
|
+
try {
|
|
1369
|
+
await $2`cd ${output} && ./gradlew ${buildType}`;
|
|
1370
|
+
const apkPath = release ? "app/build/outputs/apk/release/app-release.apk" : "app/build/outputs/apk/debug/app-debug.apk";
|
|
1371
|
+
console.log(`\u2705 APK built: ${apkPath}`);
|
|
1372
|
+
} catch (error) {
|
|
1373
|
+
console.error("Build failed. Open in Android Studio for details.");
|
|
1374
|
+
throw error;
|
|
1375
|
+
}
|
|
1376
|
+
console.log("");
|
|
1377
|
+
}
|
|
1378
|
+
async function open2(options) {
|
|
1379
|
+
const { output } = options;
|
|
1380
|
+
if (!existsSync5(output)) {
|
|
1381
|
+
throw new Error(`No Android project found in ${output}. Run 'craft android init' first.`);
|
|
1382
|
+
}
|
|
1383
|
+
console.log(`\uD83D\uDE80 Opening Android project in Android Studio...`);
|
|
1384
|
+
try {
|
|
1385
|
+
await $2`open -a "Android Studio" ${output}`.quiet();
|
|
1386
|
+
} catch {
|
|
1387
|
+
try {
|
|
1388
|
+
await $2`studio ${output}`.quiet();
|
|
1389
|
+
} catch {
|
|
1390
|
+
console.log("\u26A0\uFE0F Android Studio not found.");
|
|
1391
|
+
console.log(" Please open the project manually in Android Studio:");
|
|
1392
|
+
console.log(` ${output}`);
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
async function run2(options) {
|
|
1397
|
+
const { device, output } = options;
|
|
1398
|
+
await build2({ output });
|
|
1399
|
+
console.log("\uD83D\uDCF1 Installing and running on device...");
|
|
1400
|
+
try {
|
|
1401
|
+
const apkPath = join3(output, "app/build/outputs/apk/debug/app-debug.apk");
|
|
1402
|
+
if (device) {
|
|
1403
|
+
await $2`adb -s ${device} install -r ${apkPath}`;
|
|
1404
|
+
} else {
|
|
1405
|
+
await $2`adb install -r ${apkPath}`;
|
|
1406
|
+
}
|
|
1407
|
+
const config = JSON.parse(readFileSync3(join3(output, "craft.config.json"), "utf-8"));
|
|
1408
|
+
const launchCmd = `${config.packageName}/.MainActivity`;
|
|
1409
|
+
if (device) {
|
|
1410
|
+
await $2`adb -s ${device} shell am start -n ${launchCmd}`;
|
|
1411
|
+
} else {
|
|
1412
|
+
await $2`adb shell am start -n ${launchCmd}`;
|
|
1413
|
+
}
|
|
1414
|
+
console.log("\u2705 App launched");
|
|
1415
|
+
} catch (error) {
|
|
1416
|
+
console.error("Failed to install/run. Make sure:");
|
|
1417
|
+
console.error(" 1. ADB is installed and in PATH");
|
|
1418
|
+
console.error(" 2. A device/emulator is connected (run: adb devices)");
|
|
1419
|
+
throw error;
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
var $2, TEMPLATES_DIR2;
|
|
1423
|
+
var init_src2 = __esm(() => {
|
|
1424
|
+
({ $: $2 } = globalThis.Bun);
|
|
1425
|
+
TEMPLATES_DIR2 = join3(dirname2(import.meta.dir), "templates");
|
|
1426
|
+
});
|
|
1427
|
+
|
|
1428
|
+
// ../../node_modules/@stacksjs/clapp/dist/index.js
|
|
1429
|
+
import { existsSync } from "fs";
|
|
1430
|
+
import fs from "fs/promises";
|
|
1431
|
+
import os from "os";
|
|
1432
|
+
import path from "path";
|
|
1433
|
+
import process2 from "process";
|
|
1434
|
+
import { EventEmitter } from "events";
|
|
1435
|
+
import process5 from "process";
|
|
1436
|
+
import process22 from "process";
|
|
1437
|
+
import process3 from "process";
|
|
1438
|
+
import process4 from "process";
|
|
1439
|
+
import process7 from "process";
|
|
1440
|
+
import tty from "tty";
|
|
1441
|
+
import process8, { stdin, stdout } from "process";
|
|
1442
|
+
import process9, { stdin as stdin2, stdout as stdout2 } from "process";
|
|
1443
|
+
import readline2 from "readline";
|
|
1444
|
+
class Telemetry {
|
|
1445
|
+
configPath;
|
|
1446
|
+
config = null;
|
|
1447
|
+
events = [];
|
|
1448
|
+
retryCount = 0;
|
|
1449
|
+
maxRetries = 3;
|
|
1450
|
+
retryDelayMs = 1000;
|
|
1451
|
+
constructor() {
|
|
1452
|
+
const homeDir = os.homedir();
|
|
1453
|
+
const configDir = path.join(homeDir, ".config", "clapp");
|
|
1454
|
+
this.configPath = path.join(configDir, "telemetry.json");
|
|
1455
|
+
}
|
|
1456
|
+
async isEnabled() {
|
|
1457
|
+
if (process2.env.DO_NOT_TRACK === "1" || process2.env.DO_NOT_TRACK === "true") {
|
|
1458
|
+
return false;
|
|
1459
|
+
}
|
|
1460
|
+
if (process2.env.NO_TELEMETRY === "1" || process2.env.NO_TELEMETRY === "true") {
|
|
1461
|
+
return false;
|
|
1462
|
+
}
|
|
1463
|
+
const config = await this.loadConfig();
|
|
1464
|
+
return config.enabled;
|
|
1465
|
+
}
|
|
1466
|
+
async enable() {
|
|
1467
|
+
const config = await this.loadConfig();
|
|
1468
|
+
config.enabled = true;
|
|
1469
|
+
if (!config.userId) {
|
|
1470
|
+
config.userId = this.generateUserId();
|
|
1471
|
+
}
|
|
1472
|
+
await this.saveConfig(config);
|
|
1473
|
+
}
|
|
1474
|
+
async disable() {
|
|
1475
|
+
const config = await this.loadConfig();
|
|
1476
|
+
config.enabled = false;
|
|
1477
|
+
await this.saveConfig(config);
|
|
1478
|
+
}
|
|
1479
|
+
async track(event, data) {
|
|
1480
|
+
const enabled = await this.isEnabled();
|
|
1481
|
+
if (!enabled) {
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
const telemetryEvent = {
|
|
1485
|
+
event,
|
|
1486
|
+
...data,
|
|
1487
|
+
timestamp: Date.now(),
|
|
1488
|
+
platform: os.platform(),
|
|
1489
|
+
nodeVersion: process2.version
|
|
1490
|
+
};
|
|
1491
|
+
this.events.push(telemetryEvent);
|
|
1492
|
+
if (this.events.length >= 10) {
|
|
1493
|
+
await this.send();
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
async trackCommand(command, duration) {
|
|
1497
|
+
await this.track("command", {
|
|
1498
|
+
command,
|
|
1499
|
+
duration
|
|
1500
|
+
});
|
|
1501
|
+
}
|
|
1502
|
+
async trackError(error, command) {
|
|
1503
|
+
await this.track("error", {
|
|
1504
|
+
error,
|
|
1505
|
+
command
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
async send() {
|
|
1509
|
+
const enabled = await this.isEnabled();
|
|
1510
|
+
if (!enabled || this.events.length === 0) {
|
|
1511
|
+
return true;
|
|
1512
|
+
}
|
|
1513
|
+
try {
|
|
1514
|
+
this.events = [];
|
|
1515
|
+
this.retryCount = 0;
|
|
1516
|
+
const config = await this.loadConfig();
|
|
1517
|
+
config.lastSent = Date.now();
|
|
1518
|
+
await this.saveConfig(config);
|
|
1519
|
+
return true;
|
|
1520
|
+
} catch {
|
|
1521
|
+
if (this.retryCount < this.maxRetries) {
|
|
1522
|
+
this.retryCount++;
|
|
1523
|
+
const delay = this.retryDelayMs * 2 ** (this.retryCount - 1);
|
|
1524
|
+
await this.sleep(delay);
|
|
1525
|
+
return this.send();
|
|
1526
|
+
}
|
|
1527
|
+
this.events = [];
|
|
1528
|
+
this.retryCount = 0;
|
|
1529
|
+
return false;
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
async flush() {
|
|
1533
|
+
if (this.events.length === 0) {
|
|
1534
|
+
return true;
|
|
1535
|
+
}
|
|
1536
|
+
return this.send();
|
|
1537
|
+
}
|
|
1538
|
+
sleep(ms) {
|
|
1539
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1540
|
+
}
|
|
1541
|
+
async status() {
|
|
1542
|
+
const config = await this.loadConfig();
|
|
1543
|
+
return {
|
|
1544
|
+
enabled: config.enabled,
|
|
1545
|
+
doNotTrack: process2.env.DO_NOT_TRACK === "1" || process2.env.DO_NOT_TRACK === "true",
|
|
1546
|
+
eventsQueued: this.events.length,
|
|
1547
|
+
lastSent: config.lastSent
|
|
1548
|
+
};
|
|
1549
|
+
}
|
|
1550
|
+
async loadConfig() {
|
|
1551
|
+
if (this.config) {
|
|
1552
|
+
return this.config;
|
|
1553
|
+
}
|
|
1554
|
+
try {
|
|
1555
|
+
if (existsSync(this.configPath)) {
|
|
1556
|
+
const data = await fs.readFile(this.configPath, "utf-8");
|
|
1557
|
+
this.config = JSON.parse(data);
|
|
1558
|
+
return this.config;
|
|
1559
|
+
}
|
|
1560
|
+
} catch {}
|
|
1561
|
+
this.config = {
|
|
1562
|
+
enabled: false
|
|
1563
|
+
};
|
|
1564
|
+
return this.config;
|
|
1565
|
+
}
|
|
1566
|
+
async saveConfig(config) {
|
|
1567
|
+
this.config = config;
|
|
1568
|
+
try {
|
|
1569
|
+
const configDir = path.dirname(this.configPath);
|
|
1570
|
+
await fs.mkdir(configDir, { recursive: true });
|
|
1571
|
+
await fs.writeFile(this.configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
1572
|
+
} catch {}
|
|
1573
|
+
}
|
|
1574
|
+
generateUserId() {
|
|
1575
|
+
const random = Math.random().toString(36).substring(2, 15);
|
|
1576
|
+
const timestamp = Date.now().toString(36);
|
|
1577
|
+
return `${random}-${timestamp}`;
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
var telemetry = new Telemetry;
|
|
1581
|
+
function parseArgv(argv, opts = {}) {
|
|
1582
|
+
const result = { _: [] };
|
|
1583
|
+
const alias = opts.alias || {};
|
|
1584
|
+
const booleans = new Set(opts.boolean || []);
|
|
1585
|
+
const aliasOf = {};
|
|
1586
|
+
for (const key of Object.keys(alias)) {
|
|
1587
|
+
for (const a of alias[key]) {
|
|
1588
|
+
aliasOf[a] = key;
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
for (const b of booleans) {
|
|
1592
|
+
if (alias[b]) {
|
|
1593
|
+
for (const a of alias[b])
|
|
1594
|
+
booleans.add(a);
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
function setKey(key, value) {
|
|
1598
|
+
const canonical = aliasOf[key] || key;
|
|
1599
|
+
result[canonical] = value;
|
|
1600
|
+
if (alias[canonical]) {
|
|
1601
|
+
for (const a of alias[canonical])
|
|
1602
|
+
result[a] = value;
|
|
1603
|
+
}
|
|
1604
|
+
if (aliasOf[key] && alias[aliasOf[key]]) {
|
|
1605
|
+
for (const a of alias[aliasOf[key]])
|
|
1606
|
+
result[a] = value;
|
|
1607
|
+
}
|
|
1608
|
+
result[key] = value;
|
|
1609
|
+
}
|
|
1610
|
+
for (let i = 0;i < argv.length; i++) {
|
|
1611
|
+
const arg = argv[i];
|
|
1612
|
+
if (arg === "--") {
|
|
1613
|
+
result._.push(...argv.slice(i + 1));
|
|
1614
|
+
break;
|
|
1615
|
+
}
|
|
1616
|
+
if (arg.startsWith("--")) {
|
|
1617
|
+
const eqIdx = arg.indexOf("=");
|
|
1618
|
+
if (eqIdx !== -1) {
|
|
1619
|
+
const key = arg.slice(2, eqIdx);
|
|
1620
|
+
const value = arg.slice(eqIdx + 1);
|
|
1621
|
+
setKey(key, value);
|
|
1622
|
+
} else {
|
|
1623
|
+
const key = arg.slice(2);
|
|
1624
|
+
if (key.startsWith("no-")) {
|
|
1625
|
+
const actualKey = key.slice(3);
|
|
1626
|
+
setKey(actualKey, false);
|
|
1627
|
+
continue;
|
|
1628
|
+
}
|
|
1629
|
+
const canonical = aliasOf[key] || key;
|
|
1630
|
+
if (booleans.has(canonical) || booleans.has(key)) {
|
|
1631
|
+
setKey(key, true);
|
|
1632
|
+
} else {
|
|
1633
|
+
const next = argv[i + 1];
|
|
1634
|
+
if (next !== undefined && !next.startsWith("-")) {
|
|
1635
|
+
setKey(key, next);
|
|
1636
|
+
i++;
|
|
1637
|
+
} else {
|
|
1638
|
+
setKey(key, true);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
} else if (arg.startsWith("-") && arg.length > 1) {
|
|
1643
|
+
const chars = arg.slice(1);
|
|
1644
|
+
for (let j = 0;j < chars.length; j++) {
|
|
1645
|
+
const ch = chars[j];
|
|
1646
|
+
const canonical = aliasOf[ch] || ch;
|
|
1647
|
+
if (j === chars.length - 1 && !booleans.has(canonical) && !booleans.has(ch)) {
|
|
1648
|
+
const next = argv[i + 1];
|
|
1649
|
+
if (next !== undefined && !next.startsWith("-")) {
|
|
1650
|
+
setKey(ch, next);
|
|
1651
|
+
i++;
|
|
1652
|
+
} else {
|
|
1653
|
+
setKey(ch, true);
|
|
1654
|
+
}
|
|
1655
|
+
} else {
|
|
1656
|
+
setKey(ch, true);
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
} else {
|
|
1660
|
+
result._.push(arg);
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
return result;
|
|
1664
|
+
}
|
|
1665
|
+
function removeBrackets(v) {
|
|
1666
|
+
return v.replace(/[<[].+/, "").trim();
|
|
1667
|
+
}
|
|
1668
|
+
function findAllBrackets(v) {
|
|
1669
|
+
const ANGLED_BRACKET_RE_GLOBAL = /<([^>]+)>/g;
|
|
1670
|
+
const SQUARE_BRACKET_RE_GLOBAL = /\[([^\]]+)\]/g;
|
|
1671
|
+
const res = [];
|
|
1672
|
+
const parse = (match) => {
|
|
1673
|
+
let variadic = false;
|
|
1674
|
+
let value = match[1];
|
|
1675
|
+
if (value.startsWith("...")) {
|
|
1676
|
+
value = value.slice(3);
|
|
1677
|
+
variadic = true;
|
|
1678
|
+
}
|
|
1679
|
+
return {
|
|
1680
|
+
required: match[0].startsWith("<"),
|
|
1681
|
+
value,
|
|
1682
|
+
variadic
|
|
1683
|
+
};
|
|
1684
|
+
};
|
|
1685
|
+
let angledMatch;
|
|
1686
|
+
while (angledMatch = ANGLED_BRACKET_RE_GLOBAL.exec(v)) {
|
|
1687
|
+
res.push(parse(angledMatch));
|
|
1688
|
+
}
|
|
1689
|
+
let squareMatch;
|
|
1690
|
+
while (squareMatch = SQUARE_BRACKET_RE_GLOBAL.exec(v)) {
|
|
1691
|
+
res.push(parse(squareMatch));
|
|
1692
|
+
}
|
|
1693
|
+
return res;
|
|
1694
|
+
}
|
|
1695
|
+
function getMriOptions(options) {
|
|
1696
|
+
const result = { alias: {}, boolean: [] };
|
|
1697
|
+
for (const [index, option] of options.entries()) {
|
|
1698
|
+
if (option.names.length > 1) {
|
|
1699
|
+
result.alias[option.names[0]] = option.names.slice(1);
|
|
1700
|
+
}
|
|
1701
|
+
if (option.isBoolean) {
|
|
1702
|
+
if (option.negated) {
|
|
1703
|
+
const hasStringTypeOption = options.some((o, i) => {
|
|
1704
|
+
return i !== index && o.names.some((name) => option.names.includes(name)) && typeof o.required === "boolean";
|
|
1705
|
+
});
|
|
1706
|
+
if (!hasStringTypeOption) {
|
|
1707
|
+
result.boolean.push(option.names[0]);
|
|
1708
|
+
}
|
|
1709
|
+
} else {
|
|
1710
|
+
result.boolean.push(option.names[0]);
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
return result;
|
|
1715
|
+
}
|
|
1716
|
+
function findLongest(arr) {
|
|
1717
|
+
return arr.reduce((a, b) => a.length >= b.length ? a : b, "");
|
|
1718
|
+
}
|
|
1719
|
+
function padRight(str, length) {
|
|
1720
|
+
return str.length >= length ? str : `${str}${" ".repeat(length - str.length)}`;
|
|
1721
|
+
}
|
|
1722
|
+
function camelcase(input) {
|
|
1723
|
+
return input.replace(/([a-z])-([a-z])/g, (_, p1, p2) => {
|
|
1724
|
+
return p1 + p2.toUpperCase();
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
function setDotProp(obj, keys, val) {
|
|
1728
|
+
let i = 0;
|
|
1729
|
+
const length = keys.length;
|
|
1730
|
+
let t = obj;
|
|
1731
|
+
let x;
|
|
1732
|
+
for (;i < length; ++i) {
|
|
1733
|
+
x = t[keys[i]];
|
|
1734
|
+
t = t[keys[i]] = i === length - 1 ? val : x != null ? x : !!~keys[i + 1].indexOf(".") || !(+keys[i + 1] > -1) ? {} : [];
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
function setByType(obj, transforms) {
|
|
1738
|
+
for (const key of Object.keys(transforms)) {
|
|
1739
|
+
const transform = transforms[key];
|
|
1740
|
+
if (transform.shouldTransform) {
|
|
1741
|
+
obj[key] = Array.prototype.concat.call([], obj[key]);
|
|
1742
|
+
if (typeof transform.transformFunction === "function") {
|
|
1743
|
+
obj[key] = obj[key].map(transform.transformFunction);
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
function getFileName(input) {
|
|
1749
|
+
const m = /([^\\/]+)$/.exec(input);
|
|
1750
|
+
return m ? m[1] : "";
|
|
1751
|
+
}
|
|
1752
|
+
function camelcaseOptionName(name) {
|
|
1753
|
+
return name.split(".").map((v, i) => {
|
|
1754
|
+
return i === 0 ? camelcase(v) : v;
|
|
1755
|
+
}).join(".");
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
class ClappError extends Error {
|
|
1759
|
+
exitCode = 2;
|
|
1760
|
+
isUsageError = true;
|
|
1761
|
+
constructor(message) {
|
|
1762
|
+
super(message);
|
|
1763
|
+
this.name = this.constructor.name;
|
|
1764
|
+
if (typeof Error.captureStackTrace === "function") {
|
|
1765
|
+
Error.captureStackTrace(this, this.constructor);
|
|
1766
|
+
} else {
|
|
1767
|
+
this.stack = new Error(message).stack;
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
format(showStack = false) {
|
|
1771
|
+
if (showStack && this.stack) {
|
|
1772
|
+
return `${this.message}
|
|
1773
|
+
|
|
1774
|
+
Stack trace:
|
|
1775
|
+
${this.stack}`;
|
|
1776
|
+
}
|
|
1777
|
+
return this.message;
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
function isUnicodeSupported() {
|
|
1781
|
+
const { env } = process22;
|
|
1782
|
+
const { TERM, TERM_PROGRAM } = env;
|
|
1783
|
+
if (process22.platform !== "win32") {
|
|
1784
|
+
return TERM !== "linux";
|
|
1785
|
+
}
|
|
1786
|
+
return Boolean(env.WT_SESSION) || Boolean(env.TERMINUS_SUBLIME) || env.ConEmuTask === "{cmd::Cmder}" || TERM_PROGRAM === "Terminus-Sublime" || TERM_PROGRAM === "vscode" || TERM === "xterm-256color" || TERM === "alacritty" || TERM === "rxvt-unicode" || TERM === "rxvt-unicode-256color" || env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
|
|
1787
|
+
}
|
|
1788
|
+
var ESC = "\x1B";
|
|
1789
|
+
var CSI = `${ESC}[`;
|
|
1790
|
+
var cursor = {
|
|
1791
|
+
to(x, y) {
|
|
1792
|
+
if (!y)
|
|
1793
|
+
return `${CSI}${x + 1}G`;
|
|
1794
|
+
return `${CSI}${y + 1};${x + 1}H`;
|
|
1795
|
+
},
|
|
1796
|
+
move(x, y) {
|
|
1797
|
+
let ret = "";
|
|
1798
|
+
if (x < 0)
|
|
1799
|
+
ret += `${CSI}${-x}D`;
|
|
1800
|
+
else if (x > 0)
|
|
1801
|
+
ret += `${CSI}${x}C`;
|
|
1802
|
+
if (y < 0)
|
|
1803
|
+
ret += `${CSI}${-y}A`;
|
|
1804
|
+
else if (y > 0)
|
|
1805
|
+
ret += `${CSI}${y}B`;
|
|
1806
|
+
return ret;
|
|
1807
|
+
},
|
|
1808
|
+
up: (count = 1) => `${CSI}${count}A`,
|
|
1809
|
+
down: (count = 1) => `${CSI}${count}B`,
|
|
1810
|
+
forward: (count = 1) => `${CSI}${count}C`,
|
|
1811
|
+
backward: (count = 1) => `${CSI}${count}D`,
|
|
1812
|
+
nextLine: (count = 1) => `${CSI}E`.repeat(count),
|
|
1813
|
+
prevLine: (count = 1) => `${CSI}F`.repeat(count),
|
|
1814
|
+
left: `${CSI}G`,
|
|
1815
|
+
hide: `${CSI}?25l`,
|
|
1816
|
+
show: `${CSI}?25h`,
|
|
1817
|
+
save: `${ESC}7`,
|
|
1818
|
+
restore: `${ESC}8`
|
|
1819
|
+
};
|
|
1820
|
+
var erase = {
|
|
1821
|
+
screen: `${CSI}2J`,
|
|
1822
|
+
up: (count = 1) => `${CSI}1J`.repeat(count),
|
|
1823
|
+
down: (count = 1) => `${CSI}J`.repeat(count),
|
|
1824
|
+
line: `${CSI}2K`,
|
|
1825
|
+
lineEnd: `${CSI}K`,
|
|
1826
|
+
lineStart: `${CSI}1K`,
|
|
1827
|
+
lines(count) {
|
|
1828
|
+
let clear = "";
|
|
1829
|
+
for (let i = 0;i < count; i++)
|
|
1830
|
+
clear += this.line + (i < count - 1 ? cursor.up() : "");
|
|
1831
|
+
if (count)
|
|
1832
|
+
clear += cursor.left;
|
|
1833
|
+
return clear;
|
|
1834
|
+
}
|
|
1835
|
+
};
|
|
1836
|
+
var clear = {
|
|
1837
|
+
screen: `${ESC}c`
|
|
1838
|
+
};
|
|
1839
|
+
function levenshteinDistance(a, b) {
|
|
1840
|
+
const matrix = [];
|
|
1841
|
+
for (let i = 0;i <= b.length; i++) {
|
|
1842
|
+
matrix[i] = [i];
|
|
1843
|
+
}
|
|
1844
|
+
for (let j = 0;j <= a.length; j++) {
|
|
1845
|
+
matrix[0][j] = j;
|
|
1846
|
+
}
|
|
1847
|
+
for (let i = 1;i <= b.length; i++) {
|
|
1848
|
+
for (let j = 1;j <= a.length; j++) {
|
|
1849
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
1850
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
1851
|
+
} else {
|
|
1852
|
+
matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
return matrix[b.length][a.length];
|
|
1857
|
+
}
|
|
1858
|
+
function findSimilarCommands(input, commands, maxDistance = 2, maxSuggestions = 3) {
|
|
1859
|
+
return commands.map((cmd) => ({ cmd, distance: levenshteinDistance(input, cmd) })).filter(({ distance }) => distance <= maxDistance).sort((a, b) => a.distance - b.distance).slice(0, maxSuggestions).map(({ cmd }) => cmd);
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
class Option {
|
|
1863
|
+
rawName;
|
|
1864
|
+
description;
|
|
1865
|
+
name;
|
|
1866
|
+
names;
|
|
1867
|
+
isBoolean;
|
|
1868
|
+
required;
|
|
1869
|
+
config;
|
|
1870
|
+
negated;
|
|
1871
|
+
constructor(rawName, description, config) {
|
|
1872
|
+
this.rawName = rawName;
|
|
1873
|
+
this.description = description;
|
|
1874
|
+
this.config = Object.assign({}, config);
|
|
1875
|
+
rawName = rawName.replace(/\.\*/g, "");
|
|
1876
|
+
this.negated = false;
|
|
1877
|
+
this.names = removeBrackets(rawName).split(",").map((v) => {
|
|
1878
|
+
let name = v.trim().replace(/^-{1,2}/, "");
|
|
1879
|
+
if (name.startsWith("no-")) {
|
|
1880
|
+
this.negated = true;
|
|
1881
|
+
name = name.replace(/^no-/, "");
|
|
1882
|
+
}
|
|
1883
|
+
return camelcaseOptionName(name);
|
|
1884
|
+
}).sort((a, b) => a.length > b.length ? 1 : -1);
|
|
1885
|
+
this.name = this.names[this.names.length - 1];
|
|
1886
|
+
if (this.negated && this.config.default == null) {
|
|
1887
|
+
this.config.default = true;
|
|
1888
|
+
}
|
|
1889
|
+
if (rawName.includes("<")) {
|
|
1890
|
+
this.required = true;
|
|
1891
|
+
} else if (rawName.includes("[")) {
|
|
1892
|
+
this.required = false;
|
|
1893
|
+
} else {
|
|
1894
|
+
this.isBoolean = true;
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
var processArgs = process3.argv;
|
|
1899
|
+
var platformInfo = `${process3.platform}-${process3.arch} bun-v${typeof Bun !== "undefined" ? Bun.version : "unknown"}`;
|
|
1900
|
+
var processArgs2 = process4.argv;
|
|
1901
|
+
var platformInfo2 = `${process4.platform}-${process4.arch} node-${process4.version}`;
|
|
1902
|
+
|
|
1903
|
+
class Command {
|
|
1904
|
+
rawName;
|
|
1905
|
+
description;
|
|
1906
|
+
config;
|
|
1907
|
+
cli;
|
|
1908
|
+
options;
|
|
1909
|
+
aliasNames;
|
|
1910
|
+
name;
|
|
1911
|
+
namespace;
|
|
1912
|
+
args;
|
|
1913
|
+
commandAction;
|
|
1914
|
+
usageText;
|
|
1915
|
+
versionNumber;
|
|
1916
|
+
examples;
|
|
1917
|
+
helpCallback;
|
|
1918
|
+
globalCommand;
|
|
1919
|
+
beforeHooks;
|
|
1920
|
+
afterHooks;
|
|
1921
|
+
middleware;
|
|
1922
|
+
constructor(rawName, description, config, cli) {
|
|
1923
|
+
this.rawName = rawName;
|
|
1924
|
+
this.description = description;
|
|
1925
|
+
this.config = config;
|
|
1926
|
+
this.cli = cli;
|
|
1927
|
+
this.options = [];
|
|
1928
|
+
this.aliasNames = [];
|
|
1929
|
+
this.name = removeBrackets(rawName);
|
|
1930
|
+
const nameWithoutBrackets = removeBrackets(rawName);
|
|
1931
|
+
const colonIndex = nameWithoutBrackets.indexOf(":");
|
|
1932
|
+
if (colonIndex > 0) {
|
|
1933
|
+
this.namespace = nameWithoutBrackets.substring(0, colonIndex);
|
|
1934
|
+
this.name = nameWithoutBrackets.substring(colonIndex + 1);
|
|
1935
|
+
}
|
|
1936
|
+
this.args = findAllBrackets(rawName);
|
|
1937
|
+
this.examples = [];
|
|
1938
|
+
this.beforeHooks = [];
|
|
1939
|
+
this.afterHooks = [];
|
|
1940
|
+
this.middleware = [];
|
|
1941
|
+
if (!config) {
|
|
1942
|
+
this.config = {};
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
usage(text) {
|
|
1946
|
+
this.usageText = text;
|
|
1947
|
+
return this;
|
|
1948
|
+
}
|
|
1949
|
+
allowUnknownOptions() {
|
|
1950
|
+
this.config.allowUnknownOptions = true;
|
|
1951
|
+
return this;
|
|
1952
|
+
}
|
|
1953
|
+
ignoreOptionDefaultValue() {
|
|
1954
|
+
this.config.ignoreOptionDefaultValue = true;
|
|
1955
|
+
return this;
|
|
1956
|
+
}
|
|
1957
|
+
version(version, customFlags = "-v, --version") {
|
|
1958
|
+
this.versionNumber = version;
|
|
1959
|
+
this.option(customFlags, "Display version number");
|
|
1960
|
+
return this;
|
|
1961
|
+
}
|
|
1962
|
+
example(example) {
|
|
1963
|
+
this.examples.push(example);
|
|
1964
|
+
return this;
|
|
1965
|
+
}
|
|
1966
|
+
option(rawName, description, config) {
|
|
1967
|
+
const option = new Option(rawName, description, config);
|
|
1968
|
+
this.options.push(option);
|
|
1969
|
+
return this;
|
|
1970
|
+
}
|
|
1971
|
+
alias(name) {
|
|
1972
|
+
this.aliasNames.push(name);
|
|
1973
|
+
return this;
|
|
1974
|
+
}
|
|
1975
|
+
action(callback) {
|
|
1976
|
+
this.commandAction = callback;
|
|
1977
|
+
return this;
|
|
1978
|
+
}
|
|
1979
|
+
before(handler) {
|
|
1980
|
+
this.beforeHooks.push(handler);
|
|
1981
|
+
return this;
|
|
1982
|
+
}
|
|
1983
|
+
after(handler) {
|
|
1984
|
+
this.afterHooks.push(handler);
|
|
1985
|
+
return this;
|
|
1986
|
+
}
|
|
1987
|
+
use(handler) {
|
|
1988
|
+
this.middleware.push(handler);
|
|
1989
|
+
return this;
|
|
1990
|
+
}
|
|
1991
|
+
isMatched(name) {
|
|
1992
|
+
if (this.aliasNames.includes(name))
|
|
1993
|
+
return true;
|
|
1994
|
+
if (this.namespace)
|
|
1995
|
+
return `${this.namespace}:${this.name}` === name;
|
|
1996
|
+
return this.name === name;
|
|
1997
|
+
}
|
|
1998
|
+
get isDefaultCommand() {
|
|
1999
|
+
return this.name === "" || this.aliasNames.includes("!");
|
|
2000
|
+
}
|
|
2001
|
+
get isGlobalCommand() {
|
|
2002
|
+
return this instanceof GlobalCommand;
|
|
2003
|
+
}
|
|
2004
|
+
get displayName() {
|
|
2005
|
+
return this.namespace ? `${this.namespace}:${this.name}` : this.name;
|
|
2006
|
+
}
|
|
2007
|
+
hasOption(name) {
|
|
2008
|
+
name = name.split(".")[0];
|
|
2009
|
+
return !!this.options.find((option) => {
|
|
2010
|
+
return option.names.includes(name);
|
|
2011
|
+
});
|
|
2012
|
+
}
|
|
2013
|
+
outputHelp() {
|
|
2014
|
+
const { name, commands } = this.cli;
|
|
2015
|
+
const {
|
|
2016
|
+
versionNumber,
|
|
2017
|
+
options: globalOptions,
|
|
2018
|
+
helpCallback
|
|
2019
|
+
} = this.cli.globalCommand;
|
|
2020
|
+
let sections = [
|
|
2021
|
+
{
|
|
2022
|
+
body: `${name}${versionNumber ? `/${versionNumber}` : ""}`
|
|
2023
|
+
}
|
|
2024
|
+
];
|
|
2025
|
+
sections.push({
|
|
2026
|
+
title: "Usage",
|
|
2027
|
+
body: ` $ ${name} ${this.usageText || this.rawName}`
|
|
2028
|
+
});
|
|
2029
|
+
const showCommands = (this.isGlobalCommand || this.isDefaultCommand) && commands.length > 0;
|
|
2030
|
+
if (showCommands) {
|
|
2031
|
+
const longestCommandName = findLongest(commands.map((command) => command.rawName));
|
|
2032
|
+
const namespaceGroups = new Map;
|
|
2033
|
+
const noNamespaceCommands = [];
|
|
2034
|
+
for (const command of commands) {
|
|
2035
|
+
if (command.namespace) {
|
|
2036
|
+
if (!namespaceGroups.has(command.namespace)) {
|
|
2037
|
+
namespaceGroups.set(command.namespace, []);
|
|
2038
|
+
}
|
|
2039
|
+
namespaceGroups.get(command.namespace).push(command);
|
|
2040
|
+
} else {
|
|
2041
|
+
noNamespaceCommands.push(command);
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
let commandBody = "";
|
|
2045
|
+
if (noNamespaceCommands.length > 0) {
|
|
2046
|
+
commandBody += noNamespaceCommands.map((command) => {
|
|
2047
|
+
return ` ${padRight(command.rawName, longestCommandName.length)} ${command.description}`;
|
|
2048
|
+
}).join(`
|
|
2049
|
+
`);
|
|
2050
|
+
}
|
|
2051
|
+
const sortedNamespaces = Array.from(namespaceGroups.keys()).sort();
|
|
2052
|
+
for (const namespace of sortedNamespaces) {
|
|
2053
|
+
const namespaceCommands = namespaceGroups.get(namespace);
|
|
2054
|
+
if (commandBody.length > 0) {
|
|
2055
|
+
commandBody += `
|
|
2056
|
+
|
|
2057
|
+
`;
|
|
2058
|
+
}
|
|
2059
|
+
commandBody += ` ${namespace}:
|
|
2060
|
+
`;
|
|
2061
|
+
commandBody += namespaceCommands.map((command) => {
|
|
2062
|
+
return ` ${padRight(command.rawName, longestCommandName.length - 2)} ${command.description}`;
|
|
2063
|
+
}).join(`
|
|
2064
|
+
`);
|
|
2065
|
+
}
|
|
2066
|
+
sections.push({
|
|
2067
|
+
title: "Commands",
|
|
2068
|
+
body: commandBody
|
|
2069
|
+
});
|
|
2070
|
+
sections.push({
|
|
2071
|
+
title: `For more info, run any command with the \`--help\` flag`,
|
|
2072
|
+
body: commands.map((command) => ` $ ${name}${command.displayName === "" ? "" : ` ${command.displayName}`} --help`).join(`
|
|
2073
|
+
`)
|
|
2074
|
+
});
|
|
2075
|
+
}
|
|
2076
|
+
let options = this.isGlobalCommand ? globalOptions : [...this.options, ...globalOptions || []];
|
|
2077
|
+
if (!this.isGlobalCommand && !this.isDefaultCommand) {
|
|
2078
|
+
options = options.filter((option) => option.name !== "version");
|
|
2079
|
+
}
|
|
2080
|
+
if (options.length > 0) {
|
|
2081
|
+
const longestOptionName = findLongest(options.map((option) => option.rawName));
|
|
2082
|
+
sections.push({
|
|
2083
|
+
title: "Options",
|
|
2084
|
+
body: options.map((option) => {
|
|
2085
|
+
return ` ${padRight(option.rawName, longestOptionName.length)} ${option.description} ${option.config.default === undefined ? "" : `(default: ${option.config.default})`}`;
|
|
2086
|
+
}).join(`
|
|
2087
|
+
`)
|
|
2088
|
+
});
|
|
2089
|
+
}
|
|
2090
|
+
if (this.examples.length > 0) {
|
|
2091
|
+
sections.push({
|
|
2092
|
+
title: "Examples",
|
|
2093
|
+
body: this.examples.map((example) => {
|
|
2094
|
+
if (typeof example === "function") {
|
|
2095
|
+
return example(name);
|
|
2096
|
+
}
|
|
2097
|
+
return example;
|
|
2098
|
+
}).join(`
|
|
2099
|
+
`)
|
|
2100
|
+
});
|
|
2101
|
+
}
|
|
2102
|
+
if (helpCallback) {
|
|
2103
|
+
sections = helpCallback(sections) || sections;
|
|
2104
|
+
}
|
|
2105
|
+
console.log(sections.map((section) => {
|
|
2106
|
+
return section.title ? `${section.title}:
|
|
2107
|
+
${section.body}` : section.body;
|
|
2108
|
+
}).join(`
|
|
2109
|
+
|
|
2110
|
+
`));
|
|
2111
|
+
}
|
|
2112
|
+
outputVersion() {
|
|
2113
|
+
const { name } = this.cli;
|
|
2114
|
+
const { versionNumber } = this.cli.globalCommand;
|
|
2115
|
+
if (versionNumber) {
|
|
2116
|
+
const platformInfo3 = typeof Bun !== "undefined" ? platformInfo : platformInfo2;
|
|
2117
|
+
console.log(`${name}/${versionNumber} ${platformInfo3}`);
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
checkRequiredArgs() {
|
|
2121
|
+
const minimalArgsCount = this.args.filter((arg) => arg.required).length;
|
|
2122
|
+
if (this.cli.args.length < minimalArgsCount) {
|
|
2123
|
+
const requiredArgs = this.args.filter((arg) => arg.required);
|
|
2124
|
+
const missingArgs = requiredArgs.slice(this.cli.args.length);
|
|
2125
|
+
const argNames = missingArgs.map((arg) => `<${arg.value}>`).join(" ");
|
|
2126
|
+
throw new ClappError(`Missing required argument${missingArgs.length > 1 ? "s" : ""}: ${argNames}
|
|
2127
|
+
|
|
2128
|
+
` + `Run \`${this.cli.name} ${this.rawName} --help\` for usage information.`);
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
checkUnknownOptions() {
|
|
2132
|
+
const { options, globalCommand } = this.cli;
|
|
2133
|
+
if (!this.config.allowUnknownOptions) {
|
|
2134
|
+
for (const name of Object.keys(options)) {
|
|
2135
|
+
if (name !== "--" && !this.hasOption(name) && !globalCommand.hasOption(name)) {
|
|
2136
|
+
const allOptions = [...globalCommand.options, ...this.options];
|
|
2137
|
+
const allOptionNames = allOptions.flatMap((opt) => opt.names);
|
|
2138
|
+
const optionFlag = name.length > 1 ? `--${name}` : `-${name}`;
|
|
2139
|
+
const suggestions = findSimilarCommands(name, allOptionNames);
|
|
2140
|
+
let errorMsg = `Unknown option \`${optionFlag}\``;
|
|
2141
|
+
if (suggestions.length > 0) {
|
|
2142
|
+
errorMsg += `
|
|
2143
|
+
|
|
2144
|
+
Did you mean one of these?`;
|
|
2145
|
+
suggestions.forEach((suggestion) => {
|
|
2146
|
+
const suggestedFlag = suggestion.length > 1 ? `--${suggestion}` : `-${suggestion}`;
|
|
2147
|
+
errorMsg += `
|
|
2148
|
+
\u2022 ${suggestedFlag}`;
|
|
2149
|
+
});
|
|
2150
|
+
}
|
|
2151
|
+
errorMsg += `
|
|
2152
|
+
|
|
2153
|
+
Run \`${this.cli.name} ${this.rawName} --help\` to see available options.`;
|
|
2154
|
+
throw new ClappError(errorMsg);
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
checkOptionValue() {
|
|
2160
|
+
const { options: parsedOptions, globalCommand } = this.cli;
|
|
2161
|
+
const options = [...globalCommand.options, ...this.options];
|
|
2162
|
+
for (const option of options) {
|
|
2163
|
+
const value = parsedOptions[option.name.split(".")[0]];
|
|
2164
|
+
if (option.required) {
|
|
2165
|
+
const hasNegated = options.some((o) => o.negated && o.names.includes(option.name));
|
|
2166
|
+
if (value === true || value === false && !hasNegated) {
|
|
2167
|
+
throw new ClappError(`Option \`${option.rawName}\` requires a value.
|
|
2168
|
+
|
|
2169
|
+
` + `Example: ${this.cli.name} ${this.rawName} ${option.rawName} <value>`);
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
class GlobalCommand extends Command {
|
|
2177
|
+
constructor(cli) {
|
|
2178
|
+
super("@@global@@", "", {}, cli);
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
var Command_default = Command;
|
|
2182
|
+
var codes = {
|
|
2183
|
+
red: ["\x1B[31m", "\x1B[39m"],
|
|
2184
|
+
green: ["\x1B[32m", "\x1B[39m"],
|
|
2185
|
+
blue: ["\x1B[34m", "\x1B[39m"],
|
|
2186
|
+
yellow: ["\x1B[33m", "\x1B[39m"],
|
|
2187
|
+
cyan: ["\x1B[36m", "\x1B[39m"],
|
|
2188
|
+
magenta: ["\x1B[35m", "\x1B[39m"],
|
|
2189
|
+
white: ["\x1B[37m", "\x1B[39m"],
|
|
2190
|
+
gray: ["\x1B[90m", "\x1B[39m"],
|
|
2191
|
+
bgRed: ["\x1B[41m", "\x1B[49m"],
|
|
2192
|
+
bgGreen: ["\x1B[42m", "\x1B[49m"],
|
|
2193
|
+
bgBlue: ["\x1B[44m", "\x1B[49m"],
|
|
2194
|
+
bgYellow: ["\x1B[43m", "\x1B[49m"],
|
|
2195
|
+
bgCyan: ["\x1B[46m", "\x1B[49m"],
|
|
2196
|
+
bgMagenta: ["\x1B[45m", "\x1B[49m"],
|
|
2197
|
+
bold: ["\x1B[1m", "\x1B[22m"],
|
|
2198
|
+
italic: ["\x1B[3m", "\x1B[23m"],
|
|
2199
|
+
underline: ["\x1B[4m", "\x1B[24m"],
|
|
2200
|
+
dim: ["\x1B[2m", "\x1B[22m"],
|
|
2201
|
+
inverse: ["\x1B[7m", "\x1B[27m"],
|
|
2202
|
+
hidden: ["\x1B[8m", "\x1B[28m"],
|
|
2203
|
+
strikethrough: ["\x1B[9m", "\x1B[29m"]
|
|
2204
|
+
};
|
|
2205
|
+
var theme = {
|
|
2206
|
+
primary: "blue",
|
|
2207
|
+
secondary: "cyan",
|
|
2208
|
+
success: "green",
|
|
2209
|
+
warning: "yellow",
|
|
2210
|
+
error: "red",
|
|
2211
|
+
info: "magenta",
|
|
2212
|
+
muted: "gray"
|
|
2213
|
+
};
|
|
2214
|
+
function detectColorSupport() {
|
|
2215
|
+
return true;
|
|
2216
|
+
}
|
|
2217
|
+
function createStyler() {
|
|
2218
|
+
const styler = {};
|
|
2219
|
+
styler.supportsColor = detectColorSupport();
|
|
2220
|
+
function createStyleFunction(styleName, styles = []) {
|
|
2221
|
+
const styleList = styleName === "" ? [] : [...styles, styleName];
|
|
2222
|
+
const fn = function(text) {
|
|
2223
|
+
if (!styler.supportsColor)
|
|
2224
|
+
return text;
|
|
2225
|
+
let openCodes = "";
|
|
2226
|
+
let closeCodes = "";
|
|
2227
|
+
for (const style of styleList) {
|
|
2228
|
+
if (style in theme && theme[style] in codes) {
|
|
2229
|
+
const themeStyle = theme[style];
|
|
2230
|
+
openCodes += codes[themeStyle][0];
|
|
2231
|
+
closeCodes = codes[themeStyle][1] + closeCodes;
|
|
2232
|
+
} else if (style in codes) {
|
|
2233
|
+
openCodes += codes[style][0];
|
|
2234
|
+
closeCodes = codes[style][1] + closeCodes;
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
return openCodes + text + closeCodes;
|
|
2238
|
+
};
|
|
2239
|
+
const allStyles2 = [...Object.keys(codes), ...Object.keys(theme)];
|
|
2240
|
+
for (const style of allStyles2) {
|
|
2241
|
+
if (!(style in fn)) {
|
|
2242
|
+
Object.defineProperty(fn, style, {
|
|
2243
|
+
get() {
|
|
2244
|
+
return createStyleFunction(style, styleList);
|
|
2245
|
+
}
|
|
2246
|
+
});
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
return fn;
|
|
2250
|
+
}
|
|
2251
|
+
const allStyles = [...Object.keys(codes), ...Object.keys(theme)];
|
|
2252
|
+
for (const style of allStyles) {
|
|
2253
|
+
if (!(style in styler)) {
|
|
2254
|
+
Object.defineProperty(styler, style, {
|
|
2255
|
+
get() {
|
|
2256
|
+
return createStyleFunction(style);
|
|
2257
|
+
}
|
|
2258
|
+
});
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
return styler;
|
|
2262
|
+
}
|
|
2263
|
+
var style = createStyler();
|
|
2264
|
+
class CLI extends EventEmitter {
|
|
2265
|
+
name;
|
|
2266
|
+
commands;
|
|
2267
|
+
globalCommand;
|
|
2268
|
+
matchedCommand;
|
|
2269
|
+
matchedCommandName;
|
|
2270
|
+
rawArgs;
|
|
2271
|
+
args;
|
|
2272
|
+
options;
|
|
2273
|
+
showHelpOnExit;
|
|
2274
|
+
showVersionOnExit;
|
|
2275
|
+
enableDidYouMean = true;
|
|
2276
|
+
signalHandlersSet = false;
|
|
2277
|
+
isVerbose = false;
|
|
2278
|
+
isQuiet = false;
|
|
2279
|
+
isDebug = false;
|
|
2280
|
+
isNoInteraction = false;
|
|
2281
|
+
environment;
|
|
2282
|
+
isDryRun = false;
|
|
2283
|
+
isForce = false;
|
|
2284
|
+
useEmoji = true;
|
|
2285
|
+
theme;
|
|
2286
|
+
isNoCache = false;
|
|
2287
|
+
constructor(name = "") {
|
|
2288
|
+
super();
|
|
2289
|
+
this.name = name;
|
|
2290
|
+
this.commands = [];
|
|
2291
|
+
this.rawArgs = [];
|
|
2292
|
+
this.args = [];
|
|
2293
|
+
this.options = {};
|
|
2294
|
+
this.globalCommand = new GlobalCommand(this);
|
|
2295
|
+
this.globalCommand.usage("<command> [options]");
|
|
2296
|
+
}
|
|
2297
|
+
handleSignals(cleanup) {
|
|
2298
|
+
if (this.signalHandlersSet) {
|
|
2299
|
+
return this;
|
|
2300
|
+
}
|
|
2301
|
+
const handleSignal = async (signal) => {
|
|
2302
|
+
console.log(`
|
|
2303
|
+
|
|
2304
|
+
Received ${signal}, cleaning up...`);
|
|
2305
|
+
if (cleanup) {
|
|
2306
|
+
try {
|
|
2307
|
+
await cleanup();
|
|
2308
|
+
} catch (error) {
|
|
2309
|
+
console.error("Error during cleanup:", error);
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
process5.exit(0);
|
|
2313
|
+
};
|
|
2314
|
+
process5.on("SIGINT", () => handleSignal("SIGINT"));
|
|
2315
|
+
process5.on("SIGTERM", () => handleSignal("SIGTERM"));
|
|
2316
|
+
this.signalHandlersSet = true;
|
|
2317
|
+
return this;
|
|
2318
|
+
}
|
|
2319
|
+
didYouMean(enabled = true) {
|
|
2320
|
+
this.enableDidYouMean = enabled;
|
|
2321
|
+
return this;
|
|
2322
|
+
}
|
|
2323
|
+
verbose() {
|
|
2324
|
+
this.globalCommand.option("-v, --verbose", "Enable verbose output");
|
|
2325
|
+
return this;
|
|
2326
|
+
}
|
|
2327
|
+
quiet() {
|
|
2328
|
+
this.globalCommand.option("-q, --quiet", "Suppress non-essential output");
|
|
2329
|
+
return this;
|
|
2330
|
+
}
|
|
2331
|
+
debug() {
|
|
2332
|
+
this.globalCommand.option("--debug", "Enable debug mode with detailed error information");
|
|
2333
|
+
return this;
|
|
2334
|
+
}
|
|
2335
|
+
noInteraction() {
|
|
2336
|
+
this.globalCommand.option("-n, --no-interaction", "Do not ask any interactive questions (for CI/CD)");
|
|
2337
|
+
return this;
|
|
2338
|
+
}
|
|
2339
|
+
env() {
|
|
2340
|
+
this.globalCommand.option("--env <environment>", "Target environment (e.g., production, staging, local)");
|
|
2341
|
+
return this;
|
|
2342
|
+
}
|
|
2343
|
+
dryRun() {
|
|
2344
|
+
this.globalCommand.option("--dry-run", "Preview actions without executing them");
|
|
2345
|
+
return this;
|
|
2346
|
+
}
|
|
2347
|
+
force() {
|
|
2348
|
+
this.globalCommand.option("-f, --force", "Skip confirmation prompts");
|
|
2349
|
+
return this;
|
|
2350
|
+
}
|
|
2351
|
+
emoji() {
|
|
2352
|
+
this.globalCommand.option("--no-emoji", "Disable emoji in output");
|
|
2353
|
+
return this;
|
|
2354
|
+
}
|
|
2355
|
+
themes() {
|
|
2356
|
+
this.globalCommand.option("--theme <theme>", "Color theme (default, dracula, nord, solarized, monokai)");
|
|
2357
|
+
return this;
|
|
2358
|
+
}
|
|
2359
|
+
cache() {
|
|
2360
|
+
this.globalCommand.option("--no-cache", "Disable caching");
|
|
2361
|
+
return this;
|
|
2362
|
+
}
|
|
2363
|
+
usage(text) {
|
|
2364
|
+
this.globalCommand.usage(text);
|
|
2365
|
+
return this;
|
|
2366
|
+
}
|
|
2367
|
+
command(rawName, description, config) {
|
|
2368
|
+
if (!config) {
|
|
2369
|
+
config = {};
|
|
2370
|
+
}
|
|
2371
|
+
const command = new Command_default(rawName, description || "", config, this);
|
|
2372
|
+
command.globalCommand = this.globalCommand;
|
|
2373
|
+
this.commands.push(command);
|
|
2374
|
+
return command;
|
|
2375
|
+
}
|
|
2376
|
+
option(rawName, description, config) {
|
|
2377
|
+
this.globalCommand.option(rawName, description, config);
|
|
2378
|
+
return this;
|
|
2379
|
+
}
|
|
2380
|
+
help(callback) {
|
|
2381
|
+
this.globalCommand.option("-h, --help", "Display this message");
|
|
2382
|
+
this.globalCommand.helpCallback = callback;
|
|
2383
|
+
this.showHelpOnExit = true;
|
|
2384
|
+
return this;
|
|
2385
|
+
}
|
|
2386
|
+
version(version, customFlags = "-v, --version") {
|
|
2387
|
+
this.globalCommand.version(version, customFlags);
|
|
2388
|
+
this.showVersionOnExit = true;
|
|
2389
|
+
return this;
|
|
2390
|
+
}
|
|
2391
|
+
example(example) {
|
|
2392
|
+
this.globalCommand.example(example);
|
|
2393
|
+
return this;
|
|
2394
|
+
}
|
|
2395
|
+
outputHelp() {
|
|
2396
|
+
if (this.matchedCommand) {
|
|
2397
|
+
this.matchedCommand.outputHelp();
|
|
2398
|
+
} else {
|
|
2399
|
+
this.globalCommand.outputHelp();
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
outputVersion() {
|
|
2403
|
+
this.globalCommand.outputVersion();
|
|
2404
|
+
}
|
|
2405
|
+
setParsedInfo({ args, options }, matchedCommand, matchedCommandName) {
|
|
2406
|
+
this.args = args;
|
|
2407
|
+
this.options = options;
|
|
2408
|
+
if (matchedCommand) {
|
|
2409
|
+
this.matchedCommand = matchedCommand;
|
|
2410
|
+
}
|
|
2411
|
+
if (matchedCommandName) {
|
|
2412
|
+
this.matchedCommandName = matchedCommandName;
|
|
2413
|
+
}
|
|
2414
|
+
return this;
|
|
2415
|
+
}
|
|
2416
|
+
unsetMatchedCommand() {
|
|
2417
|
+
this.matchedCommand = undefined;
|
|
2418
|
+
this.matchedCommandName = undefined;
|
|
2419
|
+
}
|
|
2420
|
+
showCommandNotFound(input) {
|
|
2421
|
+
console.log(style.red(`
|
|
2422
|
+
\u2717 Command "${input}" not found.
|
|
2423
|
+
`));
|
|
2424
|
+
if (this.enableDidYouMean) {
|
|
2425
|
+
const allCommandNames = [];
|
|
2426
|
+
for (const command of this.commands) {
|
|
2427
|
+
if (command.displayName) {
|
|
2428
|
+
allCommandNames.push(command.displayName);
|
|
2429
|
+
}
|
|
2430
|
+
if (command.aliasNames) {
|
|
2431
|
+
allCommandNames.push(...command.aliasNames);
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
const suggestions = findSimilarCommands(input, allCommandNames);
|
|
2435
|
+
if (suggestions.length > 0) {
|
|
2436
|
+
console.log(style.yellow("Did you mean one of these?"));
|
|
2437
|
+
suggestions.forEach((cmd) => console.log(` ${style.dim("\u2022")} ${this.name} ${cmd}`));
|
|
2438
|
+
console.log("");
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
console.log(style.dim("Run"), `${this.name} --help`, style.dim("to see all available commands"));
|
|
2442
|
+
process5.exit(1);
|
|
2443
|
+
}
|
|
2444
|
+
async parse(argv = processArgs2, options = {}) {
|
|
2445
|
+
let { run = true, exitOnError = false } = options;
|
|
2446
|
+
if (exitOnError) {
|
|
2447
|
+
try {
|
|
2448
|
+
return await this.parse(argv, { run });
|
|
2449
|
+
} catch (err) {
|
|
2450
|
+
this.handleUsageError(err);
|
|
2451
|
+
throw err;
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
this.rawArgs = argv;
|
|
2455
|
+
if (!this.name) {
|
|
2456
|
+
this.name = argv[1] ? getFileName(argv[1]) : "cli";
|
|
2457
|
+
}
|
|
2458
|
+
let shouldParse = true;
|
|
2459
|
+
const slicedArgv = argv.slice(2);
|
|
2460
|
+
const candidateName = slicedArgv[0];
|
|
2461
|
+
if (candidateName && !candidateName.startsWith("-")) {
|
|
2462
|
+
for (const command of this.commands) {
|
|
2463
|
+
if (command.isMatched(candidateName)) {
|
|
2464
|
+
const parsed = this.mri(slicedArgv, command);
|
|
2465
|
+
shouldParse = false;
|
|
2466
|
+
const parsedInfo = {
|
|
2467
|
+
...parsed,
|
|
2468
|
+
args: parsed.args.slice(1)
|
|
2469
|
+
};
|
|
2470
|
+
this.setParsedInfo(parsedInfo, command, candidateName);
|
|
2471
|
+
this.emit(`command:${candidateName}`, command);
|
|
2472
|
+
break;
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
if (shouldParse) {
|
|
2477
|
+
for (const command of this.commands) {
|
|
2478
|
+
if (command.name === "") {
|
|
2479
|
+
shouldParse = false;
|
|
2480
|
+
const parsed = this.mri(slicedArgv, command);
|
|
2481
|
+
this.setParsedInfo(parsed, command);
|
|
2482
|
+
this.emit(`command:!`, command);
|
|
2483
|
+
break;
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
if (shouldParse) {
|
|
2488
|
+
const parsed = this.mri(argv.slice(2));
|
|
2489
|
+
this.setParsedInfo(parsed);
|
|
2490
|
+
}
|
|
2491
|
+
if (this.options.verbose) {
|
|
2492
|
+
this.isVerbose = true;
|
|
2493
|
+
}
|
|
2494
|
+
if (this.options.quiet) {
|
|
2495
|
+
this.isQuiet = true;
|
|
2496
|
+
}
|
|
2497
|
+
if (this.options.debug) {
|
|
2498
|
+
this.isDebug = true;
|
|
2499
|
+
}
|
|
2500
|
+
if (this.options.noInteraction) {
|
|
2501
|
+
this.isNoInteraction = true;
|
|
2502
|
+
}
|
|
2503
|
+
if (this.options.env) {
|
|
2504
|
+
this.environment = String(this.options.env);
|
|
2505
|
+
}
|
|
2506
|
+
if (this.options.dryRun) {
|
|
2507
|
+
this.isDryRun = true;
|
|
2508
|
+
}
|
|
2509
|
+
if (this.options.force) {
|
|
2510
|
+
this.isForce = true;
|
|
2511
|
+
}
|
|
2512
|
+
if (this.options.noEmoji !== undefined) {
|
|
2513
|
+
this.useEmoji = !this.options.noEmoji;
|
|
2514
|
+
}
|
|
2515
|
+
if (this.options.theme) {
|
|
2516
|
+
this.theme = String(this.options.theme);
|
|
2517
|
+
}
|
|
2518
|
+
if (this.options.noCache !== undefined) {
|
|
2519
|
+
this.isNoCache = Boolean(this.options.noCache);
|
|
2520
|
+
}
|
|
2521
|
+
if (this.options.help && this.showHelpOnExit) {
|
|
2522
|
+
this.outputHelp();
|
|
2523
|
+
run = false;
|
|
2524
|
+
this.unsetMatchedCommand();
|
|
2525
|
+
}
|
|
2526
|
+
if (this.options.version && this.showVersionOnExit && this.matchedCommandName == null) {
|
|
2527
|
+
this.outputVersion();
|
|
2528
|
+
run = false;
|
|
2529
|
+
this.unsetMatchedCommand();
|
|
2530
|
+
}
|
|
2531
|
+
const parsedArgv = { args: this.args, options: this.options };
|
|
2532
|
+
if (run) {
|
|
2533
|
+
await this.runMatchedCommand();
|
|
2534
|
+
}
|
|
2535
|
+
if (!this.matchedCommand && this.args[0]) {
|
|
2536
|
+
this.emit("command:*");
|
|
2537
|
+
const hasWildcardListener = this.listenerCount("command:*") > 0;
|
|
2538
|
+
if (!hasWildcardListener) {
|
|
2539
|
+
this.showCommandNotFound(this.args[0]);
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
return parsedArgv;
|
|
2543
|
+
}
|
|
2544
|
+
mri(argv, command) {
|
|
2545
|
+
const cliOptions = [
|
|
2546
|
+
...this.globalCommand.options,
|
|
2547
|
+
...command ? command.options : []
|
|
2548
|
+
];
|
|
2549
|
+
const mriOptions = getMriOptions(cliOptions);
|
|
2550
|
+
let argsAfterDoubleDashes = [];
|
|
2551
|
+
const doubleDashesIndex = argv.indexOf("--");
|
|
2552
|
+
if (doubleDashesIndex > -1) {
|
|
2553
|
+
argsAfterDoubleDashes = argv.slice(doubleDashesIndex + 1);
|
|
2554
|
+
argv = argv.slice(0, doubleDashesIndex);
|
|
2555
|
+
}
|
|
2556
|
+
const raw = parseArgv(argv, mriOptions);
|
|
2557
|
+
const parsed = { _: raw._ };
|
|
2558
|
+
for (const name of Object.keys(raw)) {
|
|
2559
|
+
if (name !== "_") {
|
|
2560
|
+
parsed[camelcaseOptionName(name)] = raw[name];
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
const args = parsed._;
|
|
2564
|
+
const options = {
|
|
2565
|
+
"--": argsAfterDoubleDashes
|
|
2566
|
+
};
|
|
2567
|
+
const ignoreDefault = command && command.config.ignoreOptionDefaultValue ? command.config.ignoreOptionDefaultValue : this.globalCommand.config.ignoreOptionDefaultValue;
|
|
2568
|
+
const transforms = Object.create(null);
|
|
2569
|
+
for (const cliOption of cliOptions) {
|
|
2570
|
+
if (!ignoreDefault && cliOption.config.default !== undefined) {
|
|
2571
|
+
for (const name of cliOption.names) {
|
|
2572
|
+
options[name] = cliOption.config.default;
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
if (Array.isArray(cliOption.config.type)) {
|
|
2576
|
+
if (transforms[cliOption.name] === undefined) {
|
|
2577
|
+
transforms[cliOption.name] = {
|
|
2578
|
+
shouldTransform: true,
|
|
2579
|
+
transformFunction: cliOption.config.type[0]
|
|
2580
|
+
};
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
for (const key of Object.keys(parsed)) {
|
|
2585
|
+
if (key !== "_") {
|
|
2586
|
+
const keys = key.split(".");
|
|
2587
|
+
setDotProp(options, keys, parsed[key]);
|
|
2588
|
+
setByType(options, transforms);
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
return {
|
|
2592
|
+
args,
|
|
2593
|
+
options
|
|
2594
|
+
};
|
|
2595
|
+
}
|
|
2596
|
+
async run(argv = processArgs2) {
|
|
2597
|
+
return this.parse(argv, { run: true, exitOnError: true });
|
|
2598
|
+
}
|
|
2599
|
+
handleUsageError(err) {
|
|
2600
|
+
const isClappError = !!err && typeof err === "object" && err.name === "ClappError";
|
|
2601
|
+
const isUsage = isClappError && err.isUsageError !== false;
|
|
2602
|
+
if (!isUsage)
|
|
2603
|
+
return;
|
|
2604
|
+
const e = err;
|
|
2605
|
+
const raw = e.message ?? "command-line error";
|
|
2606
|
+
const label = this.name ? `${this.name}: ` : "";
|
|
2607
|
+
const suffix = /--help/.test(raw) ? "" : `
|
|
2608
|
+
Run \`${this.name ?? "cli"} --help\` for usage.`;
|
|
2609
|
+
process5.stderr.write(`${label}${raw}${suffix}
|
|
2610
|
+
`);
|
|
2611
|
+
process5.exit(e.exitCode ?? 2);
|
|
2612
|
+
}
|
|
2613
|
+
async runMatchedCommand() {
|
|
2614
|
+
const { args, options, matchedCommand: command } = this;
|
|
2615
|
+
if (!command || !command.commandAction)
|
|
2616
|
+
return;
|
|
2617
|
+
command.checkUnknownOptions();
|
|
2618
|
+
command.checkOptionValue();
|
|
2619
|
+
command.checkRequiredArgs();
|
|
2620
|
+
const actionArgs = [];
|
|
2621
|
+
command.args.forEach((arg, index) => {
|
|
2622
|
+
if (arg.variadic) {
|
|
2623
|
+
actionArgs.push(args.slice(index));
|
|
2624
|
+
} else {
|
|
2625
|
+
actionArgs.push(args[index]);
|
|
2626
|
+
}
|
|
2627
|
+
});
|
|
2628
|
+
actionArgs.push(options);
|
|
2629
|
+
const context = {
|
|
2630
|
+
command,
|
|
2631
|
+
args: actionArgs,
|
|
2632
|
+
options
|
|
2633
|
+
};
|
|
2634
|
+
for (const hook of command.beforeHooks) {
|
|
2635
|
+
await hook(context);
|
|
2636
|
+
}
|
|
2637
|
+
let actionResult;
|
|
2638
|
+
const executeAction = async () => {
|
|
2639
|
+
const result = command.commandAction.apply(this, actionArgs);
|
|
2640
|
+
if (result instanceof Promise) {
|
|
2641
|
+
actionResult = await result;
|
|
2642
|
+
} else {
|
|
2643
|
+
actionResult = result;
|
|
2644
|
+
}
|
|
2645
|
+
return actionResult;
|
|
2646
|
+
};
|
|
2647
|
+
if (command.middleware.length > 0) {
|
|
2648
|
+
let index = 0;
|
|
2649
|
+
const next = async () => {
|
|
2650
|
+
if (index < command.middleware.length) {
|
|
2651
|
+
const middleware = command.middleware[index++];
|
|
2652
|
+
await middleware({ ...context, next });
|
|
2653
|
+
} else {
|
|
2654
|
+
await executeAction();
|
|
2655
|
+
}
|
|
2656
|
+
};
|
|
2657
|
+
await next();
|
|
2658
|
+
} else {
|
|
2659
|
+
await executeAction();
|
|
2660
|
+
}
|
|
2661
|
+
for (const hook of command.afterHooks) {
|
|
2662
|
+
await hook(context);
|
|
2663
|
+
}
|
|
2664
|
+
return actionResult;
|
|
2665
|
+
}
|
|
2666
|
+
removeSignalHandlers() {
|
|
2667
|
+
if (!this.signalHandlersSet) {
|
|
2668
|
+
return this;
|
|
2669
|
+
}
|
|
2670
|
+
process5.removeAllListeners("SIGINT");
|
|
2671
|
+
process5.removeAllListeners("SIGTERM");
|
|
2672
|
+
this.signalHandlersSet = false;
|
|
2673
|
+
return this;
|
|
2674
|
+
}
|
|
2675
|
+
destroy() {
|
|
2676
|
+
this.removeSignalHandlers();
|
|
2677
|
+
this.commands = [];
|
|
2678
|
+
this.rawArgs = [];
|
|
2679
|
+
this.args = [];
|
|
2680
|
+
this.options = {};
|
|
2681
|
+
this.matchedCommand = undefined;
|
|
2682
|
+
this.matchedCommandName = undefined;
|
|
2683
|
+
this.removeAllListeners();
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
class CLICache {
|
|
2687
|
+
cache;
|
|
2688
|
+
enabled;
|
|
2689
|
+
cleanupInterval = null;
|
|
2690
|
+
hits = 0;
|
|
2691
|
+
misses = 0;
|
|
2692
|
+
constructor() {
|
|
2693
|
+
this.cache = new Map;
|
|
2694
|
+
this.enabled = true;
|
|
2695
|
+
this.startCleanupInterval();
|
|
2696
|
+
}
|
|
2697
|
+
startCleanupInterval() {
|
|
2698
|
+
if (this.cleanupInterval) {
|
|
2699
|
+
return;
|
|
2700
|
+
}
|
|
2701
|
+
this.cleanupInterval = setInterval(() => {
|
|
2702
|
+
this.cleanup();
|
|
2703
|
+
}, 30000);
|
|
2704
|
+
this.cleanupInterval.unref();
|
|
2705
|
+
}
|
|
2706
|
+
stopCleanup() {
|
|
2707
|
+
if (this.cleanupInterval) {
|
|
2708
|
+
clearInterval(this.cleanupInterval);
|
|
2709
|
+
this.cleanupInterval = null;
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
isEnabled() {
|
|
2713
|
+
return this.enabled;
|
|
2714
|
+
}
|
|
2715
|
+
get(key) {
|
|
2716
|
+
if (!this.enabled) {
|
|
2717
|
+
this.misses++;
|
|
2718
|
+
return;
|
|
2719
|
+
}
|
|
2720
|
+
const entry = this.cache.get(key);
|
|
2721
|
+
if (!entry) {
|
|
2722
|
+
this.misses++;
|
|
2723
|
+
return;
|
|
2724
|
+
}
|
|
2725
|
+
const now = Date.now();
|
|
2726
|
+
if (now - entry.timestamp > entry.ttl) {
|
|
2727
|
+
this.cache.delete(key);
|
|
2728
|
+
this.misses++;
|
|
2729
|
+
return;
|
|
2730
|
+
}
|
|
2731
|
+
this.hits++;
|
|
2732
|
+
return entry.value;
|
|
2733
|
+
}
|
|
2734
|
+
set(key, value, ttl = 5000) {
|
|
2735
|
+
if (!this.enabled) {
|
|
2736
|
+
return;
|
|
2737
|
+
}
|
|
2738
|
+
this.cache.set(key, {
|
|
2739
|
+
value,
|
|
2740
|
+
timestamp: Date.now(),
|
|
2741
|
+
ttl
|
|
2742
|
+
});
|
|
2743
|
+
}
|
|
2744
|
+
has(key) {
|
|
2745
|
+
if (!this.enabled) {
|
|
2746
|
+
return false;
|
|
2747
|
+
}
|
|
2748
|
+
const entry = this.cache.get(key);
|
|
2749
|
+
if (!entry) {
|
|
2750
|
+
return false;
|
|
2751
|
+
}
|
|
2752
|
+
const now = Date.now();
|
|
2753
|
+
if (now - entry.timestamp > entry.ttl) {
|
|
2754
|
+
this.cache.delete(key);
|
|
2755
|
+
return false;
|
|
2756
|
+
}
|
|
2757
|
+
return true;
|
|
2758
|
+
}
|
|
2759
|
+
delete(key) {
|
|
2760
|
+
this.cache.delete(key);
|
|
2761
|
+
}
|
|
2762
|
+
clear() {
|
|
2763
|
+
this.cache.clear();
|
|
2764
|
+
}
|
|
2765
|
+
disable() {
|
|
2766
|
+
this.enabled = false;
|
|
2767
|
+
this.clear();
|
|
2768
|
+
}
|
|
2769
|
+
enable() {
|
|
2770
|
+
this.enabled = true;
|
|
2771
|
+
}
|
|
2772
|
+
stats() {
|
|
2773
|
+
return {
|
|
2774
|
+
size: this.cache.size,
|
|
2775
|
+
enabled: this.enabled,
|
|
2776
|
+
hits: this.hits,
|
|
2777
|
+
misses: this.misses
|
|
2778
|
+
};
|
|
2779
|
+
}
|
|
2780
|
+
resetStats() {
|
|
2781
|
+
this.hits = 0;
|
|
2782
|
+
this.misses = 0;
|
|
2783
|
+
}
|
|
2784
|
+
keys() {
|
|
2785
|
+
return Array.from(this.cache.keys());
|
|
2786
|
+
}
|
|
2787
|
+
cleanup() {
|
|
2788
|
+
const now = Date.now();
|
|
2789
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
2790
|
+
if (now - entry.timestamp > entry.ttl) {
|
|
2791
|
+
this.cache.delete(key);
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
destroy() {
|
|
2796
|
+
this.stopCleanup();
|
|
2797
|
+
this.clear();
|
|
2798
|
+
this.resetStats();
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
var cliCache = new CLICache;
|
|
2802
|
+
function detectColorSupport2() {
|
|
2803
|
+
if ("FORCE_COLOR" in process7.env) {
|
|
2804
|
+
return process7.env.FORCE_COLOR !== "0";
|
|
2805
|
+
}
|
|
2806
|
+
if ("NO_COLOR" in process7.env || process7.env.TERM === "dumb") {
|
|
2807
|
+
return false;
|
|
2808
|
+
}
|
|
2809
|
+
if (process7.platform === "win32") {
|
|
2810
|
+
return true;
|
|
2811
|
+
}
|
|
2812
|
+
return tty.isatty(1) && tty.isatty(2);
|
|
2813
|
+
}
|
|
2814
|
+
var enabled = detectColorSupport2();
|
|
2815
|
+
function wrap(open, close) {
|
|
2816
|
+
if (!enabled)
|
|
2817
|
+
return (text) => text;
|
|
2818
|
+
return (text) => open + text + close;
|
|
2819
|
+
}
|
|
2820
|
+
var reset = wrap("\x1B[0m", "\x1B[0m");
|
|
2821
|
+
var red = wrap("\x1B[31m", "\x1B[39m");
|
|
2822
|
+
var green = wrap("\x1B[32m", "\x1B[39m");
|
|
2823
|
+
var yellow = wrap("\x1B[33m", "\x1B[39m");
|
|
2824
|
+
var blue = wrap("\x1B[34m", "\x1B[39m");
|
|
2825
|
+
var magenta = wrap("\x1B[35m", "\x1B[39m");
|
|
2826
|
+
var cyan = wrap("\x1B[36m", "\x1B[39m");
|
|
2827
|
+
var white = wrap("\x1B[37m", "\x1B[39m");
|
|
2828
|
+
var gray = wrap("\x1B[90m", "\x1B[39m");
|
|
2829
|
+
var bold = wrap("\x1B[1m", "\x1B[22m");
|
|
2830
|
+
var italic = wrap("\x1B[3m", "\x1B[23m");
|
|
2831
|
+
var underline = wrap("\x1B[4m", "\x1B[24m");
|
|
2832
|
+
var dim = wrap("\x1B[2m", "\x1B[22m");
|
|
2833
|
+
var inverse = wrap("\x1B[7m", "\x1B[27m");
|
|
2834
|
+
var hidden = wrap("\x1B[8m", "\x1B[28m");
|
|
2835
|
+
var strikethrough = wrap("\x1B[9m", "\x1B[29m");
|
|
2836
|
+
var bgRed = wrap("\x1B[41m", "\x1B[49m");
|
|
2837
|
+
var bgGreen = wrap("\x1B[42m", "\x1B[49m");
|
|
2838
|
+
var bgYellow = wrap("\x1B[43m", "\x1B[49m");
|
|
2839
|
+
var bgBlue = wrap("\x1B[44m", "\x1B[49m");
|
|
2840
|
+
var bgMagenta = wrap("\x1B[45m", "\x1B[49m");
|
|
2841
|
+
var bgCyan = wrap("\x1B[46m", "\x1B[49m");
|
|
2842
|
+
var bgWhite = wrap("\x1B[47m", "\x1B[49m");
|
|
2843
|
+
var isColorSupported = enabled;
|
|
2844
|
+
var colors = {
|
|
2845
|
+
reset,
|
|
2846
|
+
red,
|
|
2847
|
+
green,
|
|
2848
|
+
yellow,
|
|
2849
|
+
blue,
|
|
2850
|
+
magenta,
|
|
2851
|
+
cyan,
|
|
2852
|
+
white,
|
|
2853
|
+
gray,
|
|
2854
|
+
bold,
|
|
2855
|
+
italic,
|
|
2856
|
+
underline,
|
|
2857
|
+
dim,
|
|
2858
|
+
inverse,
|
|
2859
|
+
hidden,
|
|
2860
|
+
strikethrough,
|
|
2861
|
+
bgRed,
|
|
2862
|
+
bgGreen,
|
|
2863
|
+
bgYellow,
|
|
2864
|
+
bgBlue,
|
|
2865
|
+
bgMagenta,
|
|
2866
|
+
bgCyan,
|
|
2867
|
+
bgWhite,
|
|
2868
|
+
isColorSupported
|
|
2869
|
+
};
|
|
2870
|
+
var colors_default = colors;
|
|
2871
|
+
var actions = ["up", "down", "left", "right", "space", "enter", "cancel"];
|
|
2872
|
+
var settings = {
|
|
2873
|
+
actions: new Set(actions),
|
|
2874
|
+
aliases: new Map([
|
|
2875
|
+
["k", "up"],
|
|
2876
|
+
["j", "down"],
|
|
2877
|
+
["h", "left"],
|
|
2878
|
+
["l", "right"],
|
|
2879
|
+
["\x03", "cancel"],
|
|
2880
|
+
["escape", "cancel"]
|
|
2881
|
+
]),
|
|
2882
|
+
messages: {
|
|
2883
|
+
cancel: "Canceled",
|
|
2884
|
+
error: "Something went wrong"
|
|
2885
|
+
}
|
|
2886
|
+
};
|
|
2887
|
+
function isActionKey(key, action) {
|
|
2888
|
+
if (typeof key === "string") {
|
|
2889
|
+
return settings.aliases.get(key) === action;
|
|
2890
|
+
}
|
|
2891
|
+
for (const value of key) {
|
|
2892
|
+
if (value === undefined)
|
|
2893
|
+
continue;
|
|
2894
|
+
if (isActionKey(value, action)) {
|
|
2895
|
+
return true;
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
return false;
|
|
2899
|
+
}
|
|
2900
|
+
function diffLines(a, b) {
|
|
2901
|
+
if (a === b)
|
|
2902
|
+
return [];
|
|
2903
|
+
const aLines = a.split(`
|
|
2904
|
+
`);
|
|
2905
|
+
const bLines = b.split(`
|
|
2906
|
+
`);
|
|
2907
|
+
const diff = [];
|
|
2908
|
+
for (let i = 0;i < Math.max(aLines.length, bLines.length); i++) {
|
|
2909
|
+
if (aLines[i] !== bLines[i])
|
|
2910
|
+
diff.push(i);
|
|
2911
|
+
}
|
|
2912
|
+
return diff;
|
|
2913
|
+
}
|
|
2914
|
+
var isWindows = process8.platform.startsWith("win");
|
|
2915
|
+
var CANCEL_SYMBOL = Symbol("clapp:cancel");
|
|
2916
|
+
function setRawMode(input, value) {
|
|
2917
|
+
const i = input;
|
|
2918
|
+
if (i.isTTY)
|
|
2919
|
+
i.setRawMode(value);
|
|
2920
|
+
}
|
|
2921
|
+
function stripAnsi(str) {
|
|
2922
|
+
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
2923
|
+
}
|
|
2924
|
+
function stringWidth(str) {
|
|
2925
|
+
return stripAnsi(str).length;
|
|
2926
|
+
}
|
|
2927
|
+
function wrapAnsi(text, columns, options) {
|
|
2928
|
+
if (!columns || columns < 1)
|
|
2929
|
+
return text;
|
|
2930
|
+
const hard = options?.hard ?? false;
|
|
2931
|
+
const trim = options?.trim ?? true;
|
|
2932
|
+
const lines = text.split(`
|
|
2933
|
+
`);
|
|
2934
|
+
const result = [];
|
|
2935
|
+
for (const line of lines) {
|
|
2936
|
+
if (stringWidth(line) <= columns) {
|
|
2937
|
+
result.push(trim ? line.trimEnd() : line);
|
|
2938
|
+
continue;
|
|
2939
|
+
}
|
|
2940
|
+
if (hard) {
|
|
2941
|
+
let current = "";
|
|
2942
|
+
let currentWidth = 0;
|
|
2943
|
+
let i = 0;
|
|
2944
|
+
while (i < line.length) {
|
|
2945
|
+
const ansiMatch = line.slice(i).match(/^\x1b\[[0-9;]*m/);
|
|
2946
|
+
if (ansiMatch) {
|
|
2947
|
+
current += ansiMatch[0];
|
|
2948
|
+
i += ansiMatch[0].length;
|
|
2949
|
+
continue;
|
|
2950
|
+
}
|
|
2951
|
+
if (currentWidth >= columns) {
|
|
2952
|
+
result.push(trim ? current.trimEnd() : current);
|
|
2953
|
+
current = "";
|
|
2954
|
+
currentWidth = 0;
|
|
2955
|
+
}
|
|
2956
|
+
current += line[i];
|
|
2957
|
+
currentWidth++;
|
|
2958
|
+
i++;
|
|
2959
|
+
}
|
|
2960
|
+
if (current) {
|
|
2961
|
+
result.push(trim ? current.trimEnd() : current);
|
|
2962
|
+
}
|
|
2963
|
+
} else {
|
|
2964
|
+
result.push(trim ? line.trimEnd() : line);
|
|
2965
|
+
}
|
|
2966
|
+
}
|
|
2967
|
+
return result.join(`
|
|
2968
|
+
`);
|
|
2969
|
+
}
|
|
2970
|
+
|
|
2971
|
+
class Prompt {
|
|
2972
|
+
input;
|
|
2973
|
+
output;
|
|
2974
|
+
_abortSignal;
|
|
2975
|
+
rl;
|
|
2976
|
+
opts;
|
|
2977
|
+
_render;
|
|
2978
|
+
_track = false;
|
|
2979
|
+
_prevFrame = "";
|
|
2980
|
+
_subscribers = new Map;
|
|
2981
|
+
_cursor = 0;
|
|
2982
|
+
_manualLine = "";
|
|
2983
|
+
state = "initial";
|
|
2984
|
+
error = "";
|
|
2985
|
+
value;
|
|
2986
|
+
userInput = "";
|
|
2987
|
+
constructor(options, trackValue = true) {
|
|
2988
|
+
const { input = stdin2, output = stdout2, render, signal, ...opts } = options;
|
|
2989
|
+
this.opts = opts;
|
|
2990
|
+
this.onKeypress = this.onKeypress.bind(this);
|
|
2991
|
+
this.close = this.close.bind(this);
|
|
2992
|
+
this.render = this.render.bind(this);
|
|
2993
|
+
this._render = render.bind(this);
|
|
2994
|
+
this._track = trackValue;
|
|
2995
|
+
this._abortSignal = signal;
|
|
2996
|
+
this.input = input;
|
|
2997
|
+
this.output = output;
|
|
2998
|
+
}
|
|
2999
|
+
unsubscribe() {
|
|
3000
|
+
this._subscribers.clear();
|
|
3001
|
+
}
|
|
3002
|
+
setSubscriber(event, opts) {
|
|
3003
|
+
const params = this._subscribers.get(event) ?? [];
|
|
3004
|
+
params.push(opts);
|
|
3005
|
+
this._subscribers.set(event, params);
|
|
3006
|
+
}
|
|
3007
|
+
on(event, cb) {
|
|
3008
|
+
this.setSubscriber(event, { cb });
|
|
3009
|
+
return this;
|
|
3010
|
+
}
|
|
3011
|
+
once(event, cb) {
|
|
3012
|
+
this.setSubscriber(event, { cb, once: true });
|
|
3013
|
+
return this;
|
|
3014
|
+
}
|
|
3015
|
+
emit(event, ...data) {
|
|
3016
|
+
const cbs = this._subscribers.get(event) ?? [];
|
|
3017
|
+
const cleanup = [];
|
|
3018
|
+
for (const subscriber of cbs) {
|
|
3019
|
+
subscriber.cb(...data);
|
|
3020
|
+
if (subscriber.once) {
|
|
3021
|
+
cleanup.push(() => cbs.splice(cbs.indexOf(subscriber), 1));
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
for (const cb of cleanup) {
|
|
3025
|
+
cb();
|
|
3026
|
+
}
|
|
3027
|
+
return this;
|
|
3028
|
+
}
|
|
3029
|
+
prompt() {
|
|
3030
|
+
return new Promise((resolve) => {
|
|
3031
|
+
if (this._abortSignal) {
|
|
3032
|
+
if (this._abortSignal.aborted) {
|
|
3033
|
+
this.state = "cancel";
|
|
3034
|
+
this.close();
|
|
3035
|
+
return resolve(CANCEL_SYMBOL);
|
|
3036
|
+
}
|
|
3037
|
+
this._abortSignal.addEventListener("abort", () => {
|
|
3038
|
+
this.state = "cancel";
|
|
3039
|
+
this.close();
|
|
3040
|
+
}, { once: true });
|
|
3041
|
+
}
|
|
3042
|
+
this.rl = readline2.createInterface({
|
|
3043
|
+
input: this.input,
|
|
3044
|
+
tabSize: 2,
|
|
3045
|
+
prompt: "",
|
|
3046
|
+
escapeCodeTimeout: 50,
|
|
3047
|
+
terminal: true
|
|
3048
|
+
});
|
|
3049
|
+
this.rl.prompt();
|
|
3050
|
+
if (this.opts.initialUserInput !== undefined) {
|
|
3051
|
+
this._setUserInput(this.opts.initialUserInput, true);
|
|
3052
|
+
}
|
|
3053
|
+
this.input.on("keypress", this.onKeypress);
|
|
3054
|
+
setRawMode(this.input, true);
|
|
3055
|
+
this.output.on("resize", this.render);
|
|
3056
|
+
this.render();
|
|
3057
|
+
this.once("submit", () => {
|
|
3058
|
+
this.output.write(cursor.show);
|
|
3059
|
+
this.output.off("resize", this.render);
|
|
3060
|
+
setRawMode(this.input, false);
|
|
3061
|
+
resolve(this.value);
|
|
3062
|
+
});
|
|
3063
|
+
this.once("cancel", () => {
|
|
3064
|
+
this.output.write(cursor.show);
|
|
3065
|
+
this.output.off("resize", this.render);
|
|
3066
|
+
setRawMode(this.input, false);
|
|
3067
|
+
resolve(CANCEL_SYMBOL);
|
|
3068
|
+
});
|
|
3069
|
+
});
|
|
3070
|
+
}
|
|
3071
|
+
_isActionKey(char, _key) {
|
|
3072
|
+
return char === "\t";
|
|
3073
|
+
}
|
|
3074
|
+
_setValue(value) {
|
|
3075
|
+
this.value = value;
|
|
3076
|
+
this.emit("value", this.value);
|
|
3077
|
+
}
|
|
3078
|
+
_setUserInput(value, write) {
|
|
3079
|
+
this.userInput = value ?? "";
|
|
3080
|
+
this.emit("userInput", this.userInput);
|
|
3081
|
+
if (write && this._track && this.rl) {
|
|
3082
|
+
this.rl.write(this.userInput);
|
|
3083
|
+
this._cursor = this.rl.cursor;
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
onKeypress(char, key) {
|
|
3087
|
+
if (this._track && key.name !== "return") {
|
|
3088
|
+
if (key.name && this._isActionKey(char, key)) {
|
|
3089
|
+
this.rl?.write(null, { ctrl: true, name: "h" });
|
|
3090
|
+
}
|
|
3091
|
+
this._cursor = this.rl?.cursor ?? 0;
|
|
3092
|
+
const isSpecialKey = key.name === "tab" || key.name === "escape" || key.name === "backspace" || key.name === "delete" || key.name === "enter" || key.name === "return" || key.name && ["up", "down", "left", "right"].includes(key.name);
|
|
3093
|
+
if (char && !isSpecialKey && char.length === 1 && char >= " ") {
|
|
3094
|
+
this._manualLine += char;
|
|
3095
|
+
} else if (key.name === "backspace" && this._manualLine.length > 0) {
|
|
3096
|
+
this._manualLine = this._manualLine.slice(0, -1);
|
|
3097
|
+
}
|
|
3098
|
+
const effectiveLine = this._manualLine.length >= (this.rl?.line?.length || 0) ? this._manualLine : this.rl?.line;
|
|
3099
|
+
this._setUserInput(effectiveLine);
|
|
3100
|
+
}
|
|
3101
|
+
if (this.state === "error") {
|
|
3102
|
+
this.state = "active";
|
|
3103
|
+
this.error = "";
|
|
3104
|
+
}
|
|
3105
|
+
if (key?.name) {
|
|
3106
|
+
if (!this._track && settings.aliases.has(key.name)) {
|
|
3107
|
+
this.emit("cursor", settings.aliases.get(key.name));
|
|
3108
|
+
}
|
|
3109
|
+
if (settings.actions.has(key.name)) {
|
|
3110
|
+
this.emit("cursor", key.name);
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
if (char && (char.toLowerCase() === "y" || char.toLowerCase() === "n")) {
|
|
3114
|
+
this.emit("confirm", char.toLowerCase() === "y");
|
|
3115
|
+
}
|
|
3116
|
+
this.emit("key", char?.toLowerCase(), key);
|
|
3117
|
+
if (key?.name === "return") {
|
|
3118
|
+
if (this.opts.validate) {
|
|
3119
|
+
const problem = this.opts.validate(this.value);
|
|
3120
|
+
if (problem) {
|
|
3121
|
+
this.error = problem instanceof Error ? problem.message : problem;
|
|
3122
|
+
this.state = "error";
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
if (this.state !== "error") {
|
|
3126
|
+
this.state = "submit";
|
|
3127
|
+
}
|
|
3128
|
+
}
|
|
3129
|
+
if (isActionKey([char, key?.name, key?.sequence], "cancel")) {
|
|
3130
|
+
this.state = "cancel";
|
|
3131
|
+
}
|
|
3132
|
+
if (this.state === "submit" || this.state === "cancel") {
|
|
3133
|
+
this.emit("finalize");
|
|
3134
|
+
}
|
|
3135
|
+
this.render();
|
|
3136
|
+
if (this.state === "submit" || this.state === "cancel") {
|
|
3137
|
+
this.close();
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
close() {
|
|
3141
|
+
this.input.unpipe();
|
|
3142
|
+
this.input.removeListener("keypress", this.onKeypress);
|
|
3143
|
+
this.output.write(`
|
|
3144
|
+
`);
|
|
3145
|
+
setRawMode(this.input, false);
|
|
3146
|
+
this.rl?.close();
|
|
3147
|
+
this.rl = undefined;
|
|
3148
|
+
this.emit(`${this.state}`, this.value);
|
|
3149
|
+
if (this.state === "cancel") {
|
|
3150
|
+
setTimeout(() => {
|
|
3151
|
+
this.unsubscribe();
|
|
3152
|
+
}, 10);
|
|
3153
|
+
} else {
|
|
3154
|
+
this.unsubscribe();
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
restoreCursor() {
|
|
3158
|
+
const lines = wrapAnsi(this._prevFrame, process9.stdout.columns, { hard: true, trim: false }).split(`
|
|
3159
|
+
`).length - 1;
|
|
3160
|
+
this.output.write(cursor.move(-999, lines * -1));
|
|
3161
|
+
}
|
|
3162
|
+
render() {
|
|
3163
|
+
const frame = wrapAnsi(this._render(this) ?? "", process9.stdout.columns, {
|
|
3164
|
+
hard: true,
|
|
3165
|
+
trim: false
|
|
3166
|
+
});
|
|
3167
|
+
if (frame === this._prevFrame) {
|
|
3168
|
+
return;
|
|
3169
|
+
}
|
|
3170
|
+
if (this.state === "initial") {
|
|
3171
|
+
this.output.write(cursor.hide);
|
|
3172
|
+
} else {
|
|
3173
|
+
const diff = diffLines(this._prevFrame, frame);
|
|
3174
|
+
this.restoreCursor();
|
|
3175
|
+
if (diff && diff?.length === 1) {
|
|
3176
|
+
const diffLine = diff[0];
|
|
3177
|
+
this.output.write(cursor.move(0, diffLine));
|
|
3178
|
+
this.output.write(erase.lines(1));
|
|
3179
|
+
const lines = frame.split(`
|
|
3180
|
+
`);
|
|
3181
|
+
this.output.write(lines[diffLine]);
|
|
3182
|
+
this._prevFrame = frame;
|
|
3183
|
+
this.output.write(cursor.move(0, lines.length - diffLine - 1));
|
|
3184
|
+
return;
|
|
3185
|
+
}
|
|
3186
|
+
if (diff && diff?.length > 1) {
|
|
3187
|
+
const diffLine = diff[0];
|
|
3188
|
+
this.output.write(cursor.move(0, diffLine));
|
|
3189
|
+
this.output.write(erase.down());
|
|
3190
|
+
const lines = frame.split(`
|
|
3191
|
+
`);
|
|
3192
|
+
const newLines = lines.slice(diffLine);
|
|
3193
|
+
this.output.write(newLines.join(`
|
|
3194
|
+
`));
|
|
3195
|
+
this._prevFrame = frame;
|
|
3196
|
+
return;
|
|
3197
|
+
}
|
|
3198
|
+
this.output.write(erase.down());
|
|
3199
|
+
}
|
|
3200
|
+
this.output.write(frame);
|
|
3201
|
+
if (this.state === "initial") {
|
|
3202
|
+
this.state = "active";
|
|
3203
|
+
}
|
|
3204
|
+
this._prevFrame = frame;
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
function getCursorForValue(selected, items) {
|
|
3208
|
+
if (selected === undefined) {
|
|
3209
|
+
return 0;
|
|
3210
|
+
}
|
|
3211
|
+
const currLength = items.length;
|
|
3212
|
+
if (currLength === 0) {
|
|
3213
|
+
return 0;
|
|
3214
|
+
}
|
|
3215
|
+
const index = items.findIndex((item) => item.value === selected);
|
|
3216
|
+
return index !== -1 ? index : 0;
|
|
3217
|
+
}
|
|
3218
|
+
function defaultFilter(input, option) {
|
|
3219
|
+
const label = option.label ?? String(option.value);
|
|
3220
|
+
return label.toLowerCase().includes(input.toLowerCase());
|
|
3221
|
+
}
|
|
3222
|
+
function normalisedValue(multiple, values) {
|
|
3223
|
+
if (!values) {
|
|
3224
|
+
return;
|
|
3225
|
+
}
|
|
3226
|
+
if (multiple) {
|
|
3227
|
+
return values;
|
|
3228
|
+
}
|
|
3229
|
+
return values[0];
|
|
3230
|
+
}
|
|
3231
|
+
|
|
3232
|
+
class AutocompletePrompt extends Prompt {
|
|
3233
|
+
filteredOptions;
|
|
3234
|
+
multiple;
|
|
3235
|
+
isNavigating = false;
|
|
3236
|
+
selectedValues = [];
|
|
3237
|
+
focusedValue;
|
|
3238
|
+
#cursor = 0;
|
|
3239
|
+
#lastUserInput = "";
|
|
3240
|
+
#filterFn;
|
|
3241
|
+
#options;
|
|
3242
|
+
get cursor() {
|
|
3243
|
+
return this.#cursor;
|
|
3244
|
+
}
|
|
3245
|
+
get userInputWithCursor() {
|
|
3246
|
+
if (!this.userInput) {
|
|
3247
|
+
return colors_default.inverse(colors_default.hidden("_"));
|
|
3248
|
+
}
|
|
3249
|
+
if (this._cursor >= this.userInput.length) {
|
|
3250
|
+
return `${this.userInput}\u2588`;
|
|
3251
|
+
}
|
|
3252
|
+
const s1 = this.userInput.slice(0, this._cursor);
|
|
3253
|
+
const [s2, ...s3] = this.userInput.slice(this._cursor);
|
|
3254
|
+
return `${s1}${colors_default.inverse(s2)}${s3.join("")}`;
|
|
3255
|
+
}
|
|
3256
|
+
get options() {
|
|
3257
|
+
if (typeof this.#options === "function") {
|
|
3258
|
+
return this.#options();
|
|
3259
|
+
}
|
|
3260
|
+
return this.#options;
|
|
3261
|
+
}
|
|
3262
|
+
constructor(opts) {
|
|
3263
|
+
super(opts);
|
|
3264
|
+
this.#options = opts.options;
|
|
3265
|
+
const options = this.options;
|
|
3266
|
+
this.filteredOptions = [...options];
|
|
3267
|
+
this.multiple = opts.multiple === true;
|
|
3268
|
+
this.#filterFn = opts.filter ?? defaultFilter;
|
|
3269
|
+
let initialValues;
|
|
3270
|
+
if (opts.initialValue && Array.isArray(opts.initialValue)) {
|
|
3271
|
+
if (this.multiple) {
|
|
3272
|
+
initialValues = opts.initialValue;
|
|
3273
|
+
} else {
|
|
3274
|
+
initialValues = opts.initialValue.slice(0, 1);
|
|
3275
|
+
}
|
|
3276
|
+
} else {
|
|
3277
|
+
if (!this.multiple && this.options.length > 0) {
|
|
3278
|
+
initialValues = [this.options[0].value];
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
if (initialValues) {
|
|
3282
|
+
for (const selectedValue of initialValues) {
|
|
3283
|
+
const selectedIndex = options.findIndex((opt) => opt.value === selectedValue);
|
|
3284
|
+
if (selectedIndex !== -1) {
|
|
3285
|
+
this.toggleSelected(selectedValue);
|
|
3286
|
+
this.#cursor = selectedIndex;
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3289
|
+
}
|
|
3290
|
+
this.focusedValue = this.options[this.#cursor]?.value;
|
|
3291
|
+
this.on("key", (char, key) => this.#onKey(char, key));
|
|
3292
|
+
this.on("userInput", (value) => this.#onUserInputChanged(value));
|
|
3293
|
+
}
|
|
3294
|
+
_isActionKey(char, key) {
|
|
3295
|
+
return char === "\t" || this.multiple && this.isNavigating && key.name === "space" && char !== undefined && char !== "";
|
|
3296
|
+
}
|
|
3297
|
+
#onKey(_char, key) {
|
|
3298
|
+
const isUpKey = key.name === "up";
|
|
3299
|
+
const isDownKey = key.name === "down";
|
|
3300
|
+
const isReturnKey = key.name === "return";
|
|
3301
|
+
if (isUpKey || isDownKey) {
|
|
3302
|
+
this.#cursor = Math.max(0, Math.min(this.#cursor + (isUpKey ? -1 : 1), this.filteredOptions.length - 1));
|
|
3303
|
+
this.focusedValue = this.filteredOptions[this.#cursor]?.value;
|
|
3304
|
+
if (!this.multiple) {
|
|
3305
|
+
this.selectedValues = [this.focusedValue];
|
|
3306
|
+
}
|
|
3307
|
+
this.isNavigating = true;
|
|
3308
|
+
} else if (isReturnKey) {
|
|
3309
|
+
this.value = normalisedValue(this.multiple, this.selectedValues);
|
|
3310
|
+
} else {
|
|
3311
|
+
if (this.multiple) {
|
|
3312
|
+
if (this.focusedValue !== undefined && (key.name === "tab" || this.isNavigating && key.name === "space")) {
|
|
3313
|
+
this.toggleSelected(this.focusedValue);
|
|
3314
|
+
} else {
|
|
3315
|
+
this.isNavigating = false;
|
|
3316
|
+
}
|
|
3317
|
+
} else {
|
|
3318
|
+
if (this.focusedValue) {
|
|
3319
|
+
this.selectedValues = [this.focusedValue];
|
|
3320
|
+
}
|
|
3321
|
+
this.isNavigating = false;
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3325
|
+
deselectAll() {
|
|
3326
|
+
this.selectedValues = [];
|
|
3327
|
+
}
|
|
3328
|
+
toggleSelected(value) {
|
|
3329
|
+
if (this.filteredOptions.length === 0) {
|
|
3330
|
+
return;
|
|
3331
|
+
}
|
|
3332
|
+
if (this.multiple) {
|
|
3333
|
+
if (this.selectedValues.includes(value)) {
|
|
3334
|
+
this.selectedValues = this.selectedValues.filter((v) => v !== value);
|
|
3335
|
+
} else {
|
|
3336
|
+
this.selectedValues = [...this.selectedValues, value];
|
|
3337
|
+
}
|
|
3338
|
+
} else {
|
|
3339
|
+
this.selectedValues = [value];
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
#onUserInputChanged(value) {
|
|
3343
|
+
if (value !== this.#lastUserInput) {
|
|
3344
|
+
this.#lastUserInput = value;
|
|
3345
|
+
const options = this.options;
|
|
3346
|
+
if (value) {
|
|
3347
|
+
this.filteredOptions = options.filter((opt) => this.#filterFn(value, opt));
|
|
3348
|
+
} else {
|
|
3349
|
+
this.filteredOptions = [...options];
|
|
3350
|
+
}
|
|
3351
|
+
this.#cursor = getCursorForValue(this.focusedValue, this.filteredOptions);
|
|
3352
|
+
this.focusedValue = this.filteredOptions[this.#cursor]?.value;
|
|
3353
|
+
if (!this.multiple) {
|
|
3354
|
+
if (this.focusedValue !== undefined) {
|
|
3355
|
+
this.toggleSelected(this.focusedValue);
|
|
3356
|
+
} else {
|
|
3357
|
+
this.deselectAll();
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
class GroupMultiSelectPrompt extends Prompt {
|
|
3364
|
+
options;
|
|
3365
|
+
cursor = 0;
|
|
3366
|
+
#selectableGroups;
|
|
3367
|
+
getGroupItems(group) {
|
|
3368
|
+
return this.options.filter((o) => o.group === group);
|
|
3369
|
+
}
|
|
3370
|
+
isGroupSelected(group) {
|
|
3371
|
+
const items = this.getGroupItems(group);
|
|
3372
|
+
const value = this.value;
|
|
3373
|
+
if (value === undefined) {
|
|
3374
|
+
return false;
|
|
3375
|
+
}
|
|
3376
|
+
return items.every((i) => value.includes(i.value));
|
|
3377
|
+
}
|
|
3378
|
+
toggleValue() {
|
|
3379
|
+
const item = this.options[this.cursor];
|
|
3380
|
+
if (this.value === undefined) {
|
|
3381
|
+
this.value = [];
|
|
3382
|
+
}
|
|
3383
|
+
if (item.group === true) {
|
|
3384
|
+
const group = String(item.value);
|
|
3385
|
+
const groupedItems = this.getGroupItems(group);
|
|
3386
|
+
if (this.isGroupSelected(group)) {
|
|
3387
|
+
this.value = this.value.filter((v) => groupedItems.findIndex((i) => i.value === v) === -1);
|
|
3388
|
+
} else {
|
|
3389
|
+
this.value = [...this.value, ...groupedItems.map((i) => i.value)];
|
|
3390
|
+
}
|
|
3391
|
+
this.value = Array.from(new Set(this.value));
|
|
3392
|
+
} else {
|
|
3393
|
+
const selected = this.value.includes(item.value);
|
|
3394
|
+
this.value = selected ? this.value.filter((v) => v !== item.value) : [...this.value, item.value];
|
|
3395
|
+
}
|
|
3396
|
+
}
|
|
3397
|
+
constructor(opts) {
|
|
3398
|
+
super(opts, false);
|
|
3399
|
+
const { options } = opts;
|
|
3400
|
+
this.#selectableGroups = opts.selectableGroups !== false;
|
|
3401
|
+
this.options = Object.entries(options).flatMap(([key, option]) => [
|
|
3402
|
+
{ value: key, group: true, label: key },
|
|
3403
|
+
...option.map((opt) => ({ ...opt, group: key }))
|
|
3404
|
+
]);
|
|
3405
|
+
this.value = [...opts.initialValues ?? []];
|
|
3406
|
+
this.cursor = Math.max(this.options.findIndex(({ value }) => value === opts.cursorAt), this.#selectableGroups ? 0 : 1);
|
|
3407
|
+
this.on("cursor", (key) => {
|
|
3408
|
+
switch (key) {
|
|
3409
|
+
case "left":
|
|
3410
|
+
case "up": {
|
|
3411
|
+
this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1;
|
|
3412
|
+
const currentIsGroup = this.options[this.cursor]?.group === true;
|
|
3413
|
+
if (!this.#selectableGroups && currentIsGroup) {
|
|
3414
|
+
this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1;
|
|
3415
|
+
}
|
|
3416
|
+
break;
|
|
3417
|
+
}
|
|
3418
|
+
case "down":
|
|
3419
|
+
case "right": {
|
|
3420
|
+
this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1;
|
|
3421
|
+
const currentIsGroup = this.options[this.cursor]?.group === true;
|
|
3422
|
+
if (!this.#selectableGroups && currentIsGroup) {
|
|
3423
|
+
this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1;
|
|
3424
|
+
}
|
|
3425
|
+
break;
|
|
3426
|
+
}
|
|
3427
|
+
case "space":
|
|
3428
|
+
this.toggleValue();
|
|
3429
|
+
break;
|
|
3430
|
+
}
|
|
3431
|
+
});
|
|
3432
|
+
}
|
|
3433
|
+
}
|
|
3434
|
+
var unicode = isUnicodeSupported();
|
|
3435
|
+
var unicodeOr = (c, fallback) => unicode ? c : fallback;
|
|
3436
|
+
var S_STEP_ACTIVE = unicodeOr("\u25C6", "*");
|
|
3437
|
+
var S_STEP_CANCEL = unicodeOr("\u25A0", "x");
|
|
3438
|
+
var S_STEP_ERROR = unicodeOr("\u25B2", "x");
|
|
3439
|
+
var S_STEP_SUBMIT = unicodeOr("\u25C7", "o");
|
|
3440
|
+
var S_BAR_START = unicodeOr("\u250C", "T");
|
|
3441
|
+
var S_BAR = unicodeOr("\u2502", "|");
|
|
3442
|
+
var S_BAR_END = unicodeOr("\u2514", "\u2014");
|
|
3443
|
+
var S_RADIO_ACTIVE = unicodeOr("\u25CF", ">");
|
|
3444
|
+
var S_RADIO_INACTIVE = unicodeOr("\u25CB", " ");
|
|
3445
|
+
var S_CHECKBOX_ACTIVE = unicodeOr("\u25FB", "[\u2022]");
|
|
3446
|
+
var S_CHECKBOX_SELECTED = unicodeOr("\u25FC", "[+]");
|
|
3447
|
+
var S_CHECKBOX_INACTIVE = unicodeOr("\u25FB", "[ ]");
|
|
3448
|
+
var S_PASSWORD_MASK = unicodeOr("\u25AA", "\u2022");
|
|
3449
|
+
var S_BAR_H = unicodeOr("\u2500", "-");
|
|
3450
|
+
var S_CORNER_TOP_RIGHT = unicodeOr("\u256E", "+");
|
|
3451
|
+
var S_CONNECT_LEFT = unicodeOr("\u251C", "+");
|
|
3452
|
+
var S_CORNER_BOTTOM_RIGHT = unicodeOr("\u256F", "+");
|
|
3453
|
+
var S_INFO = unicodeOr("\u25CF", "\u2022");
|
|
3454
|
+
var S_SUCCESS = unicodeOr("\u25C6", "*");
|
|
3455
|
+
var S_WARN = unicodeOr("\u25B2", "!");
|
|
3456
|
+
var S_ERROR = unicodeOr("\u25A0", "x");
|
|
3457
|
+
var S_PROGRESS_CHAR = {
|
|
3458
|
+
light: unicodeOr("\u2500", "-"),
|
|
3459
|
+
heavy: unicodeOr("\u2501", "="),
|
|
3460
|
+
block: unicodeOr("\u2588", "#")
|
|
3461
|
+
};
|
|
3462
|
+
function getPrefix() {
|
|
3463
|
+
return `${colors_default.gray(S_BAR)} `;
|
|
3464
|
+
}
|
|
3465
|
+
var prefix = getPrefix();
|
|
3466
|
+
|
|
3467
|
+
// bin/cli.ts
|
|
3468
|
+
import { spawn as spawn2 } from "child_process";
|
|
3469
|
+
import { existsSync as existsSync6 } from "fs";
|
|
3470
|
+
import process6 from "process";
|
|
3471
|
+
|
|
3472
|
+
// src/binary-resolver.ts
|
|
3473
|
+
import { existsSync as existsSync2 } from "fs";
|
|
3474
|
+
function resolveCraftBinary(explicit) {
|
|
3475
|
+
if (explicit) {
|
|
3476
|
+
if (!existsSync2(explicit)) {
|
|
3477
|
+
throw new Error(`Custom Craft binary path not found: ${explicit}`);
|
|
3478
|
+
}
|
|
3479
|
+
return explicit;
|
|
3480
|
+
}
|
|
3481
|
+
const fromEnv = typeof process !== "undefined" ? process.env.CRAFT_BIN : undefined;
|
|
3482
|
+
if (fromEnv) {
|
|
3483
|
+
if (!existsSync2(fromEnv)) {
|
|
3484
|
+
throw new Error(`CRAFT_BIN points to ${fromEnv}, which does not exist`);
|
|
3485
|
+
}
|
|
3486
|
+
return fromEnv;
|
|
3487
|
+
}
|
|
3488
|
+
return "craft";
|
|
3489
|
+
}
|
|
3490
|
+
function craftBinaryNotFoundMessage(triedPath) {
|
|
3491
|
+
return [
|
|
3492
|
+
`Craft native binary not found (tried "${triedPath}").`,
|
|
3493
|
+
"",
|
|
3494
|
+
"Craft ships through the pantry package registry. Install it with:",
|
|
3495
|
+
"",
|
|
3496
|
+
" pantry install craft",
|
|
3497
|
+
"",
|
|
3498
|
+
"or, in a project, declare it in deps.yaml / pantry.jsonc and run",
|
|
3499
|
+
" pantry install",
|
|
3500
|
+
"",
|
|
3501
|
+
"See https://pantry.dev/quickstart for pantry installation instructions.",
|
|
3502
|
+
"For monorepo development you can also point CRAFT_BIN at a local build."
|
|
3503
|
+
].join(`
|
|
3504
|
+
`);
|
|
3505
|
+
}
|
|
3506
|
+
// package.json
|
|
3507
|
+
var version = "0.0.49";
|
|
3508
|
+
|
|
3509
|
+
// bin/cli.ts
|
|
3510
|
+
var cli = new CLI("craft");
|
|
3511
|
+
async function runCraftBinary(args) {
|
|
3512
|
+
const craftPath = resolveCraftBinary();
|
|
3513
|
+
return new Promise((resolve, reject) => {
|
|
3514
|
+
const proc = spawn2(craftPath, args, { stdio: "inherit" });
|
|
3515
|
+
proc.on("exit", (code) => {
|
|
3516
|
+
if (code === 0 || code === null)
|
|
3517
|
+
resolve();
|
|
3518
|
+
else
|
|
3519
|
+
reject(new Error(`Craft exited with code ${code}`));
|
|
3520
|
+
});
|
|
3521
|
+
proc.on("error", (error) => {
|
|
3522
|
+
if (error.code === "ENOENT") {
|
|
3523
|
+
reject(new Error(craftBinaryNotFoundMessage(craftPath)));
|
|
3524
|
+
return;
|
|
3525
|
+
}
|
|
3526
|
+
reject(error);
|
|
3527
|
+
});
|
|
3528
|
+
});
|
|
3529
|
+
}
|
|
3530
|
+
function parseIntInRange(name, value, min, max) {
|
|
3531
|
+
const n = typeof value === "number" ? value : Number.parseInt(String(value), 10);
|
|
3532
|
+
if (!Number.isFinite(n) || !Number.isInteger(n)) {
|
|
3533
|
+
throw new Error(`--${name} must be an integer (got: ${String(value)})`);
|
|
3534
|
+
}
|
|
3535
|
+
if (n < min || n > max) {
|
|
3536
|
+
throw new Error(`--${name} must be between ${min} and ${max} (got: ${n})`);
|
|
3537
|
+
}
|
|
3538
|
+
return n;
|
|
3539
|
+
}
|
|
3540
|
+
function validateUrl(value) {
|
|
3541
|
+
let parsed;
|
|
3542
|
+
try {
|
|
3543
|
+
parsed = new URL(value);
|
|
3544
|
+
} catch {
|
|
3545
|
+
throw new Error(`--url must be a valid URL (got: ${value})`);
|
|
3546
|
+
}
|
|
3547
|
+
const allowed = new Set(["http:", "https:", "file:", "data:"]);
|
|
3548
|
+
if (!allowed.has(parsed.protocol)) {
|
|
3549
|
+
throw new Error(`--url uses unsupported scheme "${parsed.protocol}"; allowed: ${[...allowed].join(", ")}`);
|
|
3550
|
+
}
|
|
3551
|
+
return parsed.toString();
|
|
3552
|
+
}
|
|
3553
|
+
cli.command("[url]", "Launch a Craft desktop app").option("--title <title>", "Window title").option("--width <width>", "Window width", { default: 800 }).option("--height <height>", "Window height", { default: 600 }).option("--x <x>", "Window x position").option("--y <y>", "Window y position").option("--frameless", "Frameless window").option("--transparent", "Transparent window").option("--always-on-top", "Always on top").option("--fullscreen", "Start in fullscreen").option("--dark-mode", "Enable dark mode").option("--hot-reload", "Enable hot reload").option("--dev-tools", "Enable developer tools").option("--no-resize", "Disable window resizing").example("craft http://localhost:3000").example('craft http://localhost:3000 --title "My App" --width 1200 --height 800').example("craft http://localhost:3000 --frameless --transparent --always-on-top").action(async (url, options) => {
|
|
3554
|
+
const args = [];
|
|
3555
|
+
try {
|
|
3556
|
+
if (url) {
|
|
3557
|
+
args.push("--url", validateUrl(url));
|
|
3558
|
+
}
|
|
3559
|
+
if (options?.title) {
|
|
3560
|
+
const title = String(options.title);
|
|
3561
|
+
if (title.length > 256)
|
|
3562
|
+
throw new Error("--title must be 256 chars or fewer");
|
|
3563
|
+
args.push("--title", title);
|
|
3564
|
+
}
|
|
3565
|
+
if (options?.width !== undefined)
|
|
3566
|
+
args.push("--width", String(parseIntInRange("width", options.width, 100, 1e4)));
|
|
3567
|
+
if (options?.height !== undefined)
|
|
3568
|
+
args.push("--height", String(parseIntInRange("height", options.height, 100, 1e4)));
|
|
3569
|
+
if (options?.x !== undefined)
|
|
3570
|
+
args.push("--x", String(parseIntInRange("x", options.x, -1e5, 1e5)));
|
|
3571
|
+
if (options?.y !== undefined)
|
|
3572
|
+
args.push("--y", String(parseIntInRange("y", options.y, -1e5, 1e5)));
|
|
3573
|
+
if (options?.frameless)
|
|
3574
|
+
args.push("--frameless");
|
|
3575
|
+
if (options?.transparent)
|
|
3576
|
+
args.push("--transparent");
|
|
3577
|
+
if (options?.alwaysOnTop)
|
|
3578
|
+
args.push("--always-on-top");
|
|
3579
|
+
if (options?.fullscreen)
|
|
3580
|
+
args.push("--fullscreen");
|
|
3581
|
+
if (options?.darkMode)
|
|
3582
|
+
args.push("--dark-mode");
|
|
3583
|
+
if (options?.hotReload)
|
|
3584
|
+
args.push("--hot-reload");
|
|
3585
|
+
if (options?.devTools)
|
|
3586
|
+
args.push("--dev-tools");
|
|
3587
|
+
if (options?.noResize)
|
|
3588
|
+
args.push("--no-resize");
|
|
3589
|
+
} catch (validationError) {
|
|
3590
|
+
console.error(`Error: ${validationError.message}`);
|
|
3591
|
+
process6.exit(2);
|
|
3592
|
+
}
|
|
3593
|
+
try {
|
|
3594
|
+
await runCraftBinary(args);
|
|
3595
|
+
} catch (error) {
|
|
3596
|
+
console.error("Error:", error.message);
|
|
3597
|
+
process6.exit(1);
|
|
3598
|
+
}
|
|
3599
|
+
});
|
|
3600
|
+
cli.command("version", "Show the version").action(() => {
|
|
3601
|
+
console.log(`v${version}`);
|
|
3602
|
+
});
|
|
3603
|
+
cli.command("build", "Build the Craft application").option("--release", "Build in release mode", { default: false }).option("--platform <platforms>", "Target platforms (ios,android,macos,windows,linux)", { default: "current" }).option("--config <path>", "Path to craft.config.ts").example("craft build").example("craft build --release").example("craft build --platform ios,android").example("craft build --platform macos,windows,linux").action(async (options) => {
|
|
3604
|
+
const platforms = options?.platform === "current" ? [process6.platform === "darwin" ? "macos" : process6.platform === "win32" ? "windows" : "linux"] : options?.platform.split(",").map((p) => p.trim());
|
|
3605
|
+
console.log(`
|
|
3606
|
+
\u26A1 Craft Build
|
|
3607
|
+
`);
|
|
3608
|
+
console.log(`Platforms: ${platforms.join(", ")}`);
|
|
3609
|
+
console.log(`Mode: ${options?.release ? "Release" : "Debug"}
|
|
3610
|
+
`);
|
|
3611
|
+
const { existsSync: existsSync7 } = await import("fs");
|
|
3612
|
+
const { spawn: spawn3 } = await import("child_process");
|
|
3613
|
+
for (const platform of platforms) {
|
|
3614
|
+
console.log(`\uD83D\uDCE6 Building for ${platform}...`);
|
|
3615
|
+
try {
|
|
3616
|
+
if (platform === "ios") {
|
|
3617
|
+
if (process6.platform !== "darwin") {
|
|
3618
|
+
console.log(` \u26A0\uFE0F iOS builds require macOS`);
|
|
3619
|
+
continue;
|
|
3620
|
+
}
|
|
3621
|
+
const iosDir = "./ios";
|
|
3622
|
+
if (existsSync7(iosDir)) {
|
|
3623
|
+
const buildType = options?.release ? "Release" : "Debug";
|
|
3624
|
+
await new Promise((resolve, reject) => {
|
|
3625
|
+
const proc = spawn3("xcodebuild", [
|
|
3626
|
+
"-project",
|
|
3627
|
+
`${iosDir}/App.xcodeproj`,
|
|
3628
|
+
"-scheme",
|
|
3629
|
+
"App",
|
|
3630
|
+
"-configuration",
|
|
3631
|
+
buildType,
|
|
3632
|
+
"-sdk",
|
|
3633
|
+
"iphoneos",
|
|
3634
|
+
"build"
|
|
3635
|
+
], { stdio: "inherit" });
|
|
3636
|
+
proc.on("exit", (code) => code === 0 ? resolve() : reject(new Error(`Exit ${code}`)));
|
|
3637
|
+
proc.on("error", reject);
|
|
3638
|
+
});
|
|
3639
|
+
console.log(` \u2705 iOS ${buildType} build complete`);
|
|
3640
|
+
} else {
|
|
3641
|
+
console.log(` \u26A0\uFE0F No iOS project found. Run: craft ios init`);
|
|
3642
|
+
}
|
|
3643
|
+
} else if (platform === "android") {
|
|
3644
|
+
const androidDir = "./android";
|
|
3645
|
+
if (existsSync7(androidDir)) {
|
|
3646
|
+
const task = options?.release ? "assembleRelease" : "assembleDebug";
|
|
3647
|
+
await new Promise((resolve, reject) => {
|
|
3648
|
+
const proc = spawn3("./gradlew", [task], {
|
|
3649
|
+
cwd: androidDir,
|
|
3650
|
+
stdio: "inherit"
|
|
3651
|
+
});
|
|
3652
|
+
proc.on("exit", (code) => code === 0 ? resolve() : reject(new Error(`Exit ${code}`)));
|
|
3653
|
+
proc.on("error", reject);
|
|
3654
|
+
});
|
|
3655
|
+
console.log(` \u2705 Android ${options?.release ? "Release" : "Debug"} build complete`);
|
|
3656
|
+
} else {
|
|
3657
|
+
console.log(` \u26A0\uFE0F No Android project found. Run: craft android init`);
|
|
3658
|
+
}
|
|
3659
|
+
} else if (platform === "macos" || platform === "windows" || platform === "linux") {
|
|
3660
|
+
const zigDir = existsSync7("./packages/zig") ? "./packages/zig" : ".";
|
|
3661
|
+
const optimizeFlag = options?.release ? "-Doptimize=ReleaseSafe" : "";
|
|
3662
|
+
await new Promise((resolve, reject) => {
|
|
3663
|
+
const args = ["build"];
|
|
3664
|
+
if (optimizeFlag)
|
|
3665
|
+
args.push(optimizeFlag);
|
|
3666
|
+
const proc = spawn3("zig", args, {
|
|
3667
|
+
cwd: zigDir,
|
|
3668
|
+
stdio: "inherit"
|
|
3669
|
+
});
|
|
3670
|
+
proc.on("exit", (code) => code === 0 ? resolve() : reject(new Error(`Exit ${code}`)));
|
|
3671
|
+
proc.on("error", reject);
|
|
3672
|
+
});
|
|
3673
|
+
console.log(` \u2705 ${platform} build complete`);
|
|
3674
|
+
}
|
|
3675
|
+
} catch (error) {
|
|
3676
|
+
console.error(` \u274C ${platform} build failed: ${error.message}`);
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3679
|
+
console.log(`
|
|
3680
|
+
\u2728 Build complete
|
|
3681
|
+
`);
|
|
3682
|
+
});
|
|
3683
|
+
cli.command("dev [url]", "Launch in development mode with hot reload").option("--title <title>", "Window title", { default: "Craft Dev" }).option("--width <width>", "Window width", { default: 1200 }).option("--height <height>", "Window height", { default: 800 }).example("craft dev http://localhost:3000").action(async (url, options) => {
|
|
3684
|
+
const args = [
|
|
3685
|
+
"--url",
|
|
3686
|
+
url || "http://localhost:3000",
|
|
3687
|
+
"--title",
|
|
3688
|
+
options?.title || "Craft Dev",
|
|
3689
|
+
"--width",
|
|
3690
|
+
String(options?.width || 1200),
|
|
3691
|
+
"--height",
|
|
3692
|
+
String(options?.height || 800),
|
|
3693
|
+
"--hot-reload",
|
|
3694
|
+
"--dev-tools",
|
|
3695
|
+
"--dark-mode"
|
|
3696
|
+
];
|
|
3697
|
+
try {
|
|
3698
|
+
await runCraftBinary(args);
|
|
3699
|
+
} catch (error) {
|
|
3700
|
+
console.error("Error:", error.message);
|
|
3701
|
+
process6.exit(1);
|
|
3702
|
+
}
|
|
3703
|
+
});
|
|
3704
|
+
cli.command("package", "Create installers for your Craft application").option("--name <name>", "Application name").option("--version <version>", "Application version").option("--binary <path>", "Path to application binary").option("--description <text>", "Application description").option("--author <name>", "Author/Maintainer name").option("--bundle-id <id>", "Bundle identifier (macOS/iOS)").option("--out <dir>", "Output directory (default: ./dist)").option("--icon <path>", "Application icon path").option("--platforms <list>", "Comma-separated platforms (macos,windows,linux)").option("--config <path>", "Load config from JSON file").option("--dmg", "Create DMG installer (macOS)").option("--pkg", "Create PKG installer (macOS)").option("--msi", "Create MSI installer (Windows)").option("--zip", "Create ZIP archive (Windows)").option("--deb", "Create DEB package (Linux)").option("--rpm", "Create RPM package (Linux)").option("--appimage", "Create AppImage (Linux)").example('craft package --name "My App" --version "1.0.0" --binary ./build/myapp').example("craft package --config package.json").example('craft package --name "My App" --version "1.0.0" --binary ./build/myapp --platforms macos,windows,linux').action(async (options) => {
|
|
3705
|
+
const packageModule = await Promise.resolve().then(() => (init_package(), exports_package));
|
|
3706
|
+
const { packageApp: packageApp2 } = packageModule;
|
|
3707
|
+
let config;
|
|
3708
|
+
if (options?.config) {
|
|
3709
|
+
if (!existsSync6(options.config)) {
|
|
3710
|
+
console.error(`\u274C Config file not found: ${options.config}`);
|
|
3711
|
+
process6.exit(1);
|
|
3712
|
+
}
|
|
3713
|
+
const configContent = await Bun.file(options.config).text();
|
|
3714
|
+
config = JSON.parse(configContent);
|
|
3715
|
+
} else {
|
|
3716
|
+
if (!options?.name) {
|
|
3717
|
+
console.error("\u274C --name is required");
|
|
3718
|
+
process6.exit(1);
|
|
3719
|
+
}
|
|
3720
|
+
if (!options?.version) {
|
|
3721
|
+
console.error("\u274C --version is required");
|
|
3722
|
+
process6.exit(1);
|
|
3723
|
+
}
|
|
3724
|
+
if (!options?.binary) {
|
|
3725
|
+
console.error("\u274C --binary is required");
|
|
3726
|
+
process6.exit(1);
|
|
3727
|
+
}
|
|
3728
|
+
config = {
|
|
3729
|
+
name: options.name,
|
|
3730
|
+
version: options.version,
|
|
3731
|
+
binaryPath: options.binary,
|
|
3732
|
+
description: options.description,
|
|
3733
|
+
author: options.author,
|
|
3734
|
+
bundleId: options.bundleId,
|
|
3735
|
+
outDir: options.out,
|
|
3736
|
+
iconPath: options.icon,
|
|
3737
|
+
platforms: options.platforms?.split(",")
|
|
3738
|
+
};
|
|
3739
|
+
if (options.dmg || options.pkg) {
|
|
3740
|
+
config.macos = {
|
|
3741
|
+
dmg: options.dmg,
|
|
3742
|
+
pkg: options.pkg
|
|
3743
|
+
};
|
|
3744
|
+
}
|
|
3745
|
+
if (options.msi || options.zip) {
|
|
3746
|
+
config.windows = {
|
|
3747
|
+
msi: options.msi,
|
|
3748
|
+
zip: options.zip
|
|
3749
|
+
};
|
|
3750
|
+
}
|
|
3751
|
+
if (options.deb || options.rpm || options.appimage) {
|
|
3752
|
+
config.linux = {
|
|
3753
|
+
deb: options.deb,
|
|
3754
|
+
rpm: options.rpm,
|
|
3755
|
+
appImage: options.appimage
|
|
3756
|
+
};
|
|
3757
|
+
}
|
|
3758
|
+
}
|
|
3759
|
+
console.log(`\uD83D\uDCE6 Craft Packaging Tool
|
|
3760
|
+
`);
|
|
3761
|
+
console.log(`Application: ${config.name} v${config.version}`);
|
|
3762
|
+
console.log(`Platforms: ${(config.platforms || ["current"]).join(", ")}
|
|
3763
|
+
`);
|
|
3764
|
+
try {
|
|
3765
|
+
const results = await packageApp2(config);
|
|
3766
|
+
console.log(`
|
|
3767
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`);
|
|
3768
|
+
console.log(`\uD83D\uDCCA Packaging Results
|
|
3769
|
+
`);
|
|
3770
|
+
for (const result of results) {
|
|
3771
|
+
const status = result.success ? "\u2705" : "\u274C";
|
|
3772
|
+
const format = result.format.toUpperCase();
|
|
3773
|
+
console.log(`${status} ${result.platform}/${format}`);
|
|
3774
|
+
if (result.success && result.outputPath) {
|
|
3775
|
+
console.log(` \uD83D\uDCC1 ${result.outputPath}`);
|
|
3776
|
+
} else if (result.error) {
|
|
3777
|
+
console.log(` \u26A0\uFE0F ${result.error}`);
|
|
3778
|
+
}
|
|
3779
|
+
}
|
|
3780
|
+
const successCount = results.filter((r) => r.success).length;
|
|
3781
|
+
const totalCount = results.length;
|
|
3782
|
+
console.log(`
|
|
3783
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`);
|
|
3784
|
+
console.log(`
|
|
3785
|
+
\u2728 Complete: ${successCount}/${totalCount} packages created
|
|
3786
|
+
`);
|
|
3787
|
+
process6.exit(successCount === totalCount ? 0 : 1);
|
|
3788
|
+
} catch (error) {
|
|
3789
|
+
console.error(`
|
|
3790
|
+
\u274C Error: ${error.message}
|
|
3791
|
+
`);
|
|
3792
|
+
process6.exit(1);
|
|
3793
|
+
}
|
|
3794
|
+
});
|
|
3795
|
+
cli.command("ios init <name>", "Initialize a new iOS project").option("--bundle-id <id>", "Bundle identifier (e.g., com.example.app)").option("--team-id <id>", "Apple Developer Team ID").option("-o, --output <dir>", "Output directory", { default: "./ios" }).example("craft ios init MyApp").example("craft ios init MyApp --bundle-id com.example.myapp").action(async (name, options) => {
|
|
3796
|
+
const iosModule = await Promise.resolve().then(() => (init_src(), exports_src));
|
|
3797
|
+
await iosModule.init({
|
|
3798
|
+
name,
|
|
3799
|
+
bundleId: options?.bundleId,
|
|
3800
|
+
teamId: options?.teamId,
|
|
3801
|
+
output: options?.output || "./ios"
|
|
3802
|
+
});
|
|
3803
|
+
});
|
|
3804
|
+
cli.command("ios build", "Build iOS project").option("--html-path <path>", "Path to HTML file").option("-d, --dev-server <url>", "Development server URL").option("-o, --output <dir>", "iOS project directory", { default: "./ios" }).option("-w, --watch", "Watch for file changes and rebuild").example("craft ios build").example("craft ios build --html-path ./dist/index.html").example("craft ios build --dev-server http://localhost:3456").example("craft ios build --watch").action(async (options) => {
|
|
3805
|
+
const iosModule = await Promise.resolve().then(() => (init_src(), exports_src));
|
|
3806
|
+
const doBuild = async () => {
|
|
3807
|
+
await iosModule.build({
|
|
3808
|
+
htmlPath: options?.htmlPath,
|
|
3809
|
+
devServer: options?.devServer,
|
|
3810
|
+
output: options?.output || "./ios"
|
|
3811
|
+
});
|
|
3812
|
+
};
|
|
3813
|
+
await doBuild();
|
|
3814
|
+
if (options?.watch) {
|
|
3815
|
+
console.log(`
|
|
3816
|
+
\uD83D\uDC40 Watching for changes...
|
|
3817
|
+
`);
|
|
3818
|
+
const { watch } = await import("fs");
|
|
3819
|
+
const { dirname: dirname3 } = await import("path");
|
|
3820
|
+
const watchPath = options?.htmlPath ? dirname3(options.htmlPath) : ".";
|
|
3821
|
+
watch(watchPath, { recursive: true }, async (event, filename) => {
|
|
3822
|
+
if (filename && (filename.endsWith(".html") || filename.endsWith(".js") || filename.endsWith(".css"))) {
|
|
3823
|
+
console.log(`
|
|
3824
|
+
\uD83D\uDCDD ${filename} changed, rebuilding...`);
|
|
3825
|
+
await doBuild();
|
|
3826
|
+
}
|
|
3827
|
+
});
|
|
3828
|
+
await new Promise(() => {});
|
|
3829
|
+
}
|
|
3830
|
+
});
|
|
3831
|
+
cli.command("ios open", "Open iOS project in Xcode").option("-o, --output <dir>", "iOS project directory", { default: "./ios" }).action(async (options) => {
|
|
3832
|
+
const iosModule = await Promise.resolve().then(() => (init_src(), exports_src));
|
|
3833
|
+
await iosModule.open({
|
|
3834
|
+
output: options?.output || "./ios"
|
|
3835
|
+
});
|
|
3836
|
+
});
|
|
3837
|
+
cli.command("ios run", "Build and run on iOS device or simulator").option("-s, --simulator", "Run on simulator instead of device").option("-o, --output <dir>", "iOS project directory", { default: "./ios" }).example("craft ios run").example("craft ios run --simulator").action(async (options) => {
|
|
3838
|
+
const iosModule = await Promise.resolve().then(() => (init_src(), exports_src));
|
|
3839
|
+
await iosModule.run({
|
|
3840
|
+
simulator: options?.simulator || false,
|
|
3841
|
+
output: options?.output || "./ios"
|
|
3842
|
+
});
|
|
3843
|
+
});
|
|
3844
|
+
cli.command("android init <name>", "Initialize a new Android project").option("--package <name>", "Package name (e.g., com.example.app)").option("-o, --output <dir>", "Output directory", { default: "./android" }).example("craft android init MyApp").example("craft android init MyApp --package com.example.myapp").action(async (name, options) => {
|
|
3845
|
+
const androidModule = await Promise.resolve().then(() => (init_src2(), exports_src2));
|
|
3846
|
+
await androidModule.init({
|
|
3847
|
+
name,
|
|
3848
|
+
packageName: options?.package,
|
|
3849
|
+
output: options?.output || "./android"
|
|
3850
|
+
});
|
|
3851
|
+
});
|
|
3852
|
+
cli.command("android build", "Build Android project").option("--html-path <path>", "Path to HTML file").option("-d, --dev-server <url>", "Development server URL").option("-o, --output <dir>", "Android project directory", { default: "./android" }).option("--release", "Build release APK").option("-w, --watch", "Watch for file changes and rebuild").example("craft android build").example("craft android build --release").example("craft android build --watch").action(async (options) => {
|
|
3853
|
+
const androidModule = await Promise.resolve().then(() => (init_src2(), exports_src2));
|
|
3854
|
+
const doBuild = async () => {
|
|
3855
|
+
await androidModule.build({
|
|
3856
|
+
htmlPath: options?.htmlPath,
|
|
3857
|
+
devServer: options?.devServer,
|
|
3858
|
+
output: options?.output || "./android",
|
|
3859
|
+
release: options?.release || false
|
|
3860
|
+
});
|
|
3861
|
+
};
|
|
3862
|
+
await doBuild();
|
|
3863
|
+
if (options?.watch) {
|
|
3864
|
+
console.log(`
|
|
3865
|
+
\uD83D\uDC40 Watching for changes...
|
|
3866
|
+
`);
|
|
3867
|
+
const { watch } = await import("fs");
|
|
3868
|
+
const { dirname: dirname3 } = await import("path");
|
|
3869
|
+
const watchPath = options?.htmlPath ? dirname3(options.htmlPath) : ".";
|
|
3870
|
+
watch(watchPath, { recursive: true }, async (event, filename) => {
|
|
3871
|
+
if (filename && (filename.endsWith(".html") || filename.endsWith(".js") || filename.endsWith(".css"))) {
|
|
3872
|
+
console.log(`
|
|
3873
|
+
\uD83D\uDCDD ${filename} changed, rebuilding...`);
|
|
3874
|
+
await doBuild();
|
|
3875
|
+
}
|
|
3876
|
+
});
|
|
3877
|
+
await new Promise(() => {});
|
|
3878
|
+
}
|
|
3879
|
+
});
|
|
3880
|
+
cli.command("android open", "Open Android project in Android Studio").option("-o, --output <dir>", "Android project directory", { default: "./android" }).action(async (options) => {
|
|
3881
|
+
const androidModule = await Promise.resolve().then(() => (init_src2(), exports_src2));
|
|
3882
|
+
await androidModule.open({
|
|
3883
|
+
output: options?.output || "./android"
|
|
3884
|
+
});
|
|
3885
|
+
});
|
|
3886
|
+
cli.command("android run", "Build and run on Android device or emulator").option("-d, --device <id>", "Target device ID").option("-o, --output <dir>", "Android project directory", { default: "./android" }).example("craft android run").example("craft android run --device emulator-5554").action(async (options) => {
|
|
3887
|
+
const androidModule = await Promise.resolve().then(() => (init_src2(), exports_src2));
|
|
3888
|
+
await androidModule.run({
|
|
3889
|
+
device: options?.device,
|
|
3890
|
+
output: options?.output || "./android"
|
|
3891
|
+
});
|
|
3892
|
+
});
|
|
3893
|
+
cli.command("preview [path]", "Preview app in browser before building native").option("-p, --port <port>", "Port to serve on", { default: 3456 }).option("--host <host>", "Host to bind to", { default: "localhost" }).example("craft preview ./dist").example("craft preview ./index.html --port 8080").action(async (path2, options) => {
|
|
3894
|
+
const servePath = path2 || ".";
|
|
3895
|
+
const port = options?.port || 3456;
|
|
3896
|
+
const host = options?.host || "localhost";
|
|
3897
|
+
console.log(`
|
|
3898
|
+
\uD83C\uDF10 Starting preview server...`);
|
|
3899
|
+
console.log(` Path: ${servePath}`);
|
|
3900
|
+
console.log(` URL: http://${host}:${port}
|
|
3901
|
+
`);
|
|
3902
|
+
const { spawn: spawn3 } = await import("child_process");
|
|
3903
|
+
const proc = spawn3("bunx", ["--bun", "serve", "-p", String(port), servePath], {
|
|
3904
|
+
stdio: "inherit"
|
|
3905
|
+
});
|
|
3906
|
+
proc.on("error", () => {
|
|
3907
|
+
console.log("Falling back to python http.server...");
|
|
3908
|
+
spawn3("python3", ["-m", "http.server", String(port), "--directory", servePath], {
|
|
3909
|
+
stdio: "inherit"
|
|
3910
|
+
});
|
|
3911
|
+
});
|
|
3912
|
+
});
|
|
3913
|
+
cli.command("publish", "Publish app to App Store or Play Store").option("--ios", "Publish to App Store (TestFlight)").option("--android", "Publish to Play Store").option("--api-key <path>", "App Store Connect API key path").option("--service-account <path>", "Google Play service account JSON").option("-o, --output <dir>", "Project directory", { default: "." }).example("craft publish --ios").example("craft publish --android").action(async (options) => {
|
|
3914
|
+
const { existsSync: existsSync7 } = await import("fs");
|
|
3915
|
+
const { join: join4 } = await import("path");
|
|
3916
|
+
const { $: $3 } = await Promise.resolve(globalThis.Bun);
|
|
3917
|
+
if (options?.ios) {
|
|
3918
|
+
console.log(`
|
|
3919
|
+
\uD83D\uDCF1 Publishing to App Store (TestFlight)...
|
|
3920
|
+
`);
|
|
3921
|
+
const iosDir = join4(options?.output || ".", "ios");
|
|
3922
|
+
if (!existsSync7(iosDir)) {
|
|
3923
|
+
console.error("\u274C No iOS project found. Run: craft ios init");
|
|
3924
|
+
process6.exit(1);
|
|
3925
|
+
}
|
|
3926
|
+
console.log("1. Building archive...");
|
|
3927
|
+
try {
|
|
3928
|
+
await $3`cd ${iosDir} && xcodebuild -scheme App -configuration Release -archivePath ./build/App.xcarchive archive`;
|
|
3929
|
+
console.log("\u2705 Archive created");
|
|
3930
|
+
console.log("2. Exporting IPA...");
|
|
3931
|
+
await $3`cd ${iosDir} && xcodebuild -exportArchive -archivePath ./build/App.xcarchive -exportPath ./build -exportOptionsPlist ExportOptions.plist`;
|
|
3932
|
+
console.log("\u2705 IPA exported");
|
|
3933
|
+
console.log("3. Uploading to TestFlight...");
|
|
3934
|
+
if (options?.apiKey) {
|
|
3935
|
+
await $3`xcrun altool --upload-app -f ${iosDir}/build/*.ipa --apiKey ${options.apiKey} --type ios`;
|
|
3936
|
+
} else {
|
|
3937
|
+
await $3`xcrun altool --upload-app -f ${iosDir}/build/*.ipa --type ios`;
|
|
3938
|
+
}
|
|
3939
|
+
console.log("\u2705 Uploaded to TestFlight!");
|
|
3940
|
+
} catch (error) {
|
|
3941
|
+
console.error("\u274C Publish failed. Make sure you have:");
|
|
3942
|
+
console.error(" - Valid signing certificates");
|
|
3943
|
+
console.error(" - App Store Connect API key (--api-key)");
|
|
3944
|
+
console.error(" - ExportOptions.plist in your ios directory");
|
|
3945
|
+
}
|
|
3946
|
+
}
|
|
3947
|
+
if (options?.android) {
|
|
3948
|
+
console.log(`
|
|
3949
|
+
\uD83E\uDD16 Publishing to Play Store...
|
|
3950
|
+
`);
|
|
3951
|
+
const androidDir = join4(options?.output || ".", "android");
|
|
3952
|
+
if (!existsSync7(androidDir)) {
|
|
3953
|
+
console.error("\u274C No Android project found. Run: craft android init");
|
|
3954
|
+
process6.exit(1);
|
|
3955
|
+
}
|
|
3956
|
+
console.log("1. Building release AAB...");
|
|
3957
|
+
try {
|
|
3958
|
+
await $3`cd ${androidDir} && ./gradlew bundleRelease`;
|
|
3959
|
+
console.log("\u2705 AAB created");
|
|
3960
|
+
const aabPath = join4(androidDir, "app/build/outputs/bundle/release/app-release.aab");
|
|
3961
|
+
if (options?.serviceAccount) {
|
|
3962
|
+
console.log("2. Uploading to Play Store...");
|
|
3963
|
+
console.log("\u26A0\uFE0F Automatic Play Store upload requires fastlane or Google Play API integration.");
|
|
3964
|
+
console.log(` AAB file: ${aabPath}`);
|
|
3965
|
+
console.log(" Upload manually via Play Console or use:");
|
|
3966
|
+
console.log(" fastlane supply --aab " + aabPath);
|
|
3967
|
+
} else {
|
|
3968
|
+
console.log("\u2705 Release AAB ready:");
|
|
3969
|
+
console.log(` ${aabPath}`);
|
|
3970
|
+
console.log("");
|
|
3971
|
+
console.log("Upload manually via Play Console, or use fastlane:");
|
|
3972
|
+
console.log(" fastlane supply --aab " + aabPath);
|
|
3973
|
+
}
|
|
3974
|
+
} catch (error) {
|
|
3975
|
+
console.error("\u274C Build failed. Make sure you have:");
|
|
3976
|
+
console.error(" - Valid signing key (keystore)");
|
|
3977
|
+
console.error(" - Release signing config in build.gradle.kts");
|
|
3978
|
+
}
|
|
3979
|
+
}
|
|
3980
|
+
if (!options?.ios && !options?.android) {
|
|
3981
|
+
console.log("Please specify a platform:");
|
|
3982
|
+
console.log(" craft publish --ios # Publish to App Store");
|
|
3983
|
+
console.log(" craft publish --android # Publish to Play Store");
|
|
3984
|
+
}
|
|
3985
|
+
});
|
|
3986
|
+
cli.command("init <name>", "Initialize a new Craft project").option("--template <type>", "Project template (blank, tabs, drawer, dashboard, desktop, ios, android, all)", { default: "blank" }).option("--bundle-id <id>", "Bundle identifier for mobile").example("craft init MyApp").example("craft init MyApp --template tabs").example("craft init MyApp --template dashboard").example("craft init MyApp --template all").action(async (name, options) => {
|
|
3987
|
+
console.log(`
|
|
3988
|
+
\u26A1 Creating new Craft project: ${name}
|
|
3989
|
+
`);
|
|
3990
|
+
const template = options?.template || "blank";
|
|
3991
|
+
const bundleId = options?.bundleId || `com.example.${name.toLowerCase().replace(/[^a-z0-9]/g, "")}`;
|
|
3992
|
+
const appNameSlug = name.toLowerCase().replace(/[^a-z0-9]/g, "-");
|
|
3993
|
+
const { mkdirSync: mkdirSync4, writeFileSync: writeFileSync4, existsSync: existsSync7, readdirSync: readdirSync3, readFileSync: readFileSync4, cpSync: cpSync2 } = await import("fs");
|
|
3994
|
+
const { join: join4, dirname: dirname3 } = await import("path");
|
|
3995
|
+
const replaceVars = (content) => {
|
|
3996
|
+
return content.replace(/\{\{APP_NAME\}\}/g, name).replace(/\{\{APP_NAME_SLUG\}\}/g, appNameSlug).replace(/\{\{BUNDLE_ID\}\}/g, bundleId).replace(/\{\{AUTHOR\}\}/g, "Developer");
|
|
3997
|
+
};
|
|
3998
|
+
const copyTemplate = async (templateName, destDir) => {
|
|
3999
|
+
const templateDir = join4(import.meta.dir, "templates/projects", templateName);
|
|
4000
|
+
if (!existsSync7(templateDir)) {
|
|
4001
|
+
console.log(` \u26A0\uFE0F Template '${templateName}' not found, using blank template`);
|
|
4002
|
+
return false;
|
|
4003
|
+
}
|
|
4004
|
+
const copyRecursive = (src, dest) => {
|
|
4005
|
+
if (!existsSync7(dest)) {
|
|
4006
|
+
mkdirSync4(dest, { recursive: true });
|
|
4007
|
+
}
|
|
4008
|
+
const entries = readdirSync3(src, { withFileTypes: true });
|
|
4009
|
+
for (const entry of entries) {
|
|
4010
|
+
const srcPath = join4(src, entry.name);
|
|
4011
|
+
const destPath = join4(dest, entry.name);
|
|
4012
|
+
if (entry.isDirectory()) {
|
|
4013
|
+
copyRecursive(srcPath, destPath);
|
|
4014
|
+
} else {
|
|
4015
|
+
const content = readFileSync4(srcPath, "utf-8");
|
|
4016
|
+
const processedContent = replaceVars(content);
|
|
4017
|
+
writeFileSync4(destPath, processedContent);
|
|
4018
|
+
}
|
|
4019
|
+
}
|
|
4020
|
+
};
|
|
4021
|
+
copyRecursive(templateDir, destDir);
|
|
4022
|
+
return true;
|
|
4023
|
+
};
|
|
4024
|
+
if (["blank", "tabs", "drawer", "dashboard"].includes(template)) {
|
|
4025
|
+
console.log(`\uD83D\uDCC1 Creating ${template} project from template...`);
|
|
4026
|
+
if (!existsSync7(name)) {
|
|
4027
|
+
mkdirSync4(name, { recursive: true });
|
|
4028
|
+
}
|
|
4029
|
+
const copied = await copyTemplate(template, name);
|
|
4030
|
+
if (!copied) {
|
|
4031
|
+
mkdirSync4(join4(name, "src"), { recursive: true });
|
|
4032
|
+
writeFileSync4(join4(name, "index.html"), replaceVars(`<!DOCTYPE html>
|
|
4033
|
+
<html lang="en">
|
|
4034
|
+
<head>
|
|
4035
|
+
<meta charset="UTF-8">
|
|
4036
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
4037
|
+
<title>{{APP_NAME}}</title>
|
|
4038
|
+
</head>
|
|
4039
|
+
<body>
|
|
4040
|
+
<div id="app">
|
|
4041
|
+
<h1>{{APP_NAME}}</h1>
|
|
4042
|
+
<p>Built with Craft</p>
|
|
4043
|
+
</div>
|
|
4044
|
+
</body>
|
|
4045
|
+
</html>`));
|
|
4046
|
+
writeFileSync4(join4(name, "package.json"), replaceVars(JSON.stringify({
|
|
4047
|
+
name: "{{APP_NAME_SLUG}}",
|
|
4048
|
+
version: "1.0.0",
|
|
4049
|
+
private: true,
|
|
4050
|
+
type: "module",
|
|
4051
|
+
scripts: {
|
|
4052
|
+
dev: "craft dev",
|
|
4053
|
+
build: "craft build"
|
|
4054
|
+
},
|
|
4055
|
+
dependencies: {
|
|
4056
|
+
"craft-native": "workspace:*"
|
|
4057
|
+
}
|
|
4058
|
+
}, null, 2)));
|
|
4059
|
+
}
|
|
4060
|
+
console.log(`\u2705 ${template} project created`);
|
|
4061
|
+
}
|
|
4062
|
+
if (template === "desktop" || template === "all") {
|
|
4063
|
+
console.log("\uD83D\uDCC1 Creating desktop project structure...");
|
|
4064
|
+
if (!existsSync7(name)) {
|
|
4065
|
+
mkdirSync4(name, { recursive: true });
|
|
4066
|
+
}
|
|
4067
|
+
const configContent = `import type { CraftConfig } from 'craft-native'
|
|
4068
|
+
|
|
4069
|
+
export default {
|
|
4070
|
+
name: '${name}',
|
|
4071
|
+
window: {
|
|
4072
|
+
title: '${name}',
|
|
4073
|
+
width: 1200,
|
|
4074
|
+
height: 800,
|
|
4075
|
+
darkMode: true,
|
|
4076
|
+
},
|
|
4077
|
+
} satisfies CraftConfig
|
|
4078
|
+
`;
|
|
4079
|
+
writeFileSync4(`${name}/craft.config.ts`, configContent);
|
|
4080
|
+
const htmlContent = `<!DOCTYPE html>
|
|
4081
|
+
<html lang="en">
|
|
4082
|
+
<head>
|
|
4083
|
+
<meta charset="UTF-8">
|
|
4084
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
4085
|
+
<title>${name}</title>
|
|
4086
|
+
<style>
|
|
4087
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
4088
|
+
body {
|
|
4089
|
+
font-family: -apple-system, system-ui, sans-serif;
|
|
4090
|
+
background: #1a1a2e;
|
|
4091
|
+
color: white;
|
|
4092
|
+
min-height: 100vh;
|
|
4093
|
+
display: flex;
|
|
4094
|
+
justify-content: center;
|
|
4095
|
+
align-items: center;
|
|
4096
|
+
}
|
|
4097
|
+
.container { text-align: center; }
|
|
4098
|
+
h1 { font-size: 3rem; margin-bottom: 1rem; }
|
|
4099
|
+
p { opacity: 0.7; }
|
|
4100
|
+
</style>
|
|
4101
|
+
</head>
|
|
4102
|
+
<body>
|
|
4103
|
+
<div class="container">
|
|
4104
|
+
<h1>\u26A1 ${name}</h1>
|
|
4105
|
+
<p>Built with Craft</p>
|
|
4106
|
+
</div>
|
|
4107
|
+
</body>
|
|
4108
|
+
</html>
|
|
4109
|
+
`;
|
|
4110
|
+
writeFileSync4(`${name}/index.html`, htmlContent);
|
|
4111
|
+
const packageJson = {
|
|
4112
|
+
name: appNameSlug,
|
|
4113
|
+
version: "0.1.0",
|
|
4114
|
+
private: true,
|
|
4115
|
+
scripts: {
|
|
4116
|
+
dev: "craft dev http://localhost:3000",
|
|
4117
|
+
build: "craft build",
|
|
4118
|
+
"ios:init": "craft ios init " + name,
|
|
4119
|
+
"ios:build": "craft ios build",
|
|
4120
|
+
"ios:open": "craft ios open"
|
|
4121
|
+
},
|
|
4122
|
+
devDependencies: {
|
|
4123
|
+
"craft-native": "*"
|
|
4124
|
+
}
|
|
4125
|
+
};
|
|
4126
|
+
writeFileSync4(`${name}/package.json`, JSON.stringify(packageJson, null, 2));
|
|
4127
|
+
console.log("\u2705 Desktop project created");
|
|
4128
|
+
}
|
|
4129
|
+
if (template === "ios" || template === "all") {
|
|
4130
|
+
console.log("\uD83D\uDCF1 Creating iOS project...");
|
|
4131
|
+
const iosModule = await Promise.resolve().then(() => (init_src(), exports_src));
|
|
4132
|
+
await iosModule.init({
|
|
4133
|
+
name,
|
|
4134
|
+
bundleId: options?.bundleId,
|
|
4135
|
+
output: template === "all" ? `${name}/ios` : "./ios"
|
|
4136
|
+
});
|
|
4137
|
+
console.log("\u2705 iOS project created");
|
|
4138
|
+
}
|
|
4139
|
+
if (template === "android" || template === "all") {
|
|
4140
|
+
console.log("\uD83E\uDD16 Creating Android project...");
|
|
4141
|
+
const androidModule = await Promise.resolve().then(() => (init_src2(), exports_src2));
|
|
4142
|
+
await androidModule.init({
|
|
4143
|
+
name,
|
|
4144
|
+
packageName: options?.bundleId,
|
|
4145
|
+
output: template === "all" ? `${name}/android` : "./android"
|
|
4146
|
+
});
|
|
4147
|
+
console.log("\u2705 Android project created");
|
|
4148
|
+
}
|
|
4149
|
+
console.log("");
|
|
4150
|
+
console.log("Next steps:");
|
|
4151
|
+
console.log(` cd ${name}`);
|
|
4152
|
+
console.log(" bun install");
|
|
4153
|
+
if (template === "desktop" || template === "all") {
|
|
4154
|
+
console.log(" craft dev http://localhost:3000");
|
|
4155
|
+
}
|
|
4156
|
+
if (template === "ios" || template === "all") {
|
|
4157
|
+
console.log(" craft ios build");
|
|
4158
|
+
console.log(" craft ios open");
|
|
4159
|
+
}
|
|
4160
|
+
if (template === "android" || template === "all") {
|
|
4161
|
+
console.log(" craft android build");
|
|
4162
|
+
console.log(" craft android open");
|
|
4163
|
+
}
|
|
4164
|
+
console.log("");
|
|
4165
|
+
});
|
|
4166
|
+
cli.version(version);
|
|
4167
|
+
cli.help();
|
|
4168
|
+
cli.parse();
|