Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Feat(CLI): Allow use of Astro template #203

Merged
merged 18 commits into from
Dec 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 13 additions & 10 deletions libs/create-qwikdev-astro/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,17 @@

| Name | Description |
| :--------------------------------------| :----------------------------------------|
| `--help` (`-h`) | Display available flags. |
| `--dry-run` | Walk through steps without executing. |
| `--force` / `--no-force` (`-f` / `--no-f`) | Overwrite target directory if it exists. |
| `--add` / `--no-add` (`-a` / `--no-a`) | Add QwikDev/astro to existing project. |
| `--install` / `--no-install` (`-i` / `--no-i`) | Install dependencies. |
| `--help` (`-h`) | Display available flags. |
| `--template` (`-t`) | Start from an Astro template. |
| `--biome` / `--no-biome` | Prefer Biome to ESLint/Prettier. |
| `--install` / `--no-install` (`-i` / `--no-i`) | Install dependencies. |
| `--git` / `--no-git` | Initialize Git repository. |
| `--ci` / `--no-ci` | Add CI workflow. |
| `--yes` (`-y`) | Skip all prompts by accepting defaults. |
| `--no` (`-n`) | Skip all prompts by declining defaults. |
| `--add` / `--no-add` (`-a` / `--no-a`) | Add QwikDev/astro to existing project. |
| `--force` / `--no-force` (`-f` / `--no-f`) | Overwrite target directory if it exists. |
| `--dry-run` | Walk through steps without executing. |

### 📦 API

