@stratify/cli 0.2.3 → 0.2.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/README.md CHANGED
@@ -12,8 +12,16 @@ npm install -g @stratify/cli
12
12
 
13
13
  ## Usage
14
14
 
15
+ With `npx` (no global install required):
16
+
17
+ ```bash
18
+ npx @stratify/cli new <app-name>
19
+ ```
20
+
21
+ Or, after a global install:
22
+
15
23
  ```bash
16
- npx stratify-cli new <app-name>
24
+ stratify-cli new <app-name>
17
25
  ```
18
26
 
19
27
  Creates a new project with:
package/dist/bin.js ADDED
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ import { resolve } from "node:path";
3
+ import { runNew } from "./new.js";
4
+ function sanitizeAppName(value) {
5
+ if (!value) {
6
+ return undefined;
7
+ }
8
+ const trimmed = value.trim();
9
+ if (!trimmed || trimmed.includes("..") || /[\\/]/.test(trimmed)) {
10
+ return undefined;
11
+ }
12
+ return /^[a-z0-9][a-z0-9-_]*$/i.test(trimmed) ? trimmed : undefined;
13
+ }
14
+ async function main() {
15
+ const [cmd, rawName] = process.argv.slice(2).map((arg) => arg?.trim());
16
+ if (!cmd || cmd !== "new") {
17
+ console.error(`Usage:
18
+ npx @stratify/cli new <app-name>
19
+
20
+ Commands:
21
+ new Scaffold a new Stratify app
22
+ `);
23
+ process.exit(1);
24
+ }
25
+ const name = sanitizeAppName(rawName);
26
+ if (!name) {
27
+ console.error("Please provide a valid app name (letters, numbers, dashes or underscores), e.g. npx @stratify/cli new my-app");
28
+ process.exit(1);
29
+ }
30
+ const templateDir = resolve(new URL("../template", import.meta.url).pathname);
31
+ await runNew({ appName: name, templateDir });
32
+ }
33
+ main().catch((err) => {
34
+ console.error(err?.stack || String(err));
35
+ process.exit(1);
36
+ });
package/dist/new.js ADDED
@@ -0,0 +1,59 @@
1
+ import { promises as fs } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ async function pathExists(p) {
4
+ try {
5
+ await fs.access(p);
6
+ return true;
7
+ }
8
+ catch {
9
+ return false;
10
+ }
11
+ }
12
+ async function copyDir(src, dest) {
13
+ await fs.mkdir(dest, { recursive: true });
14
+ const entries = await fs.readdir(src, { withFileTypes: true });
15
+ for (const e of entries) {
16
+ const s = join(src, e.name);
17
+ const d = join(dest, e.name);
18
+ if (e.isDirectory()) {
19
+ await copyDir(s, d);
20
+ }
21
+ else if (e.isFile()) {
22
+ await fs.copyFile(s, d);
23
+ }
24
+ }
25
+ }
26
+ async function replaceFileContent(p, replacer) {
27
+ const s = await fs.readFile(p, "utf8");
28
+ await fs.writeFile(p, replacer(s), "utf8");
29
+ }
30
+ async function finalizeGitignore(target) {
31
+ const src = join(target, "_gitignore");
32
+ const dest = join(target, ".gitignore");
33
+ try {
34
+ await fs.rename(src, dest);
35
+ }
36
+ catch {
37
+ // no _gitignore — ignore silently
38
+ }
39
+ }
40
+ export async function runNew({ appName, templateDir }) {
41
+ const target = resolve(process.cwd(), appName);
42
+ if (await pathExists(target)) {
43
+ throw new Error(`Directory "${appName}" already exists.`);
44
+ }
45
+ await copyDir(templateDir, target);
46
+ const pkgPath = join(target, "package.json");
47
+ await replaceFileContent(pkgPath, (s) => s.replace(/"name":\s*".+?"/, `"name": "${appName}"`));
48
+ await finalizeGitignore(target);
49
+ console.log(`\nScaffolded "${appName}" successfully.
50
+
51
+ Next steps:
52
+
53
+ cd ${appName}
54
+ npm install
55
+ npm run dev
56
+
57
+ Happy hacking with Stratify!
58
+ `);
59
+ }
@@ -0,0 +1,54 @@
1
+ import { test, describe } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { promises as fs } from "node:fs";
4
+ import { join, resolve } from "node:path";
5
+ import { tmpdir } from "node:os";
6
+ import { mkdtemp, rm } from "node:fs/promises";
7
+ import { runNew } from "../new";
8
+ const TEMPLATE_FIXTURE = resolve("template");
9
+ async function pathExists(p) {
10
+ try {
11
+ await fs.access(p);
12
+ return true;
13
+ }
14
+ catch {
15
+ return false;
16
+ }
17
+ }
18
+ describe("runNew()", () => {
19
+ test("copies template and replaces package name", async () => {
20
+ const tmpRoot = await mkdtemp(join(tmpdir(), "stratify-cli-"));
21
+ const appName = "my-app";
22
+ const target = join(tmpRoot, appName);
23
+ const prevCwd = process.cwd();
24
+ process.chdir(tmpRoot);
25
+ try {
26
+ await runNew({ appName, templateDir: TEMPLATE_FIXTURE });
27
+ const pkgPath = join(target, "package.json");
28
+ assert.ok(await pathExists(pkgPath), "package.json should exist");
29
+ const pkg = JSON.parse(await fs.readFile(pkgPath, "utf8"));
30
+ assert.equal(pkg.name, appName);
31
+ const srcDir = join(target, "src");
32
+ assert.ok(await pathExists(srcDir), "src/ should exist");
33
+ }
34
+ finally {
35
+ process.chdir(prevCwd);
36
+ await rm(tmpRoot, { recursive: true, force: true });
37
+ }
38
+ });
39
+ test("throws if target directory already exists", async () => {
40
+ const tmpRoot = await mkdtemp(join(tmpdir(), "stratify-cli-"));
41
+ const appName = "existing-app";
42
+ const target = join(tmpRoot, appName);
43
+ await fs.mkdir(target, { recursive: true });
44
+ const prevCwd = process.cwd();
45
+ process.chdir(tmpRoot);
46
+ try {
47
+ await assert.rejects(() => runNew({ appName, templateDir: TEMPLATE_FIXTURE }), /already exists/);
48
+ }
49
+ finally {
50
+ process.chdir(prevCwd);
51
+ await rm(tmpRoot, { recursive: true, force: true });
52
+ }
53
+ });
54
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stratify/cli",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "stratify-cli": "./dist/bin.js"