ds-01 1.0.4 → 1.0.6
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/.turbo/turbo-build.log +1 -1
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +57 -31
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +109 -29
- package/dist/commands/logout.d.ts.map +1 -1
- package/dist/commands/logout.js +27 -7
- package/dist/index.js +2 -0
- package/dist/test-key.d.ts +2 -0
- package/dist/test-key.d.ts.map +1 -0
- package/dist/test-key.js +7 -0
- package/dist/utils/config.d.ts +0 -1
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +0 -13
- package/dist/utils/deviceKey.d.ts +3 -0
- package/dist/utils/deviceKey.d.ts.map +1 -0
- package/dist/utils/deviceKey.js +13 -0
- package/dist/utils/secureKeyStore.d.ts +10 -0
- package/dist/utils/secureKeyStore.d.ts.map +1 -0
- package/dist/utils/secureKeyStore.js +266 -0
- package/package.json +2 -1
- package/src/commands/add.ts +231 -76
- package/src/commands/login.ts +285 -52
- package/src/commands/logout.ts +39 -10
- package/src/index.ts +2 -0
- package/src/native/macos-key-helper.swift +206 -0
- package/src/test-key.ts +20 -0
- package/src/utils/config.ts +1 -12
- package/src/utils/deviceKey.ts +19 -0
- package/src/utils/secureKeyStore.ts +361 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
// cli/src/utils/secureKeyStore.ts
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
const KEY_NAME = "DS01-CLI-Device-Key";
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
function getMacHelperPath() {
|
|
12
|
+
// dist/utils/secureKeyStore.js
|
|
13
|
+
// ../native/macos-key-helper.swift
|
|
14
|
+
return path.resolve(__dirname, "../native/macos-key-helper.swift");
|
|
15
|
+
}
|
|
16
|
+
function ensureSupportedOS() {
|
|
17
|
+
const platform = os.platform();
|
|
18
|
+
if (platform !== "win32" && platform !== "darwin") {
|
|
19
|
+
throw new Error("DS01 secure device keys currently support Windows and macOS.");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* ---------------------------------------------------------
|
|
24
|
+
* WINDOWS
|
|
25
|
+
* ---------------------------------------------------------
|
|
26
|
+
*/
|
|
27
|
+
async function createWindowsDeviceKey() {
|
|
28
|
+
const script = `
|
|
29
|
+
$ErrorActionPreference = "Stop"
|
|
30
|
+
|
|
31
|
+
$existing = Get-ChildItem Cert:\\CurrentUser\\My |
|
|
32
|
+
Where-Object {
|
|
33
|
+
$_.Subject -eq "CN=${KEY_NAME}"
|
|
34
|
+
} |
|
|
35
|
+
Select-Object -First 1
|
|
36
|
+
|
|
37
|
+
if ($existing) {
|
|
38
|
+
Write-Output "EXISTS"
|
|
39
|
+
exit 0
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
$params = @{
|
|
43
|
+
Type = "Custom"
|
|
44
|
+
Subject = "CN=${KEY_NAME}"
|
|
45
|
+
|
|
46
|
+
Provider = "Microsoft Platform Crypto Provider"
|
|
47
|
+
|
|
48
|
+
KeyAlgorithm = "RSA"
|
|
49
|
+
KeyLength = 2048
|
|
50
|
+
|
|
51
|
+
KeyExportPolicy = "NonExportable"
|
|
52
|
+
|
|
53
|
+
KeyUsage = "DigitalSignature"
|
|
54
|
+
KeySpec = "Signature"
|
|
55
|
+
|
|
56
|
+
CertStoreLocation = "Cert:\\CurrentUser\\My"
|
|
57
|
+
|
|
58
|
+
NotAfter = (Get-Date).AddYears(10)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
New-SelfSignedCertificate @params | Out-Null
|
|
62
|
+
|
|
63
|
+
Write-Output "CREATED"
|
|
64
|
+
`;
|
|
65
|
+
const { stdout } = await execFileAsync("powershell.exe", [
|
|
66
|
+
"-NoProfile",
|
|
67
|
+
"-NonInteractive",
|
|
68
|
+
"-ExecutionPolicy",
|
|
69
|
+
"Bypass",
|
|
70
|
+
"-Command",
|
|
71
|
+
script,
|
|
72
|
+
], {
|
|
73
|
+
windowsHide: true,
|
|
74
|
+
});
|
|
75
|
+
const result = stdout.trim();
|
|
76
|
+
if (result !== "CREATED" && result !== "EXISTS") {
|
|
77
|
+
throw new Error("Failed to create Windows DS01 device key.");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async function getWindowsPublicKey() {
|
|
81
|
+
const script = `
|
|
82
|
+
$ErrorActionPreference = "Stop"
|
|
83
|
+
|
|
84
|
+
$cert = Get-ChildItem Cert:\\CurrentUser\\My |
|
|
85
|
+
Where-Object {
|
|
86
|
+
$_.Subject -eq "CN=${KEY_NAME}"
|
|
87
|
+
} |
|
|
88
|
+
Select-Object -First 1
|
|
89
|
+
|
|
90
|
+
if (-not $cert) {
|
|
91
|
+
throw "DS01 device key not found."
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
[Convert]::ToBase64String($cert.RawData)
|
|
95
|
+
`;
|
|
96
|
+
const { stdout } = await execFileAsync("powershell.exe", [
|
|
97
|
+
"-NoProfile",
|
|
98
|
+
"-NonInteractive",
|
|
99
|
+
"-ExecutionPolicy",
|
|
100
|
+
"Bypass",
|
|
101
|
+
"-Command",
|
|
102
|
+
script,
|
|
103
|
+
], {
|
|
104
|
+
windowsHide: true,
|
|
105
|
+
});
|
|
106
|
+
const certificate = stdout.trim();
|
|
107
|
+
if (!certificate) {
|
|
108
|
+
throw new Error("Unable to read Windows DS01 public certificate.");
|
|
109
|
+
}
|
|
110
|
+
return certificate;
|
|
111
|
+
}
|
|
112
|
+
async function signWindowsDeviceKey(data) {
|
|
113
|
+
const dataBase64 = Buffer.from(data).toString("base64");
|
|
114
|
+
const script = `
|
|
115
|
+
$ErrorActionPreference = "Stop"
|
|
116
|
+
|
|
117
|
+
$cert = Get-ChildItem Cert:\\CurrentUser\\My |
|
|
118
|
+
Where-Object {
|
|
119
|
+
$_.Subject -eq "CN=${KEY_NAME}"
|
|
120
|
+
} |
|
|
121
|
+
Select-Object -First 1
|
|
122
|
+
|
|
123
|
+
if (-not $cert) {
|
|
124
|
+
throw "DS01 device key not found."
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (-not $cert.HasPrivateKey) {
|
|
128
|
+
throw "DS01 device certificate has no private key."
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
$rsa = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
|
|
132
|
+
|
|
133
|
+
if (-not $rsa) {
|
|
134
|
+
throw "Unable to access DS01 private signing key."
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
$data = [Convert]::FromBase64String("${dataBase64}")
|
|
139
|
+
|
|
140
|
+
$signature = $rsa.SignData(
|
|
141
|
+
$data,
|
|
142
|
+
[System.Security.Cryptography.HashAlgorithmName]::SHA256,
|
|
143
|
+
[System.Security.Cryptography.RSASignaturePadding]::Pkcs1
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
[Convert]::ToBase64String($signature)
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
$rsa.Dispose()
|
|
150
|
+
}
|
|
151
|
+
`;
|
|
152
|
+
const { stdout } = await execFileAsync("powershell.exe", [
|
|
153
|
+
"-NoProfile",
|
|
154
|
+
"-NonInteractive",
|
|
155
|
+
"-ExecutionPolicy",
|
|
156
|
+
"Bypass",
|
|
157
|
+
"-Command",
|
|
158
|
+
script,
|
|
159
|
+
], {
|
|
160
|
+
windowsHide: true,
|
|
161
|
+
});
|
|
162
|
+
const signature = stdout.trim();
|
|
163
|
+
if (!signature) {
|
|
164
|
+
throw new Error("Failed to sign data with Windows device key.");
|
|
165
|
+
}
|
|
166
|
+
return new Uint8Array(Buffer.from(signature, "base64"));
|
|
167
|
+
}
|
|
168
|
+
async function deleteWindowsDeviceKey() {
|
|
169
|
+
const script = `
|
|
170
|
+
$ErrorActionPreference = "Stop"
|
|
171
|
+
|
|
172
|
+
$cert = Get-ChildItem Cert:\\CurrentUser\\My |
|
|
173
|
+
Where-Object {
|
|
174
|
+
$_.Subject -eq "CN=${KEY_NAME}"
|
|
175
|
+
} |
|
|
176
|
+
Select-Object -First 1
|
|
177
|
+
|
|
178
|
+
if ($cert) {
|
|
179
|
+
Remove-Item $cert.PSPath
|
|
180
|
+
}
|
|
181
|
+
`;
|
|
182
|
+
await execFileAsync("powershell.exe", [
|
|
183
|
+
"-NoProfile",
|
|
184
|
+
"-NonInteractive",
|
|
185
|
+
"-ExecutionPolicy",
|
|
186
|
+
"Bypass",
|
|
187
|
+
"-Command",
|
|
188
|
+
script,
|
|
189
|
+
], {
|
|
190
|
+
windowsHide: true,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* ---------------------------------------------------------
|
|
195
|
+
* MACOS
|
|
196
|
+
* ---------------------------------------------------------
|
|
197
|
+
*/
|
|
198
|
+
async function runMacHelper(operation, data) {
|
|
199
|
+
const helper = getMacHelperPath();
|
|
200
|
+
const args = [helper, operation];
|
|
201
|
+
if (data) {
|
|
202
|
+
args.push(Buffer.from(data).toString("base64"));
|
|
203
|
+
}
|
|
204
|
+
const { stdout } = await execFileAsync("swift", args, {
|
|
205
|
+
windowsHide: true,
|
|
206
|
+
});
|
|
207
|
+
return stdout.trim();
|
|
208
|
+
}
|
|
209
|
+
async function createMacDeviceKey() {
|
|
210
|
+
const result = await runMacHelper("create");
|
|
211
|
+
if (result !== "CREATED" && result !== "EXISTS") {
|
|
212
|
+
throw new Error(result || "Failed to create macOS Secure Enclave device key.");
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
async function getMacPublicKey() {
|
|
216
|
+
const result = await runMacHelper("public");
|
|
217
|
+
if (!result) {
|
|
218
|
+
throw new Error("Unable to read macOS DS01 public key.");
|
|
219
|
+
}
|
|
220
|
+
return result;
|
|
221
|
+
}
|
|
222
|
+
async function signMacDeviceKey(data) {
|
|
223
|
+
const result = await runMacHelper("sign", data);
|
|
224
|
+
if (!result) {
|
|
225
|
+
throw new Error("Failed to sign data with macOS Secure Enclave key.");
|
|
226
|
+
}
|
|
227
|
+
return new Uint8Array(Buffer.from(result, "base64"));
|
|
228
|
+
}
|
|
229
|
+
async function deleteMacDeviceKey() {
|
|
230
|
+
await runMacHelper("delete");
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* ---------------------------------------------------------
|
|
234
|
+
* PUBLIC API
|
|
235
|
+
* ---------------------------------------------------------
|
|
236
|
+
*/
|
|
237
|
+
export async function createDeviceKey() {
|
|
238
|
+
ensureSupportedOS();
|
|
239
|
+
if (os.platform() === "win32") {
|
|
240
|
+
await createWindowsDeviceKey();
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
await createMacDeviceKey();
|
|
244
|
+
}
|
|
245
|
+
export async function getDevicePublicKey() {
|
|
246
|
+
ensureSupportedOS();
|
|
247
|
+
if (os.platform() === "win32") {
|
|
248
|
+
return getWindowsPublicKey();
|
|
249
|
+
}
|
|
250
|
+
return getMacPublicKey();
|
|
251
|
+
}
|
|
252
|
+
export async function signWithDeviceKey(data) {
|
|
253
|
+
ensureSupportedOS();
|
|
254
|
+
if (os.platform() === "win32") {
|
|
255
|
+
return signWindowsDeviceKey(data);
|
|
256
|
+
}
|
|
257
|
+
return signMacDeviceKey(data);
|
|
258
|
+
}
|
|
259
|
+
export async function deleteDeviceKey() {
|
|
260
|
+
ensureSupportedOS();
|
|
261
|
+
if (os.platform() === "win32") {
|
|
262
|
+
await deleteWindowsDeviceKey();
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
await deleteMacDeviceKey();
|
|
266
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ds-01",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Make your site best with DS01",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"@clack/prompts": "^1.7.0",
|
|
17
17
|
"chalk": "^5.6.2",
|
|
18
18
|
"commander": "^14",
|
|
19
|
+
"cross-keychain": "^1.1.0",
|
|
19
20
|
"node-machine-id": "^1",
|
|
20
21
|
"open": "^10",
|
|
21
22
|
"ora": "^8",
|
package/src/commands/add.ts
CHANGED
|
@@ -4,46 +4,60 @@ import pc from "picocolors";
|
|
|
4
4
|
import fs from "fs";
|
|
5
5
|
import path from "path";
|
|
6
6
|
import { execSync } from "child_process";
|
|
7
|
-
import { getToken, getMachineId } from "../utils/config.js";
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
import { getToken } from "../utils/config.js";
|
|
9
|
+
import { signWithDeviceKey } from "../utils/secureKeyStore.js";
|
|
10
|
+
|
|
11
|
+
import machineIdPkg from "node-machine-id";
|
|
12
|
+
|
|
13
|
+
const { machineIdSync } = machineIdPkg;
|
|
14
|
+
|
|
15
|
+
const API_BASE_URL =
|
|
16
|
+
process.env.DS01_API_URL || "https://ds-01.vercel.app";
|
|
10
17
|
|
|
11
18
|
// Helper to verify Tailwind exists before doing anything
|
|
12
19
|
function checkTailwindInstallation() {
|
|
13
20
|
const targetDir = process.cwd();
|
|
14
21
|
const pkgJsonPath = path.join(targetDir, "package.json");
|
|
15
22
|
|
|
16
|
-
// 1. Ensure they are in a valid Node.js project
|
|
17
23
|
if (!fs.existsSync(pkgJsonPath)) {
|
|
18
24
|
cancel(
|
|
19
25
|
pc.red(
|
|
20
|
-
"No package.json found. Please run this command inside a Node.js project."
|
|
21
|
-
)
|
|
26
|
+
"No package.json found. Please run this command inside a Node.js project.",
|
|
27
|
+
),
|
|
22
28
|
);
|
|
23
29
|
process.exit(1);
|
|
24
30
|
}
|
|
25
31
|
|
|
26
|
-
|
|
27
|
-
|
|
32
|
+
const pkgJson = JSON.parse(
|
|
33
|
+
fs.readFileSync(pkgJsonPath, "utf-8"),
|
|
34
|
+
);
|
|
35
|
+
|
|
28
36
|
const allDeps = {
|
|
29
37
|
...pkgJson.dependencies,
|
|
30
38
|
...pkgJson.devDependencies,
|
|
31
39
|
};
|
|
32
40
|
|
|
33
|
-
// 3. Strict Tailwind Check
|
|
34
41
|
if (!allDeps["tailwindcss"]) {
|
|
35
42
|
cancel(
|
|
36
|
-
pc.red(
|
|
43
|
+
pc.red(
|
|
44
|
+
"Tailwind CSS is missing from your project dependencies.\n",
|
|
45
|
+
) +
|
|
37
46
|
pc.gray(
|
|
38
|
-
"DS01 components rely strictly on Tailwind CSS for styling.\n\n"
|
|
47
|
+
"DS01 components rely strictly on Tailwind CSS for styling.\n\n",
|
|
39
48
|
) +
|
|
40
49
|
pc.white(
|
|
41
|
-
"Kindly install it and configure your project, then retry:\n"
|
|
50
|
+
"Kindly install it and configure your project, then retry:\n",
|
|
51
|
+
) +
|
|
52
|
+
pc.cyan(
|
|
53
|
+
" npm install tailwindcss @tailwindcss/postcss postcss\n\n",
|
|
42
54
|
) +
|
|
43
|
-
pc.cyan(" npm install tailwindcss @tailwindcss/postcss postcss\n\n") +
|
|
44
55
|
pc.gray("Official Setup Guide: ") +
|
|
45
|
-
pc.underline(
|
|
56
|
+
pc.underline(
|
|
57
|
+
"https://tailwindcss.com/docs/installation",
|
|
58
|
+
),
|
|
46
59
|
);
|
|
60
|
+
|
|
47
61
|
process.exit(1);
|
|
48
62
|
}
|
|
49
63
|
}
|
|
@@ -51,134 +65,275 @@ function checkTailwindInstallation() {
|
|
|
51
65
|
export const add = new Command()
|
|
52
66
|
.name("add")
|
|
53
67
|
.description("Add a component from DS01 to your project")
|
|
54
|
-
.argument(
|
|
68
|
+
.argument(
|
|
69
|
+
"<component>",
|
|
70
|
+
"The name of the component (e.g., premium-section)",
|
|
71
|
+
)
|
|
55
72
|
.action(async (componentName: string) => {
|
|
56
|
-
console.log();
|
|
73
|
+
console.log();
|
|
57
74
|
|
|
58
|
-
// 1. Premium Header
|
|
59
75
|
intro(
|
|
60
|
-
`${pc.bgWhite(pc.black(pc.bold(" DS01 ")))} ${pc.gray(
|
|
76
|
+
`${pc.bgWhite(pc.black(pc.bold(" DS01 ")))} ${pc.gray(
|
|
77
|
+
"Adding Component",
|
|
78
|
+
)}`,
|
|
61
79
|
);
|
|
62
80
|
|
|
63
|
-
// 2. Run Pre-Flight Tailwind Check
|
|
64
81
|
checkTailwindInstallation();
|
|
65
82
|
|
|
66
83
|
const cwd = process.cwd();
|
|
67
|
-
|
|
84
|
+
|
|
85
|
+
const configPath = path.join(
|
|
86
|
+
cwd,
|
|
87
|
+
"ds01.config.json",
|
|
88
|
+
);
|
|
68
89
|
|
|
69
90
|
if (!fs.existsSync(configPath)) {
|
|
70
|
-
cancel(
|
|
91
|
+
cancel(
|
|
92
|
+
pc.red(
|
|
93
|
+
"ds01.config.json not found. Run 'npx ds-01 init' first.",
|
|
94
|
+
),
|
|
95
|
+
);
|
|
96
|
+
|
|
71
97
|
process.exit(1);
|
|
72
98
|
}
|
|
73
99
|
|
|
74
|
-
const config = JSON.parse(
|
|
75
|
-
|
|
76
|
-
|
|
100
|
+
const config = JSON.parse(
|
|
101
|
+
fs.readFileSync(configPath, "utf-8"),
|
|
102
|
+
);
|
|
103
|
+
|
|
77
104
|
const token = getToken();
|
|
78
|
-
const machineId =
|
|
105
|
+
const machineId = machineIdSync();
|
|
79
106
|
|
|
80
107
|
if (!token || !machineId) {
|
|
81
|
-
cancel(
|
|
108
|
+
cancel(
|
|
109
|
+
pc.red(
|
|
110
|
+
"You are not authenticated. Run 'npx ds-01 login' first.",
|
|
111
|
+
),
|
|
112
|
+
);
|
|
113
|
+
|
|
82
114
|
process.exit(1);
|
|
83
115
|
}
|
|
84
116
|
|
|
85
117
|
const s = spinner();
|
|
86
|
-
|
|
118
|
+
|
|
119
|
+
s.start(
|
|
120
|
+
`Fetching ${pc.cyan(
|
|
121
|
+
`<${componentName} />`,
|
|
122
|
+
)} from registry...`,
|
|
123
|
+
);
|
|
87
124
|
|
|
88
125
|
try {
|
|
89
|
-
//
|
|
126
|
+
// --------------------------------------------------
|
|
127
|
+
// 1. Create request-specific message
|
|
128
|
+
// --------------------------------------------------
|
|
129
|
+
|
|
130
|
+
const timestamp = Date.now().toString();
|
|
131
|
+
|
|
132
|
+
const message = [
|
|
133
|
+
"GET",
|
|
134
|
+
`/api/registry/${componentName}`,
|
|
135
|
+
timestamp,
|
|
136
|
+
machineId,
|
|
137
|
+
].join("\n");
|
|
138
|
+
|
|
139
|
+
// --------------------------------------------------
|
|
140
|
+
// 2. Sign message using the device private key
|
|
141
|
+
// --------------------------------------------------
|
|
142
|
+
|
|
143
|
+
const signature = await signWithDeviceKey(
|
|
144
|
+
new TextEncoder().encode(message),
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
const signatureBase64 =
|
|
148
|
+
Buffer.from(signature).toString("base64");
|
|
149
|
+
|
|
150
|
+
// --------------------------------------------------
|
|
151
|
+
// 3. Send token + machine ID + signature
|
|
152
|
+
// --------------------------------------------------
|
|
153
|
+
|
|
90
154
|
const response = await fetch(
|
|
91
155
|
`${API_BASE_URL}/api/registry/${componentName}`,
|
|
92
156
|
{
|
|
157
|
+
method: "GET",
|
|
158
|
+
|
|
93
159
|
headers: {
|
|
94
160
|
Authorization: `Bearer ${token}`,
|
|
95
|
-
|
|
161
|
+
|
|
162
|
+
"X-Machine-ID": machineId,
|
|
163
|
+
|
|
164
|
+
"X-DS01-Timestamp": timestamp,
|
|
165
|
+
|
|
166
|
+
"X-DS01-Signature": signatureBase64,
|
|
96
167
|
},
|
|
97
|
-
}
|
|
168
|
+
},
|
|
98
169
|
);
|
|
99
170
|
|
|
100
171
|
if (!response.ok) {
|
|
101
|
-
s.stop(
|
|
102
|
-
|
|
172
|
+
s.stop(
|
|
173
|
+
pc.red("Component fetch failed."),
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
cancel(
|
|
177
|
+
`Component not found or server error (HTTP ${response.status})`,
|
|
178
|
+
);
|
|
179
|
+
|
|
103
180
|
process.exit(1);
|
|
104
181
|
}
|
|
105
182
|
|
|
106
|
-
const componentData =
|
|
183
|
+
const componentData =
|
|
184
|
+
await response.json();
|
|
185
|
+
|
|
186
|
+
// --------------------------------------------------
|
|
187
|
+
// 4. Create component folder
|
|
188
|
+
// --------------------------------------------------
|
|
189
|
+
|
|
190
|
+
const targetDir = path.join(
|
|
191
|
+
cwd,
|
|
192
|
+
config.componentsPath,
|
|
193
|
+
componentName,
|
|
194
|
+
);
|
|
107
195
|
|
|
108
|
-
// 4. Create dedicated component folder
|
|
109
|
-
const targetDir = path.join(cwd, config.componentsPath, componentName);
|
|
110
196
|
if (!fs.existsSync(targetDir)) {
|
|
111
|
-
fs.mkdirSync(targetDir, {
|
|
197
|
+
fs.mkdirSync(targetDir, {
|
|
198
|
+
recursive: true,
|
|
199
|
+
});
|
|
112
200
|
}
|
|
113
201
|
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
202
|
+
// --------------------------------------------------
|
|
203
|
+
// 5. Extract bundled file names
|
|
204
|
+
// --------------------------------------------------
|
|
205
|
+
|
|
206
|
+
const bundledFileNames =
|
|
207
|
+
componentData.files.map(
|
|
208
|
+
(file: any) =>
|
|
209
|
+
file.name.replace(
|
|
210
|
+
/\.[^/.]+$/,
|
|
211
|
+
"",
|
|
212
|
+
),
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
// --------------------------------------------------
|
|
216
|
+
// 6. Write component files
|
|
217
|
+
// --------------------------------------------------
|
|
119
218
|
|
|
120
|
-
// 5. Inject the pure code files
|
|
121
219
|
for (const file of componentData.files) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
220
|
+
const filePath = path.join(
|
|
221
|
+
targetDir,
|
|
222
|
+
file.name,
|
|
223
|
+
);
|
|
224
|
+
|
|
125
225
|
let content = file.content;
|
|
126
226
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
227
|
+
bundledFileNames.forEach(
|
|
228
|
+
(fileName: string) => {
|
|
229
|
+
const regex = new RegExp(
|
|
230
|
+
`from\\s+["']\\.[^"']*?\\/${fileName}["']`,
|
|
231
|
+
"g",
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
content = content.replace(
|
|
235
|
+
regex,
|
|
236
|
+
`from "./${fileName}"`,
|
|
237
|
+
);
|
|
238
|
+
},
|
|
239
|
+
);
|
|
134
240
|
|
|
135
|
-
fs.writeFileSync(
|
|
241
|
+
fs.writeFileSync(
|
|
242
|
+
filePath,
|
|
243
|
+
content,
|
|
244
|
+
);
|
|
136
245
|
}
|
|
137
246
|
|
|
138
247
|
s.stop(
|
|
139
248
|
pc.green(
|
|
140
|
-
`Downloaded ${componentData.files.length} files into ${pc.white(
|
|
141
|
-
|
|
249
|
+
`Downloaded ${componentData.files.length} files into ${pc.white(
|
|
250
|
+
`/${config.componentsPath}/${componentName}`,
|
|
251
|
+
)}`,
|
|
252
|
+
),
|
|
142
253
|
);
|
|
143
254
|
|
|
144
|
-
//
|
|
145
|
-
|
|
146
|
-
|
|
255
|
+
// --------------------------------------------------
|
|
256
|
+
// 7. Install dependencies
|
|
257
|
+
// --------------------------------------------------
|
|
258
|
+
|
|
259
|
+
if (
|
|
260
|
+
componentData.dependencies &&
|
|
261
|
+
componentData.dependencies.length > 0
|
|
262
|
+
) {
|
|
263
|
+
const depsToInstall =
|
|
264
|
+
componentData.dependencies.join(" ");
|
|
265
|
+
|
|
147
266
|
s.start(
|
|
148
|
-
`Installing missing dependencies: ${pc.cyan(
|
|
267
|
+
`Installing missing dependencies: ${pc.cyan(
|
|
268
|
+
depsToInstall,
|
|
269
|
+
)}...`,
|
|
149
270
|
);
|
|
150
271
|
|
|
151
272
|
try {
|
|
152
|
-
|
|
153
|
-
|
|
273
|
+
execSync(
|
|
274
|
+
`npm install ${depsToInstall}`,
|
|
275
|
+
{
|
|
276
|
+
stdio: "ignore",
|
|
277
|
+
},
|
|
278
|
+
);
|
|
279
|
+
|
|
154
280
|
s.stop(
|
|
155
281
|
pc.green(
|
|
156
|
-
`Dependencies installed successfully: ${pc.gray(
|
|
157
|
-
|
|
282
|
+
`Dependencies installed successfully: ${pc.gray(
|
|
283
|
+
depsToInstall,
|
|
284
|
+
)}`,
|
|
285
|
+
),
|
|
286
|
+
);
|
|
287
|
+
} catch {
|
|
288
|
+
s.stop(
|
|
289
|
+
pc.red(
|
|
290
|
+
"Failed to auto-install dependencies.",
|
|
291
|
+
),
|
|
158
292
|
);
|
|
159
|
-
|
|
160
|
-
s.stop(pc.red("Failed to auto-install dependencies."));
|
|
293
|
+
|
|
161
294
|
note(
|
|
162
|
-
`Please run: ${pc.cyan(
|
|
163
|
-
|
|
295
|
+
`Please run: ${pc.cyan(
|
|
296
|
+
`npm install ${depsToInstall}`,
|
|
297
|
+
)} manually.`,
|
|
298
|
+
"Manual Action Required",
|
|
164
299
|
);
|
|
165
300
|
}
|
|
166
301
|
}
|
|
167
302
|
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
|
|
171
|
-
|
|
303
|
+
// --------------------------------------------------
|
|
304
|
+
// 8. Success
|
|
305
|
+
// --------------------------------------------------
|
|
306
|
+
|
|
307
|
+
const mainFile =
|
|
308
|
+
bundledFileNames.find(
|
|
309
|
+
(name: string) =>
|
|
310
|
+
name.toLowerCase() ===
|
|
311
|
+
componentName.toLowerCase(),
|
|
312
|
+
) || bundledFileNames[0];
|
|
313
|
+
|
|
172
314
|
outro(
|
|
173
|
-
`${pc.white("✔")} ${pc.bold(
|
|
174
|
-
|
|
315
|
+
`${pc.white("✔")} ${pc.bold(
|
|
316
|
+
`Component <${componentName} /> is ready!`,
|
|
317
|
+
)}\n` +
|
|
318
|
+
pc.gray("Import it: ") +
|
|
175
319
|
pc.cyan(
|
|
176
|
-
`import { ${mainFile} } from "@/${config.componentsPath}/${componentName}/${mainFile}"
|
|
177
|
-
)
|
|
320
|
+
`import { ${mainFile} } from "@/${config.componentsPath}/${componentName}/${mainFile}"`,
|
|
321
|
+
),
|
|
178
322
|
);
|
|
179
323
|
} catch (error: any) {
|
|
180
|
-
s.stop(
|
|
181
|
-
|
|
324
|
+
s.stop(
|
|
325
|
+
pc.red(
|
|
326
|
+
"An error occurred during injection.",
|
|
327
|
+
),
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
cancel(
|
|
331
|
+
pc.gray(
|
|
332
|
+
error?.message ||
|
|
333
|
+
"Unknown CLI Error",
|
|
334
|
+
),
|
|
335
|
+
);
|
|
336
|
+
|
|
182
337
|
process.exit(1);
|
|
183
338
|
}
|
|
184
339
|
});
|