@nyaitter/nap 0.0.0-stage → 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.md +141 -2
- package/cli.js +157 -0
- package/client/README.md +135 -0
- package/client/nap-client.js +613 -0
- package/client/nap-page.js +187 -0
- package/client/package.json +1 -0
- package/lib/core.js +166 -0
- package/lib/declaration.js +89 -0
- package/lib/fork.js +147 -0
- package/lib/get.js +209 -0
- package/lib/git.js +39 -0
- package/lib/glob.js +59 -0
- package/lib/init.js +53 -0
- package/lib/inspect.js +120 -0
- package/lib/publish.js +772 -0
- package/lib/read.js +68 -0
- package/lib/remove.js +70 -0
- package/lib/search.js +92 -0
- package/package.json +17 -4
package/lib/get.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { parseDeclaration } = require('./declaration');
|
|
6
|
+
|
|
7
|
+
const ID_PATTERN = /^[0-9a-f]{64}$/;
|
|
8
|
+
const MODULES_DIR = 'nap_modules';
|
|
9
|
+
|
|
10
|
+
// 取り寄せの指定を分解する。版まで含む指定と、
|
|
11
|
+
// ソースだけの指定の両方を受ける。
|
|
12
|
+
// ソースだけの場合は ref が null になり、最新の正式版に解決する。
|
|
13
|
+
function parseGetTarget(text) {
|
|
14
|
+
if (typeof text !== 'string' || !text.trim()) {
|
|
15
|
+
throw new Error(`指定が不正です: ${text}`);
|
|
16
|
+
}
|
|
17
|
+
const spec = text.trim();
|
|
18
|
+
const parts = spec.split('/');
|
|
19
|
+
if (parts.length === 3 && parts.every((part) => part.length > 0)) {
|
|
20
|
+
return { repo: `${parts[0]}/${parts[1]}`, ref: parts[2] };
|
|
21
|
+
}
|
|
22
|
+
if (parts.length === 2 && parts.every((part) => part.length > 0)) {
|
|
23
|
+
return { repo: spec, ref: null };
|
|
24
|
+
}
|
|
25
|
+
throw new Error(
|
|
26
|
+
`指定が不正です: ${spec}。「<所有者>/<リポジトリ>」または「<所有者>/<リポジトリ>/<版>」で指定してください`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ソースから、nap.json の依存関係に書く参照を導く。参照は「<組織ID>/<名前>」の形。
|
|
31
|
+
function dependencyRefFor(repo) {
|
|
32
|
+
const parts = String(repo).split('/').filter((part) => part.length > 0);
|
|
33
|
+
if (parts.length !== 2) {
|
|
34
|
+
throw new Error(`依存関係の参照を導けません: ${repo}`);
|
|
35
|
+
}
|
|
36
|
+
return `${parts[0].toLowerCase()}/${parts[1].toLowerCase()}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function defaultOutDir(repo) {
|
|
40
|
+
const base = repo.split('/').pop();
|
|
41
|
+
return path.resolve(process.cwd(), base || 'nap-package');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function assertSafeRel(rel) {
|
|
45
|
+
const segments = rel.split('/');
|
|
46
|
+
if (path.isAbsolute(rel) || segments.includes('..') || segments.includes('')) {
|
|
47
|
+
throw new Error(`不正なファイルパスです: ${rel}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function writePackageFiles(dir, pkg) {
|
|
52
|
+
if (pkg.manifest) {
|
|
53
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
54
|
+
fs.writeFileSync(
|
|
55
|
+
path.join(dir, 'version-manifest.json'),
|
|
56
|
+
`${JSON.stringify(pkg.manifest, null, 2)}\n`,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
for (const [rel, bytes] of Object.entries(pkg.files || {})) {
|
|
60
|
+
assertSafeRel(rel);
|
|
61
|
+
const target = path.join(dir, rel);
|
|
62
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
63
|
+
fs.writeFileSync(target, bytes);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function writeExtract(dir, pkg, label) {
|
|
68
|
+
writePackageFiles(dir, pkg);
|
|
69
|
+
console.log(`${label}: ${Object.keys(pkg.files || {}).length} 点 → ${dir}`);
|
|
70
|
+
for (const [ref, lib] of Object.entries(pkg.libraries || {})) {
|
|
71
|
+
writeExtract(path.join(dir, 'libraries', ref), lib, `構成員 ${ref}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 導入先にパッケージを置く。そのパッケージの構成員は、同じ nap_modules の
|
|
76
|
+
// 直下へ並べて置く。依存関係への記録は、直接導入したものだけが行う。
|
|
77
|
+
function isForkDir(dir) {
|
|
78
|
+
return fs.existsSync(path.join(dir, '.git'));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// writeInstalled はフォーク済みの場所を上書きしない。
|
|
82
|
+
function writeInstalled(modulesDir, ref, pkg) {
|
|
83
|
+
if (isForkDir(path.join(modulesDir, ref))) {
|
|
84
|
+
console.log(`フォーク済みのため、そのままにしました: ${ref}`);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const dir = path.join(modulesDir, ref);
|
|
88
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
89
|
+
writePackageFiles(dir, pkg);
|
|
90
|
+
for (const [libRef, lib] of Object.entries(pkg.libraries || {})) {
|
|
91
|
+
writeInstalled(modulesDir, libRef, lib);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// nap.json の依存関係 composition に、参照と版を記録した新しい宣言文を作る。
|
|
96
|
+
// 記録の形は検証を通してから返す。呼び出し側は、設置が終わるまで書き込まない。
|
|
97
|
+
function recordDependency(declarationText, ref, versionId) {
|
|
98
|
+
const doc = JSON.parse(declarationText);
|
|
99
|
+
const composition = Array.isArray(doc.composition) ? [...doc.composition] : [];
|
|
100
|
+
const entryName = (entry) => (typeof entry === 'string' ? entry : entry && entry.name);
|
|
101
|
+
const index = composition.findIndex((entry) => entryName(entry) === ref);
|
|
102
|
+
const record = { name: ref, version: versionId };
|
|
103
|
+
if (index >= 0) composition[index] = record;
|
|
104
|
+
else composition.push(record);
|
|
105
|
+
doc.composition = composition;
|
|
106
|
+
const text = `${JSON.stringify(doc, null, 2)}\n`;
|
|
107
|
+
parseDeclaration(text);
|
|
108
|
+
return text;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 実行ディレクトリの nap.json に記録された依存関係を、すべて導入する。
|
|
112
|
+
// 取得先のソースは、参照から導いたものを使う。参照は「<組織ID>/<名前>」の形。
|
|
113
|
+
async function installAll() {
|
|
114
|
+
const cwd = process.cwd();
|
|
115
|
+
const declarationPath = path.join(cwd, 'nap.json');
|
|
116
|
+
if (!fs.existsSync(declarationPath)) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
'nap.json がありません。依存の一括導入は、プロジェクトのフォルダで実行してください。',
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
const declarationText = fs.readFileSync(declarationPath, 'utf8');
|
|
122
|
+
const declaration = parseDeclaration(declarationText);
|
|
123
|
+
if (!declaration.composition || declaration.composition.length === 0) {
|
|
124
|
+
console.log('依存関係は記録されていません。');
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const { createClient } = await import('../client/nap-client.js');
|
|
128
|
+
const client = createClient({ rawBase: process.env.NAP_RAW_BASE || undefined });
|
|
129
|
+
let text = declarationText;
|
|
130
|
+
for (const member of declaration.composition) {
|
|
131
|
+
if (isForkDir(path.join(cwd, MODULES_DIR, member.ref))) {
|
|
132
|
+
console.log(`フォーク済みのため、そのままにしました: ${member.ref}`);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
let ref = member.version;
|
|
136
|
+
if (!ref) {
|
|
137
|
+
const latest = await client.latestVersion(member.ref);
|
|
138
|
+
if (!latest) {
|
|
139
|
+
throw new Error(`正式版はまだありません: ${member.ref}`);
|
|
140
|
+
}
|
|
141
|
+
ref = latest.id;
|
|
142
|
+
}
|
|
143
|
+
const result = await client.fetchVersion(member.ref, ref);
|
|
144
|
+
writeInstalled(path.join(cwd, MODULES_DIR), member.ref, result);
|
|
145
|
+
text = recordDependency(text, member.ref, result.versionId);
|
|
146
|
+
console.log(`導入: ${member.ref} = ${result.versionId}`);
|
|
147
|
+
}
|
|
148
|
+
fs.writeFileSync(declarationPath, text);
|
|
149
|
+
console.log('依存関係をすべて導入しました。');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function get(specText, options = {}) {
|
|
153
|
+
if (specText === undefined || specText === null) {
|
|
154
|
+
if (options.out) {
|
|
155
|
+
throw new Error('一括導入では --out は使えません。');
|
|
156
|
+
}
|
|
157
|
+
return installAll();
|
|
158
|
+
}
|
|
159
|
+
const { repo, ref } = parseGetTarget(specText);
|
|
160
|
+
const { createClient } = await import('../client/nap-client.js');
|
|
161
|
+
const client = createClient({ rawBase: process.env.NAP_RAW_BASE || undefined });
|
|
162
|
+
|
|
163
|
+
let targetRef = ref;
|
|
164
|
+
if (!targetRef) {
|
|
165
|
+
const latest = await client.latestVersion(repo);
|
|
166
|
+
if (!latest) {
|
|
167
|
+
throw new Error(`正式版はまだありません: ${repo}`);
|
|
168
|
+
}
|
|
169
|
+
targetRef = latest.id;
|
|
170
|
+
}
|
|
171
|
+
const result = await client.fetchVersion(repo, targetRef);
|
|
172
|
+
|
|
173
|
+
// 実行ディレクトリに nap.json があり、出力先の明示が無い場合は「導入」になる。
|
|
174
|
+
const cwd = process.cwd();
|
|
175
|
+
const declarationPath = path.join(cwd, 'nap.json');
|
|
176
|
+
if (!options.out && fs.existsSync(declarationPath)) {
|
|
177
|
+
const depRef = dependencyRefFor(repo);
|
|
178
|
+
const modulesDir = path.join(cwd, MODULES_DIR);
|
|
179
|
+
if (isForkDir(path.join(modulesDir, depRef))) {
|
|
180
|
+
console.log(
|
|
181
|
+
`版ID: ${result.versionId}${result.tag ? ` / 開発中の版: ${result.tag}` : ''}`,
|
|
182
|
+
);
|
|
183
|
+
console.log(`フォーク済みのため、そのままにしました: ${depRef}`);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const declarationText = fs.readFileSync(declarationPath, 'utf8');
|
|
187
|
+
parseDeclaration(declarationText);
|
|
188
|
+
const updated = recordDependency(declarationText, depRef, result.versionId);
|
|
189
|
+
writeInstalled(modulesDir, depRef, result);
|
|
190
|
+
fs.writeFileSync(declarationPath, updated);
|
|
191
|
+
console.log(`版ID: ${result.versionId}${result.tag ? ` / 開発中の版: ${result.tag}` : ''}`);
|
|
192
|
+
console.log(`導入: ${depRef} → ${path.join(modulesDir, depRef)}`);
|
|
193
|
+
console.log(`依存関係に記録しました: ${depRef} = ${result.versionId}`);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const outDir = options.out ? path.resolve(options.out) : defaultOutDir(repo);
|
|
198
|
+
if (fs.existsSync(outDir) && fs.readdirSync(outDir).length > 0) {
|
|
199
|
+
throw new Error(`出力先が空ではありません: ${outDir}`);
|
|
200
|
+
}
|
|
201
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
202
|
+
console.log(`版ID: ${result.versionId}${result.tag ? ` / 開発中の版: ${result.tag}` : ''}`);
|
|
203
|
+
writeExtract(outDir, result, '本体');
|
|
204
|
+
console.log(
|
|
205
|
+
`取得: ${result.stats.fetched} 件 / キャッシュ: ${result.stats.cached} 件`,
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
module.exports = { get, installAll, parseGetTarget, dependencyRefFor, recordDependency, isForkDir, MODULES_DIR };
|
package/lib/git.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { execFileSync } = require('child_process');
|
|
4
|
+
|
|
5
|
+
const MAX_BUFFER = 512 * 1024 * 1024;
|
|
6
|
+
|
|
7
|
+
// git を実行して標準出力を Buffer で返す。失敗したら例外。
|
|
8
|
+
function git(args, { cwd, env, input } = {}) {
|
|
9
|
+
return execFileSync('git', args, {
|
|
10
|
+
cwd,
|
|
11
|
+
env: { ...process.env, ...(env || {}) },
|
|
12
|
+
input,
|
|
13
|
+
maxBuffer: MAX_BUFFER,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function gitText(args, opts) {
|
|
18
|
+
return git(args, opts).toString('utf8').trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function refExists(root, ref) {
|
|
22
|
+
try {
|
|
23
|
+
gitText(['rev-parse', '--verify', '--quiet', ref], { cwd: root });
|
|
24
|
+
return true;
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// リモート名から URL を引く。引けない場合は指定そのものを URL/パスとして扱う。
|
|
31
|
+
function remoteUrl(root, remote) {
|
|
32
|
+
try {
|
|
33
|
+
return gitText(['remote', 'get-url', remote], { cwd: root });
|
|
34
|
+
} catch {
|
|
35
|
+
return remote;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = { git, gitText, refExists, remoteUrl };
|
package/lib/glob.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function escapeRegExpChar(ch) {
|
|
4
|
+
return /[.+^${}()|[\]\\]/.test(ch) ? `\\${ch}` : ch;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function patternToRegExp(pattern) {
|
|
8
|
+
let source = '';
|
|
9
|
+
let i = 0;
|
|
10
|
+
while (i < pattern.length) {
|
|
11
|
+
const ch = pattern[i];
|
|
12
|
+
if (ch === '*') {
|
|
13
|
+
if (pattern[i + 1] === '*') {
|
|
14
|
+
if (pattern[i + 2] === '/') {
|
|
15
|
+
source += '(?:.*/)?';
|
|
16
|
+
i += 3;
|
|
17
|
+
} else {
|
|
18
|
+
source += '.*';
|
|
19
|
+
i += 2;
|
|
20
|
+
}
|
|
21
|
+
} else {
|
|
22
|
+
source += '[^/]*';
|
|
23
|
+
i += 1;
|
|
24
|
+
}
|
|
25
|
+
} else if (ch === '?') {
|
|
26
|
+
source += '[^/]';
|
|
27
|
+
i += 1;
|
|
28
|
+
} else {
|
|
29
|
+
source += escapeRegExpChar(ch);
|
|
30
|
+
i += 1;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return new RegExp(`^${source}$`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function compilePattern(pattern) {
|
|
37
|
+
const regex = patternToRegExp(pattern);
|
|
38
|
+
const hasGlob = /[*?]/.test(pattern);
|
|
39
|
+
return (relPath) => {
|
|
40
|
+
if (regex.test(relPath)) return true;
|
|
41
|
+
// ワイルドカードのない指定がフォルダを指す場合は、その中身すべてが対象になる。
|
|
42
|
+
return !hasGlob && relPath.startsWith(`${pattern}/`);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function createMatcher(patterns) {
|
|
47
|
+
const includes = [];
|
|
48
|
+
const excludes = [];
|
|
49
|
+
for (const raw of patterns) {
|
|
50
|
+
if (raw.startsWith('!')) excludes.push(compilePattern(raw.slice(1)));
|
|
51
|
+
else includes.push(compilePattern(raw));
|
|
52
|
+
}
|
|
53
|
+
return (relPath) => {
|
|
54
|
+
if (!includes.some((test) => test(relPath))) return false;
|
|
55
|
+
return !excludes.some((test) => test(relPath));
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = { createMatcher };
|
package/lib/init.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { git, gitText } = require('./git');
|
|
6
|
+
const { DECLARATION_FILE, NAME_PATTERN } = require('./declaration');
|
|
7
|
+
|
|
8
|
+
// origin の URL からリポジトリ名を引き出す。
|
|
9
|
+
function nameFromRemote(root) {
|
|
10
|
+
let url;
|
|
11
|
+
try {
|
|
12
|
+
url = gitText(['remote', 'get-url', 'origin'], { cwd: root });
|
|
13
|
+
} catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const cleaned = url.replace(/\.git$/, '').replace(/[/:]$/, '');
|
|
17
|
+
const segment = cleaned.split(/[/:]/).pop();
|
|
18
|
+
return segment || null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// カレントディレクトリに宣言 nap.json を作る。
|
|
22
|
+
async function init(nameArg) {
|
|
23
|
+
const root = process.cwd();
|
|
24
|
+
const file = path.join(root, DECLARATION_FILE);
|
|
25
|
+
if (fs.existsSync(file)) {
|
|
26
|
+
throw new Error(`${DECLARATION_FILE} は既にあります。`);
|
|
27
|
+
}
|
|
28
|
+
let inRepo = true;
|
|
29
|
+
try {
|
|
30
|
+
git(['rev-parse', '--git-dir'], { cwd: root });
|
|
31
|
+
} catch {
|
|
32
|
+
inRepo = false;
|
|
33
|
+
}
|
|
34
|
+
const name = (
|
|
35
|
+
nameArg ||
|
|
36
|
+
(inRepo ? nameFromRemote(root) : null) ||
|
|
37
|
+
path.basename(root)
|
|
38
|
+
).toLowerCase();
|
|
39
|
+
if (!NAME_PATTERN.test(name)) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`パッケージ名が不正です: ${name}。小文字の英数字と . _ - で書いてください。引数で名前を指定できます`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
const doc = { name, include: ['**'] };
|
|
45
|
+
fs.writeFileSync(file, `${JSON.stringify(doc, null, 2)}\n`, 'utf8');
|
|
46
|
+
console.log(`${DECLARATION_FILE} を作りました。パッケージ名: ${name}`);
|
|
47
|
+
console.log('include はパッケージに含めるファイルのパターンです。必要に応じて編集してください。');
|
|
48
|
+
if (!inRepo) {
|
|
49
|
+
console.log('このフォルダは Git リポジトリではありません。発行には Git リポジトリが必要です。');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = { init };
|
package/lib/inspect.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { git, gitText } = require('./git');
|
|
5
|
+
const {
|
|
6
|
+
MANIFEST_NAME,
|
|
7
|
+
INDEX_BRANCH,
|
|
8
|
+
versionIdOf,
|
|
9
|
+
parseManifest,
|
|
10
|
+
parseVersionRef,
|
|
11
|
+
tagRefFor,
|
|
12
|
+
isVersionCode,
|
|
13
|
+
} = require('./core');
|
|
14
|
+
const { readIndexAt, openSnapshot, listTagIds } = require('./read');
|
|
15
|
+
const { resolveRepoRoot } = require('./publish');
|
|
16
|
+
|
|
17
|
+
async function list(repoSpec, options = {}) {
|
|
18
|
+
const root = resolveRepoRoot(repoSpec);
|
|
19
|
+
const snapshot = openSnapshot(root, options.remote || 'origin');
|
|
20
|
+
const { head, entries } = readIndexAt(snapshot, `refs/heads/${INDEX_BRANCH}`);
|
|
21
|
+
if (entries.length === 0) {
|
|
22
|
+
console.log('版はまだありません。');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
console.log(`版の総数: ${head.total} / 表示: ${entries.length} 件`);
|
|
26
|
+
let latestFormal = -1;
|
|
27
|
+
entries.forEach((entry, index) => {
|
|
28
|
+
if (!entry.tag) latestFormal = index;
|
|
29
|
+
});
|
|
30
|
+
entries.forEach((entry, index) => {
|
|
31
|
+
const marks = [];
|
|
32
|
+
if (entry.tag) marks.push(`開発中: ${entry.tag}`);
|
|
33
|
+
if (index === latestFormal) marks.push('最新');
|
|
34
|
+
const display = entry.tag ? `${entry.id}-${entry.tag}` : entry.id;
|
|
35
|
+
const code = entry.code ? ` code=${entry.code}` : '';
|
|
36
|
+
console.log(
|
|
37
|
+
` ${entry.id.slice(0, 12)} ${display}${code} commit=${entry.commit.slice(0, 12)} at=${entry.at}${marks.length ? ` / ${marks.join('、')}` : ''}`,
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 「<版ID>」または「<版ID>-<ラベル>」の指定を、タグの一覧から一意に解決する。
|
|
43
|
+
// ラベルなしの指定は正式版だけを対象にする。
|
|
44
|
+
function resolveVersionRef(snapshot, refText) {
|
|
45
|
+
const { prefix, tag } = parseVersionRef(refText);
|
|
46
|
+
const matches = listTagIds(snapshot)
|
|
47
|
+
.map((suffix) => {
|
|
48
|
+
try {
|
|
49
|
+
const parsed = parseVersionRef(suffix);
|
|
50
|
+
return { id: parsed.prefix, tag: parsed.tag };
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
.filter((candidate) => candidate && candidate.id.startsWith(prefix) && candidate.tag === tag);
|
|
56
|
+
if (matches.length === 0) throw new Error(`版が見つかりません: ${refText}`);
|
|
57
|
+
if (matches.length > 1) throw new Error(`版が一意に決まりません: ${refText}`);
|
|
58
|
+
return matches[0];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function show(versionRefText, options = {}) {
|
|
62
|
+
const root = resolveRepoRoot(options.repo);
|
|
63
|
+
const snapshot = openSnapshot(root, options.remote || 'origin');
|
|
64
|
+
let resolved;
|
|
65
|
+
if (versionRefText) {
|
|
66
|
+
// 版コードは索引から引く。版IDの省略形より優先する。
|
|
67
|
+
let byCode = null;
|
|
68
|
+
if (isVersionCode(versionRefText)) {
|
|
69
|
+
const { entries: codeEntries } = readIndexAt(snapshot, `refs/heads/${INDEX_BRANCH}`);
|
|
70
|
+
const hit = codeEntries.find((entry) => entry.code === versionRefText);
|
|
71
|
+
if (hit) byCode = { id: hit.id, tag: hit.tag || null };
|
|
72
|
+
}
|
|
73
|
+
resolved = byCode || resolveVersionRef(snapshot, versionRefText);
|
|
74
|
+
} else {
|
|
75
|
+
// 版の指定が無ければ、最新の正式版を指す。TestVersion は勝手に選ばない。
|
|
76
|
+
const { entries } = readIndexAt(snapshot, `refs/heads/${INDEX_BRANCH}`);
|
|
77
|
+
if (entries.length === 0) throw new Error('版はまだありません。');
|
|
78
|
+
const formal = entries.filter((entry) => !entry.tag);
|
|
79
|
+
if (formal.length === 0) {
|
|
80
|
+
throw new Error('正式版はまだありません。開発中の版だけが発行されています。');
|
|
81
|
+
}
|
|
82
|
+
resolved = { id: formal[formal.length - 1].id, tag: null };
|
|
83
|
+
}
|
|
84
|
+
const { id, tag } = resolved;
|
|
85
|
+
const commit = gitText(
|
|
86
|
+
['--git-dir', snapshot, 'rev-parse', `refs/tags/${tagRefFor(id, tag)}`],
|
|
87
|
+
{},
|
|
88
|
+
);
|
|
89
|
+
let manifest;
|
|
90
|
+
try {
|
|
91
|
+
manifest = parseManifest(
|
|
92
|
+
git(['--git-dir', snapshot, 'cat-file', 'blob', `${commit}:${MANIFEST_NAME}`], {}),
|
|
93
|
+
);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
throw new Error(`マニフェストを読めません: ${error.message}`);
|
|
96
|
+
}
|
|
97
|
+
const recomputed = versionIdOf(manifest.files, manifest.libraries);
|
|
98
|
+
console.log(`版ID: ${id}`);
|
|
99
|
+
if (tag) console.log(`ラベル: ${tag}。開発中の版`);
|
|
100
|
+
console.log(`再計算: ${recomputed === id ? '一致' : `不一致: ${recomputed}`}`);
|
|
101
|
+
const { entries: showEntries } = readIndexAt(snapshot, `refs/heads/${INDEX_BRANCH}`);
|
|
102
|
+
const shown = showEntries.find((e) => e.id === id && (e.tag || null) === (tag || null));
|
|
103
|
+
if (shown && shown.source) console.log(`出典コミット: ${shown.source}`);
|
|
104
|
+
if (shown && shown.code) console.log(`版コード: ${shown.code}`);
|
|
105
|
+
const description = gitText(['--git-dir', snapshot, 'log', '-1', '--format=%b', commit], {});
|
|
106
|
+
if (description) console.log(`説明: ${description}`);
|
|
107
|
+
console.log(`ファイル: ${Object.keys(manifest.files).length} 点`);
|
|
108
|
+
for (const rel of Object.keys(manifest.files)) {
|
|
109
|
+
console.log(` ${rel} ${manifest.files[rel].slice(0, 12)}`);
|
|
110
|
+
}
|
|
111
|
+
const refs = Object.keys(manifest.libraries);
|
|
112
|
+
console.log(`構成員: ${refs.length} 件`);
|
|
113
|
+
for (const ref of refs) {
|
|
114
|
+
const lib = manifest.libraries[ref];
|
|
115
|
+
console.log(` ${ref} repo=${lib.repo} version=${lib.version}`);
|
|
116
|
+
}
|
|
117
|
+
return { id, tag, manifest };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
module.exports = { list, show };
|