Expand All @@ -78,14 +79,15 @@
export type Definition = {
destination: string;
adapter?: "deno" | "node" | "none";
force?: boolean;
add?: boolean;
install?: boolean;
template?: string;
biome?: boolean;
install?: boolean;
git?: boolean;
ci?: boolean;
yes?: boolean;
no?: boolean;
add?: boolean;
force?: boolean;
dryRun?: boolean;
};
```
Expand All @@ -96,14 +98,15 @@
export const defaultDefinition = {
destination: "./qwik-astro-app",
adapter: "none",
force: undefined,
add: undefined,
template: "",
install: undefined,
biome: undefined,
git: undefined,
ci: undefined,
yes: undefined,
no: undefined,
add: undefined,
force: undefined,
dryRun: undefined
} as const;
```
Expand Down
146 changes: 122 additions & 24 deletions libs/create-qwikdev-astro/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import fs, { cpSync } from "node:fs";
import path from "node:path";
import { copySync, ensureDirSync } from "fs-extra/esm";
import { copySync, ensureDirSync, pathExistsSync } from "fs-extra/esm";
import pkg from "../package.json";
import { ensureString } from "./console";
import { type Definition as BaseDefinition, Program } from "./core";
import { $, $pmInstall, $pmX } from "./process";
import { $, $pmCreate, $pmInstall, $pmX } from "./process";
import {
__dirname,
clearDir,
deepMergeJsonFile,
getPackageJson,
getPackageManager,
notEmptyDir,
Expand All @@ -22,12 +23,13 @@ import {
export type Definition = BaseDefinition & {
destination: string;
adapter: Adapter;
force?: boolean;
template?: string;
install?: boolean;
biome?: boolean;
git?: boolean;
ci?: boolean;
add?: boolean;
force?: boolean;
dryRun?: boolean;
};

Expand All @@ -37,15 +39,16 @@ export type UserDefinition = Partial<Definition>;
export const defaultDefinition = {
destination: "./qwik-astro-app",
adapter: "none",
force: undefined,
install: undefined,
template: undefined,
biome: undefined,
install: undefined,
git: undefined,
ci: undefined,
yes: undefined,
no: undefined,
dryRun: undefined,
add: undefined
add: undefined,
force: undefined,
dryRun: undefined
} as const;

export type Adapter = "node" | "deno" | "none";
Expand Down Expand Up @@ -84,6 +87,12 @@ export class Application extends Program<Definition, Input> {
desc: "Server adapter",
choices: ["deno", "node", "none"]
})
.argument("template", {
alias: "t",
type: "string",
default: defaultDefinition.template,
desc: "Start from an Astro template"
})
.option("add", {
alias: "a",
type: "boolean",
Expand Down Expand Up @@ -148,6 +157,7 @@ export class Application extends Program<Definition, Input> {
return {
destination: definition.destination,
adapter: definition.adapter,
template: definition.template ?? "",
force:
definition.force ?? (definition.add ? false : !!definition.yes && !definition.no),
add: !!definition.add,
Expand All @@ -166,7 +176,7 @@ export class Application extends Program<Definition, Input> {
definition.destination === defaultDefinition.destination
? await this.scanString(
`Where would you like to create your new project? ${this.gray(
`(Use '.' or 'qwik-astro-app' for current directory)`
`(Use '.' for current directory)`
)}`,
definition.destination
)
Expand All @@ -176,14 +186,13 @@ export class Application extends Program<Definition, Input> {
const outDir = resolveAbsoluteDir(destination.trim());
const exists = notEmptyDir(outDir);

const add =
definition.add === undefined
? exists &&
!!(await this.scanBoolean(
definition,
"Do you want to add @QwikDev/astro to your existing project?"
))
: definition.add;
const add = !!(definition.add === undefined && !definition.force
? exists &&
(await this.scanBoolean(
definition,
"Do you want to add @QwikDev/astro to your existing project?"
))
: definition.add);

const force =
definition.force === undefined
Expand All @@ -196,13 +205,24 @@ export class Application extends Program<Definition, Input> {
)}" already exists and is not empty. What would you like to overwrite it?`,
false
))
: false;
: definition.force;

const template: string =
definition.template === undefined &&
(await this.scanBoolean(definition, "Would you like to use a template?"))
? await this.scanString("What template would you like to use?", "")
: (definition.template ?? "");

const ask = !exists || add || force;

let adapter: Adapter;

if (ask && (!add || force) && definition.adapter === defaultDefinition.adapter) {
if (
!template &&
ask &&
(!add || force) &&
definition.adapter === defaultDefinition.adapter
) {
const adapterInput =
((await this.scanBoolean(
definition,
Expand Down Expand Up @@ -270,6 +290,7 @@ export class Application extends Program<Definition, Input> {
return {
destination,
adapter,
template,
biome,
ci,
install,
Expand Down Expand Up @@ -305,7 +326,7 @@ export class Application extends Program<Definition, Input> {
}
}

async runCreate(input: Input) {
async prepareDir(input: Input) {
const outDir = input.outDir;

if (notEmptyDir(outDir)) {
Expand All @@ -323,6 +344,12 @@ export class Application extends Program<Definition, Input> {
process.exit(1);
}
}
}

async runCreate(input: Input) {
const outDir = input.outDir;

await this.prepareDir(input);

let starterKit = input.adapter;

Expand All @@ -337,12 +364,83 @@ export class Application extends Program<Definition, Input> {
this.copyGitignore(input);
}

async runTemplate(input: Input) {
const args = [
"astro",
input.destination,
"--",
"--skip-houston",
"--template",
input.template,
"--add",
"@qwikdev/astro",
input.install ? "--install" : "--no-install",
input.git ? "--git" : "--no-git"
];

if (input.dryRun) {
args.push("--dry-run");
}

await this.prepareDir(input);
await $pmCreate(args.join(" "), process.cwd());

const outDir = input.outDir;
const stubPath = path.join(
__dirname,
"..",
"stubs",
"templates",
`none${input.biome ? "-biome" : ""}`
);

const configFiles = input.biome
? ["biome.json"]
: [".eslintignore", ".eslintrc.cjs", ".prettierignore", "prettier.config.cjs"];

const vscodeDir = path.join(stubPath, ".vscode");
const vscodeFiles = ["extensions.json", "launch.json"];

const projectPackageJsonFile = path.join(outDir, "package.json");
const projectTsconfigJsonFile = path.join(outDir, "tsconfig.json");
const templatePackageJsonFile = path.join(stubPath, "package.json");
const templateTsconfigJsonFile = path.join(stubPath, "tsconfig.json");

this.step(`Copying template files into ${this.bgBlue(` ${outDir} `)} ... 🐇`);

for (const vscodeFile of vscodeFiles) {
const vscodeFilePath = path.join(vscodeDir, vscodeFile);
const projectVscodeFilePath = path.join(outDir, ".vscode", vscodeFile);

pathExistsSync(projectVscodeFilePath)
? deepMergeJsonFile(projectVscodeFilePath, vscodeFilePath, true)
: cpSync(vscodeFilePath, projectVscodeFilePath, {
force: true
});
}

for (const configFile of configFiles) {
cpSync(path.join(stubPath, configFile), path.join(outDir, configFile), {
force: true
});
}

deepMergeJsonFile(projectPackageJsonFile, templatePackageJsonFile, true);
deepMergeJsonFile(projectTsconfigJsonFile, templateTsconfigJsonFile, true);

return input.install;
}

async start(input: Input): Promise<boolean> {
this.intro(`Let's create a ${this.bgBlue(" QwikDev/astro App ")} ✨`);
this.intro(
`Let's create a ${this.bgBlue(" QwikDev")}${this.bgMagenta("Astro")} App ✨`
);

let ranInstall: boolean;

if (input.add) {
if (input.template) {
ranInstall = await this.runTemplate(input);
} else if (input.add) {
ranInstall = await this.runInstall(input);
await this.runAdd(input);
} else {
Expand Down Expand Up @@ -445,10 +543,10 @@ export class Application extends Program<Definition, Input> {
try {
if (!input.dryRun) {
const res = [];
res.push(await $("git", ["init"], outDir).install);
res.push(await $("git", ["add", "-A"], outDir).install);
res.push(await $("git", ["init"], outDir).process);
res.push(await $("git", ["add", "-A"], outDir).process);
res.push(
await $("git", ["commit", "-m", "Initial commit 🎉"], outDir).install
await $("git", ["commit", "-m", "Initial commit 🎉"], outDir).process
);

if (res.some((r) => r === false)) {
Expand Down
18 changes: 15 additions & 3 deletions libs/create-qwikdev-astro/src/process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const isPackageManagerInstalled = (packageManager: string) => {
export function $(cmd: string, args: string[], cwd: string) {
let child: ChildProcess;

const install = new Promise<boolean>((resolve) => {
const process = new Promise<boolean>((resolve) => {
try {
child = spawn(cmd, args, {
cwd,
Expand Down Expand Up @@ -42,7 +42,7 @@ export function $(cmd: string, args: string[], cwd: string) {
}
};

return { abort, install };
return { abort, process };
}

export const $pm = async (
Expand All @@ -52,7 +52,15 @@ export const $pm = async (
) => {
const packageManager = getPackageManager();
args = Array.isArray(args) ? args : [args];
if (["exec", "dlx"].includes(args[0])) {

if (args[0] === "create" && packageManager === "deno") {
const packageName = args[1];
const parts = packageName.split("/", 2);
const createCommand = parts[1]
? `npm:${parts[0]}/create-${parts[1]}`
: `npm:create-${parts[0]}`;
args = ["run", "-A", createCommand, ...args.slice(2)];
} else if (["exec", "dlx"].includes(args[0])) {
switch (packageManager) {
case "pnpm":
case "yarn":
Expand Down Expand Up @@ -97,6 +105,10 @@ export const $pmRun = async (script: string, cwd: string) => {
await $pm(["run", ...script.split(/\s+/)], cwd);
};

export const $pmCreate = async (script: string, cwd: string) => {
await $pm(["create", ...script.split(/\s+/)], cwd);
};

export const $pmExec = async (command: string, cwd: string) => {
await $pm(["exec", ...command.split(/\s+/)], cwd);
};
Expand Down
Loading
Loading