create-gtkx 0.21.0
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/LICENSE +373 -0
- package/bin/create-gtkx.js +5 -0
- package/dist/cli.d.ts +24 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +19 -0
- package/dist/cli.js.map +1 -0
- package/dist/command.d.ts +65 -0
- package/dist/command.d.ts.map +1 -0
- package/dist/command.js +69 -0
- package/dist/command.js.map +1 -0
- package/dist/create.d.ts +3 -0
- package/dist/create.d.ts.map +1 -0
- package/dist/create.js +7 -0
- package/dist/create.js.map +1 -0
- package/dist/deps.d.ts +3 -0
- package/dist/deps.d.ts.map +1 -0
- package/dist/deps.js +51 -0
- package/dist/deps.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/options.d.ts +25 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +13 -0
- package/dist/options.js.map +1 -0
- package/dist/scaffolder.d.ts +51 -0
- package/dist/scaffolder.d.ts.map +1 -0
- package/dist/scaffolder.js +191 -0
- package/dist/scaffolder.js.map +1 -0
- package/dist/templates.d.ts +11 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/templates.js +25 -0
- package/dist/templates.js.map +1 -0
- package/package.json +65 -0
- package/src/cli.ts +21 -0
- package/src/command.ts +87 -0
- package/src/create.ts +7 -0
- package/src/deps.ts +51 -0
- package/src/index.ts +4 -0
- package/src/options.ts +24 -0
- package/src/scaffolder.ts +306 -0
- package/src/templates.ts +42 -0
- package/templates/claude/EXAMPLES.md.ejs +790 -0
- package/templates/claude/SKILL.md.ejs +126 -0
- package/templates/claude/WIDGETS.md.ejs +934 -0
- package/templates/config/vitest.config.ts.ejs +11 -0
- package/templates/gitignore.ejs +4 -0
- package/templates/gtkx.config.ts.ejs +6 -0
- package/templates/package.json.ejs +16 -0
- package/templates/src/app.tsx.ejs +48 -0
- package/templates/src/gtkx-env.d.ts.ejs +2 -0
- package/templates/src/index.tsx.ejs +4 -0
- package/templates/tests/app.test.tsx.ejs +16 -0
- package/templates/tsconfig.json.ejs +14 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACnG,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAC/G,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAA0B,SAAS,EAAE,MAAM,cAAc,CAAC;AACnG,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAA2C,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type TestingOption = "vitest" | "none";
|
|
2
|
+
export declare const PACKAGE_MANAGERS: readonly [{
|
|
3
|
+
readonly value: "pnpm";
|
|
4
|
+
readonly label: "pnpm";
|
|
5
|
+
readonly runDev: "pnpm dev";
|
|
6
|
+
readonly recommended: true;
|
|
7
|
+
}, {
|
|
8
|
+
readonly value: "npm";
|
|
9
|
+
readonly label: "npm";
|
|
10
|
+
readonly runDev: "npm run dev";
|
|
11
|
+
readonly recommended: false;
|
|
12
|
+
}, {
|
|
13
|
+
readonly value: "yarn";
|
|
14
|
+
readonly label: "yarn";
|
|
15
|
+
readonly runDev: "yarn dev";
|
|
16
|
+
readonly recommended: false;
|
|
17
|
+
}];
|
|
18
|
+
export type PackageManager = (typeof PACKAGE_MANAGERS)[number]["value"];
|
|
19
|
+
export declare const PACKAGE_MANAGER_VALUES: PackageManager[];
|
|
20
|
+
export declare const isKnownPackageManager: (name: string) => name is PackageManager;
|
|
21
|
+
export declare const isTestingOption: (value: string) => value is TestingOption;
|
|
22
|
+
export declare const PACKAGE_MANAGER_FLAG_DESCRIPTION: string;
|
|
23
|
+
export declare const TESTING_FLAG_DESCRIPTION: string;
|
|
24
|
+
export declare const isValidProjectName: (name: string) => boolean;
|
|
25
|
+
//# sourceMappingURL=options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,MAAM,CAAC;AAI9C,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;EAInB,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC;AAExE,eAAO,MAAM,sBAAsB,EAAE,cAAc,EAAqD,CAAC;AAEzG,eAAO,MAAM,qBAAqB,GAAI,MAAM,MAAM,KAAG,IAAI,IAAI,cACN,CAAC;AAExD,eAAO,MAAM,eAAe,GAAI,OAAO,MAAM,KAAG,KAAK,IAAI,aAA8D,CAAC;AAExH,eAAO,MAAM,gCAAgC,QAA2D,CAAC;AAEzG,eAAO,MAAM,wBAAwB,QAAkD,CAAC;AAExF,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,KAAG,OAAoC,CAAC"}
|
package/dist/options.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const TESTING_OPTIONS = ["vitest", "none"];
|
|
2
|
+
export const PACKAGE_MANAGERS = [
|
|
3
|
+
{ value: "pnpm", label: "pnpm", runDev: "pnpm dev", recommended: true },
|
|
4
|
+
{ value: "npm", label: "npm", runDev: "npm run dev", recommended: false },
|
|
5
|
+
{ value: "yarn", label: "yarn", runDev: "yarn dev", recommended: false },
|
|
6
|
+
];
|
|
7
|
+
export const PACKAGE_MANAGER_VALUES = PACKAGE_MANAGERS.map((manager) => manager.value);
|
|
8
|
+
export const isKnownPackageManager = (name) => PACKAGE_MANAGER_VALUES.includes(name);
|
|
9
|
+
export const isTestingOption = (value) => TESTING_OPTIONS.includes(value);
|
|
10
|
+
export const PACKAGE_MANAGER_FLAG_DESCRIPTION = `Package manager (${PACKAGE_MANAGER_VALUES.join(", ")})`;
|
|
11
|
+
export const TESTING_FLAG_DESCRIPTION = `Testing setup (${TESTING_OPTIONS.join(", ")})`;
|
|
12
|
+
export const isValidProjectName = (name) => /^[a-z0-9-]+$/.test(name);
|
|
13
|
+
//# sourceMappingURL=options.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.js","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAEA,MAAM,eAAe,GAAoB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;AAE5D,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC5B,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,IAAI,EAAE;IACvE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,WAAW,EAAE,KAAK,EAAE;IACzE,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,KAAK,EAAE;CAClE,CAAC;AAIX,MAAM,CAAC,MAAM,sBAAsB,GAAqB,gBAAgB,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAEzG,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,IAAY,EAA0B,EAAE,CACzE,sBAAmC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AAExD,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,KAAa,EAA0B,EAAE,CAAE,eAA4B,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAExH,MAAM,CAAC,MAAM,gCAAgC,GAAG,oBAAoB,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;AAEzG,MAAM,CAAC,MAAM,wBAAwB,GAAG,kBAAkB,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;AAExF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,IAAY,EAAW,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type PackageManager, type TestingOption } from "./options.js";
|
|
2
|
+
import { type TemplateContext } from "./templates.js";
|
|
3
|
+
export type CreateOptions = {
|
|
4
|
+
name?: string | undefined;
|
|
5
|
+
applicationId?: string | undefined;
|
|
6
|
+
packageManager?: PackageManager | undefined;
|
|
7
|
+
testing?: TestingOption | undefined;
|
|
8
|
+
claudeSkills?: boolean | undefined;
|
|
9
|
+
};
|
|
10
|
+
type ScaffolderPrompts = Pick<typeof import("@clack/prompts"), "intro" | "note" | "cancel" | "text" | "select" | "confirm" | "isCancel"> & {
|
|
11
|
+
spinner(): {
|
|
12
|
+
start(message: string): void;
|
|
13
|
+
stop(message: string): void;
|
|
14
|
+
};
|
|
15
|
+
log: {
|
|
16
|
+
info(message: string): void;
|
|
17
|
+
error(message: string): void;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
type ScaffolderFs = {
|
|
21
|
+
existsSync(path: string): boolean;
|
|
22
|
+
mkdirSync(path: string, opts: {
|
|
23
|
+
recursive: boolean;
|
|
24
|
+
}): void;
|
|
25
|
+
writeFileSync(path: string, content: string): void;
|
|
26
|
+
};
|
|
27
|
+
type InstallDependenciesFn = (opts: {
|
|
28
|
+
cwd: string;
|
|
29
|
+
packageManager: PackageManager;
|
|
30
|
+
dependencies: string[];
|
|
31
|
+
dev: boolean;
|
|
32
|
+
}) => Promise<void>;
|
|
33
|
+
type GitInitFn = (cwd: string) => Promise<void>;
|
|
34
|
+
export type ScaffolderDeps = {
|
|
35
|
+
cwd(): string;
|
|
36
|
+
gtkxVersion: string;
|
|
37
|
+
fs: ScaffolderFs;
|
|
38
|
+
prompts: ScaffolderPrompts;
|
|
39
|
+
listTemplates(): string[];
|
|
40
|
+
render(template: string, context: TemplateContext): string;
|
|
41
|
+
install: InstallDependenciesFn;
|
|
42
|
+
gitInit: GitInitFn;
|
|
43
|
+
detectPackageManager(cwd: string): Promise<PackageManager | undefined>;
|
|
44
|
+
exit(code: number): never;
|
|
45
|
+
};
|
|
46
|
+
export type Scaffolder = {
|
|
47
|
+
run(options?: CreateOptions): Promise<void>;
|
|
48
|
+
};
|
|
49
|
+
export declare const createScaffolder: (deps: ScaffolderDeps) => Scaffolder;
|
|
50
|
+
export {};
|
|
51
|
+
//# sourceMappingURL=scaffolder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaffolder.d.ts","sourceRoot":"","sources":["../src/scaffolder.ts"],"names":[],"mappings":"AAEA,OAAO,EAAwC,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7G,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEvE,MAAM,MAAM,aAAa,GAAG;IACxB,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,cAAc,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;IAC5C,OAAO,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IACpC,YAAY,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACtC,CAAC;AAUF,KAAK,iBAAiB,GAAG,IAAI,CACzB,cAAc,gBAAgB,CAAC,EAC/B,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,CAC3E,GAAG;IACA,OAAO,IAAI;QAAE,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IACzE,GAAG,EAAE;QAAE,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;CACtE,CAAC;AAEF,KAAK,YAAY,GAAG;IAChB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5D,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACtD,CAAC;AAEF,KAAK,qBAAqB,GAAG,CAAC,IAAI,EAAE;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,cAAc,EAAE,cAAc,CAAC;IAC/B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,GAAG,EAAE,OAAO,CAAC;CAChB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEpB,KAAK,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEhD,MAAM,MAAM,cAAc,GAAG;IACzB,GAAG,IAAI,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,EAAE,EAAE,YAAY,CAAC;IACjB,OAAO,EAAE,iBAAiB,CAAC;IAC3B,aAAa,IAAI,MAAM,EAAE,CAAC;IAC1B,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,MAAM,CAAC;IAC3D,OAAO,EAAE,qBAAqB,CAAC;IAC/B,OAAO,EAAE,SAAS,CAAC;IACnB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;IACvE,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACrB,GAAG,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C,CAAC;AA8NF,eAAO,MAAM,gBAAgB,GAAI,MAAM,cAAc,KAAG,UAwBtD,CAAC"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { dirname, join, resolve } from "node:path";
|
|
2
|
+
import { errorMessage, isValidApplicationId, renderEmptyGtkxEnvModule, toUpperFirst } from "@gtkx/utils";
|
|
3
|
+
import { isValidProjectName, PACKAGE_MANAGERS } from "./options.js";
|
|
4
|
+
import { TEMPLATE_SUFFIX } from "./templates.js";
|
|
5
|
+
const DEPENDENCIES = ["@gtkx/css", "@gtkx/ffi", "@gtkx/react", "react"];
|
|
6
|
+
const DEV_DEPENDENCIES = ["@gtkx/cli", "@gtkx/config", "@types/react", "typescript", "vite"];
|
|
7
|
+
const pinGtkxDependency = (name, version) => name.startsWith("@gtkx/") ? `${name}@^${version}` : name;
|
|
8
|
+
const TESTING_DEV_DEPENDENCIES = ["@gtkx/testing", "vitest"];
|
|
9
|
+
const RUN_DEV_COMMAND = Object.fromEntries(PACKAGE_MANAGERS.map((manager) => [manager.value, manager.runDev]));
|
|
10
|
+
const getRunCommand = (pm) => RUN_DEV_COMMAND[pm];
|
|
11
|
+
const titleFromName = (name) => name.split("-").map(toUpperFirst).join(" ");
|
|
12
|
+
const suggestApplicationId = (name) => `com.${name.replaceAll("-", "")}.app`;
|
|
13
|
+
const getDevDependencies = (testing) => {
|
|
14
|
+
const devDeps = [...DEV_DEPENDENCIES];
|
|
15
|
+
if (testing === "vitest") {
|
|
16
|
+
devDeps.push(...TESTING_DEV_DEPENDENCIES);
|
|
17
|
+
}
|
|
18
|
+
return devDeps;
|
|
19
|
+
};
|
|
20
|
+
const guardCancellation = (deps, value) => {
|
|
21
|
+
if (deps.prompts.isCancel(value)) {
|
|
22
|
+
deps.prompts.cancel("Operation canceled");
|
|
23
|
+
deps.exit(0);
|
|
24
|
+
}
|
|
25
|
+
return value;
|
|
26
|
+
};
|
|
27
|
+
const validateProjectName = (deps, value) => {
|
|
28
|
+
if (!value)
|
|
29
|
+
return "Project name is required";
|
|
30
|
+
if (!isValidProjectName(value)) {
|
|
31
|
+
return "Project name must be lowercase letters, numbers, and hyphens only";
|
|
32
|
+
}
|
|
33
|
+
if (deps.fs.existsSync(resolve(deps.cwd(), value))) {
|
|
34
|
+
return `Directory "${value}" already exists`;
|
|
35
|
+
}
|
|
36
|
+
return undefined;
|
|
37
|
+
};
|
|
38
|
+
const validateApplicationIdInput = (value) => {
|
|
39
|
+
if (!value)
|
|
40
|
+
return "Application ID is required";
|
|
41
|
+
if (!isValidApplicationId(value)) {
|
|
42
|
+
return "Application ID must be reverse domain notation (e.g., com.example.myapp)";
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
};
|
|
46
|
+
const promptName = async (deps) => guardCancellation(deps, await deps.prompts.text({
|
|
47
|
+
message: "Project name",
|
|
48
|
+
placeholder: "my-app",
|
|
49
|
+
validate: (value) => validateProjectName(deps, value),
|
|
50
|
+
}));
|
|
51
|
+
const promptApplicationId = async (deps, name) => {
|
|
52
|
+
const defaultApplicationId = suggestApplicationId(name);
|
|
53
|
+
return guardCancellation(deps, await deps.prompts.text({
|
|
54
|
+
message: "Application ID",
|
|
55
|
+
placeholder: defaultApplicationId,
|
|
56
|
+
initialValue: defaultApplicationId,
|
|
57
|
+
validate: validateApplicationIdInput,
|
|
58
|
+
}));
|
|
59
|
+
};
|
|
60
|
+
const promptPackageManager = async (deps) => {
|
|
61
|
+
const detected = await deps.detectPackageManager(deps.cwd()).catch(() => undefined);
|
|
62
|
+
const initial = detected ?? "pnpm";
|
|
63
|
+
return guardCancellation(deps, await deps.prompts.select({
|
|
64
|
+
message: "Package manager",
|
|
65
|
+
options: PACKAGE_MANAGERS.map((manager) => {
|
|
66
|
+
const hint = detected === manager.value ? "detected" : manager.recommended ? "recommended" : undefined;
|
|
67
|
+
return { value: manager.value, label: manager.label, ...(hint === undefined ? {} : { hint }) };
|
|
68
|
+
}),
|
|
69
|
+
initialValue: initial,
|
|
70
|
+
}));
|
|
71
|
+
};
|
|
72
|
+
const promptTesting = async (deps) => {
|
|
73
|
+
const enable = guardCancellation(deps, await deps.prompts.confirm({
|
|
74
|
+
message: "Include testing setup (Vitest)?",
|
|
75
|
+
initialValue: true,
|
|
76
|
+
}));
|
|
77
|
+
return enable ? "vitest" : "none";
|
|
78
|
+
};
|
|
79
|
+
const promptClaudeSkills = async (deps) => guardCancellation(deps, await deps.prompts.confirm({
|
|
80
|
+
message: "Include Claude Code skills?",
|
|
81
|
+
initialValue: true,
|
|
82
|
+
}));
|
|
83
|
+
const promptForOptions = async (deps, options) => {
|
|
84
|
+
const name = options.name ?? (await promptName(deps));
|
|
85
|
+
const applicationId = options.applicationId ?? (await promptApplicationId(deps, name));
|
|
86
|
+
const packageManager = options.packageManager ?? (await promptPackageManager(deps));
|
|
87
|
+
const testing = options.testing ?? (await promptTesting(deps));
|
|
88
|
+
const claudeSkills = options.claudeSkills ?? (await promptClaudeSkills(deps));
|
|
89
|
+
return { name, applicationId, packageManager, testing, claudeSkills };
|
|
90
|
+
};
|
|
91
|
+
const TESTING_TEMPLATE_PREFIXES = ["config/", "tests/"];
|
|
92
|
+
const CLAUDE_TEMPLATE_PREFIX = "claude/";
|
|
93
|
+
const CLAUDE_SKILLS_DIR = ".claude/skills/developing-gtkx-apps";
|
|
94
|
+
const TEMPLATE_DESTINATIONS = {
|
|
95
|
+
gitignore: ".gitignore",
|
|
96
|
+
"config/vitest.config.ts": "vitest.config.ts",
|
|
97
|
+
};
|
|
98
|
+
const destinationFor = (templateRelativePath) => {
|
|
99
|
+
if (templateRelativePath.startsWith(CLAUDE_TEMPLATE_PREFIX)) {
|
|
100
|
+
return `${CLAUDE_SKILLS_DIR}/${templateRelativePath.slice(CLAUDE_TEMPLATE_PREFIX.length)}`;
|
|
101
|
+
}
|
|
102
|
+
return TEMPLATE_DESTINATIONS[templateRelativePath] ?? templateRelativePath;
|
|
103
|
+
};
|
|
104
|
+
const isTemplateIncluded = (templateRelativePath, resolved) => {
|
|
105
|
+
if (templateRelativePath.startsWith(CLAUDE_TEMPLATE_PREFIX)) {
|
|
106
|
+
return resolved.claudeSkills;
|
|
107
|
+
}
|
|
108
|
+
if (TESTING_TEMPLATE_PREFIXES.some((prefix) => templateRelativePath.startsWith(prefix))) {
|
|
109
|
+
return resolved.testing === "vitest";
|
|
110
|
+
}
|
|
111
|
+
return true;
|
|
112
|
+
};
|
|
113
|
+
const scaffoldProject = (deps, projectPath, resolved) => {
|
|
114
|
+
const { name, applicationId, testing } = resolved;
|
|
115
|
+
const context = { name, applicationId, title: titleFromName(name), testing };
|
|
116
|
+
deps.fs.mkdirSync(projectPath, { recursive: true });
|
|
117
|
+
for (const template of deps.listTemplates()) {
|
|
118
|
+
const relativeTemplate = template.slice(0, -TEMPLATE_SUFFIX.length);
|
|
119
|
+
if (!isTemplateIncluded(relativeTemplate, resolved))
|
|
120
|
+
continue;
|
|
121
|
+
const destination = join(projectPath, destinationFor(relativeTemplate));
|
|
122
|
+
deps.fs.mkdirSync(dirname(destination), { recursive: true });
|
|
123
|
+
deps.fs.writeFileSync(destination, deps.render(template, context));
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
const installAllDependencies = async (deps, options) => {
|
|
127
|
+
const { projectPath, name, packageManager, devDependencies } = options;
|
|
128
|
+
const spinner = deps.prompts.spinner();
|
|
129
|
+
spinner.start("Installing dependencies...");
|
|
130
|
+
const pin = (names) => names.map((name) => pinGtkxDependency(name, deps.gtkxVersion));
|
|
131
|
+
try {
|
|
132
|
+
await deps.install({ cwd: projectPath, packageManager, dependencies: pin(DEPENDENCIES), dev: false });
|
|
133
|
+
await deps.install({ cwd: projectPath, packageManager, dependencies: pin(devDependencies), dev: true });
|
|
134
|
+
spinner.stop("Dependencies installed!");
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
spinner.stop("Failed to install dependencies");
|
|
138
|
+
deps.prompts.log.error(`Error: ${errorMessage(error)}`);
|
|
139
|
+
deps.prompts.log.info("You can install dependencies manually by running:");
|
|
140
|
+
deps.prompts.log.info(` cd ${name}`);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
const writeInitialSchemaEnv = (deps, projectPath) => {
|
|
144
|
+
const storeDir = join(projectPath, "node_modules", ".gtkx");
|
|
145
|
+
deps.fs.mkdirSync(storeDir, { recursive: true });
|
|
146
|
+
deps.fs.writeFileSync(join(storeDir, "env.d.ts"), renderEmptyGtkxEnvModule());
|
|
147
|
+
};
|
|
148
|
+
const initializeGitRepo = async (deps, projectPath) => {
|
|
149
|
+
const spinner = deps.prompts.spinner();
|
|
150
|
+
spinner.start("Initializing git repository...");
|
|
151
|
+
try {
|
|
152
|
+
await deps.gitInit(projectPath);
|
|
153
|
+
spinner.stop("Git repository initialized!");
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
spinner.stop("Failed to initialize git repository");
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
const HEADLESS_COMPOSITOR_NOTE = `
|
|
160
|
+
|
|
161
|
+
To run tests, you need a headless Wayland compositor installed:
|
|
162
|
+
Fedora: sudo dnf install weston
|
|
163
|
+
Ubuntu: sudo apt install weston`;
|
|
164
|
+
const printNextSteps = (deps, resolved) => {
|
|
165
|
+
const runCmd = getRunCommand(resolved.packageManager);
|
|
166
|
+
const nextSteps = `cd ${resolved.name}\n${runCmd}`;
|
|
167
|
+
const testingNote = resolved.testing === "none" ? "" : HEADLESS_COMPOSITOR_NOTE;
|
|
168
|
+
deps.prompts.note(`${nextSteps}${testingNote}`, "Next steps");
|
|
169
|
+
};
|
|
170
|
+
export const createScaffolder = (deps) => ({
|
|
171
|
+
async run(options = {}) {
|
|
172
|
+
deps.prompts.intro("Create GTKX App");
|
|
173
|
+
const resolved = await promptForOptions(deps, options);
|
|
174
|
+
const projectPath = resolve(deps.cwd(), resolved.name);
|
|
175
|
+
const devDeps = getDevDependencies(resolved.testing);
|
|
176
|
+
const projectSpinner = deps.prompts.spinner();
|
|
177
|
+
projectSpinner.start("Creating project structure...");
|
|
178
|
+
scaffoldProject(deps, projectPath, resolved);
|
|
179
|
+
projectSpinner.stop("Project structure created!");
|
|
180
|
+
await installAllDependencies(deps, {
|
|
181
|
+
projectPath,
|
|
182
|
+
name: resolved.name,
|
|
183
|
+
packageManager: resolved.packageManager,
|
|
184
|
+
devDependencies: devDeps,
|
|
185
|
+
});
|
|
186
|
+
writeInitialSchemaEnv(deps, projectPath);
|
|
187
|
+
await initializeGitRepo(deps, projectPath);
|
|
188
|
+
printNextSteps(deps, resolved);
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
//# sourceMappingURL=scaffolder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaffolder.js","sourceRoot":"","sources":["../src/scaffolder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { TestingOption } from "./options.js";
|
|
2
|
+
export type TemplateContext = {
|
|
3
|
+
name: string;
|
|
4
|
+
applicationId: string;
|
|
5
|
+
title: string;
|
|
6
|
+
testing: TestingOption;
|
|
7
|
+
};
|
|
8
|
+
export declare const TEMPLATE_SUFFIX = ".ejs";
|
|
9
|
+
export declare const listTemplates: () => string[];
|
|
10
|
+
export declare const renderFile: (templateName: string, context: TemplateContext) => string;
|
|
11
|
+
//# sourceMappingURL=templates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD,MAAM,MAAM,eAAe,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,aAAa,CAAC;CAC1B,CAAC;AAEF,eAAO,MAAM,eAAe,SAAS,CAAC;AAWtC,eAAO,MAAM,aAAa,QAAO,MAAM,EAWlC,CAAC;AAEN,eAAO,MAAM,UAAU,GAAI,cAAc,MAAM,EAAE,SAAS,eAAe,KAAG,MAI3E,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { sortedAlpha } from "@gtkx/utils";
|
|
4
|
+
import ejs from "ejs";
|
|
5
|
+
export const TEMPLATE_SUFFIX = ".ejs";
|
|
6
|
+
const getTemplatesDir = () => {
|
|
7
|
+
return join(import.meta.dirname, "..", "templates");
|
|
8
|
+
};
|
|
9
|
+
const renderTemplate = (templatePath, context) => {
|
|
10
|
+
const templateContent = readFileSync(templatePath, "utf-8");
|
|
11
|
+
return ejs.render(templateContent, context);
|
|
12
|
+
};
|
|
13
|
+
export const listTemplates = () => sortedAlpha(readdirSync(getTemplatesDir(), { recursive: true, withFileTypes: true })
|
|
14
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(TEMPLATE_SUFFIX))
|
|
15
|
+
.map((entry) => join(entry.parentPath, entry.name))
|
|
16
|
+
.map((absolute) => absolute
|
|
17
|
+
.slice(getTemplatesDir().length + 1)
|
|
18
|
+
.split(/[/\\]/)
|
|
19
|
+
.join("/")));
|
|
20
|
+
export const renderFile = (templateName, context) => {
|
|
21
|
+
const templatesDir = getTemplatesDir();
|
|
22
|
+
const templatePath = join(templatesDir, templateName);
|
|
23
|
+
return renderTemplate(templatePath, context);
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=templates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates.js","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,GAAG,MAAM,KAAK,CAAC;AAUtB,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC;AAEtC,MAAM,eAAe,GAAG,GAAW,EAAE;IACjC,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;AACxD,CAAC,CAAC;AAEF,MAAM,cAAc,GAAG,CAAC,YAAoB,EAAE,OAAwB,EAAU,EAAE;IAC9E,MAAM,eAAe,GAAG,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IAC5D,OAAO,GAAG,CAAC,MAAM,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;AAChD,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG,GAAa,EAAE,CACxC,WAAW,CACP,WAAW,CAAC,eAAe,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;KACnE,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;KACzE,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;KAClD,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CACd,QAAQ;KACH,KAAK,CAAC,eAAe,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;KACnC,KAAK,CAAC,OAAO,CAAC;KACd,IAAI,CAAC,GAAG,CAAC,CACjB,CACR,CAAC;AAEN,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,YAAoB,EAAE,OAAwB,EAAU,EAAE;IACjF,MAAM,YAAY,GAAG,eAAe,EAAE,CAAC;IACvC,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;IACtD,OAAO,cAAc,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;AACjD,CAAC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-gtkx",
|
|
3
|
+
"version": "0.21.0",
|
|
4
|
+
"description": "Scaffold a new native GTK4 + React desktop application with gtkx",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"gtkx",
|
|
7
|
+
"gtk",
|
|
8
|
+
"gtk4",
|
|
9
|
+
"react",
|
|
10
|
+
"typescript",
|
|
11
|
+
"native",
|
|
12
|
+
"linux",
|
|
13
|
+
"desktop",
|
|
14
|
+
"create",
|
|
15
|
+
"scaffold"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://gtkx.dev",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/gtkx-org/gtkx/issues"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/gtkx-org/gtkx.git",
|
|
24
|
+
"directory": "packages/create-gtkx"
|
|
25
|
+
},
|
|
26
|
+
"license": "MPL-2.0",
|
|
27
|
+
"author": "Eugenio Depalo <eugeniodepalo@gmail.com>",
|
|
28
|
+
"type": "module",
|
|
29
|
+
"exports": {
|
|
30
|
+
"./package.json": "./package.json",
|
|
31
|
+
".": {
|
|
32
|
+
"source": "./src/index.ts",
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"default": "./dist/index.js"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"bin": {
|
|
38
|
+
"create-gtkx": "bin/create-gtkx.js"
|
|
39
|
+
},
|
|
40
|
+
"sideEffects": false,
|
|
41
|
+
"files": [
|
|
42
|
+
"bin",
|
|
43
|
+
"dist",
|
|
44
|
+
"src",
|
|
45
|
+
"templates"
|
|
46
|
+
],
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@clack/prompts": "^1.5.1",
|
|
49
|
+
"citty": "^0.2.2",
|
|
50
|
+
"ejs": "^6.0.1",
|
|
51
|
+
"nypm": "^0.6.7",
|
|
52
|
+
"tinyexec": "^1.2.4",
|
|
53
|
+
"@gtkx/utils": "0.21.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/ejs": "^3.1.5",
|
|
57
|
+
"@types/node": "^25.9.3",
|
|
58
|
+
"memfs": "^4.57.7",
|
|
59
|
+
"typescript": "^6.0.3",
|
|
60
|
+
"vitest": "^4.1.8"
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"build": "tsc -b"
|
|
64
|
+
}
|
|
65
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { defineCommand, runMain } from "citty";
|
|
5
|
+
import { createCommandArgs, runCreate } from "./command.js";
|
|
6
|
+
|
|
7
|
+
const { version } = createRequire(import.meta.url)("../package.json") as { version: string };
|
|
8
|
+
|
|
9
|
+
export const main = defineCommand({
|
|
10
|
+
meta: {
|
|
11
|
+
name: "create-gtkx",
|
|
12
|
+
version,
|
|
13
|
+
description: "Scaffold a new GTKX application",
|
|
14
|
+
},
|
|
15
|
+
args: createCommandArgs,
|
|
16
|
+
run: ({ args }) => runCreate(args),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === resolve(process.argv[1])) {
|
|
20
|
+
void runMain(main);
|
|
21
|
+
}
|
package/src/command.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { type ArgsDef, defineCommand } from "citty";
|
|
2
|
+
import { createApp } from "./create.js";
|
|
3
|
+
import {
|
|
4
|
+
isKnownPackageManager,
|
|
5
|
+
isTestingOption,
|
|
6
|
+
PACKAGE_MANAGER_FLAG_DESCRIPTION,
|
|
7
|
+
type PackageManager,
|
|
8
|
+
TESTING_FLAG_DESCRIPTION,
|
|
9
|
+
type TestingOption,
|
|
10
|
+
} from "./options.js";
|
|
11
|
+
|
|
12
|
+
/** Raw, unparsed command-line arguments accepted by the create command. */
|
|
13
|
+
export type CreateCommandArgs = {
|
|
14
|
+
name?: string | undefined;
|
|
15
|
+
"application-id"?: string | undefined;
|
|
16
|
+
pm?: string | undefined;
|
|
17
|
+
testing?: string | undefined;
|
|
18
|
+
"claude-skills"?: boolean | undefined;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Shared citty argument schema for both the standalone `create-gtkx` binary and
|
|
23
|
+
* the `gtkx create` subcommand.
|
|
24
|
+
*/
|
|
25
|
+
export const createCommandArgs = {
|
|
26
|
+
name: {
|
|
27
|
+
type: "positional",
|
|
28
|
+
description: "Project name",
|
|
29
|
+
required: false,
|
|
30
|
+
},
|
|
31
|
+
"application-id": {
|
|
32
|
+
type: "string",
|
|
33
|
+
description: "Application ID (e.g., com.example.myapp)",
|
|
34
|
+
},
|
|
35
|
+
pm: {
|
|
36
|
+
type: "string",
|
|
37
|
+
description: PACKAGE_MANAGER_FLAG_DESCRIPTION,
|
|
38
|
+
},
|
|
39
|
+
testing: {
|
|
40
|
+
type: "string",
|
|
41
|
+
description: TESTING_FLAG_DESCRIPTION,
|
|
42
|
+
},
|
|
43
|
+
"claude-skills": {
|
|
44
|
+
type: "boolean",
|
|
45
|
+
description: "Include Claude Code skills for AI assistance",
|
|
46
|
+
},
|
|
47
|
+
} satisfies ArgsDef;
|
|
48
|
+
|
|
49
|
+
const parsePackageManager = (value: string | undefined): PackageManager | undefined => {
|
|
50
|
+
if (value === undefined) return undefined;
|
|
51
|
+
if (!isKnownPackageManager(value)) {
|
|
52
|
+
throw new Error(`Unknown package manager "${value}". Expected one of: ${PACKAGE_MANAGER_FLAG_DESCRIPTION}.`);
|
|
53
|
+
}
|
|
54
|
+
return value;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const parseTestingOption = (value: string | undefined): TestingOption | undefined => {
|
|
58
|
+
if (value === undefined) return undefined;
|
|
59
|
+
if (!isTestingOption(value)) {
|
|
60
|
+
throw new Error(`Unknown testing setup "${value}". Expected one of: ${TESTING_FLAG_DESCRIPTION}.`);
|
|
61
|
+
}
|
|
62
|
+
return value;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Normalize raw create arguments and scaffold the application. Throws on an
|
|
67
|
+
* unknown package manager or testing option before any files are written.
|
|
68
|
+
*/
|
|
69
|
+
export const runCreate = async (args: CreateCommandArgs): Promise<void> => {
|
|
70
|
+
await createApp({
|
|
71
|
+
name: args.name,
|
|
72
|
+
applicationId: args["application-id"],
|
|
73
|
+
packageManager: parsePackageManager(args.pm),
|
|
74
|
+
testing: parseTestingOption(args.testing),
|
|
75
|
+
claudeSkills: args["claude-skills"],
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/** The `gtkx create` subcommand: scaffold a new gtkx application. */
|
|
80
|
+
export const createCommand = defineCommand({
|
|
81
|
+
meta: {
|
|
82
|
+
name: "create",
|
|
83
|
+
description: "Create a new GTKX application",
|
|
84
|
+
},
|
|
85
|
+
args: createCommandArgs,
|
|
86
|
+
run: ({ args }) => runCreate(args),
|
|
87
|
+
});
|
package/src/create.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { defaultScaffolderDeps } from "./deps.js";
|
|
2
|
+
import { createScaffolder, type CreateOptions } from "./scaffolder.js";
|
|
3
|
+
|
|
4
|
+
export const createApp = async (options: CreateOptions = {}): Promise<void> => {
|
|
5
|
+
const scaffolder = createScaffolder(defaultScaffolderDeps());
|
|
6
|
+
await scaffolder.run(options);
|
|
7
|
+
};
|
package/src/deps.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import * as p from "@clack/prompts";
|
|
4
|
+
import { addDependency, detectPackageManager as nypmDetectPackageManager } from "nypm";
|
|
5
|
+
import { x } from "tinyexec";
|
|
6
|
+
import { isKnownPackageManager, type PackageManager } from "./options.js";
|
|
7
|
+
import type { ScaffolderDeps } from "./scaffolder.js";
|
|
8
|
+
import { listTemplates, renderFile } from "./templates.js";
|
|
9
|
+
|
|
10
|
+
const { version } = createRequire(import.meta.url)("../package.json") as { version: string };
|
|
11
|
+
|
|
12
|
+
export const defaultScaffolderDeps = (): ScaffolderDeps => ({
|
|
13
|
+
cwd: () => process.cwd(),
|
|
14
|
+
gtkxVersion: version,
|
|
15
|
+
fs: { existsSync, mkdirSync, writeFileSync },
|
|
16
|
+
prompts: {
|
|
17
|
+
intro: p.intro,
|
|
18
|
+
spinner: () => {
|
|
19
|
+
const spin = p.spinner();
|
|
20
|
+
return {
|
|
21
|
+
start: (message: string) => spin.start(message),
|
|
22
|
+
stop: (message: string) => spin.stop(message),
|
|
23
|
+
};
|
|
24
|
+
},
|
|
25
|
+
note: p.note,
|
|
26
|
+
log: p.log,
|
|
27
|
+
cancel: p.cancel,
|
|
28
|
+
text: p.text,
|
|
29
|
+
select: p.select,
|
|
30
|
+
confirm: p.confirm,
|
|
31
|
+
isCancel: p.isCancel,
|
|
32
|
+
},
|
|
33
|
+
listTemplates,
|
|
34
|
+
render: renderFile,
|
|
35
|
+
install: async ({ cwd, packageManager, dependencies, dev }) => {
|
|
36
|
+
if (dependencies.length === 0) return;
|
|
37
|
+
await addDependency(dependencies, { cwd, packageManager, dev, silent: true });
|
|
38
|
+
},
|
|
39
|
+
gitInit: async (cwd: string) => {
|
|
40
|
+
const opts = { nodeOptions: { cwd }, throwOnError: true } as const;
|
|
41
|
+
await x("git", ["init"], opts);
|
|
42
|
+
await x("git", ["add", "-A"], opts);
|
|
43
|
+
await x("git", ["commit", "-m", "Initial commit"], opts);
|
|
44
|
+
},
|
|
45
|
+
detectPackageManager: async (cwd: string): Promise<PackageManager | undefined> => {
|
|
46
|
+
const detected = await nypmDetectPackageManager(cwd, { includeParentDirs: true });
|
|
47
|
+
if (!detected) return undefined;
|
|
48
|
+
return isKnownPackageManager(detected.name) ? detected.name : undefined;
|
|
49
|
+
},
|
|
50
|
+
exit: (code: number): never => process.exit(code),
|
|
51
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createCommand, createCommandArgs, type CreateCommandArgs, runCreate } from "./command.js";
|
|
2
|
+
export { createApp } from "./create.js";
|
|
3
|
+
export { isKnownPackageManager, isTestingOption, type PackageManager, type TestingOption } from "./options.js";
|
|
4
|
+
export type { CreateOptions } from "./scaffolder.js";
|
package/src/options.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type TestingOption = "vitest" | "none";
|
|
2
|
+
|
|
3
|
+
const TESTING_OPTIONS: TestingOption[] = ["vitest", "none"];
|
|
4
|
+
|
|
5
|
+
export const PACKAGE_MANAGERS = [
|
|
6
|
+
{ value: "pnpm", label: "pnpm", runDev: "pnpm dev", recommended: true },
|
|
7
|
+
{ value: "npm", label: "npm", runDev: "npm run dev", recommended: false },
|
|
8
|
+
{ value: "yarn", label: "yarn", runDev: "yarn dev", recommended: false },
|
|
9
|
+
] as const;
|
|
10
|
+
|
|
11
|
+
export type PackageManager = (typeof PACKAGE_MANAGERS)[number]["value"];
|
|
12
|
+
|
|
13
|
+
export const PACKAGE_MANAGER_VALUES: PackageManager[] = PACKAGE_MANAGERS.map((manager) => manager.value);
|
|
14
|
+
|
|
15
|
+
export const isKnownPackageManager = (name: string): name is PackageManager =>
|
|
16
|
+
(PACKAGE_MANAGER_VALUES as string[]).includes(name);
|
|
17
|
+
|
|
18
|
+
export const isTestingOption = (value: string): value is TestingOption => (TESTING_OPTIONS as string[]).includes(value);
|
|
19
|
+
|
|
20
|
+
export const PACKAGE_MANAGER_FLAG_DESCRIPTION = `Package manager (${PACKAGE_MANAGER_VALUES.join(", ")})`;
|
|
21
|
+
|
|
22
|
+
export const TESTING_FLAG_DESCRIPTION = `Testing setup (${TESTING_OPTIONS.join(", ")})`;
|
|
23
|
+
|
|
24
|
+
export const isValidProjectName = (name: string): boolean => /^[a-z0-9-]+$/.test(name);
|