@ciderjs/gasbombe 0.1.0 → 0.2.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/README.ja.md CHANGED
@@ -44,8 +44,9 @@ gasbombe
44
44
  以下の項目について質問されます。
45
45
 
46
46
  1. プロジェクト名
47
- 2. パッケージマネージャー(npm, yarn, pnpm
48
- 3. テンプレート(Vanilla TS, React)
47
+ 2. プロジェクトテンプレート(Vanilla TS, React
48
+ 3. Apps Scriptプロジェクトのセットアップ方法(`.clasp.json`)
49
+ 4. パッケージマネージャー(npm, yarn, pnpm)
49
50
 
50
51
  このツールは、指定されたプロジェクト名で新しいディレクトリを作成し、テンプレートファイルを生成して、依存関係をインストールします。
51
52
 
@@ -54,15 +55,17 @@ gasbombe
54
55
  コマンドラインオプションを指定することで、対話型のプロンプトを省略できます。これは、スクリプトや自動化に便利です。
55
56
 
56
57
  ```bash
57
- # 例: pnpmを使用して新しいReactプロジェクトを作成する
58
- gasbombe --name my-react-app --pkg pnpm --template react-tsx
58
+ # 例: pnpmを使用して新しいReactプロジェクトを作成し、同時に新しいApps Scriptプロジェクトも作成する
59
+ gasbombe --name my-react-app --template react-tsx --clasp create --pkg pnpm
59
60
  ```
60
61
 
61
- | オプション | 引数 | 説明 | 選択肢 |
62
- | :--- | :--- | :--- | :--- |
63
- | `--name` | `[projectName]` | 生成するプロジェクトの名前。 | - |
64
- | `--pkg` | `[packageManager]` | 使用するパッケージマネージャー。 | `npm`, `pnpm`, `yarn` |
65
- | `--template` | `[templateType]` | 使用するプロジェクトテンプレート。 | `vanilla-ts`, `react-tsx` |
62
+ | オプション | エイリアス | 引数 | 説明 | 選択肢 |
63
+ | :--- | :--- | :--- | :--- | :--- |
64
+ | `--name` | `-n` | `[projectName]` | 生成するプロジェクトの名前。 | - |
65
+ | `--template` | `-t` | `[templateType]` | 使用するプロジェクトテンプレート。 | `vanilla-ts`, `react-tsx` |
66
+ | `--clasp` | `-c` | `[claspOption]` | `.clasp.json`のセットアップ方法。<br/>`create`と`list`は事前にclaspへのログインが必要です。 | `create`, `list`, `input`, `skip` |
67
+ | `--pkg` | `-p` | `[packageManager]` | 使用するパッケージマネージャー。 | `npm`, `pnpm`, `yarn` |
68
+ | `--skipInstall` | | | 依存関係のインストールをスキップします。 | - |
66
69
 
67
70
  これらのオプションのいずれかが省略された場合、対話形式で値を入力するよう求められます。
68
71
 
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # **GasBombe**
1
+ # **Gasbombe**
2
2
 
3
- [![README-ja](https://img.shields.io/badge/English-blue?logo=ReadMe)](./README.ja.md)
3
+ [![README-ja](https://img.shields.io/badge/日本語-blue?logo=ReadMe)](./README.ja.md)
4
4
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
5
5
  [![npm version](https://img.shields.io/npm/v/@ciderjs/gasbombe.svg)](https://www.npmjs.com/package/@ciderjs/gasbombe)
6
6
  [![GitHub issues](https://img.shields.io/github/issues/luthpg/gasbombe.svg)](https://github.com/luthpg/gasbombe/issues)
@@ -44,8 +44,9 @@ gasbombe
44
44
  You will be asked for:
45
45
 
46
46
  1. Project name
47
- 2. Package manager (npm, yarn, pnpm)
48
- 3. Template (Vanilla TS, React)
47
+ 2. Project template (Vanilla TS, React)
48
+ 3. How to set up the Apps Script project (`.clasp.json`)
49
+ 4. Package manager (npm, yarn, pnpm)
49
50
 
50
51
  The tool will create a new directory with the specified project name, generate the template files, and install the dependencies.
51
52
 
@@ -54,15 +55,17 @@ The tool will create a new directory with the specified project name, generate t
54
55
  You can bypass the interactive prompts by providing command-line options. This is useful for scripting and automation.
55
56
 
56
57
  ```bash
57
- # Example: Create a new React project with pnpm
58
- gasbombe --name my-react-app --pkg pnpm --template react-tsx
58
+ # Example: Create a new React project with pnpm, creating a new Apps Script project along with it
59
+ gasbombe --name my-react-app --template react-tsx --clasp create --pkg pnpm
59
60
  ```
60
61
 
61
- | Option | Argument | Description | Choices |
62
- | ---------- | ------------------ | -------------------------------- | ------------------------- |
63
- | `--name` | `[projectName]` | The name of the project to generate. | - |
64
- | `--pkg` | `[packageManager]` | The package manager to use. | `npm`, `pnpm`, `yarn` |
65
- | `--template` | `[templateType]` | The project template to use. | `vanilla-ts`, `react-tsx` |
62
+ | Option | Alias | Argument | Description | Choices |
63
+ | :--- | :--- | :--- | :--- | :--- |
64
+ | `--name` | `-n` | `[projectName]` | The name of the project to generate. | - |
65
+ | `--template` | `-t` | `[templateType]` | The project template to use. | `vanilla-ts`, `react-tsx` |
66
+ | `--clasp` | `-c` | `[claspOption]` | How to set up the `.clasp.json` file.<br/>`create` and `list` require prior login to clasp. | `create`, `list`, `input`, `skip` |
67
+ | `--pkg` | `-p` | `[packageManager]` | The package manager to use. | `npm`, `pnpm`, `yarn` |
68
+ | `--skipInstall` | | | Skip installing dependencies. | - |
66
69
 
67
70
  If any of these options are omitted, you will be prompted to enter the value interactively.
68
71
 
package/dist/cli.cjs CHANGED
@@ -11,46 +11,105 @@ require('consola');
11
11
  require('ejs');
12
12
  require('glob');
13
13
 
14
- const version = "0.1.0";
14
+ const version = "0.2.0";
15
15
 
16
16
  async function main() {
17
17
  const program = new commander.Command();
18
18
  program.version(version, "-v, --version");
19
19
  program.description("Create project for GoogleAppsScript").option(
20
- "--name [projectName]",
20
+ "-n, --name [projectName]",
21
21
  "Project name what you want to generate",
22
22
  ""
23
23
  ).option(
24
- "--pkg [packageManager]",
24
+ "-p, --pkg [packageManager]",
25
25
  'Package manager what you want to use ("npm" | "pnpm" | "yarn")',
26
26
  ""
27
27
  ).option(
28
- "--template [templateType]",
28
+ "-t, --template [templateType]",
29
29
  'Project template label ("vanilla-ts" | "react-tsx")',
30
30
  ""
31
+ ).option(
32
+ "-c, --clasp [claspSetupCommand]",
33
+ 'Setup command how you want to use for Apps Script Project (create project | select project | input project id | skip)\n* If you use "create" or "select", you need to login to clasp first',
34
+ ""
35
+ ).option(
36
+ "--skipInstall",
37
+ "Skip install dependencies after generating the project",
38
+ false
31
39
  ).action(async (_param, command) => {
32
40
  let {
33
41
  name: projectName,
34
42
  pkg: packageManager,
35
- template: templateType
43
+ template: templateType,
44
+ clasp,
45
+ skipInstall
36
46
  } = command.opts();
47
+ let claspProjectId;
37
48
  try {
38
49
  projectName ||= await prompts.input({
39
50
  message: "Input project name what you want to generate..."
40
51
  });
41
- packageManager ||= await prompts.select({
42
- message: "Choise package manager what you want to use...",
43
- choices: ["npm", "pnpm", "yarn"]
44
- });
45
52
  templateType ||= await prompts.select({
46
- message: "Choise project template...",
47
- choices: ["vanilla-ts", "react-tsx"]
53
+ message: "Choice project template...",
54
+ choices: [
55
+ { name: "vanilla-ts", value: "vanilla-ts" },
56
+ { name: "react-tsx", value: "react-tsx" }
57
+ ]
58
+ });
59
+ clasp ||= await prompts.select({
60
+ message: 'How do you want to set up the Apps Script project?\n* If you use "create" or "select", you need to login to clasp first',
61
+ choices: [
62
+ {
63
+ name: "[NEW] Create a new Apps Script project (need `npx @google/clasp login`)",
64
+ value: "create",
65
+ description: "Runs `npx @google/clasp create` to generate a new project."
66
+ },
67
+ {
68
+ name: "[SELECT] Use an existing Apps Script project (need `npx @google/clasp login`)",
69
+ value: "list",
70
+ description: "Runs `npx @google/clasp list` and lets you choose from your projects."
71
+ },
72
+ {
73
+ name: "[INPUT] Input Script ID manually",
74
+ value: "input",
75
+ description: "Manually provide an existing Script ID."
76
+ },
77
+ {
78
+ name: "[NONE] Skip for now",
79
+ value: "skip",
80
+ description: "Create a `.clasp.json` file without Apps Script project ID."
81
+ }
82
+ ]
83
+ });
84
+ if (clasp === "input") {
85
+ claspProjectId = await prompts.input({
86
+ message: "Input Apps Script project ID...",
87
+ required: false
88
+ });
89
+ }
90
+ if (packageManager === "" && clasp !== "create" && clasp !== "list" && skipInstall) {
91
+ packageManager = "npm";
92
+ }
93
+ packageManager ||= await prompts.select({
94
+ message: "Choice package manager what you want to use...",
95
+ choices: [
96
+ { name: "npm", value: "npm" },
97
+ { name: "pnpm", value: "pnpm" },
98
+ { name: "yarn", value: "yarn" }
99
+ ]
48
100
  });
49
101
  } catch (e) {
50
102
  e.message === "User force closed the prompt with SIGINT" && process.exit(0);
51
103
  throw e;
52
104
  }
53
- await index.generateProject({ projectName, packageManager, templateType });
105
+ await index.generateProject({
106
+ projectName,
107
+ packageManager,
108
+ templateType,
109
+ clasp,
110
+ claspProjectId,
111
+ install: !skipInstall
112
+ });
54
113
  });
55
114
  program.parse(process.argv);
56
115
  }
package/dist/cli.mjs CHANGED
@@ -9,46 +9,105 @@ import 'consola';
9
9
  import 'ejs';
10
10
  import 'glob';
11
11
 
12
- const version = "0.1.0";
12
+ const version = "0.2.0";
13
13
 
14
14
  async function main() {
15
15
  const program = new Command();
16
16
  program.version(version, "-v, --version");
17
17
  program.description("Create project for GoogleAppsScript").option(
18
- "--name [projectName]",
18
+ "-n, --name [projectName]",
19
19
  "Project name what you want to generate",
20
20
  ""
21
21
  ).option(
22
- "--pkg [packageManager]",
22
+ "-p, --pkg [packageManager]",
23
23
  'Package manager what you want to use ("npm" | "pnpm" | "yarn")',
24
24
  ""
25
25
  ).option(
26
- "--template [templateType]",
26
+ "-t, --template [templateType]",
27
27
  'Project template label ("vanilla-ts" | "react-tsx")',
28
28
  ""
29
+ ).option(
30
+ "-c, --clasp [claspSetupCommand]",
31
+ 'Setup command how you want to use for Apps Script Project (create project | select project | input project id | skip)\n* If you use "create" or "select", you need to login to clasp first',
32
+ ""
33
+ ).option(
34
+ "--skipInstall",
35
+ "Skip install dependencies after generating the project",
36
+ false
29
37
  ).action(async (_param, command) => {
30
38
  let {
31
39
  name: projectName,
32
40
  pkg: packageManager,
33
- template: templateType
41
+ template: templateType,
42
+ clasp,
43
+ skipInstall
34
44
  } = command.opts();
45
+ let claspProjectId;
35
46
  try {
36
47
  projectName ||= await input({
37
48
  message: "Input project name what you want to generate..."
38
49
  });
39
- packageManager ||= await select({
40
- message: "Choise package manager what you want to use...",
41
- choices: ["npm", "pnpm", "yarn"]
42
- });
43
50
  templateType ||= await select({
44
- message: "Choise project template...",
45
- choices: ["vanilla-ts", "react-tsx"]
51
+ message: "Choice project template...",
52
+ choices: [
53
+ { name: "vanilla-ts", value: "vanilla-ts" },
54
+ { name: "react-tsx", value: "react-tsx" }
55
+ ]
56
+ });
57
+ clasp ||= await select({
58
+ message: 'How do you want to set up the Apps Script project?\n* If you use "create" or "select", you need to login to clasp first',
59
+ choices: [
60
+ {
61
+ name: "[NEW] Create a new Apps Script project (need `npx @google/clasp login`)",
62
+ value: "create",
63
+ description: "Runs `npx @google/clasp create` to generate a new project."
64
+ },
65
+ {
66
+ name: "[SELECT] Use an existing Apps Script project (need `npx @google/clasp login`)",
67
+ value: "list",
68
+ description: "Runs `npx @google/clasp list` and lets you choose from your projects."
69
+ },
70
+ {
71
+ name: "[INPUT] Input Script ID manually",
72
+ value: "input",
73
+ description: "Manually provide an existing Script ID."
74
+ },
75
+ {
76
+ name: "[NONE] Skip for now",
77
+ value: "skip",
78
+ description: "Create a `.clasp.json` file without Apps Script project ID."
79
+ }
80
+ ]
81
+ });
82
+ if (clasp === "input") {
83
+ claspProjectId = await input({
84
+ message: "Input Apps Script project ID...",
85
+ required: false
86
+ });
87
+ }
88
+ if (packageManager === "" && clasp !== "create" && clasp !== "list" && skipInstall) {
89
+ packageManager = "npm";
90
+ }
91
+ packageManager ||= await select({
92
+ message: "Choice package manager what you want to use...",
93
+ choices: [
94
+ { name: "npm", value: "npm" },
95
+ { name: "pnpm", value: "pnpm" },
96
+ { name: "yarn", value: "yarn" }
97
+ ]
46
98
  });
47
99
  } catch (e) {
48
100
  e.message === "User force closed the prompt with SIGINT" && process.exit(0);
49
101
  throw e;
50
102
  }
51
- await generateProject({ projectName, packageManager, templateType });
103
+ await generateProject({
104
+ projectName,
105
+ packageManager,
106
+ templateType,
107
+ clasp,
108
+ claspProjectId,
109
+ install: !skipInstall
110
+ });
52
111
  });
53
112
  program.parse(process.argv);
54
113
  }
package/dist/index.cjs CHANGED
@@ -3,6 +3,7 @@
3
3
  const node_child_process = require('node:child_process');
4
4
  const fs = require('node:fs/promises');
5
5
  const path = require('node:path');
6
+ const prompts = require('@inquirer/prompts');
6
7
  const consola = require('consola');
7
8
  const ejs = require('ejs');
8
9
  const glob = require('glob');
@@ -13,18 +14,30 @@ const fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
13
14
  const path__default = /*#__PURE__*/_interopDefaultCompat(path);
14
15
  const ejs__default = /*#__PURE__*/_interopDefaultCompat(ejs);
15
16
 
16
- async function runCommand(command, args, cwd) {
17
+ async function runCommand(command, args, cwd, capture = false) {
17
18
  return new Promise((resolve, reject) => {
18
19
  const child = node_child_process.spawn(command, args, {
19
20
  cwd,
20
- stdio: "inherit",
21
+ stdio: capture ? "pipe" : "inherit",
21
22
  shell: true
22
23
  });
24
+ let stdout = "";
25
+ let stderr = "";
26
+ if (capture) {
27
+ child.stdout?.on("data", (data) => {
28
+ stdout += data.toString();
29
+ });
30
+ child.stderr?.on("data", (data) => {
31
+ stderr += data.toString();
32
+ });
33
+ }
23
34
  child.on("close", (code) => {
24
35
  if (code === 0) {
25
- resolve();
36
+ resolve(stdout.trim());
26
37
  } else {
27
- reject(new Error(`Command failed with exit code ${code}`));
38
+ const errorMsg = `Command failed with exit code ${code}${stderr ? `:
39
+ ${stderr}` : ""}`;
40
+ reject(new Error(errorMsg));
28
41
  }
29
42
  });
30
43
  child.on("error", (err) => {
@@ -32,10 +45,119 @@ async function runCommand(command, args, cwd) {
32
45
  });
33
46
  });
34
47
  }
48
+ async function handleClaspSetup(claspOption, projectName, outputDir, claspProjectId, packageManager = "npm") {
49
+ if (claspOption === "skip") {
50
+ return;
51
+ }
52
+ const npxLikeCommand = packageManager === "npm" ? "npx" : packageManager === "pnpm" ? "pnpx" : "yarn";
53
+ consola.consola.start(
54
+ `Setting up .clasp.json with \`${npxLikeCommand} @google/clasp\`...`
55
+ );
56
+ if (claspOption === "create" || claspOption === "list") {
57
+ try {
58
+ await runCommand(npxLikeCommand, ["@google/clasp", "status"], outputDir);
59
+ } catch {
60
+ consola.consola.error(
61
+ `It seems you are not logged in to clasp. Please run \`${npxLikeCommand} @google/clasp login\` and try again.`
62
+ );
63
+ return;
64
+ }
65
+ }
66
+ let scriptId;
67
+ switch (claspOption) {
68
+ case "create":
69
+ try {
70
+ const result = await runCommand(
71
+ npxLikeCommand,
72
+ [
73
+ "@google/clasp",
74
+ "create",
75
+ "--title",
76
+ `"${projectName}"`,
77
+ "--type",
78
+ "standalone"
79
+ ],
80
+ outputDir,
81
+ true
82
+ );
83
+ const match = result.match(/Created new script: .+\/d\/(.+)\/edit/);
84
+ if (match?.[1]) {
85
+ scriptId = match[1];
86
+ consola.consola.info(`Created new Apps Script project with ID: ${scriptId}`);
87
+ } else {
88
+ throw new Error("Could not parse scriptId from clasp output.");
89
+ }
90
+ } catch (e) {
91
+ consola.consola.error("Failed to create new Apps Script project.", e);
92
+ return;
93
+ }
94
+ break;
95
+ case "list":
96
+ try {
97
+ const listOutput = await runCommand(
98
+ npxLikeCommand,
99
+ ["@google/clasp", "list"],
100
+ outputDir,
101
+ true
102
+ );
103
+ const projects = listOutput.split("\n").slice(1).map((line) => {
104
+ const parts = line.split(" - ");
105
+ if (parts.length >= 2) {
106
+ const name = parts[0]?.trim();
107
+ const url = parts[1]?.trim();
108
+ if (!name || !url) return null;
109
+ const match = url.match(/\/d\/(.+)\/edit/);
110
+ const scriptId2 = match?.[1];
111
+ if (scriptId2) {
112
+ return { name, value: scriptId2 };
113
+ }
114
+ }
115
+ return null;
116
+ }).filter((p) => p !== null);
117
+ if (projects.length === 0) {
118
+ consola.consola.warn(
119
+ "No existing Apps Script projects found. Please create one first."
120
+ );
121
+ return;
122
+ }
123
+ scriptId = await prompts.select({
124
+ message: "Choose an existing Apps Script project:",
125
+ choices: projects
126
+ });
127
+ } catch (e) {
128
+ consola.consola.error("Failed to list Apps Script projects.", e);
129
+ return;
130
+ }
131
+ break;
132
+ case "input":
133
+ scriptId = claspProjectId;
134
+ break;
135
+ }
136
+ if (scriptId) {
137
+ const claspJsonPath = path__default.join(outputDir, ".clasp.json");
138
+ let claspJson = {
139
+ scriptId
140
+ };
141
+ let successMessage = `.clasp.json created successfully with scriptId: ${scriptId}`;
142
+ try {
143
+ const existingContent = await fs__default.readFile(claspJsonPath, "utf-8");
144
+ const existingJson = JSON.parse(existingContent);
145
+ claspJson = { ...existingJson, scriptId };
146
+ successMessage = `.clasp.json updated successfully with scriptId: ${scriptId}`;
147
+ } catch {
148
+ }
149
+ const claspJsonContent = JSON.stringify(claspJson, null, 2);
150
+ await fs__default.writeFile(claspJsonPath, claspJsonContent, { encoding: "utf-8" });
151
+ consola.consola.success(successMessage);
152
+ }
153
+ }
35
154
  async function generateProject({
36
155
  projectName,
37
156
  packageManager,
38
- templateType
157
+ templateType,
158
+ clasp,
159
+ claspProjectId,
160
+ install
39
161
  }) {
40
162
  const outputDir = path__default.resolve(process.cwd(), projectName);
41
163
  const templateBaseDir = path__default.resolve(__dirname, "..", "dist", "templates");
@@ -48,6 +170,7 @@ async function generateProject({
48
170
  await fs__default.access(outputDir);
49
171
  consola.consola.error(`Directory ${projectName} already exists.`);
50
172
  process.exit(1);
173
+ return;
51
174
  } catch {
52
175
  }
53
176
  await fs__default.mkdir(outputDir, { recursive: true });
@@ -58,30 +181,36 @@ async function generateProject({
58
181
  const files = await glob.glob("./**/*", {
59
182
  cwd: dir,
60
183
  nodir: true,
61
- dot: true,
62
- dotRelative: true,
63
- follow: true,
64
- windowsPathsNoEscape: true
184
+ dot: true
65
185
  });
66
186
  for (const file of files) {
67
- const templatePath = path__default.join(dir, file);
68
- const outputPath = path__default.join(outputDir, file.replace(".ejs", ""));
187
+ const relativePath = path__default.relative(dir, path__default.resolve(dir, file));
188
+ const templatePath = path__default.join(dir, relativePath);
189
+ const outputPath = path__default.join(outputDir, relativePath.replace(".ejs", ""));
69
190
  await fs__default.mkdir(path__default.dirname(outputPath), { recursive: true });
70
191
  const templateContent = await fs__default.readFile(templatePath, {
71
192
  encoding: "utf-8"
72
193
  });
73
194
  const renderedContent = ejs__default.render(templateContent, ejsData);
74
- await fs__default.writeFile(outputPath, renderedContent);
195
+ await fs__default.writeFile(outputPath, renderedContent, { encoding: "utf-8" });
75
196
  }
76
197
  }
77
- consola.consola.start(`Installing dependencies with ${packageManager}...`);
78
- try {
79
- await runCommand(packageManager, ["install"], outputDir);
80
- consola.consola.success(`Dependencies installed successfully.`);
81
- } catch (e) {
82
- consola.consola.fail("Failed to install dependencies.");
83
- consola.consola.error(e);
84
- process.exit(1);
198
+ await handleClaspSetup(
199
+ clasp,
200
+ projectName,
201
+ outputDir,
202
+ claspProjectId,
203
+ packageManager
204
+ );
205
+ if (install) {
206
+ consola.consola.start(`Installing dependencies with ${packageManager}...`);
207
+ try {
208
+ await runCommand(packageManager, ["install"], outputDir);
209
+ consola.consola.success(`Dependencies installed successfully.`);
210
+ } catch (e) {
211
+ consola.consola.fail("Failed to install dependencies. Please do it manually.");
212
+ consola.consola.error(e);
213
+ }
85
214
  }
86
215
  consola.consola.start(`Initializing Git repository...`);
87
216
  try {
@@ -93,15 +222,28 @@ async function generateProject({
93
222
  outputDir
94
223
  );
95
224
  consola.consola.success(`Git repository initialized successfully.`);
96
- } catch {
225
+ } catch (e) {
97
226
  consola.consola.fail("Failed to initialize Git repository. Please do it manually.");
227
+ consola.consola.error(e);
98
228
  }
99
229
  consola.consola.success(`Project '${projectName}' created successfully!`);
100
- consola.consola.log(`
230
+ const messages = [];
231
+ projectName !== "." && messages.push(` cd ${projectName}`);
232
+ !install && messages.push(` ${packageManager} install`);
233
+ templateType !== "vanilla-ts" && messages.push(` ${packageManager} dev`);
234
+ if (messages.length > 0) {
235
+ consola.consola.log(`
101
236
  To get started, run:
102
237
  `);
103
- projectName !== "." && consola.consola.log(` cd ${projectName}`);
104
- templateType !== "vanilla-ts" ? consola.consola.log(` ${packageManager} dev`) : consola.consola.log(` ...and write your GAS code!`);
238
+ for (const message of messages) {
239
+ consola.consola.log(message);
240
+ }
241
+ consola.consola.log("");
242
+ consola.consola.log(`...and write your GAS code!`);
243
+ } else {
244
+ consola.consola.log(`
245
+ To get started, write your GAS code in \`src/\`!`);
246
+ }
105
247
  }
106
248
 
107
249
  exports.generateProject = generateProject;
package/dist/index.d.cts CHANGED
@@ -1,10 +1,16 @@
1
+ type PackageManager = 'npm' | 'pnpm' | 'yarn';
2
+ type TemplateType = 'vanilla-ts' | 'react-tsx';
3
+ type ClaspOption = 'create' | 'list' | 'input' | 'skip';
1
4
  interface ProjectOptions {
2
5
  projectName: string;
3
- packageManager: "npm" | "pnpm" | "yarn";
4
- templateType: "vanilla-ts" | "react-tsx";
6
+ packageManager: PackageManager;
7
+ templateType: TemplateType;
8
+ clasp: ClaspOption;
9
+ claspProjectId?: string | undefined;
10
+ install: boolean;
5
11
  }
6
12
 
7
- declare function runCommand(command: string, args: string[], cwd: string): Promise<void>;
8
- declare function generateProject({ projectName, packageManager, templateType, }: ProjectOptions): Promise<void>;
13
+ declare function runCommand(command: string, args: string[], cwd: string, capture?: boolean): Promise<string>;
14
+ declare function generateProject({ projectName, packageManager, templateType, clasp, claspProjectId, install, }: ProjectOptions): Promise<void>;
9
15
 
10
16
  export { generateProject, runCommand };
package/dist/index.d.mts CHANGED
@@ -1,10 +1,16 @@
1
+ type PackageManager = 'npm' | 'pnpm' | 'yarn';
2
+ type TemplateType = 'vanilla-ts' | 'react-tsx';
3
+ type ClaspOption = 'create' | 'list' | 'input' | 'skip';
1
4
  interface ProjectOptions {
2
5
  projectName: string;
3
- packageManager: "npm" | "pnpm" | "yarn";
4
- templateType: "vanilla-ts" | "react-tsx";
6
+ packageManager: PackageManager;
7
+ templateType: TemplateType;
8
+ clasp: ClaspOption;
9
+ claspProjectId?: string | undefined;
10
+ install: boolean;
5
11
  }
6
12
 
7
- declare function runCommand(command: string, args: string[], cwd: string): Promise<void>;
8
- declare function generateProject({ projectName, packageManager, templateType, }: ProjectOptions): Promise<void>;
13
+ declare function runCommand(command: string, args: string[], cwd: string, capture?: boolean): Promise<string>;
14
+ declare function generateProject({ projectName, packageManager, templateType, clasp, claspProjectId, install, }: ProjectOptions): Promise<void>;
9
15
 
10
16
  export { generateProject, runCommand };
package/dist/index.d.ts CHANGED
@@ -1,10 +1,16 @@
1
+ type PackageManager = 'npm' | 'pnpm' | 'yarn';
2
+ type TemplateType = 'vanilla-ts' | 'react-tsx';
3
+ type ClaspOption = 'create' | 'list' | 'input' | 'skip';
1
4
  interface ProjectOptions {
2
5
  projectName: string;
3
- packageManager: "npm" | "pnpm" | "yarn";
4
- templateType: "vanilla-ts" | "react-tsx";
6
+ packageManager: PackageManager;
7
+ templateType: TemplateType;
8
+ clasp: ClaspOption;
9
+ claspProjectId?: string | undefined;
10
+ install: boolean;
5
11
  }
6
12
 
7
- declare function runCommand(command: string, args: string[], cwd: string): Promise<void>;
8
- declare function generateProject({ projectName, packageManager, templateType, }: ProjectOptions): Promise<void>;
13
+ declare function runCommand(command: string, args: string[], cwd: string, capture?: boolean): Promise<string>;
14
+ declare function generateProject({ projectName, packageManager, templateType, clasp, claspProjectId, install, }: ProjectOptions): Promise<void>;
9
15
 
10
16
  export { generateProject, runCommand };
package/dist/index.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import fs from 'node:fs/promises';
3
3
  import path from 'node:path';
4
+ import { select } from '@inquirer/prompts';
4
5
  import { consola } from 'consola';
5
6
  import ejs from 'ejs';
6
7
  import { glob } from 'glob';
@@ -14,18 +15,30 @@ import __cjs_mod__ from 'module';
14
15
  const __filename = __cjs_url__.fileURLToPath(import.meta.url);
15
16
  const __dirname = __cjs_path__.dirname(__filename);
16
17
  const require = __cjs_mod__.createRequire(import.meta.url);
17
- async function runCommand(command, args, cwd) {
18
+ async function runCommand(command, args, cwd, capture = false) {
18
19
  return new Promise((resolve, reject) => {
19
20
  const child = spawn(command, args, {
20
21
  cwd,
21
- stdio: "inherit",
22
+ stdio: capture ? "pipe" : "inherit",
22
23
  shell: true
23
24
  });
25
+ let stdout = "";
26
+ let stderr = "";
27
+ if (capture) {
28
+ child.stdout?.on("data", (data) => {
29
+ stdout += data.toString();
30
+ });
31
+ child.stderr?.on("data", (data) => {
32
+ stderr += data.toString();
33
+ });
34
+ }
24
35
  child.on("close", (code) => {
25
36
  if (code === 0) {
26
- resolve();
37
+ resolve(stdout.trim());
27
38
  } else {
28
- reject(new Error(`Command failed with exit code ${code}`));
39
+ const errorMsg = `Command failed with exit code ${code}${stderr ? `:
40
+ ${stderr}` : ""}`;
41
+ reject(new Error(errorMsg));
29
42
  }
30
43
  });
31
44
  child.on("error", (err) => {
@@ -33,10 +46,119 @@ async function runCommand(command, args, cwd) {
33
46
  });
34
47
  });
35
48
  }
49
+ async function handleClaspSetup(claspOption, projectName, outputDir, claspProjectId, packageManager = "npm") {
50
+ if (claspOption === "skip") {
51
+ return;
52
+ }
53
+ const npxLikeCommand = packageManager === "npm" ? "npx" : packageManager === "pnpm" ? "pnpx" : "yarn";
54
+ consola.start(
55
+ `Setting up .clasp.json with \`${npxLikeCommand} @google/clasp\`...`
56
+ );
57
+ if (claspOption === "create" || claspOption === "list") {
58
+ try {
59
+ await runCommand(npxLikeCommand, ["@google/clasp", "status"], outputDir);
60
+ } catch {
61
+ consola.error(
62
+ `It seems you are not logged in to clasp. Please run \`${npxLikeCommand} @google/clasp login\` and try again.`
63
+ );
64
+ return;
65
+ }
66
+ }
67
+ let scriptId;
68
+ switch (claspOption) {
69
+ case "create":
70
+ try {
71
+ const result = await runCommand(
72
+ npxLikeCommand,
73
+ [
74
+ "@google/clasp",
75
+ "create",
76
+ "--title",
77
+ `"${projectName}"`,
78
+ "--type",
79
+ "standalone"
80
+ ],
81
+ outputDir,
82
+ true
83
+ );
84
+ const match = result.match(/Created new script: .+\/d\/(.+)\/edit/);
85
+ if (match?.[1]) {
86
+ scriptId = match[1];
87
+ consola.info(`Created new Apps Script project with ID: ${scriptId}`);
88
+ } else {
89
+ throw new Error("Could not parse scriptId from clasp output.");
90
+ }
91
+ } catch (e) {
92
+ consola.error("Failed to create new Apps Script project.", e);
93
+ return;
94
+ }
95
+ break;
96
+ case "list":
97
+ try {
98
+ const listOutput = await runCommand(
99
+ npxLikeCommand,
100
+ ["@google/clasp", "list"],
101
+ outputDir,
102
+ true
103
+ );
104
+ const projects = listOutput.split("\n").slice(1).map((line) => {
105
+ const parts = line.split(" - ");
106
+ if (parts.length >= 2) {
107
+ const name = parts[0]?.trim();
108
+ const url = parts[1]?.trim();
109
+ if (!name || !url) return null;
110
+ const match = url.match(/\/d\/(.+)\/edit/);
111
+ const scriptId2 = match?.[1];
112
+ if (scriptId2) {
113
+ return { name, value: scriptId2 };
114
+ }
115
+ }
116
+ return null;
117
+ }).filter((p) => p !== null);
118
+ if (projects.length === 0) {
119
+ consola.warn(
120
+ "No existing Apps Script projects found. Please create one first."
121
+ );
122
+ return;
123
+ }
124
+ scriptId = await select({
125
+ message: "Choose an existing Apps Script project:",
126
+ choices: projects
127
+ });
128
+ } catch (e) {
129
+ consola.error("Failed to list Apps Script projects.", e);
130
+ return;
131
+ }
132
+ break;
133
+ case "input":
134
+ scriptId = claspProjectId;
135
+ break;
136
+ }
137
+ if (scriptId) {
138
+ const claspJsonPath = path.join(outputDir, ".clasp.json");
139
+ let claspJson = {
140
+ scriptId
141
+ };
142
+ let successMessage = `.clasp.json created successfully with scriptId: ${scriptId}`;
143
+ try {
144
+ const existingContent = await fs.readFile(claspJsonPath, "utf-8");
145
+ const existingJson = JSON.parse(existingContent);
146
+ claspJson = { ...existingJson, scriptId };
147
+ successMessage = `.clasp.json updated successfully with scriptId: ${scriptId}`;
148
+ } catch {
149
+ }
150
+ const claspJsonContent = JSON.stringify(claspJson, null, 2);
151
+ await fs.writeFile(claspJsonPath, claspJsonContent, { encoding: "utf-8" });
152
+ consola.success(successMessage);
153
+ }
154
+ }
36
155
  async function generateProject({
37
156
  projectName,
38
157
  packageManager,
39
- templateType
158
+ templateType,
159
+ clasp,
160
+ claspProjectId,
161
+ install
40
162
  }) {
41
163
  const outputDir = path.resolve(process.cwd(), projectName);
42
164
  const templateBaseDir = path.resolve(__dirname, "..", "dist", "templates");
@@ -49,6 +171,7 @@ async function generateProject({
49
171
  await fs.access(outputDir);
50
172
  consola.error(`Directory ${projectName} already exists.`);
51
173
  process.exit(1);
174
+ return;
52
175
  } catch {
53
176
  }
54
177
  await fs.mkdir(outputDir, { recursive: true });
@@ -59,30 +182,36 @@ async function generateProject({
59
182
  const files = await glob("./**/*", {
60
183
  cwd: dir,
61
184
  nodir: true,
62
- dot: true,
63
- dotRelative: true,
64
- follow: true,
65
- windowsPathsNoEscape: true
185
+ dot: true
66
186
  });
67
187
  for (const file of files) {
68
- const templatePath = path.join(dir, file);
69
- const outputPath = path.join(outputDir, file.replace(".ejs", ""));
188
+ const relativePath = path.relative(dir, path.resolve(dir, file));
189
+ const templatePath = path.join(dir, relativePath);
190
+ const outputPath = path.join(outputDir, relativePath.replace(".ejs", ""));
70
191
  await fs.mkdir(path.dirname(outputPath), { recursive: true });
71
192
  const templateContent = await fs.readFile(templatePath, {
72
193
  encoding: "utf-8"
73
194
  });
74
195
  const renderedContent = ejs.render(templateContent, ejsData);
75
- await fs.writeFile(outputPath, renderedContent);
196
+ await fs.writeFile(outputPath, renderedContent, { encoding: "utf-8" });
76
197
  }
77
198
  }
78
- consola.start(`Installing dependencies with ${packageManager}...`);
79
- try {
80
- await runCommand(packageManager, ["install"], outputDir);
81
- consola.success(`Dependencies installed successfully.`);
82
- } catch (e) {
83
- consola.fail("Failed to install dependencies.");
84
- consola.error(e);
85
- process.exit(1);
199
+ await handleClaspSetup(
200
+ clasp,
201
+ projectName,
202
+ outputDir,
203
+ claspProjectId,
204
+ packageManager
205
+ );
206
+ if (install) {
207
+ consola.start(`Installing dependencies with ${packageManager}...`);
208
+ try {
209
+ await runCommand(packageManager, ["install"], outputDir);
210
+ consola.success(`Dependencies installed successfully.`);
211
+ } catch (e) {
212
+ consola.fail("Failed to install dependencies. Please do it manually.");
213
+ consola.error(e);
214
+ }
86
215
  }
87
216
  consola.start(`Initializing Git repository...`);
88
217
  try {
@@ -94,15 +223,28 @@ async function generateProject({
94
223
  outputDir
95
224
  );
96
225
  consola.success(`Git repository initialized successfully.`);
97
- } catch {
226
+ } catch (e) {
98
227
  consola.fail("Failed to initialize Git repository. Please do it manually.");
228
+ consola.error(e);
99
229
  }
100
230
  consola.success(`Project '${projectName}' created successfully!`);
101
- consola.log(`
231
+ const messages = [];
232
+ projectName !== "." && messages.push(` cd ${projectName}`);
233
+ !install && messages.push(` ${packageManager} install`);
234
+ templateType !== "vanilla-ts" && messages.push(` ${packageManager} dev`);
235
+ if (messages.length > 0) {
236
+ consola.log(`
102
237
  To get started, run:
103
238
  `);
104
- projectName !== "." && consola.log(` cd ${projectName}`);
105
- templateType !== "vanilla-ts" ? consola.log(` ${packageManager} dev`) : consola.log(` ...and write your GAS code!`);
239
+ for (const message of messages) {
240
+ consola.log(message);
241
+ }
242
+ consola.log("");
243
+ consola.log(`...and write your GAS code!`);
244
+ } else {
245
+ consola.log(`
246
+ To get started, write your GAS code in \`src/\`!`);
247
+ }
106
248
  }
107
249
 
108
250
  export { generateProject, runCommand };
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title><% projectName %></title>
7
+ <title><%= projectName %></title>
8
8
  </head>
9
9
  <body>
10
10
  <div id="root"></div>
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "<% projectName %>",
2
+ "name": "<%= projectName %>",
3
3
  "private": true,
4
4
  "version": "0.0.0",
5
5
  "type": "module",
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "<% projectName %>",
2
+ "name": "<%= projectName %>",
3
3
  "private": true,
4
4
  "version": "0.0.0",
5
5
  "type": "module",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ciderjs/gasbombe",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "A TypeScript Project Generator for GoogleAppsScript, available as CLI",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -15,12 +15,12 @@
15
15
  "module": "dist/index.mjs",
16
16
  "types": "dist/index.d.ts",
17
17
  "scripts": {
18
+ "dev": "jiti src/cli",
18
19
  "tsc": "tsgo --noEmit src",
19
20
  "check": "biome check --write",
20
21
  "generate": "jiti scripts/copyTemplates",
21
- "prebuild": "pnpm run tsc",
22
- "build": "unbuild",
23
- "postbuild": "pnpm run generate",
22
+ "test": "vitest run --coverage",
23
+ "build": "pnpm run tsc && pnpm run test --silent && unbuild && pnpm run generate",
24
24
  "postinstall": "pnpm run generate"
25
25
  },
26
26
  "keywords": [
@@ -29,7 +29,7 @@
29
29
  "typescript",
30
30
  "react"
31
31
  ],
32
- "author": "luthpg",
32
+ "author": "ciderjs/luthpg",
33
33
  "license": "MIT",
34
34
  "repository": {
35
35
  "type": "git",
@@ -51,8 +51,10 @@
51
51
  "@types/ejs": "^3.1.5",
52
52
  "@types/node": "^24.5.2",
53
53
  "@typescript/native-preview": "7.0.0-dev.20250925.1",
54
+ "@vitest/coverage-v8": "3.2.4",
54
55
  "jiti": "^2.6.0",
55
56
  "typescript": "^5.9.2",
56
- "unbuild": "^3.6.1"
57
+ "unbuild": "^3.6.1",
58
+ "vitest": "^3.2.4"
57
59
  }
58
60
  }
package/src/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import fs from 'node:fs/promises';
3
3
  import path from 'node:path';
4
+ import { select } from '@inquirer/prompts';
4
5
  import { consola } from 'consola';
5
6
  import ejs from 'ejs';
6
7
  import { glob } from 'glob';
@@ -10,30 +11,186 @@ export async function runCommand(
10
11
  command: string,
11
12
  args: string[],
12
13
  cwd: string,
13
- ): Promise<void> {
14
+ capture = false,
15
+ ): Promise<string> {
14
16
  return new Promise((resolve, reject) => {
15
17
  const child = spawn(command, args, {
16
18
  cwd,
17
- stdio: 'inherit',
19
+ stdio: capture ? 'pipe' : 'inherit',
18
20
  shell: true,
19
21
  });
22
+
23
+ let stdout = '';
24
+ let stderr = '';
25
+
26
+ if (capture) {
27
+ child.stdout?.on('data', (data) => {
28
+ stdout += data.toString();
29
+ });
30
+ child.stderr?.on('data', (data) => {
31
+ stderr += data.toString();
32
+ });
33
+ }
34
+
20
35
  child.on('close', (code) => {
21
36
  if (code === 0) {
22
- resolve();
37
+ resolve(stdout.trim());
23
38
  } else {
24
- reject(new Error(`Command failed with exit code ${code}`));
39
+ const errorMsg = `Command failed with exit code ${code}${stderr ? `:\n${stderr}` : ''}`;
40
+ reject(new Error(errorMsg));
25
41
  }
26
42
  });
43
+
27
44
  child.on('error', (err) => {
28
45
  reject(err);
29
46
  });
30
47
  });
31
48
  }
32
49
 
50
+ async function handleClaspSetup(
51
+ claspOption: ProjectOptions['clasp'],
52
+ projectName: string,
53
+ outputDir: string,
54
+ claspProjectId: string | undefined,
55
+ packageManager: ProjectOptions['packageManager'] = 'npm',
56
+ ): Promise<void> {
57
+ if (claspOption === 'skip') {
58
+ return;
59
+ }
60
+
61
+ const npxLikeCommand =
62
+ packageManager === 'npm'
63
+ ? 'npx'
64
+ : packageManager === 'pnpm'
65
+ ? 'pnpx'
66
+ : 'yarn';
67
+
68
+ consola.start(
69
+ `Setting up .clasp.json with \`${npxLikeCommand} @google/clasp\`...`,
70
+ );
71
+
72
+ if (claspOption === 'create' || claspOption === 'list') {
73
+ try {
74
+ await runCommand(npxLikeCommand, ['@google/clasp', 'status'], outputDir);
75
+ } catch {
76
+ consola.error(
77
+ `It seems you are not logged in to clasp. Please run \`${npxLikeCommand} @google/clasp login\` and try again.`,
78
+ );
79
+ return;
80
+ }
81
+ }
82
+
83
+ let scriptId: string | undefined;
84
+
85
+ switch (claspOption) {
86
+ case 'create':
87
+ try {
88
+ const result = await runCommand(
89
+ npxLikeCommand,
90
+ [
91
+ '@google/clasp',
92
+ 'create',
93
+ '--title',
94
+ `"${projectName}"`,
95
+ '--type',
96
+ 'standalone',
97
+ ],
98
+ outputDir,
99
+ true,
100
+ );
101
+ const match = result.match(/Created new script: .+\/d\/(.+)\/edit/);
102
+ if (match?.[1]) {
103
+ scriptId = match[1];
104
+ consola.info(`Created new Apps Script project with ID: ${scriptId}`);
105
+ } else {
106
+ throw new Error('Could not parse scriptId from clasp output.');
107
+ }
108
+ } catch (e) {
109
+ consola.error('Failed to create new Apps Script project.', e);
110
+ return;
111
+ }
112
+ break;
113
+
114
+ case 'list':
115
+ try {
116
+ const listOutput = await runCommand(
117
+ npxLikeCommand,
118
+ ['@google/clasp', 'list'],
119
+ outputDir,
120
+ true,
121
+ );
122
+ const projects = listOutput
123
+ .split('\n')
124
+ .slice(1) // Skip header
125
+ .map((line) => {
126
+ const parts = line.split(' - ');
127
+ if (parts.length >= 2) {
128
+ const name = parts[0]?.trim();
129
+ const url = parts[1]?.trim();
130
+ if (!name || !url) return null;
131
+
132
+ const match = url.match(/\/d\/(.+)\/edit/);
133
+ const scriptId = match?.[1];
134
+
135
+ if (scriptId) {
136
+ return { name, value: scriptId };
137
+ }
138
+ }
139
+ return null;
140
+ })
141
+ .filter((p): p is { name: string; value: string } => p !== null);
142
+
143
+ if (projects.length === 0) {
144
+ consola.warn(
145
+ 'No existing Apps Script projects found. Please create one first.',
146
+ );
147
+ return;
148
+ }
149
+
150
+ scriptId = await select({
151
+ message: 'Choose an existing Apps Script project:',
152
+ choices: projects,
153
+ });
154
+ } catch (e) {
155
+ consola.error('Failed to list Apps Script projects.', e);
156
+ return;
157
+ }
158
+ break;
159
+
160
+ case 'input':
161
+ scriptId = claspProjectId;
162
+ break;
163
+ }
164
+
165
+ if (scriptId) {
166
+ const claspJsonPath = path.join(outputDir, '.clasp.json');
167
+ let claspJson: { scriptId: string; [key: string]: string | string[] } = {
168
+ scriptId,
169
+ };
170
+ let successMessage = `.clasp.json created successfully with scriptId: ${scriptId}`;
171
+
172
+ try {
173
+ const existingContent = await fs.readFile(claspJsonPath, 'utf-8');
174
+ const existingJson = JSON.parse(existingContent);
175
+ claspJson = { ...existingJson, scriptId };
176
+ successMessage = `.clasp.json updated successfully with scriptId: ${scriptId}`;
177
+ } catch {
178
+ // If file doesn't exist or is invalid, we'll just create a new one.
179
+ }
180
+
181
+ const claspJsonContent = JSON.stringify(claspJson, null, 2);
182
+ await fs.writeFile(claspJsonPath, claspJsonContent, { encoding: 'utf-8' });
183
+ consola.success(successMessage);
184
+ }
185
+ }
186
+
33
187
  export async function generateProject({
34
188
  projectName,
35
189
  packageManager,
36
190
  templateType,
191
+ clasp,
192
+ claspProjectId,
193
+ install,
37
194
  }: ProjectOptions): Promise<void> {
38
195
  const outputDir = path.resolve(process.cwd(), projectName);
39
196
  const templateBaseDir = path.resolve(__dirname, '..', 'dist', 'templates');
@@ -48,8 +205,9 @@ export async function generateProject({
48
205
  await fs.access(outputDir);
49
206
  consola.error(`Directory ${projectName} already exists.`);
50
207
  process.exit(1);
208
+ return;
51
209
  } catch {
52
- // Directory dose not exits, which is what we want.
210
+ // Directory does not exist, which is what we want.
53
211
  }
54
212
 
55
213
  await fs.mkdir(outputDir, { recursive: true });
@@ -62,14 +220,12 @@ export async function generateProject({
62
220
  cwd: dir,
63
221
  nodir: true,
64
222
  dot: true,
65
- dotRelative: true,
66
- follow: true,
67
- windowsPathsNoEscape: true,
68
223
  });
69
224
 
70
225
  for (const file of files) {
71
- const templatePath = path.join(dir, file);
72
- const outputPath = path.join(outputDir, file.replace('.ejs', ''));
226
+ const relativePath = path.relative(dir, path.resolve(dir, file));
227
+ const templatePath = path.join(dir, relativePath);
228
+ const outputPath = path.join(outputDir, relativePath.replace('.ejs', ''));
73
229
 
74
230
  await fs.mkdir(path.dirname(outputPath), { recursive: true });
75
231
 
@@ -77,18 +233,27 @@ export async function generateProject({
77
233
  encoding: 'utf-8',
78
234
  });
79
235
  const renderedContent = ejs.render(templateContent, ejsData);
80
- await fs.writeFile(outputPath, renderedContent);
236
+ await fs.writeFile(outputPath, renderedContent, { encoding: 'utf-8' });
81
237
  }
82
238
  }
83
239
 
84
- consola.start(`Installing dependencies with ${packageManager}...`);
85
- try {
86
- await runCommand(packageManager, ['install'], outputDir);
87
- consola.success(`Dependencies installed successfully.`);
88
- } catch (e) {
89
- consola.fail('Failed to install dependencies.');
90
- consola.error(e);
91
- process.exit(1);
240
+ await handleClaspSetup(
241
+ clasp,
242
+ projectName,
243
+ outputDir,
244
+ claspProjectId,
245
+ packageManager,
246
+ );
247
+
248
+ if (install) {
249
+ consola.start(`Installing dependencies with ${packageManager}...`);
250
+ try {
251
+ await runCommand(packageManager, ['install'], outputDir);
252
+ consola.success(`Dependencies installed successfully.`);
253
+ } catch (e) {
254
+ consola.fail('Failed to install dependencies. Please do it manually.');
255
+ consola.error(e);
256
+ }
92
257
  }
93
258
 
94
259
  consola.start(`Initializing Git repository...`);
@@ -101,14 +266,26 @@ export async function generateProject({
101
266
  outputDir,
102
267
  );
103
268
  consola.success(`Git repository initialized successfully.`);
104
- } catch {
269
+ } catch (e) {
105
270
  consola.fail('Failed to initialize Git repository. Please do it manually.');
271
+ consola.error(e);
106
272
  }
107
273
 
108
274
  consola.success(`Project '${projectName}' created successfully!`);
109
- consola.log(`\nTo get started, run:\n`);
110
- projectName !== '.' && consola.log(` cd ${projectName}`);
111
- templateType !== 'vanilla-ts'
112
- ? consola.log(` ${packageManager} dev`)
113
- : consola.log(` ...and write your GAS code!`);
275
+
276
+ const messages: string[] = [];
277
+ projectName !== '.' && messages.push(` cd ${projectName}`);
278
+ !install && messages.push(` ${packageManager} install`);
279
+ templateType !== 'vanilla-ts' && messages.push(` ${packageManager} dev`);
280
+
281
+ if (messages.length > 0) {
282
+ consola.log(`\nTo get started, run:\n`);
283
+ for (const message of messages) {
284
+ consola.log(message);
285
+ }
286
+ consola.log('');
287
+ consola.log(`...and write your GAS code!`);
288
+ } else {
289
+ consola.log(`\nTo get started, write your GAS code in \`src/\`!`);
290
+ }
114
291
  }