create-skybridge 0.0.0-dev.871732d → 0.0.0-dev.8b9ef00

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/index.d.ts CHANGED
@@ -1 +1 @@
1
- export {};
1
+ export declare function init(args?: string[]): Promise<void>;
package/dist/index.js CHANGED
@@ -3,11 +3,6 @@ import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import * as prompts from "@clack/prompts";
5
5
  import mri from "mri";
6
- const argv = mri(process.argv.slice(2), {
7
- boolean: ["help", "overwrite"],
8
- alias: { h: "help" },
9
- });
10
- const cwd = process.cwd();
11
6
  const defaultProjectName = "skybridge-project";
12
7
  // prettier-ignore
13
8
  const helpMessage = `\
@@ -23,9 +18,13 @@ Examples:
23
18
  create-skybridge my-app
24
19
  create-skybridge . --overwrite
25
20
  `;
26
- async function init() {
21
+ export async function init(args = process.argv.slice(2)) {
22
+ const argv = mri(args, {
23
+ boolean: ["help", "overwrite"],
24
+ alias: { h: "help" },
25
+ });
27
26
  const argTargetDir = argv._[0]
28
- ? formatTargetDir(String(argv._[0]))
27
+ ? sanitizeTargetDir(String(argv._[0]))
29
28
  : undefined;
30
29
  const argOverwrite = argv.overwrite;
31
30
  const help = argv.help;
@@ -44,14 +43,14 @@ async function init() {
44
43
  defaultValue: defaultProjectName,
45
44
  placeholder: defaultProjectName,
46
45
  validate: (value) => {
47
- return value.length === 0 || formatTargetDir(value).length > 0
46
+ return value.length === 0 || sanitizeTargetDir(value).length > 0
48
47
  ? undefined
49
48
  : "Invalid project name";
50
49
  },
51
50
  });
52
51
  if (prompts.isCancel(projectName))
53
52
  return cancel();
54
- targetDir = formatTargetDir(projectName);
53
+ targetDir = sanitizeTargetDir(projectName);
55
54
  }
56
55
  else {
57
56
  targetDir = defaultProjectName;
@@ -95,7 +94,7 @@ async function init() {
95
94
  return;
96
95
  }
97
96
  }
98
- const root = path.join(cwd, targetDir);
97
+ const root = path.join(process.cwd(), targetDir);
99
98
  // 3. Copy the repository
100
99
  prompts.log.step(`Copying template...`);
101
100
  try {
@@ -121,8 +120,17 @@ async function init() {
121
120
  process.exit(1);
122
121
  }
123
122
  }
124
- function formatTargetDir(targetDir) {
125
- return targetDir.trim().replace(/\/+$/g, "");
123
+ function sanitizeTargetDir(targetDir) {
124
+ return (targetDir
125
+ .trim()
126
+ // Only keep alphanumeric, dash, underscore, dot, @, /
127
+ .replace(/[^a-zA-Z0-9\-_.@/]/g, "")
128
+ // Prevent path traversal
129
+ .replace(/\.\./g, "")
130
+ // Collapse multiple slashes
131
+ .replace(/\/+/g, "/")
132
+ // Remove leading/trailing slashes
133
+ .replace(/^\/+|\/+$/g, ""));
126
134
  }
127
135
  function isEmpty(path) {
128
136
  const files = fs.readdirSync(path);
@@ -139,7 +147,3 @@ function emptyDir(dir) {
139
147
  fs.rmSync(path.resolve(dir, file), { recursive: true, force: true });
140
148
  }
141
149
  }
142
- init().catch((e) => {
143
- console.error(e);
144
- process.exit(1);
145
- });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,22 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { afterEach, beforeEach, describe, it } from "vitest";
5
+ import { init } from "./index.js";
6
+ describe("create-skybridge", () => {
7
+ let tempDirName;
8
+ beforeEach(() => {
9
+ tempDirName = `test-${randomBytes(2).toString("hex")}`;
10
+ });
11
+ afterEach(async () => {
12
+ await fs.rm(path.join(process.cwd(), tempDirName), {
13
+ recursive: true,
14
+ force: true,
15
+ });
16
+ });
17
+ it("should scaffold a new project", async () => {
18
+ const name = `../../${tempDirName}//project$`;
19
+ await init([name]);
20
+ await fs.access(path.join(process.cwd(), tempDirName, "project", ".gitignore"));
21
+ });
22
+ });
package/index.js CHANGED
@@ -1,3 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import "./dist/index.js";
3
+ import { init } from "./dist/index.js";
4
+
5
+ init().catch((e) => {
6
+ console.error(e);
7
+ process.exit(1);
8
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-skybridge",
3
- "version": "0.0.0-dev.871732d",
3
+ "version": "0.0.0-dev.8b9ef00",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "author": "Alpic",
@@ -18,6 +18,8 @@
18
18
  ],
19
19
  "scripts": {
20
20
  "build": "tsc",
21
+ "test": "pnpm run test:unit && pnpm run test:type && pnpm run test:format",
22
+ "test:unit": "vitest run",
21
23
  "test:type": "tsc --noEmit",
22
24
  "test:format": "biome ci",
23
25
  "prepublishOnly": "pnpm run build"
@@ -28,6 +30,7 @@
28
30
  },
29
31
  "devDependencies": {
30
32
  "@types/node": "^25.0.3",
31
- "typescript": "^5.9.3"
33
+ "typescript": "^5.9.3",
34
+ "vitest": "^2.1.9"
32
35
  }
33
36
  }