@ciderjs/gasbombe 0.1.0 → 0.2.1
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 +98 -14
- package/dist/cli.mjs +98 -14
- package/dist/index.cjs +200 -29
- 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 +200 -29
- 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 +241 -30
- package/dist/templates/react-tsx/pnpm-lock.yaml.ejs +0 -6015
- package/dist/templates/react-tsx/pnpm-workspace.yaml.ejs +0 -3
- package/dist/templates/vanilla-ts/pnpm-lock.yaml.ejs +0 -5300
- package/dist/templates/vanilla-ts/pnpm-workspace.yaml.ejs +0 -3
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,201 @@ 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
|
+
let existingClaspJson: { scriptId: string; [key: string]: string | string[] } | null = null;
|
|
85
|
+
|
|
86
|
+
switch (claspOption) {
|
|
87
|
+
case 'create':
|
|
88
|
+
try {
|
|
89
|
+
const claspJsonPath = path.join(outputDir, '.clasp.json');
|
|
90
|
+
try {
|
|
91
|
+
const existingContent = await fs.readFile(claspJsonPath, 'utf-8');
|
|
92
|
+
existingClaspJson = JSON.parse(existingContent);
|
|
93
|
+
await fs.unlink(claspJsonPath);
|
|
94
|
+
} catch {
|
|
95
|
+
// No existing .clasp.json, which is fine.
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const result = await runCommand(
|
|
99
|
+
npxLikeCommand,
|
|
100
|
+
[
|
|
101
|
+
'@google/clasp',
|
|
102
|
+
'create',
|
|
103
|
+
'--title',
|
|
104
|
+
`"${projectName}"`,
|
|
105
|
+
'--type',
|
|
106
|
+
'standalone',
|
|
107
|
+
],
|
|
108
|
+
outputDir,
|
|
109
|
+
true,
|
|
110
|
+
);
|
|
111
|
+
const match = result.match(/Created new script: .+\/d\/(.+)\/edit/);
|
|
112
|
+
if (match?.[1]) {
|
|
113
|
+
scriptId = match[1];
|
|
114
|
+
consola.info(`Created new Apps Script project with ID: ${scriptId}`);
|
|
115
|
+
} else {
|
|
116
|
+
throw new Error('Could not parse scriptId from clasp output.');
|
|
117
|
+
}
|
|
118
|
+
} catch (e) {
|
|
119
|
+
consola.error('Failed to create new Apps Script project.', e);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
break;
|
|
123
|
+
|
|
124
|
+
case 'list':
|
|
125
|
+
try {
|
|
126
|
+
const listOutput = await runCommand(
|
|
127
|
+
npxLikeCommand,
|
|
128
|
+
['@google/clasp', 'list'],
|
|
129
|
+
outputDir,
|
|
130
|
+
true,
|
|
131
|
+
);
|
|
132
|
+
const projects = listOutput
|
|
133
|
+
.split('\n')
|
|
134
|
+
.slice(1) // Skip header
|
|
135
|
+
.map((line) => {
|
|
136
|
+
const parts = line.split(' - ');
|
|
137
|
+
if (parts.length >= 2) {
|
|
138
|
+
const name = parts[0]?.trim();
|
|
139
|
+
const url = parts[1]?.trim();
|
|
140
|
+
if (!name || !url) return null;
|
|
141
|
+
|
|
142
|
+
const match = url.match(/\/d\/(.+)\/edit/);
|
|
143
|
+
const scriptId = match?.[1];
|
|
144
|
+
|
|
145
|
+
if (scriptId) {
|
|
146
|
+
return { name, value: scriptId };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
})
|
|
151
|
+
.filter((p): p is { name: string; value: string } => p !== null);
|
|
152
|
+
|
|
153
|
+
if (projects.length === 0) {
|
|
154
|
+
consola.warn(
|
|
155
|
+
'No existing Apps Script projects found. Please create one first.',
|
|
156
|
+
);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
scriptId = await select({
|
|
161
|
+
message: 'Choose an existing Apps Script project:',
|
|
162
|
+
choices: projects,
|
|
163
|
+
});
|
|
164
|
+
} catch (e) {
|
|
165
|
+
consola.error('Failed to list Apps Script projects.', e);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
break;
|
|
169
|
+
|
|
170
|
+
case 'input':
|
|
171
|
+
scriptId = claspProjectId;
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (scriptId) {
|
|
176
|
+
const claspJsonPath = path.join(outputDir, '.clasp.json');
|
|
177
|
+
let claspJson: { scriptId: string; [key: string]: string | string[] } = {
|
|
178
|
+
scriptId,
|
|
179
|
+
};
|
|
180
|
+
let successMessage = `.clasp.json created successfully with scriptId: ${scriptId}`;
|
|
181
|
+
|
|
182
|
+
if (existingClaspJson) {
|
|
183
|
+
claspJson = { ...existingClaspJson, scriptId };
|
|
184
|
+
successMessage = `.clasp.json updated successfully with scriptId: ${scriptId}`;
|
|
185
|
+
} else {
|
|
186
|
+
try {
|
|
187
|
+
const existingContent = await fs.readFile(claspJsonPath, 'utf-8');
|
|
188
|
+
const currentClaspJson = JSON.parse(existingContent);
|
|
189
|
+
claspJson = { ...currentClaspJson, scriptId };
|
|
190
|
+
successMessage = `.clasp.json updated successfully with scriptId: ${scriptId}`;
|
|
191
|
+
} catch {
|
|
192
|
+
// If file doesn't exist or is invalid, we'll just create a new one.
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const claspJsonContent = JSON.stringify(claspJson, null, 2);
|
|
197
|
+
await fs.writeFile(claspJsonPath, claspJsonContent, { encoding: 'utf-8' });
|
|
198
|
+
consola.success(successMessage);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
33
202
|
export async function generateProject({
|
|
34
203
|
projectName,
|
|
35
204
|
packageManager,
|
|
36
205
|
templateType,
|
|
206
|
+
clasp,
|
|
207
|
+
claspProjectId,
|
|
208
|
+
install,
|
|
37
209
|
}: ProjectOptions): Promise<void> {
|
|
38
210
|
const outputDir = path.resolve(process.cwd(), projectName);
|
|
39
211
|
const templateBaseDir = path.resolve(__dirname, '..', 'dist', 'templates');
|
|
@@ -44,12 +216,32 @@ export async function generateProject({
|
|
|
44
216
|
`Creating a new Project for GoogleAppsScript in ${outputDir}...`,
|
|
45
217
|
);
|
|
46
218
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
219
|
+
if (projectName === '.') {
|
|
220
|
+
try {
|
|
221
|
+
const files = await fs.readdir(outputDir);
|
|
222
|
+
const relevantFiles = files.filter((file) => !file.startsWith('.'));
|
|
223
|
+
|
|
224
|
+
if (relevantFiles.length > 0) {
|
|
225
|
+
const proceed = await confirm(
|
|
226
|
+
'Current directory is not empty. Proceed anyway?',
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
if (!proceed) {
|
|
230
|
+
consola.warn('Operation cancelled.');
|
|
231
|
+
process.exit(0);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
} catch {
|
|
235
|
+
// Directory does not exist, then mkdir will create it.
|
|
236
|
+
}
|
|
237
|
+
} else {
|
|
238
|
+
try {
|
|
239
|
+
await fs.access(outputDir);
|
|
240
|
+
consola.error(`Directory ${projectName} already exists.`);
|
|
241
|
+
process.exit(1);
|
|
242
|
+
} catch {
|
|
243
|
+
// Directory does not exist, which is what we want.
|
|
244
|
+
}
|
|
53
245
|
}
|
|
54
246
|
|
|
55
247
|
await fs.mkdir(outputDir, { recursive: true });
|
|
@@ -62,14 +254,12 @@ export async function generateProject({
|
|
|
62
254
|
cwd: dir,
|
|
63
255
|
nodir: true,
|
|
64
256
|
dot: true,
|
|
65
|
-
dotRelative: true,
|
|
66
|
-
follow: true,
|
|
67
|
-
windowsPathsNoEscape: true,
|
|
68
257
|
});
|
|
69
258
|
|
|
70
259
|
for (const file of files) {
|
|
71
|
-
const
|
|
72
|
-
const
|
|
260
|
+
const relativePath = path.relative(dir, path.resolve(dir, file));
|
|
261
|
+
const templatePath = path.join(dir, relativePath);
|
|
262
|
+
const outputPath = path.join(outputDir, relativePath.replace('.ejs', ''));
|
|
73
263
|
|
|
74
264
|
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
75
265
|
|
|
@@ -77,18 +267,27 @@ export async function generateProject({
|
|
|
77
267
|
encoding: 'utf-8',
|
|
78
268
|
});
|
|
79
269
|
const renderedContent = ejs.render(templateContent, ejsData);
|
|
80
|
-
await fs.writeFile(outputPath, renderedContent);
|
|
270
|
+
await fs.writeFile(outputPath, renderedContent, { encoding: 'utf-8' });
|
|
81
271
|
}
|
|
82
272
|
}
|
|
83
273
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
274
|
+
await handleClaspSetup(
|
|
275
|
+
clasp,
|
|
276
|
+
projectName,
|
|
277
|
+
outputDir,
|
|
278
|
+
claspProjectId,
|
|
279
|
+
packageManager,
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
if (install) {
|
|
283
|
+
consola.start(`Installing dependencies with ${packageManager}...`);
|
|
284
|
+
try {
|
|
285
|
+
await runCommand(packageManager, ['install'], outputDir);
|
|
286
|
+
consola.success(`Dependencies installed successfully.`);
|
|
287
|
+
} catch (e) {
|
|
288
|
+
consola.fail('Failed to install dependencies. Please do it manually.');
|
|
289
|
+
consola.error(e);
|
|
290
|
+
}
|
|
92
291
|
}
|
|
93
292
|
|
|
94
293
|
consola.start(`Initializing Git repository...`);
|
|
@@ -101,14 +300,26 @@ export async function generateProject({
|
|
|
101
300
|
outputDir,
|
|
102
301
|
);
|
|
103
302
|
consola.success(`Git repository initialized successfully.`);
|
|
104
|
-
} catch {
|
|
303
|
+
} catch (e) {
|
|
105
304
|
consola.fail('Failed to initialize Git repository. Please do it manually.');
|
|
305
|
+
consola.error(e);
|
|
106
306
|
}
|
|
107
307
|
|
|
108
308
|
consola.success(`Project '${projectName}' created successfully!`);
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
309
|
+
|
|
310
|
+
const messages: string[] = [];
|
|
311
|
+
projectName !== '.' && messages.push(` cd ${projectName}`);
|
|
312
|
+
!install && messages.push(` ${packageManager} install`);
|
|
313
|
+
templateType !== 'vanilla-ts' && messages.push(` ${packageManager} dev`);
|
|
314
|
+
|
|
315
|
+
if (messages.length > 0) {
|
|
316
|
+
consola.log(`\nTo get started, run:\n`);
|
|
317
|
+
for (const message of messages) {
|
|
318
|
+
consola.log(message);
|
|
319
|
+
}
|
|
320
|
+
consola.log('');
|
|
321
|
+
consola.log(`...and write your GAS code!`);
|
|
322
|
+
} else {
|
|
323
|
+
consola.log(`\nTo get started, write your GAS code in \`src/\`!`);
|
|
324
|
+
}
|
|
114
325
|
}
|