ds-01 0.1.4 → 0.1.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 +60 -19
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +35 -29
- package/dist/index.js +6 -4
- package/package.json +1 -1
- package/src/commands/add.ts +104 -25
- package/src/commands/login.ts +47 -36
- package/src/index.ts +6 -4
package/.turbo/turbo-build.log
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkDpC,eAAO,MAAM,GAAG,SAwGZ,CAAC"}
|
package/dist/commands/add.js
CHANGED
|
@@ -1,63 +1,104 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import { intro, outro, spinner, cancel, note } from "@clack/prompts";
|
|
3
|
+
import pc from "picocolors";
|
|
4
4
|
import fs from "fs";
|
|
5
5
|
import path from "path";
|
|
6
6
|
import { execSync } from "child_process";
|
|
7
7
|
import { getToken } from "../utils/config.js";
|
|
8
|
-
const API_BASE_URL = process.env.DS01_API_URL || "https://ds-01.vercel.app";
|
|
8
|
+
const API_BASE_URL = process.env.DS01_API_URL || "https://ds-01.vercel.app";
|
|
9
|
+
// Helper to verify Tailwind exists before doing anything
|
|
10
|
+
function checkTailwindInstallation() {
|
|
11
|
+
const targetDir = process.cwd();
|
|
12
|
+
const pkgJsonPath = path.join(targetDir, "package.json");
|
|
13
|
+
// 1. Ensure they are in a valid Node.js project
|
|
14
|
+
if (!fs.existsSync(pkgJsonPath)) {
|
|
15
|
+
cancel(pc.red("No package.json found. Please run this command inside a Node.js project."));
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
// 2. Read package.json dependencies
|
|
19
|
+
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
|
|
20
|
+
const allDeps = {
|
|
21
|
+
...pkgJson.dependencies,
|
|
22
|
+
...pkgJson.devDependencies,
|
|
23
|
+
};
|
|
24
|
+
// 3. Strict Tailwind Check
|
|
25
|
+
if (!allDeps["tailwindcss"]) {
|
|
26
|
+
cancel(pc.red("Tailwind CSS is missing from your project dependencies.\n") +
|
|
27
|
+
pc.gray("DS01 components rely strictly on Tailwind CSS for styling.\n\n") +
|
|
28
|
+
pc.white("Kindly install it and configure your project, then retry:\n") +
|
|
29
|
+
pc.cyan(" npm install tailwindcss @tailwindcss/postcss postcss\n\n") +
|
|
30
|
+
pc.gray("Official Setup Guide: ") +
|
|
31
|
+
pc.underline("https://tailwindcss.com/docs/installation"));
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
9
35
|
export const add = new Command()
|
|
10
36
|
.name("add")
|
|
11
37
|
.description("Add a component from DS01 to your project")
|
|
12
38
|
.argument("<component>", "The name of the component (e.g., premium-section)")
|
|
13
39
|
.action(async (componentName) => {
|
|
40
|
+
console.log(); // Spacing for visual breathing room
|
|
41
|
+
// 1. Premium Header
|
|
42
|
+
intro(`${pc.bgWhite(pc.black(pc.bold(" DS01 ")))} ${pc.gray("Adding Component")}`);
|
|
43
|
+
// 2. Run Pre-Flight Tailwind Check
|
|
44
|
+
checkTailwindInstallation();
|
|
14
45
|
const cwd = process.cwd();
|
|
15
46
|
const configPath = path.join(cwd, "ds01.config.json");
|
|
16
47
|
if (!fs.existsSync(configPath)) {
|
|
17
|
-
|
|
18
|
-
console.log(`Run ${chalk.cyan("npx @arpit2023/ds01 init")} first.`);
|
|
48
|
+
cancel(pc.red("ds01.config.json not found. Run 'npx ds-01 init' first."));
|
|
19
49
|
process.exit(1);
|
|
20
50
|
}
|
|
21
51
|
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
22
52
|
const token = getToken();
|
|
23
|
-
const
|
|
53
|
+
const s = spinner();
|
|
54
|
+
s.start(`Fetching ${pc.cyan(`<${componentName} />`)} from registry...`);
|
|
24
55
|
try {
|
|
56
|
+
// 3. Fetch component from your API
|
|
25
57
|
const response = await fetch(`${API_BASE_URL}/api/registry/${componentName}`, {
|
|
26
|
-
headers: {
|
|
58
|
+
headers: {
|
|
59
|
+
Authorization: `Bearer ${config.token}`,
|
|
60
|
+
"X-Machine-ID": config.machineId, // <-- ADD THIS LINE
|
|
61
|
+
},
|
|
27
62
|
});
|
|
28
63
|
if (!response.ok) {
|
|
29
|
-
|
|
64
|
+
s.stop(pc.red("Component fetch failed."));
|
|
65
|
+
cancel(`Component not found or server error (HTTP ${response.status})`);
|
|
66
|
+
process.exit(1);
|
|
30
67
|
}
|
|
31
68
|
const componentData = await response.json();
|
|
32
|
-
// Create
|
|
69
|
+
// 4. Create dedicated component folder
|
|
33
70
|
const targetDir = path.join(cwd, config.componentsPath, componentName);
|
|
34
71
|
if (!fs.existsSync(targetDir)) {
|
|
35
72
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
36
73
|
}
|
|
37
|
-
//
|
|
74
|
+
// 5. Inject the pure code files
|
|
38
75
|
for (const file of componentData.files) {
|
|
39
76
|
const filePath = path.join(targetDir, file.name);
|
|
40
77
|
fs.writeFileSync(filePath, file.content);
|
|
41
78
|
}
|
|
42
|
-
|
|
43
|
-
// Auto-install
|
|
79
|
+
s.stop(pc.green(`Downloaded ${componentData.files.length} files into ${pc.white(`/${config.componentsPath}/${componentName}`)}`));
|
|
80
|
+
// 6. Auto-install Missing Component Dependencies (e.g., framer-motion)
|
|
44
81
|
if (componentData.dependencies && componentData.dependencies.length > 0) {
|
|
45
82
|
const depsToInstall = componentData.dependencies.join(" ");
|
|
46
|
-
|
|
83
|
+
s.start(`Installing missing dependencies: ${pc.cyan(depsToInstall)}...`);
|
|
47
84
|
try {
|
|
85
|
+
// Silently runs the npm install command in the background
|
|
48
86
|
execSync(`npm install ${depsToInstall}`, { stdio: "ignore" });
|
|
49
|
-
|
|
87
|
+
s.stop(pc.green(`Dependencies installed successfully: ${pc.gray(depsToInstall)}`));
|
|
50
88
|
}
|
|
51
89
|
catch (error) {
|
|
52
|
-
|
|
90
|
+
s.stop(pc.red("Failed to auto-install dependencies."));
|
|
91
|
+
note(`Please run: ${pc.cyan(`npm install ${depsToInstall}`)} manually.`, "Manual Action Required");
|
|
53
92
|
}
|
|
54
93
|
}
|
|
55
|
-
|
|
56
|
-
|
|
94
|
+
// 7. Clean Success Outro
|
|
95
|
+
outro(`${pc.white("✔")} ${pc.bold(`Component <${componentName} /> is ready!`)}\n` +
|
|
96
|
+
pc.gray(`Import it: `) +
|
|
97
|
+
pc.cyan(`import { Section } from "@/${config.componentsPath}/${componentName}/Section"`));
|
|
57
98
|
}
|
|
58
99
|
catch (error) {
|
|
59
|
-
|
|
60
|
-
|
|
100
|
+
s.stop(pc.red("An error occurred during injection."));
|
|
101
|
+
cancel(pc.gray(error.message || "Unknown CLI Error"));
|
|
61
102
|
process.exit(1);
|
|
62
103
|
}
|
|
63
104
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUpC,eAAO,MAAM,KAAK,SA4Fd,CAAC"}
|
package/dist/commands/login.js
CHANGED
|
@@ -1,43 +1,49 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import { intro, outro, spinner, note, cancel } from "@clack/prompts";
|
|
3
|
+
import pc from "picocolors";
|
|
4
4
|
import open from "open";
|
|
5
5
|
import machineIdPkg from "node-machine-id";
|
|
6
|
-
import { saveToken } from "../utils/config.js";
|
|
7
|
-
// ⚠️ In production, replace with your actual deployed Vercel domain
|
|
6
|
+
import { saveToken } from "../utils/config.js";
|
|
8
7
|
const API_BASE_URL = process.env.DS01_API_URL || "https://ds-01.vercel.app";
|
|
9
8
|
const { machineIdSync } = machineIdPkg;
|
|
10
9
|
export const login = new Command()
|
|
11
10
|
.name("login")
|
|
12
11
|
.description("Authenticate your terminal with DS01 via browser")
|
|
13
12
|
.action(async () => {
|
|
14
|
-
|
|
13
|
+
console.log(); // Spacing for breathing room
|
|
14
|
+
// 1. Header banner
|
|
15
|
+
intro(`${pc.bgWhite(pc.black(pc.bold(" DS01 ")))} ${pc.gray("v1.0.0 — Terminal Authentication")}`);
|
|
16
|
+
const s = spinner();
|
|
15
17
|
try {
|
|
16
|
-
//
|
|
18
|
+
// 2. Hardware ID Generation & Device Code Request
|
|
19
|
+
s.start("Generating hardware fingerprint & requesting pairing code...");
|
|
17
20
|
const hardwareId = machineIdSync();
|
|
18
|
-
// 2. Request a new Device Code from the Next.js API
|
|
19
21
|
const initResponse = await fetch(`${API_BASE_URL}/api/auth/device/code`, {
|
|
20
22
|
method: "POST",
|
|
21
|
-
headers: {
|
|
22
|
-
"Content-Type": "application/json",
|
|
23
|
-
},
|
|
23
|
+
headers: { "Content-Type": "application/json" },
|
|
24
24
|
body: JSON.stringify({ machineId: hardwareId }),
|
|
25
25
|
});
|
|
26
26
|
if (!initResponse.ok) {
|
|
27
|
-
|
|
27
|
+
s.stop(pc.red("Failed to reach DS01 authorization server."));
|
|
28
|
+
cancel(`Server responded with HTTP ${initResponse.status}`);
|
|
29
|
+
process.exit(1);
|
|
28
30
|
}
|
|
29
31
|
const { deviceCode, userCode, verificationUrl, interval } = await initResponse.json();
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
s.stop(pc.green("Device authorization code generated."));
|
|
33
|
+
// 3. Highlighted Box for User Action
|
|
34
|
+
note(`${pc.bold("Pairing Code:")} ${pc.cyan(pc.bold(` ${userCode} `))}\n` +
|
|
35
|
+
`${pc.bold("Verification URL:")} ${pc.underline(verificationUrl)}`, "Action Required");
|
|
36
|
+
// 4. Auto-launch Browser
|
|
37
|
+
try {
|
|
38
|
+
await open(verificationUrl);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Non-blocking fallback if browser launch fails on headless setups
|
|
42
|
+
}
|
|
43
|
+
// 5. Polling Loop
|
|
44
|
+
s.start("Waiting for web authorization...");
|
|
39
45
|
let token = null;
|
|
40
|
-
const pollInterval = (interval || 5) * 1000;
|
|
46
|
+
const pollInterval = (interval || 5) * 1000;
|
|
41
47
|
while (!token) {
|
|
42
48
|
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
43
49
|
const tokenResponse = await fetch(`${API_BASE_URL}/api/auth/device/token`, {
|
|
@@ -50,20 +56,20 @@ export const login = new Command()
|
|
|
50
56
|
token = tokenData.accessToken;
|
|
51
57
|
}
|
|
52
58
|
else if (tokenData.error !== "authorization_pending") {
|
|
53
|
-
|
|
54
|
-
|
|
59
|
+
s.stop(pc.red("Authorization failed or expired."));
|
|
60
|
+
cancel(`Reason: ${tokenData.error || "Unknown authorization error"}`);
|
|
55
61
|
process.exit(1);
|
|
56
62
|
}
|
|
57
63
|
}
|
|
58
|
-
//
|
|
59
|
-
// We pass the token and hardwareId into the wallet.
|
|
64
|
+
// 6. Save token to local wallet
|
|
60
65
|
saveToken(token, hardwareId);
|
|
61
|
-
|
|
62
|
-
|
|
66
|
+
s.stop(pc.green("Hardware signature linked & token verified!"));
|
|
67
|
+
// 7. Clean Success Outro
|
|
68
|
+
outro(`${pc.white("✔")} ${pc.bold("Terminal synced successfully")} ${pc.gray("Session token saved locally.")}`);
|
|
63
69
|
}
|
|
64
70
|
catch (error) {
|
|
65
|
-
|
|
66
|
-
|
|
71
|
+
s.stop(pc.red("An error occurred during login."));
|
|
72
|
+
cancel(pc.gray(error.message || "Unknown CLI Error"));
|
|
67
73
|
process.exit(1);
|
|
68
74
|
}
|
|
69
75
|
});
|
package/dist/index.js
CHANGED
|
@@ -3,16 +3,18 @@ import { Command } from "commander";
|
|
|
3
3
|
import { login } from "./commands/login.js";
|
|
4
4
|
import { whoami } from "./commands/whoami.js";
|
|
5
5
|
import { logout } from "./commands/logout.js";
|
|
6
|
-
import { init } from "./commands/init.js";
|
|
6
|
+
import { init } from "./commands/init.js";
|
|
7
|
+
import { add } from "./commands/add.js"; // <-- 1. Import the add command!
|
|
7
8
|
const program = new Command();
|
|
8
9
|
program
|
|
9
|
-
.name("
|
|
10
|
+
.name("ds-01")
|
|
10
11
|
.description("DS01 Custom Component Registry CLI")
|
|
11
|
-
.version("1.
|
|
12
|
+
.version("0.1.5");
|
|
12
13
|
// Register the commands
|
|
13
14
|
program.addCommand(login);
|
|
14
15
|
program.addCommand(whoami);
|
|
15
16
|
program.addCommand(logout);
|
|
16
|
-
program.addCommand(init);
|
|
17
|
+
program.addCommand(init);
|
|
18
|
+
program.addCommand(add); // <-- 2. Register the add command!
|
|
17
19
|
// Parse the arguments from the terminal
|
|
18
20
|
program.parse(process.argv);
|
package/package.json
CHANGED
package/src/commands/add.ts
CHANGED
|
@@ -1,76 +1,155 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import { intro, outro, spinner, cancel, note } from "@clack/prompts";
|
|
3
|
+
import pc from "picocolors";
|
|
4
4
|
import fs from "fs";
|
|
5
5
|
import path from "path";
|
|
6
6
|
import { execSync } from "child_process";
|
|
7
7
|
import { getToken } from "../utils/config.js";
|
|
8
8
|
|
|
9
|
-
const API_BASE_URL = process.env.DS01_API_URL || "https://ds-01.vercel.app";
|
|
9
|
+
const API_BASE_URL = process.env.DS01_API_URL || "https://ds-01.vercel.app";
|
|
10
|
+
|
|
11
|
+
// Helper to verify Tailwind exists before doing anything
|
|
12
|
+
function checkTailwindInstallation() {
|
|
13
|
+
const targetDir = process.cwd();
|
|
14
|
+
const pkgJsonPath = path.join(targetDir, "package.json");
|
|
15
|
+
|
|
16
|
+
// 1. Ensure they are in a valid Node.js project
|
|
17
|
+
if (!fs.existsSync(pkgJsonPath)) {
|
|
18
|
+
cancel(
|
|
19
|
+
pc.red(
|
|
20
|
+
"No package.json found. Please run this command inside a Node.js project.",
|
|
21
|
+
),
|
|
22
|
+
);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// 2. Read package.json dependencies
|
|
27
|
+
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
|
|
28
|
+
const allDeps = {
|
|
29
|
+
...pkgJson.dependencies,
|
|
30
|
+
...pkgJson.devDependencies,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// 3. Strict Tailwind Check
|
|
34
|
+
if (!allDeps["tailwindcss"]) {
|
|
35
|
+
cancel(
|
|
36
|
+
pc.red("Tailwind CSS is missing from your project dependencies.\n") +
|
|
37
|
+
pc.gray(
|
|
38
|
+
"DS01 components rely strictly on Tailwind CSS for styling.\n\n",
|
|
39
|
+
) +
|
|
40
|
+
pc.white(
|
|
41
|
+
"Kindly install it and configure your project, then retry:\n",
|
|
42
|
+
) +
|
|
43
|
+
pc.cyan(" npm install tailwindcss @tailwindcss/postcss postcss\n\n") +
|
|
44
|
+
pc.gray("Official Setup Guide: ") +
|
|
45
|
+
pc.underline("https://tailwindcss.com/docs/installation"),
|
|
46
|
+
);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
10
50
|
|
|
11
51
|
export const add = new Command()
|
|
12
52
|
.name("add")
|
|
13
53
|
.description("Add a component from DS01 to your project")
|
|
14
54
|
.argument("<component>", "The name of the component (e.g., premium-section)")
|
|
15
55
|
.action(async (componentName: string) => {
|
|
56
|
+
console.log(); // Spacing for visual breathing room
|
|
57
|
+
|
|
58
|
+
// 1. Premium Header
|
|
59
|
+
intro(
|
|
60
|
+
`${pc.bgWhite(pc.black(pc.bold(" DS01 ")))} ${pc.gray("Adding Component")}`,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
// 2. Run Pre-Flight Tailwind Check
|
|
64
|
+
checkTailwindInstallation();
|
|
65
|
+
|
|
16
66
|
const cwd = process.cwd();
|
|
17
67
|
const configPath = path.join(cwd, "ds01.config.json");
|
|
18
68
|
|
|
19
69
|
if (!fs.existsSync(configPath)) {
|
|
20
|
-
|
|
21
|
-
console.log(`Run ${chalk.cyan("npx @arpit2023/ds01 init")} first.`);
|
|
70
|
+
cancel(pc.red("ds01.config.json not found. Run 'npx ds-01 init' first."));
|
|
22
71
|
process.exit(1);
|
|
23
72
|
}
|
|
24
73
|
|
|
25
74
|
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
26
75
|
const token = getToken();
|
|
27
76
|
|
|
28
|
-
const
|
|
77
|
+
const s = spinner();
|
|
78
|
+
s.start(`Fetching ${pc.cyan(`<${componentName} />`)} from registry...`);
|
|
29
79
|
|
|
30
80
|
try {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
81
|
+
// 3. Fetch component from your API
|
|
82
|
+
const response = await fetch(
|
|
83
|
+
`${API_BASE_URL}/api/registry/${componentName}`,
|
|
84
|
+
{
|
|
85
|
+
headers: {
|
|
86
|
+
Authorization: `Bearer ${config.token}`,
|
|
87
|
+
"X-Machine-ID": config.machineId, // <-- ADD THIS LINE
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
);
|
|
34
91
|
|
|
35
92
|
if (!response.ok) {
|
|
36
|
-
|
|
93
|
+
s.stop(pc.red("Component fetch failed."));
|
|
94
|
+
cancel(`Component not found or server error (HTTP ${response.status})`);
|
|
95
|
+
process.exit(1);
|
|
37
96
|
}
|
|
38
97
|
|
|
39
98
|
const componentData = await response.json();
|
|
40
99
|
|
|
41
|
-
// Create
|
|
100
|
+
// 4. Create dedicated component folder
|
|
42
101
|
const targetDir = path.join(cwd, config.componentsPath, componentName);
|
|
43
102
|
if (!fs.existsSync(targetDir)) {
|
|
44
103
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
45
104
|
}
|
|
46
105
|
|
|
47
|
-
//
|
|
106
|
+
// 5. Inject the pure code files
|
|
48
107
|
for (const file of componentData.files) {
|
|
49
108
|
const filePath = path.join(targetDir, file.name);
|
|
50
109
|
fs.writeFileSync(filePath, file.content);
|
|
51
110
|
}
|
|
52
|
-
|
|
53
|
-
spinner.succeed(chalk.green(`Downloaded ${componentData.files.length} files for ${componentName}`));
|
|
54
111
|
|
|
55
|
-
|
|
112
|
+
s.stop(
|
|
113
|
+
pc.green(
|
|
114
|
+
`Downloaded ${componentData.files.length} files into ${pc.white(`/${config.componentsPath}/${componentName}`)}`,
|
|
115
|
+
),
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// 6. Auto-install Missing Component Dependencies (e.g., framer-motion)
|
|
56
119
|
if (componentData.dependencies && componentData.dependencies.length > 0) {
|
|
57
120
|
const depsToInstall = componentData.dependencies.join(" ");
|
|
58
|
-
|
|
59
|
-
|
|
121
|
+
s.start(
|
|
122
|
+
`Installing missing dependencies: ${pc.cyan(depsToInstall)}...`,
|
|
123
|
+
);
|
|
124
|
+
|
|
60
125
|
try {
|
|
126
|
+
// Silently runs the npm install command in the background
|
|
61
127
|
execSync(`npm install ${depsToInstall}`, { stdio: "ignore" });
|
|
62
|
-
|
|
128
|
+
s.stop(
|
|
129
|
+
pc.green(
|
|
130
|
+
`Dependencies installed successfully: ${pc.gray(depsToInstall)}`,
|
|
131
|
+
),
|
|
132
|
+
);
|
|
63
133
|
} catch (error) {
|
|
64
|
-
|
|
134
|
+
s.stop(pc.red("Failed to auto-install dependencies."));
|
|
135
|
+
note(
|
|
136
|
+
`Please run: ${pc.cyan(`npm install ${depsToInstall}`)} manually.`,
|
|
137
|
+
"Manual Action Required",
|
|
138
|
+
);
|
|
65
139
|
}
|
|
66
140
|
}
|
|
67
141
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
142
|
+
// 7. Clean Success Outro
|
|
143
|
+
outro(
|
|
144
|
+
`${pc.white("✔")} ${pc.bold(`Component <${componentName} /> is ready!`)}\n` +
|
|
145
|
+
pc.gray(`Import it: `) +
|
|
146
|
+
pc.cyan(
|
|
147
|
+
`import { Section } from "@/${config.componentsPath}/${componentName}/Section"`,
|
|
148
|
+
),
|
|
149
|
+
);
|
|
71
150
|
} catch (error: any) {
|
|
72
|
-
|
|
73
|
-
|
|
151
|
+
s.stop(pc.red("An error occurred during injection."));
|
|
152
|
+
cancel(pc.gray(error.message || "Unknown CLI Error"));
|
|
74
153
|
process.exit(1);
|
|
75
154
|
}
|
|
76
|
-
});
|
|
155
|
+
});
|
package/src/commands/login.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import { intro, outro, spinner, note, cancel } from "@clack/prompts";
|
|
3
|
+
import pc from "picocolors";
|
|
4
4
|
import open from "open";
|
|
5
5
|
import machineIdPkg from "node-machine-id";
|
|
6
|
-
import { saveToken } from "../utils/config.js";
|
|
6
|
+
import { saveToken } from "../utils/config.js";
|
|
7
7
|
|
|
8
|
-
// ⚠️ In production, replace with your actual deployed Vercel domain
|
|
9
8
|
const API_BASE_URL = process.env.DS01_API_URL || "https://ds-01.vercel.app";
|
|
10
9
|
const { machineIdSync } = machineIdPkg;
|
|
11
10
|
|
|
@@ -13,43 +12,56 @@ export const login = new Command()
|
|
|
13
12
|
.name("login")
|
|
14
13
|
.description("Authenticate your terminal with DS01 via browser")
|
|
15
14
|
.action(async () => {
|
|
16
|
-
|
|
15
|
+
console.log(); // Spacing for breathing room
|
|
16
|
+
|
|
17
|
+
// 1. Header banner
|
|
18
|
+
intro(
|
|
19
|
+
`${pc.bgWhite(pc.black(pc.bold(" DS01 ")))} ${pc.gray("v1.0.0 — Terminal Authentication")}`
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const s = spinner();
|
|
17
23
|
|
|
18
24
|
try {
|
|
19
|
-
//
|
|
25
|
+
// 2. Hardware ID Generation & Device Code Request
|
|
26
|
+
s.start("Generating hardware fingerprint & requesting pairing code...");
|
|
20
27
|
const hardwareId = machineIdSync();
|
|
21
28
|
|
|
22
|
-
// 2. Request a new Device Code from the Next.js API
|
|
23
29
|
const initResponse = await fetch(`${API_BASE_URL}/api/auth/device/code`, {
|
|
24
30
|
method: "POST",
|
|
25
|
-
headers: {
|
|
26
|
-
|
|
27
|
-
},
|
|
28
|
-
body: JSON.stringify({ machineId: hardwareId }),
|
|
31
|
+
headers: { "Content-Type": "application/json" },
|
|
32
|
+
body: JSON.stringify({ machineId: hardwareId }),
|
|
29
33
|
});
|
|
30
34
|
|
|
31
35
|
if (!initResponse.ok) {
|
|
32
|
-
|
|
36
|
+
s.stop(pc.red("Failed to reach DS01 authorization server."));
|
|
37
|
+
cancel(`Server responded with HTTP ${initResponse.status}`);
|
|
38
|
+
process.exit(1);
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
const { deviceCode, userCode, verificationUrl, interval } =
|
|
36
42
|
await initResponse.json();
|
|
37
43
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
s.stop(pc.green("Device authorization code generated."));
|
|
45
|
+
|
|
46
|
+
// 3. Highlighted Box for User Action
|
|
47
|
+
note(
|
|
48
|
+
`${pc.bold("Pairing Code:")} ${pc.cyan(pc.bold(` ${userCode} `))}\n` +
|
|
49
|
+
`${pc.bold("Verification URL:")} ${pc.underline(verificationUrl)}`,
|
|
50
|
+
"Action Required"
|
|
42
51
|
);
|
|
43
|
-
console.log(`If your browser does not open automatically, visit:`);
|
|
44
|
-
console.log(chalk.underline.blue(verificationUrl) + "\n");
|
|
45
52
|
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
53
|
+
// 4. Auto-launch Browser
|
|
54
|
+
try {
|
|
55
|
+
await open(verificationUrl);
|
|
56
|
+
} catch {
|
|
57
|
+
// Non-blocking fallback if browser launch fails on headless setups
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 5. Polling Loop
|
|
61
|
+
s.start("Waiting for web authorization...");
|
|
49
62
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const pollInterval = (interval || 5) * 1000; // Updated to match our 5s secure interval
|
|
63
|
+
let token: string | null = null;
|
|
64
|
+
const pollInterval = (interval || 5) * 1000;
|
|
53
65
|
|
|
54
66
|
while (!token) {
|
|
55
67
|
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
@@ -60,7 +72,7 @@ export const login = new Command()
|
|
|
60
72
|
method: "POST",
|
|
61
73
|
headers: { "Content-Type": "application/json" },
|
|
62
74
|
body: JSON.stringify({ deviceCode, machineId: hardwareId }),
|
|
63
|
-
}
|
|
75
|
+
}
|
|
64
76
|
);
|
|
65
77
|
|
|
66
78
|
const tokenData = await tokenResponse.json();
|
|
@@ -68,25 +80,24 @@ export const login = new Command()
|
|
|
68
80
|
if (tokenResponse.ok && tokenData.accessToken) {
|
|
69
81
|
token = tokenData.accessToken;
|
|
70
82
|
} else if (tokenData.error !== "authorization_pending") {
|
|
71
|
-
|
|
72
|
-
|
|
83
|
+
s.stop(pc.red("Authorization failed or expired."));
|
|
84
|
+
cancel(`Reason: ${tokenData.error || "Unknown authorization error"}`);
|
|
73
85
|
process.exit(1);
|
|
74
86
|
}
|
|
75
87
|
}
|
|
76
88
|
|
|
77
|
-
//
|
|
78
|
-
// We pass the token and hardwareId into the wallet.
|
|
89
|
+
// 6. Save token to local wallet
|
|
79
90
|
saveToken(token, hardwareId);
|
|
80
91
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
)
|
|
92
|
+
s.stop(pc.green("Hardware signature linked & token verified!"));
|
|
93
|
+
|
|
94
|
+
// 7. Clean Success Outro
|
|
95
|
+
outro(
|
|
96
|
+
`${pc.white("✔")} ${pc.bold("Terminal synced successfully")} ${pc.gray("Session token saved locally.")}`
|
|
86
97
|
);
|
|
87
98
|
} catch (error: any) {
|
|
88
|
-
|
|
89
|
-
|
|
99
|
+
s.stop(pc.red("An error occurred during login."));
|
|
100
|
+
cancel(pc.gray(error.message || "Unknown CLI Error"));
|
|
90
101
|
process.exit(1);
|
|
91
102
|
}
|
|
92
103
|
});
|
package/src/index.ts
CHANGED
|
@@ -4,20 +4,22 @@ import { Command } from "commander";
|
|
|
4
4
|
import { login } from "./commands/login.js";
|
|
5
5
|
import { whoami } from "./commands/whoami.js";
|
|
6
6
|
import { logout } from "./commands/logout.js";
|
|
7
|
-
import { init } from "./commands/init.js";
|
|
7
|
+
import { init } from "./commands/init.js";
|
|
8
|
+
import { add } from "./commands/add.js"; // <-- 1. Import the add command!
|
|
8
9
|
|
|
9
10
|
const program = new Command();
|
|
10
11
|
|
|
11
12
|
program
|
|
12
|
-
.name("
|
|
13
|
+
.name("ds-01")
|
|
13
14
|
.description("DS01 Custom Component Registry CLI")
|
|
14
|
-
.version("1.
|
|
15
|
+
.version("0.1.5");
|
|
15
16
|
|
|
16
17
|
// Register the commands
|
|
17
18
|
program.addCommand(login);
|
|
18
19
|
program.addCommand(whoami);
|
|
19
20
|
program.addCommand(logout);
|
|
20
|
-
program.addCommand(init);
|
|
21
|
+
program.addCommand(init);
|
|
22
|
+
program.addCommand(add); // <-- 2. Register the add command!
|
|
21
23
|
|
|
22
24
|
// Parse the arguments from the terminal
|
|
23
25
|
program.parse(process.argv);
|