create-gtkx 2.0.0-beta.1 → 2.0.0-beta.3

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/command.d.ts CHANGED
@@ -8,6 +8,10 @@ declare const scaffoldCommand: import("citty").CommandDef<{
8
8
  readonly type: "string";
9
9
  readonly description: "Application ID (e.g., com.example.myapp)";
10
10
  };
11
+ readonly "display-name": {
12
+ readonly type: "string";
13
+ readonly description: "Application display name (e.g., My App)";
14
+ };
11
15
  readonly "package-manager": {
12
16
  readonly type: "string";
13
17
  readonly alias: "p";
@@ -34,7 +38,11 @@ declare const scaffoldCommand: import("citty").CommandDef<{
34
38
  readonly overwrite: {
35
39
  readonly type: "boolean";
36
40
  readonly alias: "f";
37
- readonly description: "Overwrite the contents of a non-empty target directory when running without prompts";
41
+ readonly description: "Replace scaffold files in a non-empty target directory when running without prompts";
42
+ };
43
+ readonly "skip-install": {
44
+ readonly type: "boolean";
45
+ readonly description: "Create the project without installing dependencies";
38
46
  };
39
47
  }>;
40
48
  export { scaffoldCommand };
@@ -1 +1 @@
1
- {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":"AAkBA,QAAA,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+CnB,CAAC;AAgCH,OAAO,EAAE,eAAe,EAAE,CAAC"}
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":"AAqBA,QAAA,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuDnB,CAAC;AAmCH,OAAO,EAAE,eAAe,EAAE,CAAC"}
package/dist/command.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as p from "@clack/prompts";
2
+ import { assertSupportedNodeVersion } from "@gtkx/config/internal";
2
3
  import { errorMessage } from "@gtkx/utils";
3
4
  import { defineCommand } from "citty";
4
5
  import { OperationCanceledError, ScaffoldAbortedError } from "./errors.js";
@@ -19,6 +20,10 @@ const scaffoldCommand = defineCommand({
19
20
  type: "string",
20
21
  description: "Application ID (e.g., com.example.myapp)",
21
22
  },
23
+ "display-name": {
24
+ type: "string",
25
+ description: "Application display name (e.g., My App)",
26
+ },
22
27
  "package-manager": {
23
28
  type: "string",
24
29
  alias: "p",
@@ -46,7 +51,11 @@ const scaffoldCommand = defineCommand({
46
51
  overwrite: {
47
52
  type: "boolean",
48
53
  alias: "f",
49
- description: "Overwrite the contents of a non-empty target directory when running without prompts",
54
+ description: "Replace scaffold files in a non-empty target directory when running without prompts",
55
+ },
56
+ "skip-install": {
57
+ type: "boolean",
58
+ description: "Create the project without installing dependencies",
50
59
  },
51
60
  },
52
61
  run: ({ args }) => runCreate(args),
@@ -63,14 +72,17 @@ const settleScaffoldFailure = (error) => {
63
72
  const runCreate = async (args) => {
64
73
  const isInteractive = args["no-interactive"] || args.yes ? false : process.stdin.isTTY;
65
74
  try {
75
+ assertSupportedNodeVersion();
66
76
  await scaffold({
67
77
  name: args.name,
68
78
  applicationId: args["application-id"],
79
+ displayName: args["display-name"],
69
80
  packageManager: args["package-manager"],
70
81
  isTypescript: args.typescript,
71
82
  shouldIncludeTesting: args.vitest,
72
83
  isInteractive,
73
84
  shouldOverwrite: args.overwrite,
85
+ shouldInstallDependencies: args["skip-install"] !== true,
74
86
  });
75
87
  }
76
88
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"command.js","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,gBAAgB,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC3E,OAAO,EAAE,gCAAgC,EAAE,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAa3C,MAAM,eAAe,GAAG,aAAa,CAAC;IAClC,IAAI,EAAE;QACF,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,+BAA+B;KAC/C;IACD,IAAI,EAAE;QACF,IAAI,EAAE;YACF,IAAI,EAAE,YAAY;YAClB,WAAW,EAAE,gEAAgE;YAC7E,QAAQ,EAAE,KAAK;SAClB;QACD,gBAAgB,EAAE;YACd,IAAI,EAAE,QAAQ;YACd,WAAW,EAAE,0CAA0C;SAC1D;QACD,iBAAiB,EAAE;YACf,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,GAAG;YACV,WAAW,EAAE,gCAAgC;SAChD;QACD,UAAU,EAAE;YACR,IAAI,EAAE,SAAS;YACf,mBAAmB,EAAE,8DAA8D;YACnF,WAAW,EAAE,wCAAwC;SACxD;QACD,MAAM,EAAE;YACJ,IAAI,EAAE,SAAS;YACf,WAAW,EAAE,gCAAgC;SAChD;QACD,GAAG,EAAE;YACD,IAAI,EAAE,SAAS;YACf,KAAK,EAAE,GAAG;YACV,WAAW,EAAE,0DAA0D;SAC1E;QACD,gBAAgB,EAAE;YACd,IAAI,EAAE,SAAS;YACf,WAAW,EACP,yFAAyF;gBACzF,iBAAiB;SACxB;QACD,SAAS,EAAE;YACP,IAAI,EAAE,SAAS;YACf,KAAK,EAAE,GAAG;YACV,WAAW,EAAE,qFAAqF;SACrG;KACJ;IACD,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC;CACrC,CAAC,CAAC;AAEH,MAAM,qBAAqB,GAAG,CAAC,KAAc,EAAQ,EAAE;IACnD,IAAI,KAAK,YAAY,sBAAsB,EAAE,CAAC;QAC1C,OAAO;IACX,CAAC;IAED,IAAI,CAAC,CAAC,KAAK,YAAY,oBAAoB,CAAC,EAAE,CAAC;QAC3C,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;IACrC,CAAC;IAED,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACzB,CAAC,CAAC;AAEF,MAAM,SAAS,GAAG,KAAK,EAAE,IAAuB,EAAiB,EAAE;IAC/D,MAAM,aAAa,GAAG,IAAI,CAAC,gBAAgB,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC;IAEvF,IAAI,CAAC;QACD,MAAM,QAAQ,CAAC;YACX,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,aAAa,EAAE,IAAI,CAAC,gBAAgB,CAAC;YACrC,cAAc,EAAE,IAAI,CAAC,iBAAiB,CAAC;YACvC,YAAY,EAAE,IAAI,CAAC,UAAU;YAC7B,oBAAoB,EAAE,IAAI,CAAC,MAAM;YACjC,aAAa;YACb,eAAe,EAAE,IAAI,CAAC,SAAS;SAClC,CAAC,CAAC;IACP,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,qBAAqB,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC;AACL,CAAC,CAAC;AAEF,OAAO,EAAE,eAAe,EAAE,CAAC","sourcesContent":["import * as p from \"@clack/prompts\";\nimport { errorMessage } from \"@gtkx/utils\";\nimport { defineCommand } from \"citty\";\nimport { OperationCanceledError, ScaffoldAbortedError } from \"./errors.js\";\nimport { PACKAGE_MANAGER_FLAG_DESCRIPTION } from \"./package-managers.js\";\nimport { scaffold } from \"./scaffolder.js\";\n\ntype CreateCommandArgs = {\n name?: string | undefined;\n \"application-id\"?: string | undefined;\n \"package-manager\"?: string | undefined;\n typescript?: boolean | undefined;\n vitest?: boolean | undefined;\n yes?: boolean | undefined;\n \"no-interactive\"?: boolean | undefined;\n overwrite?: boolean | undefined;\n};\n\nconst scaffoldCommand = defineCommand({\n meta: {\n name: \"create\",\n description: \"Create a new GTKX application\",\n },\n args: {\n name: {\n type: \"positional\",\n description: \"Target directory or project name (e.g. my-app, ., apps/my-app)\",\n required: false,\n },\n \"application-id\": {\n type: \"string\",\n description: \"Application ID (e.g., com.example.myapp)\",\n },\n \"package-manager\": {\n type: \"string\",\n alias: \"p\",\n description: PACKAGE_MANAGER_FLAG_DESCRIPTION,\n },\n typescript: {\n type: \"boolean\",\n negativeDescription: \"Scaffold the application in JavaScript instead of TypeScript\",\n description: \"Scaffold the application in TypeScript\",\n },\n vitest: {\n type: \"boolean\",\n description: \"Include a Vitest testing setup\",\n },\n yes: {\n type: \"boolean\",\n alias: \"y\",\n description: \"Skip prompts and accept defaults for unspecified options\",\n },\n \"no-interactive\": {\n type: \"boolean\",\n description:\n \"Run without prompts, using the default for every option not passed on the command line \" +\n \"(same as --yes)\",\n },\n overwrite: {\n type: \"boolean\",\n alias: \"f\",\n description: \"Overwrite the contents of a non-empty target directory when running without prompts\",\n },\n },\n run: ({ args }) => runCreate(args),\n});\n\nconst settleScaffoldFailure = (error: unknown): void => {\n if (error instanceof OperationCanceledError) {\n return;\n }\n\n if (!(error instanceof ScaffoldAbortedError)) {\n p.log.error(errorMessage(error));\n }\n\n process.exitCode = 1;\n};\n\nconst runCreate = async (args: CreateCommandArgs): Promise<void> => {\n const isInteractive = args[\"no-interactive\"] || args.yes ? false : process.stdin.isTTY;\n\n try {\n await scaffold({\n name: args.name,\n applicationId: args[\"application-id\"],\n packageManager: args[\"package-manager\"],\n isTypescript: args.typescript,\n shouldIncludeTesting: args.vitest,\n isInteractive,\n shouldOverwrite: args.overwrite,\n });\n } catch (error) {\n settleScaffoldFailure(error);\n }\n};\n\nexport { scaffoldCommand };\n"]}
1
+ {"version":3,"file":"command.js","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":"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","sourcesContent":["import * as p from \"@clack/prompts\";\nimport { assertSupportedNodeVersion } from \"@gtkx/config/internal\";\nimport { errorMessage } from \"@gtkx/utils\";\nimport { defineCommand } from \"citty\";\nimport { OperationCanceledError, ScaffoldAbortedError } from \"./errors.js\";\nimport { PACKAGE_MANAGER_FLAG_DESCRIPTION } from \"./package-managers.js\";\nimport { scaffold } from \"./scaffolder.js\";\n\ntype CreateCommandArgs = {\n name?: string | undefined;\n \"application-id\"?: string | undefined;\n \"display-name\"?: string | undefined;\n \"package-manager\"?: string | undefined;\n typescript?: boolean | undefined;\n vitest?: boolean | undefined;\n yes?: boolean | undefined;\n \"no-interactive\"?: boolean | undefined;\n overwrite?: boolean | undefined;\n \"skip-install\"?: boolean | undefined;\n};\n\nconst scaffoldCommand = defineCommand({\n meta: {\n name: \"create\",\n description: \"Create a new GTKX application\",\n },\n args: {\n name: {\n type: \"positional\",\n description: \"Target directory or project name (e.g. my-app, ., apps/my-app)\",\n required: false,\n },\n \"application-id\": {\n type: \"string\",\n description: \"Application ID (e.g., com.example.myapp)\",\n },\n \"display-name\": {\n type: \"string\",\n description: \"Application display name (e.g., My App)\",\n },\n \"package-manager\": {\n type: \"string\",\n alias: \"p\",\n description: PACKAGE_MANAGER_FLAG_DESCRIPTION,\n },\n typescript: {\n type: \"boolean\",\n negativeDescription: \"Scaffold the application in JavaScript instead of TypeScript\",\n description: \"Scaffold the application in TypeScript\",\n },\n vitest: {\n type: \"boolean\",\n description: \"Include a Vitest testing setup\",\n },\n yes: {\n type: \"boolean\",\n alias: \"y\",\n description: \"Skip prompts and accept defaults for unspecified options\",\n },\n \"no-interactive\": {\n type: \"boolean\",\n description:\n \"Run without prompts, using the default for every option not passed on the command line \" +\n \"(same as --yes)\",\n },\n overwrite: {\n type: \"boolean\",\n alias: \"f\",\n description: \"Replace scaffold files in a non-empty target directory when running without prompts\",\n },\n \"skip-install\": {\n type: \"boolean\",\n description: \"Create the project without installing dependencies\",\n },\n },\n run: ({ args }) => runCreate(args),\n});\n\nconst settleScaffoldFailure = (error: unknown): void => {\n if (error instanceof OperationCanceledError) {\n return;\n }\n\n if (!(error instanceof ScaffoldAbortedError)) {\n p.log.error(errorMessage(error));\n }\n\n process.exitCode = 1;\n};\n\nconst runCreate = async (args: CreateCommandArgs): Promise<void> => {\n const isInteractive = args[\"no-interactive\"] || args.yes ? false : process.stdin.isTTY;\n\n try {\n assertSupportedNodeVersion();\n await scaffold({\n name: args.name,\n applicationId: args[\"application-id\"],\n displayName: args[\"display-name\"],\n packageManager: args[\"package-manager\"],\n isTypescript: args.typescript,\n shouldIncludeTesting: args.vitest,\n isInteractive,\n shouldOverwrite: args.overwrite,\n shouldInstallDependencies: args[\"skip-install\"] !== true,\n });\n } catch (error) {\n settleScaffoldFailure(error);\n }\n};\n\nexport { scaffoldCommand };\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAKA,QAAA,MAAM,cAAc,GAAI,MAAM,MAAM,EAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,KAAG,IAO3F,CAAC;AAEF,OAAO,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAMA,QAAA,MAAM,cAAc,GAAI,MAAM,MAAM,EAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,KAAG,IAY3F,CAAC;AAEF,OAAO,EAAE,cAAc,EAAE,CAAC"}
package/dist/manifest.js CHANGED
@@ -1,13 +1,19 @@
1
- import { readFileSync, renameSync, writeFileSync } from "node:fs";
1
+ import { randomUUID } from "node:crypto";
2
+ import { readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
2
3
  import { join } from "node:path";
3
4
  const PACKAGE_JSON_FILE = "package.json";
4
5
  const updateManifest = (root, mutate) => {
5
6
  const manifestPath = join(root, PACKAGE_JSON_FILE);
6
7
  const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
7
8
  mutate(manifest);
8
- const pendingPath = `${manifestPath}.pending`;
9
- writeFileSync(pendingPath, `${JSON.stringify(manifest, null, 4)}\n`);
10
- renameSync(pendingPath, manifestPath);
9
+ const pendingPath = join(root, `.package-${randomUUID()}.json`);
10
+ try {
11
+ writeFileSync(pendingPath, `${JSON.stringify(manifest, null, 4)}\n`, { flag: "wx", mode: 0o600 });
12
+ renameSync(pendingPath, manifestPath);
13
+ }
14
+ finally {
15
+ rmSync(pendingPath, { force: true });
16
+ }
11
17
  };
12
18
  export { updateManifest };
13
19
  //# sourceMappingURL=manifest.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,iBAAiB,GAAG,cAAc,CAAC;AAEzC,MAAM,cAAc,GAAG,CAAC,IAAY,EAAE,MAAmD,EAAQ,EAAE;IAC/F,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC;IACnD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC,CAA4B,CAAC;IAC3F,MAAM,CAAC,QAAQ,CAAC,CAAC;IACjB,MAAM,WAAW,GAAG,GAAG,YAAY,UAAU,CAAC;IAC9C,aAAa,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IACrE,UAAU,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;AAC1C,CAAC,CAAC;AAEF,OAAO,EAAE,cAAc,EAAE,CAAC","sourcesContent":["import { readFileSync, renameSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nconst PACKAGE_JSON_FILE = \"package.json\";\n\nconst updateManifest = (root: string, mutate: (manifest: Record<string, unknown>) => void): void => {\n const manifestPath = join(root, PACKAGE_JSON_FILE);\n const manifest = JSON.parse(readFileSync(manifestPath, \"utf8\")) as Record<string, unknown>;\n mutate(manifest);\n const pendingPath = `${manifestPath}.pending`;\n writeFileSync(pendingPath, `${JSON.stringify(manifest, null, 4)}\\n`);\n renameSync(pendingPath, manifestPath);\n};\n\nexport { updateManifest };\n"]}
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC1E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,iBAAiB,GAAG,cAAc,CAAC;AAEzC,MAAM,cAAc,GAAG,CAAC,IAAY,EAAE,MAAmD,EAAQ,EAAE;IAC/F,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC;IACnD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC,CAA4B,CAAC;IAC3F,MAAM,CAAC,QAAQ,CAAC,CAAC;IACjB,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,EAAE,YAAY,UAAU,EAAE,OAAO,CAAC,CAAC;IAEhE,IAAI,CAAC;QACD,aAAa,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAClG,UAAU,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;IAC1C,CAAC;YAAS,CAAC;QACP,MAAM,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACzC,CAAC;AACL,CAAC,CAAC;AAEF,OAAO,EAAE,cAAc,EAAE,CAAC","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { readFileSync, renameSync, rmSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nconst PACKAGE_JSON_FILE = \"package.json\";\n\nconst updateManifest = (root: string, mutate: (manifest: Record<string, unknown>) => void): void => {\n const manifestPath = join(root, PACKAGE_JSON_FILE);\n const manifest = JSON.parse(readFileSync(manifestPath, \"utf8\")) as Record<string, unknown>;\n mutate(manifest);\n const pendingPath = join(root, `.package-${randomUUID()}.json`);\n\n try {\n writeFileSync(pendingPath, `${JSON.stringify(manifest, null, 4)}\\n`, { flag: \"wx\", mode: 0o600 });\n renameSync(pendingPath, manifestPath);\n } finally {\n rmSync(pendingPath, { force: true });\n }\n};\n\nexport { updateManifest };\n"]}
@@ -4,18 +4,21 @@ declare const PACKAGE_MANAGERS: readonly [{
4
4
  readonly label: "pnpm";
5
5
  readonly devCommand: "pnpm dev";
6
6
  readonly addCommand: "pnpm add";
7
+ readonly installCommand: "pnpm install";
7
8
  readonly isRecommended: true;
8
9
  }, {
9
10
  readonly value: "npm";
10
11
  readonly label: "npm";
11
12
  readonly devCommand: "npm run dev";
12
13
  readonly addCommand: "npm install";
14
+ readonly installCommand: "npm install";
13
15
  readonly isRecommended: false;
14
16
  }, {
15
17
  readonly value: "yarn";
16
18
  readonly label: "yarn";
17
19
  readonly devCommand: "yarn dev";
18
20
  readonly addCommand: "yarn add";
21
+ readonly installCommand: "yarn install";
19
22
  readonly isRecommended: false;
20
23
  }];
21
24
  declare const PACKAGE_MANAGER_VALUES: PackageManager[];
@@ -1 +1 @@
1
- {"version":3,"file":"package-managers.d.ts","sourceRoot":"","sources":["../src/package-managers.ts"],"names":[],"mappings":"AAAA,KAAK,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC;AAEjE,QAAA,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;EAIZ,CAAC;AAEX,QAAA,MAAM,sBAAsB,EAAE,cAAc,EAAqD,CAAC;AAClG,QAAA,MAAM,gCAAgC,QAA2D,CAAC;AAElG,QAAA,MAAM,qBAAqB,GAAI,MAAM,MAAM,KAAG,IAAI,IAAI,cACC,CAAC;AAExD,OAAO,EACH,gBAAgB,EAChB,gCAAgC,EAChC,sBAAsB,EACtB,qBAAqB,EACrB,KAAK,cAAc,GACtB,CAAC"}
1
+ {"version":3,"file":"package-managers.d.ts","sourceRoot":"","sources":["../src/package-managers.ts"],"names":[],"mappings":"AAAA,KAAK,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC;AAEjE,QAAA,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;EAyBZ,CAAC;AAEX,QAAA,MAAM,sBAAsB,EAAE,cAAc,EAAqD,CAAC;AAClG,QAAA,MAAM,gCAAgC,QAA2D,CAAC;AAElG,QAAA,MAAM,qBAAqB,GAAI,MAAM,MAAM,KAAG,IAAI,IAAI,cACC,CAAC;AAExD,OAAO,EACH,gBAAgB,EAChB,gCAAgC,EAChC,sBAAsB,EACtB,qBAAqB,EACrB,KAAK,cAAc,GACtB,CAAC"}
@@ -1,7 +1,28 @@
1
1
  const PACKAGE_MANAGERS = [
2
- { value: "pnpm", label: "pnpm", devCommand: "pnpm dev", addCommand: "pnpm add", isRecommended: true },
3
- { value: "npm", label: "npm", devCommand: "npm run dev", addCommand: "npm install", isRecommended: false },
4
- { value: "yarn", label: "yarn", devCommand: "yarn dev", addCommand: "yarn add", isRecommended: false },
2
+ {
3
+ value: "pnpm",
4
+ label: "pnpm",
5
+ devCommand: "pnpm dev",
6
+ addCommand: "pnpm add",
7
+ installCommand: "pnpm install",
8
+ isRecommended: true,
9
+ },
10
+ {
11
+ value: "npm",
12
+ label: "npm",
13
+ devCommand: "npm run dev",
14
+ addCommand: "npm install",
15
+ installCommand: "npm install",
16
+ isRecommended: false,
17
+ },
18
+ {
19
+ value: "yarn",
20
+ label: "yarn",
21
+ devCommand: "yarn dev",
22
+ addCommand: "yarn add",
23
+ installCommand: "yarn install",
24
+ isRecommended: false,
25
+ },
5
26
  ];
6
27
  const PACKAGE_MANAGER_VALUES = PACKAGE_MANAGERS.map((manager) => manager.value);
7
28
  const PACKAGE_MANAGER_FLAG_DESCRIPTION = `Package manager (${PACKAGE_MANAGER_VALUES.join(", ")})`;
@@ -1 +1 @@
1
- {"version":3,"file":"package-managers.js","sourceRoot":"","sources":["../src/package-managers.ts"],"names":[],"mappings":"AAEA,MAAM,gBAAgB,GAAG;IACrB,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,IAAI,EAAE;IACrG,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAAE,KAAK,EAAE;IAC1G,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,KAAK,EAAE;CAChG,CAAC;AAEX,MAAM,sBAAsB,GAAqB,gBAAgB,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAClG,MAAM,gCAAgC,GAAG,oBAAoB,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;AAElG,MAAM,qBAAqB,GAAG,CAAC,IAAY,EAA0B,EAAE,CAClE,sBAAmC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AAExD,OAAO,EACH,gBAAgB,EAChB,gCAAgC,EAChC,sBAAsB,EACtB,qBAAqB,GAExB,CAAC","sourcesContent":["type PackageManager = (typeof PACKAGE_MANAGERS)[number][\"value\"];\n\nconst PACKAGE_MANAGERS = [\n { value: \"pnpm\", label: \"pnpm\", devCommand: \"pnpm dev\", addCommand: \"pnpm add\", isRecommended: true },\n { value: \"npm\", label: \"npm\", devCommand: \"npm run dev\", addCommand: \"npm install\", isRecommended: false },\n { value: \"yarn\", label: \"yarn\", devCommand: \"yarn dev\", addCommand: \"yarn add\", isRecommended: false },\n] as const;\n\nconst PACKAGE_MANAGER_VALUES: PackageManager[] = PACKAGE_MANAGERS.map((manager) => manager.value);\nconst PACKAGE_MANAGER_FLAG_DESCRIPTION = `Package manager (${PACKAGE_MANAGER_VALUES.join(\", \")})`;\n\nconst isKnownPackageManager = (name: string): name is PackageManager =>\n (PACKAGE_MANAGER_VALUES as string[]).includes(name);\n\nexport {\n PACKAGE_MANAGERS,\n PACKAGE_MANAGER_FLAG_DESCRIPTION,\n PACKAGE_MANAGER_VALUES,\n isKnownPackageManager,\n type PackageManager,\n};\n"]}
1
+ {"version":3,"file":"package-managers.js","sourceRoot":"","sources":["../src/package-managers.ts"],"names":[],"mappings":"AAEA,MAAM,gBAAgB,GAAG;IACrB;QACI,KAAK,EAAE,MAAM;QACb,KAAK,EAAE,MAAM;QACb,UAAU,EAAE,UAAU;QACtB,UAAU,EAAE,UAAU;QACtB,cAAc,EAAE,cAAc;QAC9B,aAAa,EAAE,IAAI;KACtB;IACD;QACI,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,KAAK;QACZ,UAAU,EAAE,aAAa;QACzB,UAAU,EAAE,aAAa;QACzB,cAAc,EAAE,aAAa;QAC7B,aAAa,EAAE,KAAK;KACvB;IACD;QACI,KAAK,EAAE,MAAM;QACb,KAAK,EAAE,MAAM;QACb,UAAU,EAAE,UAAU;QACtB,UAAU,EAAE,UAAU;QACtB,cAAc,EAAE,cAAc;QAC9B,aAAa,EAAE,KAAK;KACvB;CACK,CAAC;AAEX,MAAM,sBAAsB,GAAqB,gBAAgB,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAClG,MAAM,gCAAgC,GAAG,oBAAoB,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;AAElG,MAAM,qBAAqB,GAAG,CAAC,IAAY,EAA0B,EAAE,CAClE,sBAAmC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AAExD,OAAO,EACH,gBAAgB,EAChB,gCAAgC,EAChC,sBAAsB,EACtB,qBAAqB,GAExB,CAAC","sourcesContent":["type PackageManager = (typeof PACKAGE_MANAGERS)[number][\"value\"];\n\nconst PACKAGE_MANAGERS = [\n {\n value: \"pnpm\",\n label: \"pnpm\",\n devCommand: \"pnpm dev\",\n addCommand: \"pnpm add\",\n installCommand: \"pnpm install\",\n isRecommended: true,\n },\n {\n value: \"npm\",\n label: \"npm\",\n devCommand: \"npm run dev\",\n addCommand: \"npm install\",\n installCommand: \"npm install\",\n isRecommended: false,\n },\n {\n value: \"yarn\",\n label: \"yarn\",\n devCommand: \"yarn dev\",\n addCommand: \"yarn add\",\n installCommand: \"yarn install\",\n isRecommended: false,\n },\n] as const;\n\nconst PACKAGE_MANAGER_VALUES: PackageManager[] = PACKAGE_MANAGERS.map((manager) => manager.value);\nconst PACKAGE_MANAGER_FLAG_DESCRIPTION = `Package manager (${PACKAGE_MANAGER_VALUES.join(\", \")})`;\n\nconst isKnownPackageManager = (name: string): name is PackageManager =>\n (PACKAGE_MANAGER_VALUES as string[]).includes(name);\n\nexport {\n PACKAGE_MANAGERS,\n PACKAGE_MANAGER_FLAG_DESCRIPTION,\n PACKAGE_MANAGER_VALUES,\n isKnownPackageManager,\n type PackageManager,\n};\n"]}
@@ -1,11 +1,13 @@
1
1
  type CreateOptions = {
2
2
  name?: string | undefined;
3
3
  applicationId?: string | undefined;
4
+ displayName?: string | undefined;
4
5
  packageManager?: string | undefined;
5
6
  isTypescript?: boolean | undefined;
6
7
  shouldIncludeTesting?: boolean | undefined;
7
8
  isInteractive?: boolean | undefined;
8
9
  shouldOverwrite?: boolean | undefined;
10
+ shouldInstallDependencies?: boolean | undefined;
9
11
  };
10
12
  declare const scaffold: (options?: CreateOptions) => Promise<void>;
11
13
  export { scaffold };
@@ -1 +1 @@
1
- {"version":3,"file":"scaffolder.d.ts","sourceRoot":"","sources":["../src/scaffolder.ts"],"names":[],"mappings":"AAsBA,KAAK,aAAa,GAAG;IACjB,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,YAAY,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACnC,oBAAoB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC3C,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACpC,eAAe,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACzC,CAAC;AAkjBF,QAAA,MAAM,QAAQ,GAAU,UAAS,aAAkB,KAAG,OAAO,CAAC,IAAI,CAcjE,CAAC;AAEF,OAAO,EAAE,QAAQ,EAAE,CAAC"}
1
+ {"version":3,"file":"scaffolder.d.ts","sourceRoot":"","sources":["../src/scaffolder.ts"],"names":[],"mappings":"AAsBA,KAAK,aAAa,GAAG;IACjB,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,YAAY,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACnC,oBAAoB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC3C,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACpC,eAAe,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACtC,yBAAyB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACnD,CAAC;AA4tBF,QAAA,MAAM,QAAQ,GAAU,UAAS,aAAkB,KAAG,OAAO,CAAC,IAAI,CAejE,CAAC;AAEF,OAAO,EAAE,QAAQ,EAAE,CAAC"}
@@ -2,8 +2,8 @@ import * as p from "@clack/prompts";
2
2
  import { APPLICATION_ID_MAX_LENGTH, isValidApplicationId } from "@gtkx/config/internal";
3
3
  import { errorMessage, tryResolveExecutable, upperFirst } from "@gtkx/utils";
4
4
  import { execFileSync } from "node:child_process";
5
- import { mkdirSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
6
- import { basename, dirname, join, resolve } from "node:path";
5
+ import { lstatSync, mkdirSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
6
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
7
7
  import { addDependency, detectPackageManager as nypmDetectPackageManager } from "nypm";
8
8
  import { x } from "tinyexec";
9
9
  import packageManifest from "../package.json" with { type: "json" };
@@ -25,6 +25,7 @@ const TYPESCRIPT_DEV_DEPENDENCIES = ["@types/node", "@types/react", "typescript"
25
25
  const TESTING_DEV_DEPENDENCIES = ["@gtkx/testing", "vitest"];
26
26
  const DEV_COMMAND = Object.fromEntries(PACKAGE_MANAGERS.map((manager) => [manager.value, manager.devCommand]));
27
27
  const ADD_COMMAND = Object.fromEntries(PACKAGE_MANAGERS.map((manager) => [manager.value, manager.addCommand]));
28
+ const INSTALL_COMMAND = Object.fromEntries(PACKAGE_MANAGERS.map((manager) => [manager.value, manager.installCommand]));
28
29
  const INHERITED_SCRIPT_POLICY_VARS = ["npm_config_allow_scripts", "npm_config_allow-scripts"];
29
30
  const TESTING_TEMPLATES = new Set(["vitest.config.ts", "tests/app.test.tsx"]);
30
31
  const TYPESCRIPT_TEMPLATES = new Set(["tsconfig.json", "src/gtkx-env.d.ts"]);
@@ -39,7 +40,7 @@ const APPLICATION_ID_PREFIX = "com.";
39
40
  const APPLICATION_ID_SUFFIX = ".app";
40
41
  const APPLICATION_ID_SEGMENT_LIMIT = APPLICATION_ID_MAX_LENGTH - APPLICATION_ID_PREFIX.length -
41
42
  APPLICATION_ID_SUFFIX.length;
42
- const titleFromName = (name) => name.split("-").map((part) => upperFirst(part)).join(" ");
43
+ const displayNameFromProjectName = (name) => name.split("-").map((part) => upperFirst(part)).join(" ");
43
44
  const gitConfigValue = (key) => {
44
45
  const git = tryResolveExecutable("git");
45
46
  if (git === undefined) {
@@ -71,6 +72,10 @@ const isDirEmpty = (dir) => {
71
72
  const entries = readdirSync(dir);
72
73
  return entries.length === 0 || (entries.length === 1 && entries[0] === ".git");
73
74
  };
75
+ const shouldInitializeGit = (root) => {
76
+ const entry = lstatSync(root, { throwIfNoEntry: false });
77
+ return entry === undefined || (entry.isDirectory() && readdirSync(root).length === 0);
78
+ };
74
79
  const getDevDependencies = ({ isTypescript, shouldIncludeTesting, }) => {
75
80
  const devDeps = [...DEV_DEPENDENCIES];
76
81
  if (isTypescript) {
@@ -100,6 +105,13 @@ const validateProjectName = (value) => {
100
105
  const validateApplicationIdFormat = (value) => isValidApplicationId(value) ? undefined : APPLICATION_ID_FORMAT_ERROR;
101
106
  const validateApplicationIdInput = (value) => value ? validateApplicationIdFormat(value) : "Application ID is required";
102
107
  const validateApplicationIdAnswer = (value) => value ? validateApplicationIdFormat(value) : undefined;
108
+ const validateDisplayName = (value) => {
109
+ if (value === undefined || value.trim().length === 0) {
110
+ return "Display name is required";
111
+ }
112
+ return value.includes("\n") || value.includes("\r") ? "Display name must be a single line" : undefined;
113
+ };
114
+ const validateDisplayNameAnswer = (value) => value ? validateDisplayName(value) : undefined;
103
115
  const fail = (message) => {
104
116
  p.log.error(message);
105
117
  throw new ScaffoldAbortedError(message);
@@ -134,6 +146,15 @@ const promptApplicationId = async (name) => {
134
146
  validate: validateApplicationIdAnswer,
135
147
  }));
136
148
  };
149
+ const promptDisplayName = async (name) => {
150
+ const defaultDisplayName = displayNameFromProjectName(name);
151
+ return guardCancellation(await p.text({
152
+ message: "Display name",
153
+ placeholder: defaultDisplayName,
154
+ defaultValue: defaultDisplayName,
155
+ validate: validateDisplayNameAnswer,
156
+ }));
157
+ };
137
158
  const packageManagerOption = (manager, detected) => {
138
159
  let hint;
139
160
  if (detected === manager.value) {
@@ -166,38 +187,30 @@ const isOptionEnabled = async (value, isInteractive, message) => value ??
166
187
  (isInteractive
167
188
  ? guardCancellation(await p.confirm({ message, initialValue: true }))
168
189
  : true);
169
- const emptyDir = (root) => {
170
- for (const entry of readdirSync(root)) {
171
- if (entry === ".git") {
172
- continue;
173
- }
174
- rmSync(join(root, entry), { recursive: true, force: true });
175
- }
176
- };
177
190
  const shouldOverwriteDirectory = async (target, options) => {
178
191
  if (!options.isInteractive) {
179
192
  return options.shouldOverwrite === true;
180
193
  }
181
194
  return guardCancellation(await p.confirm({
182
- message: `Directory "${target}" is not empty. Overwrite its contents?`,
195
+ message: `Directory "${target}" is not empty. Replace scaffold files?`,
183
196
  initialValue: false,
184
197
  }));
185
198
  };
186
- const shouldEmptyTargetDirectory = async (root, target, options) => {
199
+ const validateTargetAvailability = async (root, target, options) => {
187
200
  const stats = statSync(root, { throwIfNoEntry: false });
188
201
  if (stats === undefined) {
189
- return false;
202
+ return;
190
203
  }
191
204
  if (!stats.isDirectory()) {
192
205
  return fail(`Target "${target}" is not a directory`);
193
206
  }
194
207
  if (isDirEmpty(root)) {
195
- return false;
208
+ return;
196
209
  }
197
210
  if (await shouldOverwriteDirectory(target, options)) {
198
- return true;
211
+ return;
199
212
  }
200
- return fail(`Directory "${target}" is not empty`);
213
+ fail(`Directory "${target}" is not empty`);
201
214
  };
202
215
  const resolveTarget = async (options) => {
203
216
  if (options.name === undefined && options.isInteractive) {
@@ -213,6 +226,12 @@ const resolveApplicationId = async (options, name) => {
213
226
  const error = validateApplicationIdInput(applicationId);
214
227
  return error === undefined ? applicationId : fail(error);
215
228
  };
229
+ const resolveDisplayName = async (options, name) => {
230
+ const value = options.displayName ??
231
+ (options.isInteractive ? await promptDisplayName(name) : displayNameFromProjectName(name));
232
+ const error = validateDisplayName(value);
233
+ return error === undefined ? value.trim() : fail(error);
234
+ };
216
235
  const resolvePackageManager = async (requested, options) => {
217
236
  if (requested !== undefined) {
218
237
  return requested;
@@ -226,10 +245,12 @@ const resolveOptions = async (options) => {
226
245
  const requested = requestedPackageManager(options.packageManager);
227
246
  const target = await resolveTarget(options);
228
247
  const name = deriveProjectName(target);
248
+ const displayName = await resolveDisplayName(options, name);
229
249
  const applicationId = await resolveApplicationId(options, name);
230
250
  const root = resolve(process.cwd(), target);
231
251
  const isCurrentDirectory = root === process.cwd();
232
- const shouldEmptyTarget = await shouldEmptyTargetDirectory(root, target, options);
252
+ const shouldInitialize = shouldInitializeGit(root);
253
+ await validateTargetAvailability(root, target, options);
233
254
  const packageManager = await resolvePackageManager(requested, options);
234
255
  const isTypescript = await isOptionEnabled(options.isTypescript, options.isInteractive, "Use TypeScript?");
235
256
  const shouldIncludeTesting = await isOptionEnabled(options.shouldIncludeTesting, options.isInteractive, "Include testing setup (Vitest)?");
@@ -238,11 +259,13 @@ const resolveOptions = async (options) => {
238
259
  root,
239
260
  isCurrentDirectory,
240
261
  name,
262
+ displayName,
241
263
  applicationId,
242
264
  packageManager,
243
265
  isTypescript,
244
266
  shouldIncludeTesting,
245
- shouldEmptyTarget,
267
+ shouldInitializeGit: shouldInitialize,
268
+ shouldInstallDependencies: options.shouldInstallDependencies !== false,
246
269
  };
247
270
  };
248
271
  const isTemplateIncluded = (templateRelativePath, resolved) => {
@@ -263,22 +286,98 @@ const destinationPath = (template, resolved) => {
263
286
  const templateContext = (resolved) => ({
264
287
  name: resolved.name,
265
288
  applicationId: resolved.applicationId,
266
- title: titleFromName(resolved.name),
289
+ displayName: resolved.displayName,
267
290
  shouldIncludeTesting: resolved.shouldIncludeTesting,
268
291
  isTypescript: resolved.isTypescript,
269
292
  importExtension: resolved.isTypescript ? ".js" : ".jsx",
270
- developerName: gitConfigValue("user.name") ?? titleFromName(resolved.name),
293
+ developerName: gitConfigValue("user.name") ?? resolved.displayName,
271
294
  developerEmail: gitConfigValue("user.email"),
272
295
  });
273
- const scaffoldProject = async (root, resolved) => {
296
+ const replaceTemplateDestination = (destination) => {
297
+ const entry = lstatSync(destination, { throwIfNoEntry: false });
298
+ if (entry === undefined) {
299
+ return;
300
+ }
301
+ if (!entry.isFile() && !entry.isSymbolicLink()) {
302
+ fail(`Cannot replace scaffold file because its destination is not a file: ${destination}`);
303
+ }
304
+ rmSync(destination, { force: true });
305
+ };
306
+ const projectRelativeDestination = (root, destination) => {
307
+ const relativePath = relative(root, destination);
308
+ const isOutsideProject = relativePath.length === 0 || relativePath.startsWith(`..${sep}`) ||
309
+ isAbsolute(relativePath);
310
+ if (isOutsideProject) {
311
+ fail(`Scaffold destination is outside the project: ${destination}`);
312
+ }
313
+ return relativePath;
314
+ };
315
+ const assertRootIsNotSymbolicLink = (root) => {
316
+ const rootEntry = lstatSync(root, { throwIfNoEntry: false });
317
+ if (rootEntry?.isSymbolicLink()) {
318
+ fail(`Cannot scaffold through a symbolic link: ${root}`);
319
+ }
320
+ };
321
+ const assertSafeAncestor = (ancestor) => {
322
+ const entry = lstatSync(ancestor, { throwIfNoEntry: false });
323
+ if (entry?.isSymbolicLink()) {
324
+ fail(`Cannot scaffold through a symbolic link: ${ancestor}`);
325
+ }
326
+ if (entry !== undefined && !entry.isDirectory()) {
327
+ fail(`Cannot scaffold through a non-directory path: ${ancestor}`);
328
+ }
329
+ };
330
+ const assertSafeAncestors = (root, relativePath) => {
331
+ let ancestor = root;
332
+ const segments = relativePath.split(sep);
333
+ const ancestorSegments = segments.slice(0, -1);
334
+ for (const segment of ancestorSegments) {
335
+ ancestor = join(ancestor, segment);
336
+ assertSafeAncestor(ancestor);
337
+ }
338
+ };
339
+ const assertReplaceableDestination = (destination) => {
340
+ const entry = lstatSync(destination, { throwIfNoEntry: false });
341
+ const isReplaceable = entry === undefined || entry.isFile() || entry.isSymbolicLink();
342
+ if (!isReplaceable) {
343
+ fail(`Cannot replace scaffold file because its destination is not a file: ${destination}`);
344
+ }
345
+ };
346
+ const assertSafeDestination = (root, destination) => {
347
+ const relativePath = projectRelativeDestination(root, destination);
348
+ assertRootIsNotSymbolicLink(root);
349
+ assertSafeAncestors(root, relativePath);
350
+ assertReplaceableDestination(destination);
351
+ };
352
+ const plannedScaffoldFiles = async (root, resolved) => {
274
353
  const context = templateContext(resolved);
354
+ const files = [];
275
355
  for (const template of listTemplates()) {
276
- if (!isTemplateIncluded(template, resolved)) {
277
- continue;
356
+ if (isTemplateIncluded(template, resolved)) {
357
+ files.push({
358
+ destination: join(root, destinationPath(template, resolved)),
359
+ contents: await renderFile(template, context),
360
+ });
278
361
  }
279
- const destination = join(root, destinationPath(template, resolved));
280
- mkdirSync(dirname(destination), { recursive: true });
281
- writeFileSync(destination, await renderFile(template, context));
362
+ }
363
+ return files;
364
+ };
365
+ const auxiliaryDestinations = (root, resolved) => [
366
+ ...(resolved.packageManager === "pnpm" ? [join(root, "pnpm-workspace.yaml")] : []),
367
+ ...(resolved.isTypescript ? [join(root, "node_modules", ".gtkx", "env.d.ts")] : []),
368
+ ];
369
+ const scaffoldProject = async (root, resolved) => {
370
+ const files = await plannedScaffoldFiles(root, resolved);
371
+ const destinations = [...files.map((file) => file.destination), ...auxiliaryDestinations(root, resolved)];
372
+ for (const destination of destinations) {
373
+ assertSafeDestination(root, destination);
374
+ }
375
+ for (const destination of destinations) {
376
+ replaceTemplateDestination(destination);
377
+ }
378
+ for (const file of files) {
379
+ mkdirSync(dirname(file.destination), { recursive: true });
380
+ writeFileSync(file.destination, file.contents);
282
381
  }
283
382
  if (resolved.shouldIncludeTesting) {
284
383
  updateManifest(root, (manifest) => {
@@ -309,6 +408,13 @@ const installDependencies = async (options) => {
309
408
  });
310
409
  };
311
410
  const pin = (names) => names.map((dependency) => dependency.startsWith("@gtkx/") ? `${dependency}@^${selfVersion}` : dependency);
411
+ const dependencyVersions = (names) => Object.fromEntries(names.map((dependency) => [dependency, dependency.startsWith("@gtkx/") ? `^${selfVersion}` : "latest"]));
412
+ const writeDependencies = (root, devDependencies) => {
413
+ updateManifest(root, (manifest) => {
414
+ manifest.dependencies = dependencyVersions(DEPENDENCIES);
415
+ manifest.devDependencies = dependencyVersions(devDependencies);
416
+ });
417
+ };
312
418
  const formatRecovery = ({ resolved, devDependencies }) => {
313
419
  const add = ADD_COMMAND[resolved.packageManager];
314
420
  const steps = [
@@ -361,7 +467,23 @@ const writeInitialEnvModule = (root) => {
361
467
  mkdirSync(storeDir, { recursive: true });
362
468
  writeFileSync(join(storeDir, "env.d.ts"), `${GTKX_ENV_MODULE_HEADER}\n`);
363
469
  };
364
- const initializeGitRepo = async (root) => {
470
+ const isInsideGitRepository = (root) => {
471
+ const git = tryResolveExecutable("git");
472
+ if (git === undefined) {
473
+ return false;
474
+ }
475
+ try {
476
+ execFileSync(git, ["-C", root, "rev-parse", "--show-toplevel"], { stdio: "ignore" });
477
+ return true;
478
+ }
479
+ catch {
480
+ return false;
481
+ }
482
+ };
483
+ const initializeGitRepo = async (root, shouldInitialize) => {
484
+ if (!shouldInitialize || isInsideGitRepository(root)) {
485
+ return;
486
+ }
365
487
  const spinner = p.spinner();
366
488
  spinner.start("Initializing git repository...");
367
489
  try {
@@ -378,8 +500,9 @@ const initializeGitRepo = async (root) => {
378
500
  const printNextSteps = (resolved) => {
379
501
  const devCmd = DEV_COMMAND[resolved.packageManager];
380
502
  const cdStep = resolved.isCurrentDirectory ? "" : `cd ${resolved.target}\n`;
503
+ const installStep = resolved.shouldInstallDependencies ? "" : `${INSTALL_COMMAND[resolved.packageManager]}\n`;
381
504
  const testingNote = resolved.shouldIncludeTesting ? HEADLESS_COMPOSITOR_NOTE : "";
382
- p.note(`${cdStep}${devCmd}${testingNote}`, "Next steps");
505
+ p.note(`${cdStep}${installStep}${devCmd}${testingNote}`, "Next steps");
383
506
  };
384
507
  const createProjectStructure = async (root, resolved) => {
385
508
  await runWithSpinner({
@@ -388,11 +511,11 @@ const createProjectStructure = async (root, resolved) => {
388
511
  failed: "Failed to create the project structure",
389
512
  run: async () => {
390
513
  mkdirSync(root, { recursive: true });
391
- if (resolved.shouldEmptyTarget) {
392
- emptyDir(root);
393
- }
394
514
  await scaffoldProject(root, resolved);
395
515
  writeBuildAllowance(root, resolved.packageManager);
516
+ if (resolved.isTypescript) {
517
+ writeInitialEnvModule(root);
518
+ }
396
519
  },
397
520
  });
398
521
  };
@@ -402,11 +525,13 @@ const scaffold = async (options = {}) => {
402
525
  const { root } = resolved;
403
526
  const devDeps = getDevDependencies(resolved);
404
527
  await createProjectStructure(root, resolved);
405
- await installAllDependencies({ resolved, devDependencies: devDeps });
406
- if (resolved.isTypescript) {
407
- writeInitialEnvModule(root);
528
+ if (resolved.shouldInstallDependencies) {
529
+ await installAllDependencies({ resolved, devDependencies: devDeps });
530
+ }
531
+ else {
532
+ writeDependencies(root, devDeps);
408
533
  }
409
- await initializeGitRepo(root);
534
+ await initializeGitRepo(root, resolved.shouldInitializeGit);
410
535
  printNextSteps(resolved);
411
536
  };
412
537
  export { scaffold };