@spicato-inc/create-fortenv 0.1.6 → 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.
package/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 spicato inc.
3
+ Copyright (c) 2026 spicato inc.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -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 = [
@@ -42,6 +46,7 @@ const SKIP = new Set([
42
46
  '.astro',
43
47
  'package-lock.json',
44
48
  '.DS_Store',
49
+ 'LICENSE.txt',
45
50
  ]);
46
51
 
47
52
  function parseArgs(argv) {
@@ -81,10 +86,30 @@ function cancel() {
81
86
  process.exit(0);
82
87
  }
83
88
 
84
- function templateDir(type) {
85
- const packageName = TEMPLATE_PACKAGES[type];
86
- const resolved = import.meta.resolve(`${packageName}/package.json`);
87
- 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
+ });
88
113
  }
89
114
 
90
115
  async function exists(target) {
@@ -96,12 +121,39 @@ async function exists(target) {
96
121
  }
97
122
  }
98
123
 
99
- async function assertTemplate(type) {
100
- const dir = templateDir(type);
101
- if (!(await exists(path.join(dir, 'package.json')))) {
102
- 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;
103
156
  }
104
- return dir;
105
157
  }
106
158
 
107
159
  async function promptValues(args) {
@@ -197,18 +249,25 @@ async function main() {
197
249
  }
198
250
 
199
251
  const { type, destination, name, install: shouldInstall } = await promptValues(args);
200
- const source = await assertTemplate(type);
201
252
  if (await exists(destination)) {
202
253
  throw new Error(`作成先が既にあります: ${destination}`);
203
254
  }
204
255
  await mkdir(path.dirname(destination), { recursive: true });
205
256
 
206
257
  const spinner = p.spinner();
207
- spinner.start('雛形をコピーしています');
208
- await copyTemplate(source, destination);
209
- await ensureGitignore(type, destination);
210
- await rewritePackage(destination, name);
211
- 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
+ }
212
271
 
213
272
  if (shouldInstall) {
214
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.6",
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"