@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/lib/read.js ADDED
@@ -0,0 +1,68 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('crypto');
4
+ const fs = require('fs');
5
+ const os = require('os');
6
+ const path = require('path');
7
+ const { git, gitText, refExists, remoteUrl } = require('./git');
8
+ const { INDEX_BRANCH, VERSIONS_BRANCH } = require('./core');
9
+
10
+ // 指定した ref の索引をリポジトリ内から読む。索引は先頭ファイルと全チャンク。無ければ空。
11
+ function readIndexAt(root, ref) {
12
+ if (!refExists(root, ref)) {
13
+ return { head: { chunks: [], total: 0 }, entries: [] };
14
+ }
15
+ const head = JSON.parse(git(['show', `${ref}:index.json`], { cwd: root }).toString('utf8'));
16
+ const entries = [];
17
+ for (const chunk of head.chunks) {
18
+ const list = JSON.parse(
19
+ git(['show', `${ref}:${chunk.file}`], { cwd: root }).toString('utf8'),
20
+ );
21
+ for (const entry of list) entries.push({ ...entry, chunkFile: chunk.file });
22
+ }
23
+ return { head, entries };
24
+ }
25
+
26
+ // リモートから索引・タグ・nap/versions ブランチを取り込んだ一時 bare リポジトリを作る。
27
+ // 読み取り専用の作業場所で、対象リポジトリの ref は汚さない。
28
+ function openSnapshot(root, remote) {
29
+ const url = remoteUrl(root, remote);
30
+ const key = crypto
31
+ .createHash('sha256')
32
+ .update(`${root}\n${url}`)
33
+ .digest('hex')
34
+ .slice(0, 16);
35
+ const dir = path.join(os.tmpdir(), `nap-read-${key}`);
36
+ if (!fs.existsSync(dir)) {
37
+ fs.mkdirSync(dir, { recursive: true });
38
+ git(['init', '--bare', '--quiet', dir], {});
39
+ }
40
+ const refspecs = [
41
+ `+refs/heads/${INDEX_BRANCH}:refs/heads/${INDEX_BRANCH}`,
42
+ `+refs/heads/${VERSIONS_BRANCH}:refs/heads/${VERSIONS_BRANCH}`,
43
+ '+refs/tags/nap/*:refs/tags/nap/*',
44
+ ];
45
+ for (const refspec of refspecs) {
46
+ try {
47
+ git(['--git-dir', dir, 'fetch', '--quiet', url, refspec], {});
48
+ } catch {
49
+ // リモートにまだ無い ref は取り込まない
50
+ }
51
+ }
52
+ return dir;
53
+ }
54
+
55
+ function listTagIds(snapshotDir) {
56
+ let out = '';
57
+ try {
58
+ out = gitText(['--git-dir', snapshotDir, 'tag', '-l', 'nap/*'], {});
59
+ } catch {
60
+ return [];
61
+ }
62
+ return out
63
+ .split('\n')
64
+ .filter(Boolean)
65
+ .map((tag) => tag.slice('nap/'.length));
66
+ }
67
+
68
+ module.exports = { readIndexAt, openSnapshot, listTagIds };
package/lib/remove.js ADDED
@@ -0,0 +1,70 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { parseDeclaration, DECLARATION_FILE } = require('./declaration');
6
+ const { parseGetTarget, dependencyRefFor, isForkDir, MODULES_DIR } = require('./get');
7
+ const { askYesNo } = require('./publish');
8
+
9
+ // 依存関係の記録から、指定した参照を取り除いた宣言のテキストを返す。
10
+ // 記録が無ければ null。
11
+ function dropDependency(declarationText, ref) {
12
+ const doc = JSON.parse(declarationText);
13
+ if (!Array.isArray(doc.composition)) return null;
14
+ const entryName = (entry) => (typeof entry === 'string' ? entry : entry && entry.name);
15
+ const composition = doc.composition.filter((entry) => entryName(entry) !== ref);
16
+ if (composition.length === doc.composition.length) return null;
17
+ if (composition.length === 0) delete doc.composition;
18
+ else doc.composition = composition;
19
+ const text = `${JSON.stringify(doc, null, 2)}\n`;
20
+ parseDeclaration(text);
21
+ return text;
22
+ }
23
+
24
+ // 導入したパッケージを取り除く。nap_modules の実体と、宣言の記録が対象。
25
+ async function remove(specText, options = {}) {
26
+ const cwd = process.cwd();
27
+ const declarationPath = path.join(cwd, DECLARATION_FILE);
28
+ if (!fs.existsSync(declarationPath)) {
29
+ throw new Error(
30
+ `${DECLARATION_FILE} がありません。プロジェクトのフォルダで実行してください。`,
31
+ );
32
+ }
33
+ const { repo, ref: versionRef } = parseGetTarget(specText);
34
+ if (versionRef) {
35
+ throw new Error('版の指定は要りません。');
36
+ }
37
+ const ref = dependencyRefFor(repo);
38
+ const target = path.join(cwd, MODULES_DIR, ref);
39
+ const declarationText = fs.readFileSync(declarationPath, 'utf8');
40
+ const nextText = dropDependency(declarationText, ref);
41
+ const installed = fs.existsSync(target);
42
+ if (!installed && !nextText) {
43
+ throw new Error(`導入されていません: ${ref}`);
44
+ }
45
+ if (installed && isForkDir(target) && !options.yes) {
46
+ const ok = await askYesNo(
47
+ `${ref} はフォークです。作業内容が失われますが、取り除きますか? [y/N] `,
48
+ );
49
+ if (!ok) {
50
+ console.log('取り除きませんでした。');
51
+ return;
52
+ }
53
+ }
54
+ if (installed) {
55
+ fs.rmSync(target, { recursive: true, force: true });
56
+ // 空になった組織IDのフォルダも片付ける。
57
+ const orgDir = path.dirname(target);
58
+ if (fs.existsSync(orgDir) && fs.readdirSync(orgDir).length === 0) {
59
+ fs.rmdirSync(orgDir);
60
+ }
61
+ const modulesDir = path.join(cwd, MODULES_DIR);
62
+ if (fs.existsSync(modulesDir) && fs.readdirSync(modulesDir).length === 0) {
63
+ fs.rmdirSync(modulesDir);
64
+ }
65
+ }
66
+ if (nextText) fs.writeFileSync(declarationPath, nextText, 'utf8');
67
+ console.log(`取り除きました: ${ref}`);
68
+ }
69
+
70
+ module.exports = { remove, dropDependency };
package/lib/search.js ADDED
@@ -0,0 +1,92 @@
1
+ 'use strict';
2
+
3
+ // GitHub のコード検索で nap.json を持つリポジトリを探し、
4
+ // 各リポジトリの nap.json を読んでパッケージ名と構成を表示する。
5
+ // コード検索には認証が必要なため、トークンを GITHUB_TOKEN から読む。
6
+ // 無ければ GH_TOKEN を読む。
7
+
8
+ function buildQuery(keywords) {
9
+ return ['filename:nap.json', ...keywords].join(' ');
10
+ }
11
+
12
+ // 検索結果の項目から、リポジトリ単位に重複を取り除く。
13
+ function dedupeRepos(items) {
14
+ const seen = new Map();
15
+ for (const item of items || []) {
16
+ const repo = item && item.repository && item.repository.full_name;
17
+ if (!repo || seen.has(repo)) continue;
18
+ seen.set(repo, {
19
+ repo,
20
+ defaultBranch: item.repository.default_branch || 'HEAD',
21
+ htmlUrl: item.repository.html_url || '',
22
+ });
23
+ }
24
+ return [...seen.values()];
25
+ }
26
+
27
+ async function fetchJson(url, token, fetchImpl) {
28
+ const res = await fetchImpl(url, {
29
+ headers: {
30
+ Accept: 'application/vnd.github+json',
31
+ Authorization: `Bearer ${token}`,
32
+ 'X-GitHub-Api-Version': '2022-11-28',
33
+ },
34
+ });
35
+ if (!res.ok) {
36
+ let detail = '';
37
+ try {
38
+ const body = await res.json();
39
+ detail = body && body.message ? `: ${body.message}` : '';
40
+ } catch {
41
+ detail = '';
42
+ }
43
+ throw new Error(`GitHub API がエラーを返しました。HTTP ${res.status}${detail}`);
44
+ }
45
+ return res.json();
46
+ }
47
+
48
+ async function search(keywords, options = {}) {
49
+ const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN || '';
50
+ if (!token) {
51
+ throw new Error(
52
+ 'GitHub のコード検索には認証が必要です。GITHUB_TOKEN にトークンを設定してください。',
53
+ );
54
+ }
55
+ const fetchImpl = options.fetch || ((...args) => globalThis.fetch(...args));
56
+ const apiBase = (process.env.NAP_GITHUB_API_BASE || 'https://api.github.com').replace(/\/$/, '');
57
+ const rawBase = (process.env.NAP_RAW_BASE || 'https://raw.githubusercontent.com').replace(/\/$/, '');
58
+ const limit = options.limit || 20;
59
+
60
+ const query = buildQuery(keywords);
61
+ const url = `${apiBase}/search/code?q=${encodeURIComponent(query)}&per_page=100`;
62
+ const data = await fetchJson(url, token, fetchImpl);
63
+ const candidates = dedupeRepos(data.items).slice(0, limit);
64
+ if (candidates.length === 0) {
65
+ console.log('パッケージは見つかりませんでした。');
66
+ return;
67
+ }
68
+ console.log(`候補: ${data.total_count} 件中 ${candidates.length} 件のリポジトリを確認します`);
69
+ for (const candidate of candidates) {
70
+ let summary = 'nap.json を読めませんでした';
71
+ try {
72
+ const res = await fetchImpl(
73
+ `${rawBase}/${candidate.repo}/${candidate.defaultBranch}/nap.json`,
74
+ );
75
+ if (res.ok) {
76
+ const declaration = await res.json();
77
+ const members = Array.isArray(declaration.composition)
78
+ ? declaration.composition.length
79
+ : 0;
80
+ summary = `パッケージ: ${declaration.name || '名前なし'} / 構成員: ${members} 件`;
81
+ }
82
+ } catch {
83
+ summary = 'nap.json を読めませんでした';
84
+ }
85
+ console.log(` ${candidate.repo} ${summary}`);
86
+ }
87
+ if (data.total_count > candidates.length) {
88
+ console.log(`他 ${data.total_count - candidates.length} 件。キーワードで絞り込めます`);
89
+ }
90
+ }
91
+
92
+ module.exports = { search, buildQuery, dedupeRepos };
package/package.json CHANGED
@@ -1,6 +1,19 @@
1
1
  {
2
+ "bin": {
3
+ "nap": "./cli.js"
4
+ },
5
+ "description": "NAP の CLI と、クライアント向け取得ライブラリです。Git 拡張方式の発行を行います。",
6
+ "engines": {
7
+ "node": ">=20"
8
+ },
9
+ "files": [
10
+ "cli.js",
11
+ "lib",
12
+ "client"
13
+ ],
2
14
  "name": "@nyaitter/nap",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "version": "0.2.0"
19
+ }