@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 +12 -9
- package/README.md +14 -11
- package/dist/cli.cjs +71 -12
- package/dist/cli.mjs +71 -12
- package/dist/index.cjs +166 -24
- package/dist/index.d.cts +10 -4
- package/dist/index.d.mts +10 -4
- package/dist/index.d.ts +10 -4
- package/dist/index.mjs +166 -24
- package/dist/templates/react-tsx/index.html.ejs +1 -1
- package/dist/templates/react-tsx/package.json.ejs +1 -1
- package/dist/templates/vanilla-ts/package.json.ejs +1 -1
- package/package.json +8 -6
- package/src/index.ts +202 -25
package/README.ja.md
CHANGED
|
@@ -44,8 +44,9 @@ gasbombe
|
|
|
44
44
|
以下の項目について質問されます。
|
|
45
45
|
|
|
46
46
|
1. プロジェクト名
|
|
47
|
-
2.
|
|
48
|
-
3.
|
|
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 --
|
|
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
|
-
| `--
|
|
65
|
-
| `--
|
|
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
|
-
# **
|
|
1
|
+
# **Gasbombe**
|
|
2
2
|
|
|
3
|
-
[](./README.ja.md)
|
|
4
4
|
[](LICENSE)
|
|
5
5
|
[](https://www.npmjs.com/package/@ciderjs/gasbombe)
|
|
6
6
|
[](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.
|
|
48
|
-
3.
|
|
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 --
|
|
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
|
|
62
|
-
|
|
|
63
|
-
| `--name`
|
|
64
|
-
| `--
|
|
65
|
-
| `--
|
|
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.
|
|
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: "
|
|
47
|
-
choices: [
|
|
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({
|
|
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.
|
|
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: "
|
|
45
|
-
choices: [
|
|
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({
|
|
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
|
-
|
|
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
|
|
68
|
-
const
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
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
|
-
|
|
104
|
-
|
|
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:
|
|
4
|
-
templateType:
|
|
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<
|
|
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:
|
|
4
|
-
templateType:
|
|
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<
|
|
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:
|
|
4
|
-
templateType:
|
|
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<
|
|
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
|
-
|
|
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
|
|
69
|
-
const
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
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
|
-
|
|
105
|
-
|
|
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
|
|
7
|
+
<title><%= projectName %></title>
|
|
8
8
|
</head>
|
|
9
9
|
<body>
|
|
10
10
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ciderjs/gasbombe",
|
|
3
|
-
"version": "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
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
72
|
-
const
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
}
|