ds-01 0.1.2 → 0.1.4
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 +3 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +63 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +86 -0
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +6 -18
- package/dist/commands/logout.d.ts +3 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +25 -0
- package/dist/commands/whoami.d.ts +3 -0
- package/dist/commands/whoami.d.ts.map +1 -0
- package/dist/commands/whoami.js +17 -0
- package/dist/index.js +7 -2
- package/dist/utils/config.d.ts +4 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +37 -0
- package/package.json +4 -3
- package/src/commands/add.ts +76 -0
- package/src/commands/init.ts +99 -0
- package/src/commands/login.ts +7 -28
- package/src/commands/logout.ts +29 -0
- package/src/commands/whoami.ts +20 -0
- package/src/index.ts +8 -3
- package/src/utils/config.ts +42 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUpC,eAAO,MAAM,GAAG,SAiEZ,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { execSync } from "child_process";
|
|
7
|
+
import { getToken } from "../utils/config.js";
|
|
8
|
+
const API_BASE_URL = process.env.DS01_API_URL || "https://ds-01.vercel.app"; // Point to localhost for testing
|
|
9
|
+
export const add = new Command()
|
|
10
|
+
.name("add")
|
|
11
|
+
.description("Add a component from DS01 to your project")
|
|
12
|
+
.argument("<component>", "The name of the component (e.g., premium-section)")
|
|
13
|
+
.action(async (componentName) => {
|
|
14
|
+
const cwd = process.cwd();
|
|
15
|
+
const configPath = path.join(cwd, "ds01.config.json");
|
|
16
|
+
if (!fs.existsSync(configPath)) {
|
|
17
|
+
console.log(chalk.red("✖ ds01.config.json not found."));
|
|
18
|
+
console.log(`Run ${chalk.cyan("npx @arpit2023/ds01 init")} first.`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
22
|
+
const token = getToken();
|
|
23
|
+
const spinner = ora(`Fetching <${componentName} /> from registry...`).start();
|
|
24
|
+
try {
|
|
25
|
+
const response = await fetch(`${API_BASE_URL}/api/registry/${componentName}`, {
|
|
26
|
+
headers: { Authorization: `Bearer ${token || ""}` },
|
|
27
|
+
});
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
throw new Error(`Component not found or server error (${response.status})`);
|
|
30
|
+
}
|
|
31
|
+
const componentData = await response.json();
|
|
32
|
+
// Create a dedicated folder for the component inside the user's project
|
|
33
|
+
const targetDir = path.join(cwd, config.componentsPath, componentName);
|
|
34
|
+
if (!fs.existsSync(targetDir)) {
|
|
35
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
// Write all the pure files!
|
|
38
|
+
for (const file of componentData.files) {
|
|
39
|
+
const filePath = path.join(targetDir, file.name);
|
|
40
|
+
fs.writeFileSync(filePath, file.content);
|
|
41
|
+
}
|
|
42
|
+
spinner.succeed(chalk.green(`Downloaded ${componentData.files.length} files for ${componentName}`));
|
|
43
|
+
// Auto-install dependencies like framer-motion
|
|
44
|
+
if (componentData.dependencies && componentData.dependencies.length > 0) {
|
|
45
|
+
const depsToInstall = componentData.dependencies.join(" ");
|
|
46
|
+
const installSpinner = ora(`Installing dependencies: ${chalk.cyan(depsToInstall)}...`).start();
|
|
47
|
+
try {
|
|
48
|
+
execSync(`npm install ${depsToInstall}`, { stdio: "ignore" });
|
|
49
|
+
installSpinner.succeed(chalk.green("Dependencies installed successfully."));
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
installSpinner.fail(chalk.red("Failed to auto-install dependencies. Please install manually."));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
console.log("\n" + chalk.green.bold(`✔ Component <${componentName} /> is ready!`));
|
|
56
|
+
console.log(chalk.gray(`Import it: import { Section } from "@/${config.componentsPath}/${componentName}/Section"`));
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
spinner.fail(chalk.red(`Failed to add component.`));
|
|
60
|
+
console.error(chalk.red(error.message));
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,eAAO,MAAM,IAAI,SA4Fb,CAAC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { intro, outro, spinner, text, isCancel, cancel } from "@clack/prompts";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
export const init = new Command()
|
|
7
|
+
.name("init")
|
|
8
|
+
.description("Initialize DS01 in your React/Next.js TypeScript project")
|
|
9
|
+
.action(async () => {
|
|
10
|
+
intro(chalk.bgCyan.black.bold(" DS01 Setup Wizard "));
|
|
11
|
+
const s = spinner();
|
|
12
|
+
s.start("Checking project environment...");
|
|
13
|
+
const cwd = process.cwd();
|
|
14
|
+
const packageJsonPath = path.join(cwd, "package.json");
|
|
15
|
+
const tsconfigPath = path.join(cwd, "tsconfig.json");
|
|
16
|
+
// 1. Check for package.json
|
|
17
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
18
|
+
s.stop("Environment check failed.");
|
|
19
|
+
cancel("No package.json found. Please run this command in the root of your project.");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
23
|
+
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
24
|
+
// 2. Check strictly for React or Next.js
|
|
25
|
+
if (!deps.react && !deps.next) {
|
|
26
|
+
s.stop("Environment check failed.");
|
|
27
|
+
cancel("DS01 strictly supports React and Next.js environments. Please run this in a supported project.");
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
// 3. Check strictly for TypeScript
|
|
31
|
+
if (!fs.existsSync(tsconfigPath)) {
|
|
32
|
+
s.stop("Environment check failed.");
|
|
33
|
+
cancel("DS01 requires TypeScript. No tsconfig.json found in the project root.");
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
s.stop(chalk.green("✔ React and TypeScript detected!"));
|
|
37
|
+
// 4. Ask the user where their global CSS is located
|
|
38
|
+
const cssPath = await text({
|
|
39
|
+
message: "Where is your global CSS file?",
|
|
40
|
+
placeholder: "app/globals.css",
|
|
41
|
+
initialValue: "app/globals.css",
|
|
42
|
+
});
|
|
43
|
+
if (isCancel(cssPath)) {
|
|
44
|
+
cancel("Setup cancelled.");
|
|
45
|
+
process.exit(0);
|
|
46
|
+
}
|
|
47
|
+
// 5. Inject Dark Mode / Glassmorphic CSS variables
|
|
48
|
+
const fullCssPath = path.join(cwd, cssPath);
|
|
49
|
+
if (fs.existsSync(fullCssPath)) {
|
|
50
|
+
const cssContent = fs.readFileSync(fullCssPath, "utf-8");
|
|
51
|
+
if (!cssContent.includes("/* ds01-theme-start */")) {
|
|
52
|
+
const variables = `
|
|
53
|
+
/* ds01-theme-start */
|
|
54
|
+
@layer base {
|
|
55
|
+
:root {
|
|
56
|
+
--ds-background: #0a0a0a; /* Deep charcoal */
|
|
57
|
+
--ds-foreground: #ffffff;
|
|
58
|
+
--ds-glass-border: rgba(255, 255, 255, 0.08);
|
|
59
|
+
--ds-glass-blur: blur(16px);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/* ds01-theme-end */
|
|
63
|
+
`;
|
|
64
|
+
fs.appendFileSync(fullCssPath, variables);
|
|
65
|
+
console.log(chalk.green(`✔ Injected design tokens into ${cssPath}`));
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
console.log(chalk.gray(`- Design tokens already exist in ${cssPath}`));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
console.log(chalk.yellow(`⚠ Could not find ${cssPath}. You may need to add the CSS variables manually.`));
|
|
73
|
+
}
|
|
74
|
+
// 6. Create the local configuration file so the CLI remembers this project
|
|
75
|
+
const configPath = path.join(cwd, "ds01.config.json");
|
|
76
|
+
const configData = {
|
|
77
|
+
$schema: "https://ds-01.vercel.app/schema.json",
|
|
78
|
+
style: "default",
|
|
79
|
+
typescript: true,
|
|
80
|
+
cssPath: cssPath,
|
|
81
|
+
componentsPath: "components/ui" // This is where we will inject components later
|
|
82
|
+
};
|
|
83
|
+
fs.writeFileSync(configPath, JSON.stringify(configData, null, 2));
|
|
84
|
+
console.log(chalk.green(`✔ Created ds01.config.json`));
|
|
85
|
+
outro(chalk.green.bold("DS01 successfully initialized! Ready to add components."));
|
|
86
|
+
});
|
|
@@ -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;AAWpC,eAAO,MAAM,KAAK,SAgFd,CAAC"}
|
package/dist/commands/login.js
CHANGED
|
@@ -3,13 +3,9 @@ import ora from "ora";
|
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import open from "open";
|
|
5
5
|
import machineIdPkg from "node-machine-id";
|
|
6
|
-
import
|
|
7
|
-
import path from "path";
|
|
8
|
-
import os from "os";
|
|
6
|
+
import { saveToken } from "../utils/config.js"; // <-- Imported our new wallet helper!
|
|
9
7
|
// ⚠️ In production, replace with your actual deployed Vercel domain
|
|
10
8
|
const API_BASE_URL = process.env.DS01_API_URL || "https://ds-01.vercel.app";
|
|
11
|
-
const CONFIG_DIR = path.join(os.homedir(), ".ds01");
|
|
12
|
-
const AUTH_FILE = path.join(CONFIG_DIR, "auth.json");
|
|
13
9
|
const { machineIdSync } = machineIdPkg;
|
|
14
10
|
export const login = new Command()
|
|
15
11
|
.name("login")
|
|
@@ -25,7 +21,7 @@ export const login = new Command()
|
|
|
25
21
|
headers: {
|
|
26
22
|
"Content-Type": "application/json",
|
|
27
23
|
},
|
|
28
|
-
body: JSON.stringify({ machineId: hardwareId }),
|
|
24
|
+
body: JSON.stringify({ machineId: hardwareId }),
|
|
29
25
|
});
|
|
30
26
|
if (!initResponse.ok) {
|
|
31
27
|
throw new Error("Failed to reach DS01 authorization server.");
|
|
@@ -41,9 +37,8 @@ export const login = new Command()
|
|
|
41
37
|
spinner.start("Waiting for web approval...");
|
|
42
38
|
// 4. Poll the Token Endpoint
|
|
43
39
|
let token = null;
|
|
44
|
-
const pollInterval = (interval ||
|
|
40
|
+
const pollInterval = (interval || 5) * 1000; // Updated to match our 5s secure interval
|
|
45
41
|
while (!token) {
|
|
46
|
-
// Wait before polling again
|
|
47
42
|
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
48
43
|
const tokenResponse = await fetch(`${API_BASE_URL}/api/auth/device/token`, {
|
|
49
44
|
method: "POST",
|
|
@@ -55,21 +50,14 @@ export const login = new Command()
|
|
|
55
50
|
token = tokenData.accessToken;
|
|
56
51
|
}
|
|
57
52
|
else if (tokenData.error !== "authorization_pending") {
|
|
58
|
-
// If error is anything OTHER than pending, fail out.
|
|
59
53
|
spinner.fail(chalk.red("Authorization failed or expired."));
|
|
60
54
|
console.error(chalk.red(`Reason: ${tokenData.error}`));
|
|
61
55
|
process.exit(1);
|
|
62
56
|
}
|
|
63
57
|
}
|
|
64
|
-
// 5. Save Token
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
fs.writeFileSync(AUTH_FILE, JSON.stringify({
|
|
69
|
-
token,
|
|
70
|
-
machineId: hardwareId,
|
|
71
|
-
updatedAt: new Date().toISOString(),
|
|
72
|
-
}, null, 2));
|
|
58
|
+
// 5. Save Token securely using our new config utility
|
|
59
|
+
// We pass the token and hardwareId into the wallet.
|
|
60
|
+
saveToken(token, hardwareId);
|
|
73
61
|
spinner.succeed(chalk.green.bold("Terminal paired successfully!"));
|
|
74
62
|
console.log(chalk.gray(`Session token bound to hardware signature and saved locally.`));
|
|
75
63
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logout.d.ts","sourceRoot":"","sources":["../../src/commands/logout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC,eAAO,MAAM,MAAM,SAuBf,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
import { deleteToken, getToken } from "../utils/config.js";
|
|
5
|
+
export const logout = new Command()
|
|
6
|
+
.name("logout")
|
|
7
|
+
.description("Log out of your DS01 account and clear local credentials")
|
|
8
|
+
.action(async () => {
|
|
9
|
+
const token = getToken();
|
|
10
|
+
if (!token) {
|
|
11
|
+
console.log(chalk.yellow("You are already logged out."));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const spinner = ora("Logging out...").start();
|
|
15
|
+
try {
|
|
16
|
+
// Throw away the digital wallet / token
|
|
17
|
+
deleteToken();
|
|
18
|
+
spinner.succeed(chalk.green("Successfully logged out."));
|
|
19
|
+
console.log(chalk.gray("Your local credentials have been cleared."));
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
spinner.fail(chalk.red("Failed to log out cleanly."));
|
|
23
|
+
console.error(error);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whoami.d.ts","sourceRoot":"","sources":["../../src/commands/whoami.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,eAAO,MAAM,MAAM,SAef,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { getToken } from "../utils/config.js";
|
|
4
|
+
export const whoami = new Command()
|
|
5
|
+
.name("whoami")
|
|
6
|
+
.description("Check your current authentication status")
|
|
7
|
+
.action(async () => {
|
|
8
|
+
const token = getToken();
|
|
9
|
+
if (!token) {
|
|
10
|
+
console.log(chalk.red("✖ You are not logged in."));
|
|
11
|
+
console.log(`Run ${chalk.cyan("npx @arpit2023/ds01 login")} to authenticate.`);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
// For now, we just verify the token is saved.
|
|
15
|
+
console.log(chalk.green("✔ You are securely logged in!"));
|
|
16
|
+
console.log(chalk.gray(`Token found ending in: ...${token.slice(-6)}`));
|
|
17
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import { login } from "./commands/login.js";
|
|
4
|
-
|
|
4
|
+
import { whoami } from "./commands/whoami.js";
|
|
5
|
+
import { logout } from "./commands/logout.js";
|
|
6
|
+
import { init } from "./commands/init.js"; // <-- Imported the new init command!
|
|
5
7
|
const program = new Command();
|
|
6
8
|
program
|
|
7
9
|
.name("ds01")
|
|
8
10
|
.description("DS01 Custom Component Registry CLI")
|
|
9
11
|
.version("1.0.0");
|
|
10
|
-
// Register the
|
|
12
|
+
// Register the commands
|
|
11
13
|
program.addCommand(login);
|
|
14
|
+
program.addCommand(whoami);
|
|
15
|
+
program.addCommand(logout);
|
|
16
|
+
program.addCommand(init); // <-- Registered it here!
|
|
12
17
|
// Parse the arguments from the terminal
|
|
13
18
|
program.parse(process.argv);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/utils/config.ts"],"names":[],"mappings":"AAQA,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,QAczD;AAED,wBAAgB,QAAQ,IAAI,MAAM,GAAG,IAAI,CAWxC;AAED,wBAAgB,WAAW,SAI1B"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import os from "os";
|
|
4
|
+
// We use the same path you originally had for consistency
|
|
5
|
+
const configDir = path.join(os.homedir(), ".ds01");
|
|
6
|
+
const credentialsPath = path.join(configDir, "auth.json");
|
|
7
|
+
export function saveToken(token, machineId) {
|
|
8
|
+
if (!fs.existsSync(configDir)) {
|
|
9
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
10
|
+
}
|
|
11
|
+
const payload = {
|
|
12
|
+
token,
|
|
13
|
+
machineId,
|
|
14
|
+
updatedAt: new Date().toISOString(),
|
|
15
|
+
};
|
|
16
|
+
fs.writeFileSync(credentialsPath, JSON.stringify(payload, null, 2), {
|
|
17
|
+
mode: 0o600,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
export function getToken() {
|
|
21
|
+
if (fs.existsSync(credentialsPath)) {
|
|
22
|
+
try {
|
|
23
|
+
const data = fs.readFileSync(credentialsPath, "utf-8");
|
|
24
|
+
const parsed = JSON.parse(data);
|
|
25
|
+
return parsed.token || null;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
export function deleteToken() {
|
|
34
|
+
if (fs.existsSync(credentialsPath)) {
|
|
35
|
+
fs.unlinkSync(credentialsPath);
|
|
36
|
+
}
|
|
37
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ds-01",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Make your site best with DS01",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,14 +13,15 @@
|
|
|
13
13
|
"access": "public"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"
|
|
16
|
+
"@clack/prompts": "^1.7.0",
|
|
17
|
+
"chalk": "^5.6.2",
|
|
17
18
|
"commander": "^14",
|
|
18
19
|
"node-machine-id": "^1",
|
|
19
20
|
"open": "^10",
|
|
20
21
|
"ora": "^8"
|
|
21
22
|
},
|
|
22
23
|
"devDependencies": {
|
|
23
|
-
"@types/node": "^24",
|
|
24
|
+
"@types/node": "^24.13.3",
|
|
24
25
|
"tsup": "^8.5.1",
|
|
25
26
|
"typescript": "^5"
|
|
26
27
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { execSync } from "child_process";
|
|
7
|
+
import { getToken } from "../utils/config.js";
|
|
8
|
+
|
|
9
|
+
const API_BASE_URL = process.env.DS01_API_URL || "https://ds-01.vercel.app"; // Point to localhost for testing
|
|
10
|
+
|
|
11
|
+
export const add = new Command()
|
|
12
|
+
.name("add")
|
|
13
|
+
.description("Add a component from DS01 to your project")
|
|
14
|
+
.argument("<component>", "The name of the component (e.g., premium-section)")
|
|
15
|
+
.action(async (componentName: string) => {
|
|
16
|
+
const cwd = process.cwd();
|
|
17
|
+
const configPath = path.join(cwd, "ds01.config.json");
|
|
18
|
+
|
|
19
|
+
if (!fs.existsSync(configPath)) {
|
|
20
|
+
console.log(chalk.red("✖ ds01.config.json not found."));
|
|
21
|
+
console.log(`Run ${chalk.cyan("npx @arpit2023/ds01 init")} first.`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
26
|
+
const token = getToken();
|
|
27
|
+
|
|
28
|
+
const spinner = ora(`Fetching <${componentName} /> from registry...`).start();
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const response = await fetch(`${API_BASE_URL}/api/registry/${componentName}`, {
|
|
32
|
+
headers: { Authorization: `Bearer ${token || ""}` },
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
throw new Error(`Component not found or server error (${response.status})`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const componentData = await response.json();
|
|
40
|
+
|
|
41
|
+
// Create a dedicated folder for the component inside the user's project
|
|
42
|
+
const targetDir = path.join(cwd, config.componentsPath, componentName);
|
|
43
|
+
if (!fs.existsSync(targetDir)) {
|
|
44
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Write all the pure files!
|
|
48
|
+
for (const file of componentData.files) {
|
|
49
|
+
const filePath = path.join(targetDir, file.name);
|
|
50
|
+
fs.writeFileSync(filePath, file.content);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
spinner.succeed(chalk.green(`Downloaded ${componentData.files.length} files for ${componentName}`));
|
|
54
|
+
|
|
55
|
+
// Auto-install dependencies like framer-motion
|
|
56
|
+
if (componentData.dependencies && componentData.dependencies.length > 0) {
|
|
57
|
+
const depsToInstall = componentData.dependencies.join(" ");
|
|
58
|
+
const installSpinner = ora(`Installing dependencies: ${chalk.cyan(depsToInstall)}...`).start();
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
execSync(`npm install ${depsToInstall}`, { stdio: "ignore" });
|
|
62
|
+
installSpinner.succeed(chalk.green("Dependencies installed successfully."));
|
|
63
|
+
} catch (error) {
|
|
64
|
+
installSpinner.fail(chalk.red("Failed to auto-install dependencies. Please install manually."));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
console.log("\n" + chalk.green.bold(`✔ Component <${componentName} /> is ready!`));
|
|
69
|
+
console.log(chalk.gray(`Import it: import { Section } from "@/${config.componentsPath}/${componentName}/Section"`));
|
|
70
|
+
|
|
71
|
+
} catch (error: any) {
|
|
72
|
+
spinner.fail(chalk.red(`Failed to add component.`));
|
|
73
|
+
console.error(chalk.red(error.message));
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { intro, outro, spinner, text, isCancel, cancel } from "@clack/prompts";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
|
|
7
|
+
export const init = new Command()
|
|
8
|
+
.name("init")
|
|
9
|
+
.description("Initialize DS01 in your React/Next.js TypeScript project")
|
|
10
|
+
.action(async () => {
|
|
11
|
+
intro(chalk.bgCyan.black.bold(" DS01 Setup Wizard "));
|
|
12
|
+
|
|
13
|
+
const s = spinner();
|
|
14
|
+
s.start("Checking project environment...");
|
|
15
|
+
|
|
16
|
+
const cwd = process.cwd();
|
|
17
|
+
const packageJsonPath = path.join(cwd, "package.json");
|
|
18
|
+
const tsconfigPath = path.join(cwd, "tsconfig.json");
|
|
19
|
+
|
|
20
|
+
// 1. Check for package.json
|
|
21
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
22
|
+
s.stop("Environment check failed.");
|
|
23
|
+
cancel("No package.json found. Please run this command in the root of your project.");
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
28
|
+
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
29
|
+
|
|
30
|
+
// 2. Check strictly for React or Next.js
|
|
31
|
+
if (!deps.react && !deps.next) {
|
|
32
|
+
s.stop("Environment check failed.");
|
|
33
|
+
cancel("DS01 strictly supports React and Next.js environments. Please run this in a supported project.");
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 3. Check strictly for TypeScript
|
|
38
|
+
if (!fs.existsSync(tsconfigPath)) {
|
|
39
|
+
s.stop("Environment check failed.");
|
|
40
|
+
cancel("DS01 requires TypeScript. No tsconfig.json found in the project root.");
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
s.stop(chalk.green("✔ React and TypeScript detected!"));
|
|
45
|
+
|
|
46
|
+
// 4. Ask the user where their global CSS is located
|
|
47
|
+
const cssPath = await text({
|
|
48
|
+
message: "Where is your global CSS file?",
|
|
49
|
+
placeholder: "app/globals.css",
|
|
50
|
+
initialValue: "app/globals.css",
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (isCancel(cssPath)) {
|
|
54
|
+
cancel("Setup cancelled.");
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 5. Inject Dark Mode / Glassmorphic CSS variables
|
|
59
|
+
const fullCssPath = path.join(cwd, cssPath as string);
|
|
60
|
+
if (fs.existsSync(fullCssPath)) {
|
|
61
|
+
const cssContent = fs.readFileSync(fullCssPath, "utf-8");
|
|
62
|
+
|
|
63
|
+
if (!cssContent.includes("/* ds01-theme-start */")) {
|
|
64
|
+
const variables = `
|
|
65
|
+
/* ds01-theme-start */
|
|
66
|
+
@layer base {
|
|
67
|
+
:root {
|
|
68
|
+
--ds-background: #0a0a0a; /* Deep charcoal */
|
|
69
|
+
--ds-foreground: #ffffff;
|
|
70
|
+
--ds-glass-border: rgba(255, 255, 255, 0.08);
|
|
71
|
+
--ds-glass-blur: blur(16px);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/* ds01-theme-end */
|
|
75
|
+
`;
|
|
76
|
+
fs.appendFileSync(fullCssPath, variables);
|
|
77
|
+
console.log(chalk.green(`✔ Injected design tokens into ${cssPath}`));
|
|
78
|
+
} else {
|
|
79
|
+
console.log(chalk.gray(`- Design tokens already exist in ${cssPath}`));
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
console.log(chalk.yellow(`⚠ Could not find ${cssPath}. You may need to add the CSS variables manually.`));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 6. Create the local configuration file so the CLI remembers this project
|
|
86
|
+
const configPath = path.join(cwd, "ds01.config.json");
|
|
87
|
+
const configData = {
|
|
88
|
+
$schema: "https://ds-01.vercel.app/schema.json",
|
|
89
|
+
style: "default",
|
|
90
|
+
typescript: true,
|
|
91
|
+
cssPath: cssPath,
|
|
92
|
+
componentsPath: "components/ui" // This is where we will inject components later
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
fs.writeFileSync(configPath, JSON.stringify(configData, null, 2));
|
|
96
|
+
console.log(chalk.green(`✔ Created ds01.config.json`));
|
|
97
|
+
|
|
98
|
+
outro(chalk.green.bold("DS01 successfully initialized! Ready to add components."));
|
|
99
|
+
});
|
package/src/commands/login.ts
CHANGED
|
@@ -3,15 +3,10 @@ import ora from "ora";
|
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import open from "open";
|
|
5
5
|
import machineIdPkg from "node-machine-id";
|
|
6
|
-
|
|
7
|
-
import fs from "fs";
|
|
8
|
-
import path from "path";
|
|
9
|
-
import os from "os";
|
|
6
|
+
import { saveToken } from "../utils/config.js"; // <-- Imported our new wallet helper!
|
|
10
7
|
|
|
11
8
|
// ⚠️ In production, replace with your actual deployed Vercel domain
|
|
12
9
|
const API_BASE_URL = process.env.DS01_API_URL || "https://ds-01.vercel.app";
|
|
13
|
-
const CONFIG_DIR = path.join(os.homedir(), ".ds01");
|
|
14
|
-
const AUTH_FILE = path.join(CONFIG_DIR, "auth.json");
|
|
15
10
|
const { machineIdSync } = machineIdPkg;
|
|
16
11
|
|
|
17
12
|
export const login = new Command()
|
|
@@ -30,7 +25,7 @@ export const login = new Command()
|
|
|
30
25
|
headers: {
|
|
31
26
|
"Content-Type": "application/json",
|
|
32
27
|
},
|
|
33
|
-
body: JSON.stringify({ machineId: hardwareId }),
|
|
28
|
+
body: JSON.stringify({ machineId: hardwareId }),
|
|
34
29
|
});
|
|
35
30
|
|
|
36
31
|
if (!initResponse.ok) {
|
|
@@ -54,10 +49,9 @@ export const login = new Command()
|
|
|
54
49
|
|
|
55
50
|
// 4. Poll the Token Endpoint
|
|
56
51
|
let token = null;
|
|
57
|
-
const pollInterval = (interval ||
|
|
52
|
+
const pollInterval = (interval || 5) * 1000; // Updated to match our 5s secure interval
|
|
58
53
|
|
|
59
54
|
while (!token) {
|
|
60
|
-
// Wait before polling again
|
|
61
55
|
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
62
56
|
|
|
63
57
|
const tokenResponse = await fetch(
|
|
@@ -74,30 +68,15 @@ export const login = new Command()
|
|
|
74
68
|
if (tokenResponse.ok && tokenData.accessToken) {
|
|
75
69
|
token = tokenData.accessToken;
|
|
76
70
|
} else if (tokenData.error !== "authorization_pending") {
|
|
77
|
-
// If error is anything OTHER than pending, fail out.
|
|
78
71
|
spinner.fail(chalk.red("Authorization failed or expired."));
|
|
79
72
|
console.error(chalk.red(`Reason: ${tokenData.error}`));
|
|
80
73
|
process.exit(1);
|
|
81
74
|
}
|
|
82
75
|
}
|
|
83
76
|
|
|
84
|
-
// 5. Save Token
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
fs.writeFileSync(
|
|
90
|
-
AUTH_FILE,
|
|
91
|
-
JSON.stringify(
|
|
92
|
-
{
|
|
93
|
-
token,
|
|
94
|
-
machineId: hardwareId,
|
|
95
|
-
updatedAt: new Date().toISOString(),
|
|
96
|
-
},
|
|
97
|
-
null,
|
|
98
|
-
2,
|
|
99
|
-
),
|
|
100
|
-
);
|
|
77
|
+
// 5. Save Token securely using our new config utility
|
|
78
|
+
// We pass the token and hardwareId into the wallet.
|
|
79
|
+
saveToken(token, hardwareId);
|
|
101
80
|
|
|
102
81
|
spinner.succeed(chalk.green.bold("Terminal paired successfully!"));
|
|
103
82
|
console.log(
|
|
@@ -110,4 +89,4 @@ export const login = new Command()
|
|
|
110
89
|
console.error(chalk.red(error.message));
|
|
111
90
|
process.exit(1);
|
|
112
91
|
}
|
|
113
|
-
});
|
|
92
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
import { deleteToken, getToken } from "../utils/config.js";
|
|
5
|
+
|
|
6
|
+
export const logout = new Command()
|
|
7
|
+
.name("logout")
|
|
8
|
+
.description("Log out of your DS01 account and clear local credentials")
|
|
9
|
+
.action(async () => {
|
|
10
|
+
const token = getToken();
|
|
11
|
+
|
|
12
|
+
if (!token) {
|
|
13
|
+
console.log(chalk.yellow("You are already logged out."));
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const spinner = ora("Logging out...").start();
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
// Throw away the digital wallet / token
|
|
21
|
+
deleteToken();
|
|
22
|
+
|
|
23
|
+
spinner.succeed(chalk.green("Successfully logged out."));
|
|
24
|
+
console.log(chalk.gray("Your local credentials have been cleared."));
|
|
25
|
+
} catch (error) {
|
|
26
|
+
spinner.fail(chalk.red("Failed to log out cleanly."));
|
|
27
|
+
console.error(error);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { getToken } from "../utils/config.js";
|
|
4
|
+
|
|
5
|
+
export const whoami = new Command()
|
|
6
|
+
.name("whoami")
|
|
7
|
+
.description("Check your current authentication status")
|
|
8
|
+
.action(async () => {
|
|
9
|
+
const token = getToken();
|
|
10
|
+
|
|
11
|
+
if (!token) {
|
|
12
|
+
console.log(chalk.red("✖ You are not logged in."));
|
|
13
|
+
console.log(`Run ${chalk.cyan("npx @arpit2023/ds01 login")} to authenticate.`);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// For now, we just verify the token is saved.
|
|
18
|
+
console.log(chalk.green("✔ You are securely logged in!"));
|
|
19
|
+
console.log(chalk.gray(`Token found ending in: ...${token.slice(-6)}`));
|
|
20
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import { login } from "./commands/login.js";
|
|
5
|
-
|
|
5
|
+
import { whoami } from "./commands/whoami.js";
|
|
6
|
+
import { logout } from "./commands/logout.js";
|
|
7
|
+
import { init } from "./commands/init.js"; // <-- Imported the new init command!
|
|
6
8
|
|
|
7
9
|
const program = new Command();
|
|
8
10
|
|
|
@@ -11,8 +13,11 @@ program
|
|
|
11
13
|
.description("DS01 Custom Component Registry CLI")
|
|
12
14
|
.version("1.0.0");
|
|
13
15
|
|
|
14
|
-
// Register the
|
|
16
|
+
// Register the commands
|
|
15
17
|
program.addCommand(login);
|
|
18
|
+
program.addCommand(whoami);
|
|
19
|
+
program.addCommand(logout);
|
|
20
|
+
program.addCommand(init); // <-- Registered it here!
|
|
16
21
|
|
|
17
22
|
// Parse the arguments from the terminal
|
|
18
|
-
program.parse(process.argv);
|
|
23
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import os from "os";
|
|
4
|
+
|
|
5
|
+
// We use the same path you originally had for consistency
|
|
6
|
+
const configDir = path.join(os.homedir(), ".ds01");
|
|
7
|
+
const credentialsPath = path.join(configDir, "auth.json");
|
|
8
|
+
|
|
9
|
+
export function saveToken(token: string, machineId: string) {
|
|
10
|
+
if (!fs.existsSync(configDir)) {
|
|
11
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const payload = {
|
|
15
|
+
token,
|
|
16
|
+
machineId,
|
|
17
|
+
updatedAt: new Date().toISOString(),
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
fs.writeFileSync(credentialsPath, JSON.stringify(payload, null, 2), {
|
|
21
|
+
mode: 0o600,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getToken(): string | null {
|
|
26
|
+
if (fs.existsSync(credentialsPath)) {
|
|
27
|
+
try {
|
|
28
|
+
const data = fs.readFileSync(credentialsPath, "utf-8");
|
|
29
|
+
const parsed = JSON.parse(data);
|
|
30
|
+
return parsed.token || null;
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function deleteToken() {
|
|
39
|
+
if (fs.existsSync(credentialsPath)) {
|
|
40
|
+
fs.unlinkSync(credentialsPath);
|
|
41
|
+
}
|
|
42
|
+
}
|