@spicato-inc/create-fortenv 0.1.7 → 1.0.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.
@@ -1,8 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import * as p from '@clack/prompts';
3
3
  import { spawn } from 'node:child_process';
4
- import { access, cp, mkdir, readFile, writeFile } from 'node:fs/promises';
4
+ import { createWriteStream } from 'node:fs';
5
+ import { access, cp, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
6
+ import os from 'node:os';
5
7
  import path from 'node:path';
8
+ import { Readable } from 'node:stream';
9
+ import { pipeline } from 'node:stream/promises';
6
10
  import { fileURLToPath } from 'node:url';
7
11
 
8
12
  const TEMPLATES = [
@@ -82,10 +86,30 @@ function cancel() {
82
86
  process.exit(0);
83
87
  }
84
88
 
85
- function templateDir(type) {
86
- const packageName = TEMPLATE_PACKAGES[type];
87
- const resolved = import.meta.resolve(`${packageName}/package.json`);
88
- return path.dirname(fileURLToPath(resolved));
89
+ function run(command, args, options = {}) {
90
+ return new Promise((resolve, reject) => {
91
+ const child = spawn(command, args, {
92
+ ...options,
93
+ stdio: ['ignore', 'pipe', 'pipe'],
94
+ });
95
+ let stdout = '';
96
+ let stderr = '';
97
+ child.stdout.on('data', (chunk) => {
98
+ stdout += chunk;
99
+ });
100
+ child.stderr.on('data', (chunk) => {
101
+ stderr += chunk;
102
+ });
103
+ child.on('error', reject);
104
+ child.on('exit', (code) => {
105
+ if (code === 0) {
106
+ resolve(stdout);
107
+ return;
108
+ }
109
+ const detail = stderr.trim();
110
+ reject(new Error(detail || `${command} が終了コード ${code} で失敗しました`));
111
+ });
112
+ });
89
113
  }
90
114
 
91
115
  async function exists(target) {
@@ -97,12 +121,39 @@ async function exists(target) {
97
121
  }
98
122
  }
99
123
 
100
- async function assertTemplate(type) {
101
- const dir = templateDir(type);
102
- if (!(await exists(path.join(dir, 'package.json')))) {
103
- throw new Error(`雛形が見つかりません: ${dir}`);
124
+ function tarballUrl(value, spec) {
125
+ const url = Array.isArray(value) ? value.at(-1) : value;
126
+ if (typeof url !== 'string' || !url) {
127
+ throw new Error(`雛形の取得先が不正です: ${spec}`);
128
+ }
129
+ return url;
130
+ }
131
+
132
+ async function fetchTemplate(type) {
133
+ const packageName = TEMPLATE_PACKAGES[type];
134
+ const spec = `${packageName}@latest`;
135
+ const root = await mkdtemp(path.join(os.tmpdir(), 'create-fortenv-'));
136
+ try {
137
+ const output = await run('npm', ['view', spec, 'dist.tarball', '--json'], {
138
+ cwd: root,
139
+ });
140
+ const url = tarballUrl(JSON.parse(output), spec);
141
+ const response = await fetch(url);
142
+ if (!response.ok) {
143
+ throw new Error(`雛形の取得に失敗しました: ${spec}`);
144
+ }
145
+ const archive = path.join(root, 'template.tgz');
146
+ await pipeline(Readable.fromWeb(response.body), createWriteStream(archive));
147
+ await run('tar', ['-xzf', archive, '-C', root]);
148
+ const dir = path.join(root, 'package');
149
+ if (!(await exists(path.join(dir, 'package.json')))) {
150
+ throw new Error(`雛形が見つかりません: ${spec}`);
151
+ }
152
+ return { dir, root };
153
+ } catch (error) {
154
+ await rm(root, { recursive: true, force: true });
155
+ throw error;
104
156
  }
105
- return dir;
106
157
  }
107
158
 
108
159
  async function promptValues(args) {
@@ -198,18 +249,25 @@ async function main() {
198
249
  }
199
250
 
200
251
  const { type, destination, name, install: shouldInstall } = await promptValues(args);
201
- const source = await assertTemplate(type);
202
252
  if (await exists(destination)) {
203
253
  throw new Error(`作成先が既にあります: ${destination}`);
204
254
  }
205
255
  await mkdir(path.dirname(destination), { recursive: true });
206
256
 
207
257
  const spinner = p.spinner();
208
- spinner.start('雛形をコピーしています');
209
- await copyTemplate(source, destination);
210
- await ensureGitignore(type, destination);
211
- await rewritePackage(destination, name);
212
- spinner.stop('雛形をコピーしました');
258
+ let downloaded;
259
+ try {
260
+ spinner.start('最新の雛形を取得しています');
261
+ downloaded = await fetchTemplate(type);
262
+ await copyTemplate(downloaded.dir, destination);
263
+ await ensureGitignore(type, destination);
264
+ await rewritePackage(destination, name);
265
+ spinner.stop('雛形をコピーしました');
266
+ } finally {
267
+ if (downloaded) {
268
+ await rm(downloaded.root, { recursive: true, force: true });
269
+ }
270
+ }
213
271
 
214
272
  if (shouldInstall) {
215
273
  spinner.start('npm install を実行しています');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spicato-inc/create-fortenv",
3
- "version": "0.1.7",
3
+ "version": "1.0.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -15,11 +15,7 @@
15
15
  "access": "public"
16
16
  },
17
17
  "dependencies": {
18
- "@clack/prompts": "^1.8.1",
19
- "@spicato-inc/fortenv-mpa": "*",
20
- "@spicato-inc/fortenv-spa": "*",
21
- "@spicato-inc/fortenv-wp-mpa": "*",
22
- "@spicato-inc/fortenv-wp-spa": "*"
18
+ "@clack/prompts": "^1.8.1"
23
19
  },
24
20
  "engines": {
25
21
  "node": ">=22.18.0"