create-skybridge 0.0.0-dev.9c78db9 → 0.0.0-dev.ac1e392

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
@@ -1,12 +1,8 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
3
4
  import * as prompts from "@clack/prompts";
4
5
  import mri from "mri";
5
- const argv = mri(process.argv.slice(2), {
6
- boolean: ["help", "overwrite"],
7
- alias: { h: "help" },
8
- });
9
- const cwd = process.cwd();
10
6
  const defaultProjectName = "skybridge-project";
11
7
  // prettier-ignore
12
8
  const helpMessage = `\
@@ -22,9 +18,13 @@ Examples:
22
18
  create-skybridge my-app
23
19
  create-skybridge . --overwrite
24
20
  `;
25
- 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
+ });
26
26
  const argTargetDir = argv._[0]
27
- ? formatTargetDir(String(argv._[0]))
27
+ ? sanitizeTargetDir(String(argv._[0]))
28
28
  : undefined;
29
29
  const argOverwrite = argv.overwrite;
30
30
  const help = argv.help;
@@ -43,14 +43,14 @@ async function init() {
43
43
  defaultValue: defaultProjectName,
44
44
  placeholder: defaultProjectName,
45
45
  validate: (value) => {
46
- return value.length === 0 || formatTargetDir(value).length > 0
46
+ return value.length === 0 || sanitizeTargetDir(value).length > 0
47
47
  ? undefined
48
48
  : "Invalid project name";
49
49
  },
50
50
  });
51
51
  if (prompts.isCancel(projectName))
52
52
  return cancel();
53
- targetDir = formatTargetDir(projectName);
53
+ targetDir = sanitizeTargetDir(projectName);
54
54
  }
55
55
  else {
56
56
  targetDir = defaultProjectName;
@@ -94,20 +94,23 @@ async function init() {
94
94
  return;
95
95
  }
96
96
  }
97
- const root = path.join(cwd, targetDir);
97
+ const root = path.join(process.cwd(), targetDir);
98
98
  // 3. Copy the repository
99
99
  prompts.log.step(`Copying template...`);
100
100
  try {
101
- const templateDir = new URL("../template", import.meta.url).pathname;
101
+ const templateDir = fileURLToPath(new URL("../template", import.meta.url));
102
102
  // Copy template to target directory
103
103
  fs.cpSync(templateDir, root, { recursive: true });
104
104
  // Rename _gitignore to .gitignore
105
105
  fs.renameSync(path.join(root, "_gitignore"), path.join(root, ".gitignore"));
106
106
  // Update project name in package.json
107
- const pkgPath = path.join(root, "package.json");
108
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
109
- pkg.name = path.basename(root);
110
- fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
107
+ const name = path.basename(root);
108
+ for (const dir of ["", "server", "web"]) {
109
+ const pkgPath = path.join(root, dir, "package.json");
110
+ const pkg = fs.readFileSync(pkgPath, "utf-8");
111
+ const fixed = pkg.replace(/apps-sdk-template/g, name);
112
+ fs.writeFileSync(pkgPath, fixed);
113
+ }
111
114
  prompts.log.success(`Project created in ${root}`);
112
115
  prompts.outro(`Done! Next steps:\n\n cd ${targetDir}\n pnpm install\n pnpm dev`);
113
116
  }
@@ -117,8 +120,17 @@ async function init() {
117
120
  process.exit(1);
118
121
  }
119
122
  }
120
- function formatTargetDir(targetDir) {
121
- 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, ""));
122
134
  }
123
135
  function isEmpty(path) {
124
136
  const files = fs.readdirSync(path);
@@ -135,7 +147,3 @@ function emptyDir(dir) {
135
147
  fs.rmSync(path.resolve(dir, file), { recursive: true, force: true });
136
148
  }
137
149
  }
138
- init().catch((e) => {
139
- console.error(e);
140
- process.exit(1);
141
- });
@@ -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.9c78db9",
3
+ "version": "0.0.0-dev.ac1e392",
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
  }