create-yeow 0.2.102 → 0.2.106
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/package.json +1 -1
- package/templates/default/.yeow/assets/yeow-runtime-0.1.0.jar +0 -0
- package/templates/default/.yeow/build.js +32 -4
- package/templates/default/.yeow/permissions.js +37 -0
- package/templates/default/.yeow/yeow-assets.mjs +254 -88
- package/templates/default/package.json +3 -2
- package/templates/default/tsconfig.json +2 -1
package/package.json
CHANGED
|
Binary file
|
|
@@ -4,7 +4,7 @@ import { resolve, dirname } from 'path';
|
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import AdmZip from 'adm-zip';
|
|
6
6
|
import { execSync } from 'child_process';
|
|
7
|
-
import { makeAssetPlugin, makeDedupePlugin, assetsOutDirFor } from './yeow-assets.mjs';
|
|
7
|
+
import { makeAssetPlugin, makeDedupePlugin, assetsOutDirFor, readMergedPermissions, prepareAssets, computeNativeManifest } from './yeow-assets.mjs';
|
|
8
8
|
|
|
9
9
|
const root = resolve(fileURLToPath(import.meta.url), '..', '..');
|
|
10
10
|
const cfg = JSON.parse(readFileSync(resolve(root, 'yeow.config.json'), 'utf-8'));
|
|
@@ -37,6 +37,34 @@ async function main() {
|
|
|
37
37
|
// 清空资产输出,避免旧哈希目录残留
|
|
38
38
|
rmSync(assetsOut, { recursive: true, force: true });
|
|
39
39
|
|
|
40
|
+
// ── 计算最终权限(computedPermissions:合并 + 通配归一化)──
|
|
41
|
+
const mergedPerms = readMergedPermissions(root, pkgJson);
|
|
42
|
+
// 回写 computedPermissions 到主项目 yeow.config.json(开发者声明的 permissions 保持原样)
|
|
43
|
+
try {
|
|
44
|
+
const cfgPath = resolve(root, 'yeow.config.json');
|
|
45
|
+
const cfgFile = JSON.parse(readFileSync(cfgPath, 'utf-8'));
|
|
46
|
+
if (JSON.stringify(cfgFile.computedPermissions) !== JSON.stringify(mergedPerms)) {
|
|
47
|
+
cfgFile.computedPermissions = mergedPerms;
|
|
48
|
+
writeFileSync(cfgPath, JSON.stringify(cfgFile, null, 4) + '\n');
|
|
49
|
+
console.log(' \u2713 computedPermissions written back to yeow.config.json');
|
|
50
|
+
}
|
|
51
|
+
} catch (e) { /* 写回失败不阻塞构建 */ }
|
|
52
|
+
if (mergedPerms.length > 0) {
|
|
53
|
+
console.log(' \u2713 Computed permissions (' + mergedPerms.length + '): ' + mergedPerms.join(', '));
|
|
54
|
+
} else {
|
|
55
|
+
console.log(' \u2713 No permissions declared');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ── 资产准备(id 分配 + 部署;与插件共享,保证路径一致)──
|
|
59
|
+
const prepared = prepareAssets(root, pkgJson, outDir);
|
|
60
|
+
|
|
61
|
+
// ── 原生服务可信性声明(native manifest:打包后路径 → SHA-256)──
|
|
62
|
+
const nativeManifest = computeNativeManifest(prepared);
|
|
63
|
+
if (nativeManifest.length > 0) {
|
|
64
|
+
const fileCount = nativeManifest.reduce((n, e) => n + e.files.length, 0);
|
|
65
|
+
console.log(' \u2713 Native manifest (' + nativeManifest.length + ' services, ' + fileCount + ' files, SHA-256)');
|
|
66
|
+
}
|
|
67
|
+
|
|
40
68
|
// ── 打包 ──
|
|
41
69
|
await esbuild.build({
|
|
42
70
|
entryPoints: [resolve(root, entry)],
|
|
@@ -52,7 +80,7 @@ async function main() {
|
|
|
52
80
|
sourcemap: isDev ? 'linked' : false,
|
|
53
81
|
plugins: [
|
|
54
82
|
makeDedupePlugin(root),
|
|
55
|
-
makeAssetPlugin({ root, pkgJson, outDir }),
|
|
83
|
+
makeAssetPlugin({ root, pkgJson, outDir, prepared }),
|
|
56
84
|
],
|
|
57
85
|
});
|
|
58
86
|
console.log(' \u2713 Bundled (' + (statSync(resolve(outDir, 'main.js')).size / 1024).toFixed(1) + ' KB)');
|
|
@@ -88,7 +116,7 @@ async function main() {
|
|
|
88
116
|
console.log(' \u2713 Assets included (' + files.length + ' files)');
|
|
89
117
|
}
|
|
90
118
|
|
|
91
|
-
zip.addFile('yeow.json', Buffer.from(JSON.stringify(cfg)));
|
|
119
|
+
zip.addFile('yeow.json', Buffer.from(JSON.stringify({ ...cfg, computedPermissions: mergedPerms, native: nativeManifest })));
|
|
92
120
|
const outJar = resolve(root, 'dist', isDev ? 'plugins' : '', name + '-' + version + '.jar');
|
|
93
121
|
mkdirSync(dirname(outJar), { recursive: true });
|
|
94
122
|
zip.writeZip(outJar);
|
|
@@ -110,7 +138,7 @@ async function main() {
|
|
|
110
138
|
pkgZip.addFile('assets/' + f.replace(/\\/g, '/'), readFileSync(resolve(assetsOut, f)));
|
|
111
139
|
}
|
|
112
140
|
}
|
|
113
|
-
pkgZip.addFile('yeow.json', Buffer.from(JSON.stringify(cfg)));
|
|
141
|
+
pkgZip.addFile('yeow.json', Buffer.from(JSON.stringify({ ...cfg, computedPermissions: mergedPerms, native: nativeManifest })));
|
|
114
142
|
const outZip = resolve(root, 'dist', isDev ? 'plugins' : '', name + '-' + version + '.yeow.zip');
|
|
115
143
|
mkdirSync(dirname(outZip), { recursive: true });
|
|
116
144
|
pkgZip.writeZip(outZip);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { collectPermissionsWithSources, readMergedPermissions } from './yeow-assets.mjs';
|
|
5
|
+
|
|
6
|
+
const root = resolve(fileURLToPath(import.meta.url), '..', '..');
|
|
7
|
+
const cfg = JSON.parse(readFileSync(resolve(root, 'yeow.config.json'), 'utf-8'));
|
|
8
|
+
const pkgJson = JSON.parse(readFileSync(resolve(root, 'package.json'), 'utf-8'));
|
|
9
|
+
|
|
10
|
+
// ── 权限分布统计(每个权限来自哪个依赖项)──
|
|
11
|
+
const sources = collectPermissionsWithSources(root, pkgJson);
|
|
12
|
+
const computed = readMergedPermissions(root, pkgJson);
|
|
13
|
+
|
|
14
|
+
console.log('── Permissions by source ─────────────────────────');
|
|
15
|
+
let total = 0;
|
|
16
|
+
for (const [perm, owners] of sources) {
|
|
17
|
+
const from = [...owners].join(', ');
|
|
18
|
+
console.log(' ' + perm.padEnd(28) + '← ' + from);
|
|
19
|
+
total += owners.size;
|
|
20
|
+
}
|
|
21
|
+
console.log(' (' + sources.size + ' declarations from ' + new Set([...sources.values()].flatMap(s => [...s])).size + ' packages)');
|
|
22
|
+
|
|
23
|
+
console.log('\n── Computed permissions (' + computed.length + ') ─────────────────');
|
|
24
|
+
for (const p of computed) console.log(' ' + p);
|
|
25
|
+
if (computed.length === 0) console.log(' (none)');
|
|
26
|
+
|
|
27
|
+
// ── 回写 computedPermissions 到 yeow.config.json ──
|
|
28
|
+
const cfgPath = resolve(root, 'yeow.config.json');
|
|
29
|
+
const current = JSON.stringify(cfg.computedPermissions);
|
|
30
|
+
const next = JSON.stringify(computed);
|
|
31
|
+
if (current !== next) {
|
|
32
|
+
cfg.computedPermissions = computed;
|
|
33
|
+
writeFileSync(cfgPath, JSON.stringify(cfg, null, 4) + '\n');
|
|
34
|
+
console.log('\n✓ computedPermissions written back to yeow.config.json');
|
|
35
|
+
} else {
|
|
36
|
+
console.log('\n✓ computedPermissions unchanged');
|
|
37
|
+
}
|
|
@@ -1,113 +1,281 @@
|
|
|
1
|
-
import { readFileSync, readdirSync, existsSync, statSync, mkdirSync, copyFileSync } from 'fs';
|
|
2
|
-
import { resolve
|
|
1
|
+
import { readFileSync, readdirSync, existsSync, statSync, mkdirSync, copyFileSync, realpathSync } from 'fs';
|
|
2
|
+
import { resolve } from 'path';
|
|
3
3
|
import { createRequire } from 'module';
|
|
4
|
-
import { createHash } from 'crypto';
|
|
4
|
+
import { randomBytes, createHash } from 'crypto';
|
|
5
5
|
|
|
6
6
|
const require = createRequire(import.meta.url);
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
children[e.name] = buildTree(p);
|
|
19
|
-
} else {
|
|
20
|
-
children[e.name] = {
|
|
21
|
-
isFile: true,
|
|
22
|
-
contentHash: createHash('md5').update(readFileSync(p)).digest('hex').slice(0, 8),
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
const parts = Object.keys(children).sort().map(k => {
|
|
27
|
-
const c = children[k];
|
|
28
|
-
return k + ':' + (c.isFile ? c.contentHash : c.hash);
|
|
29
|
-
});
|
|
30
|
-
return { children, isFile: false, hash: createHash('md5').update(parts.join(',')).digest('hex').slice(0, 8) };
|
|
8
|
+
const slash = p => p.replace(/\\/g, '/');
|
|
9
|
+
const normKey = p => slash(resolve(p)).toLowerCase();
|
|
10
|
+
|
|
11
|
+
/** 读取 yeow.config.json 的 permissions(缺失/解析失败 → 空数组)。 */
|
|
12
|
+
function readPerms(configPath) {
|
|
13
|
+
try {
|
|
14
|
+
const j = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
15
|
+
if (Array.isArray(j.permissions)) return j.permissions.filter(x => typeof x === 'string');
|
|
16
|
+
} catch { /* 无 yeow.config.json 或解析失败 */ }
|
|
17
|
+
return [];
|
|
31
18
|
}
|
|
32
19
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
20
|
+
/** 读取 yeow.config.json 的 native 可信性声明([{serviceId, files[], source}])。 */
|
|
21
|
+
function readNatives(configPath) {
|
|
22
|
+
try {
|
|
23
|
+
const j = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
24
|
+
if (Array.isArray(j.native)) return j.native;
|
|
25
|
+
} catch { /* 无声明 */ }
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ── 依赖项收集(node_modules 扫描)─────────────────────────────
|
|
30
|
+
// 规则:
|
|
31
|
+
// - 主项目无条件参与(始终分配 id,保证 getAssetsPath 恒可用;有 assets/ 才复制)
|
|
32
|
+
// - 依赖包:node_modules 顶层目录(含 @scope/name 两级),要求
|
|
33
|
+
// assets/ 目录存在 且 peerDependencies 含 yeow-api 键
|
|
34
|
+
// - 每个候选同时读取其 yeow.config.json 的 permissions(依赖包可自行声明权限)
|
|
35
|
+
// 键:<name>-<version>。npm/pnpm 扁平布局支持良好;yarn 的 hoisting
|
|
36
|
+
// 差异可能导致依赖不在预期位置(见文档说明)。
|
|
37
|
+
function collectCandidates(root, pkgJson) {
|
|
38
|
+
const candidates = [];
|
|
39
|
+
const ownAssets = resolve(root, 'assets');
|
|
40
|
+
candidates.push({
|
|
41
|
+
key: pkgJson.name + '-' + pkgJson.version,
|
|
42
|
+
pkgDir: root,
|
|
43
|
+
absSrc: ownAssets,
|
|
44
|
+
hasAssets: existsSync(ownAssets),
|
|
45
|
+
perms: readPerms(resolve(root, 'yeow.config.json')),
|
|
46
|
+
natives: readNatives(resolve(root, 'yeow.config.json')),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const nm = resolve(root, 'node_modules');
|
|
50
|
+
if (existsSync(nm)) {
|
|
51
|
+
const names = [];
|
|
52
|
+
for (const entry of readdirSync(nm)) {
|
|
53
|
+
const p = resolve(nm, entry);
|
|
54
|
+
let st;
|
|
55
|
+
try { st = statSync(p); } catch { continue; } // statSync 跟随 symlink(pnpm)
|
|
56
|
+
if (!st.isDirectory()) continue;
|
|
57
|
+
if (entry.startsWith('@')) {
|
|
58
|
+
for (const sub of readdirSync(p)) {
|
|
59
|
+
const sp = resolve(p, sub);
|
|
60
|
+
try { if (statSync(sp).isDirectory()) names.push(entry + '/' + sub); } catch { /* 跳过 */ }
|
|
52
61
|
}
|
|
53
|
-
|
|
62
|
+
} else {
|
|
63
|
+
names.push(entry);
|
|
54
64
|
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (
|
|
65
|
+
}
|
|
66
|
+
for (const name of names) {
|
|
67
|
+
const pkgDir = resolve(nm, ...name.split('/'));
|
|
68
|
+
let meta;
|
|
69
|
+
try { meta = JSON.parse(readFileSync(resolve(pkgDir, 'package.json'), 'utf-8')); } catch { continue; }
|
|
70
|
+
if (!meta.peerDependencies || !meta.peerDependencies['yeow-api']) continue;
|
|
71
|
+
const pkgAssets = resolve(pkgDir, 'assets');
|
|
72
|
+
if (!existsSync(pkgAssets)) continue;
|
|
73
|
+
candidates.push({
|
|
74
|
+
key: meta.name + '-' + (meta.version || '0.0.0'),
|
|
75
|
+
pkgDir,
|
|
76
|
+
absSrc: pkgAssets,
|
|
77
|
+
hasAssets: true,
|
|
78
|
+
perms: readPerms(resolve(pkgDir, 'yeow.config.json')),
|
|
79
|
+
natives: readNatives(resolve(pkgDir, 'yeow.config.json')),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return candidates;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 收集每个权限节点的来源:permission → 声明它的依赖项键集合(主项目 key 在前)。
|
|
88
|
+
* 仅直接依赖(node_modules 顶层)参与——依赖包的依赖所需权限无需计算。
|
|
89
|
+
*/
|
|
90
|
+
export function collectPermissionsWithSources(root, pkgJson) {
|
|
91
|
+
const map = new Map(); // perm → Set<key>
|
|
92
|
+
for (const c of collectCandidates(root, pkgJson)) {
|
|
93
|
+
for (const p of c.perms) {
|
|
94
|
+
if (!map.has(p)) map.set(p, new Set());
|
|
95
|
+
map.get(p).add(c.key);
|
|
63
96
|
}
|
|
64
97
|
}
|
|
98
|
+
return map;
|
|
65
99
|
}
|
|
66
100
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
101
|
+
/**
|
|
102
|
+
* 计算最终生效权限(computedPermissions):合并主项目与全部依赖包的
|
|
103
|
+
* 声明(主项目在前、依赖包按收集顺序追加、去重),再做通配归一化:
|
|
104
|
+
* - `X:*`(如 fs:*)覆盖全部 `X:...` 节点(含二级通配与子节点)
|
|
105
|
+
* - `X:level.*`(如 fs:server.*)覆盖 `X:level.<op>` 子节点
|
|
106
|
+
* 最后把 `fs:*` 展开为 `fs:outer.*, fs:server.*`——权限语义等价
|
|
107
|
+
* (plugin 级免声明),但让开发者/服主对实际影响范围有明确感知。
|
|
108
|
+
*/
|
|
109
|
+
export function readMergedPermissions(root, pkgJson) {
|
|
110
|
+
const merged = [];
|
|
111
|
+
const seen = new Set();
|
|
112
|
+
for (const c of collectCandidates(root, pkgJson)) {
|
|
113
|
+
for (const p of c.perms) {
|
|
114
|
+
if (!seen.has(p)) { seen.add(p); merged.push(p); }
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const channelWildcards = new Set(); // X:*
|
|
118
|
+
const levelWildcards = new Set(); // X:level.*
|
|
119
|
+
for (const p of merged) {
|
|
120
|
+
if (p.endsWith(':*')) channelWildcards.add(p.slice(0, -2));
|
|
121
|
+
else if (p.endsWith('.*')) levelWildcards.add(p.slice(0, -2));
|
|
122
|
+
}
|
|
123
|
+
let normalized = merged;
|
|
124
|
+
if (channelWildcards.size > 0 || levelWildcards.size > 0) {
|
|
125
|
+
normalized = merged.filter(p => {
|
|
126
|
+
if (p.endsWith(':*')) return true;
|
|
127
|
+
const dot = p.lastIndexOf('.');
|
|
128
|
+
const levelPrefix = dot > 0 ? p.slice(0, dot) : null; // fs:server.readFile → fs:server
|
|
129
|
+
const col = p.indexOf(':');
|
|
130
|
+
const channel = col > 0 ? p.slice(0, col) : null; // fs:server.readFile → fs
|
|
131
|
+
if (p.endsWith('.*')) return channel === null || !channelWildcards.has(channel);
|
|
132
|
+
if (channel !== null && channelWildcards.has(channel)) return false;
|
|
133
|
+
if (levelPrefix !== null && levelWildcards.has(levelPrefix)) return false;
|
|
134
|
+
return true;
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
// fs:* 展开(语义等价:fs:outer.* + fs:server.* 覆盖各自级别,plugin 级免声明)
|
|
138
|
+
if (normalized.includes('fs:*')) {
|
|
139
|
+
const out = [];
|
|
140
|
+
for (const p of normalized) {
|
|
141
|
+
if (p === 'fs:*') { out.push('fs:outer.*', 'fs:server.*'); }
|
|
142
|
+
else out.push(p);
|
|
143
|
+
}
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
return normalized;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ── id 分配(8 位 hex,不哈希内容,仅保证构建内唯一)──────────
|
|
150
|
+
// 每个依赖项的资产部署到 .assets/<id>/,id 唯一 → 无同名冲突。
|
|
151
|
+
function assignIds(candidates) {
|
|
152
|
+
const used = new Set();
|
|
153
|
+
const ids = new Map(); // 路径(realpath + 原始路径,lowercase)→ id
|
|
154
|
+
for (const c of candidates) {
|
|
155
|
+
let id;
|
|
156
|
+
do { id = randomBytes(4).toString('hex'); } while (used.has(id));
|
|
157
|
+
used.add(id);
|
|
158
|
+
c.id = id;
|
|
159
|
+
ids.set(normKey(c.pkgDir), id);
|
|
160
|
+
try { ids.set(normKey(realpathSync(c.pkgDir)), id); } catch { /* 保持原始路径 */ }
|
|
161
|
+
}
|
|
162
|
+
return ids;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ── 原样部署到 .assets/<id>/(无改名,相对引用天然有效)────────
|
|
166
|
+
function copyDir(src, dst) {
|
|
167
|
+
mkdirSync(dst, { recursive: true });
|
|
168
|
+
for (const entry of readdirSync(src, { withFileTypes: true })) {
|
|
169
|
+
const s = resolve(src, entry.name);
|
|
170
|
+
const d = resolve(dst, entry.name);
|
|
171
|
+
if (entry.isDirectory()) copyDir(s, d);
|
|
172
|
+
else copyFileSync(s, d);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function deployAll(candidates, assetsOutDir) {
|
|
177
|
+
for (const c of candidates) {
|
|
178
|
+
if (!c.hasAssets) continue;
|
|
179
|
+
copyDir(c.absSrc, resolve(assetsOutDir, c.id));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ── 资产准备(收集 + id 分配 + 部署)──────────────────────────
|
|
184
|
+
// 供 build.js 与 esbuild 插件共享——id 分配一次,保证打包路径(assets/<id>/...)
|
|
185
|
+
// 在 yeow.json(native manifest)与插件内注入的 getAssetsPath 中一致。
|
|
186
|
+
export function prepareAssets(root, pkgJson, outDir) {
|
|
187
|
+
const candidates = collectCandidates(root, pkgJson);
|
|
188
|
+
const ids = assignIds(candidates);
|
|
189
|
+
const assetsOutDir = resolve(outDir, '.assets');
|
|
190
|
+
deployAll(candidates, assetsOutDir);
|
|
191
|
+
return { candidates, ids, rootId: ids.get(normKey(root)), assetsOutDir };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ── 原生服务可信性声明(native manifest)───────────────────────
|
|
195
|
+
// 依赖包 / 主项目在 yeow.config.json 声明 native:[{serviceId, files[], source}];
|
|
196
|
+
// 构建时把 files 映射为打包后路径(assets/<id>/...)并计算 SHA-256,
|
|
197
|
+
// 相同 serviceId 合并(files 归并到一项)。产物写入 yeow.json 的 native 字段:
|
|
198
|
+
// [{ "serviceId": "...", "files": [{ "<打包后路径>": "<sha256>" }, ...], "source": "..." }]
|
|
199
|
+
export function computeNativeManifest(prepared) {
|
|
200
|
+
const merged = new Map(); // serviceId → { files: Map<path, {abs, packaged}>, source }
|
|
201
|
+
for (const c of prepared.candidates) {
|
|
202
|
+
for (const n of c.natives || []) {
|
|
203
|
+
const sid = n.serviceId;
|
|
204
|
+
if (!sid) continue;
|
|
205
|
+
let e = merged.get(sid);
|
|
206
|
+
if (!e) { e = { files: new Map(), source: n.source || '' }; merged.set(sid, e); }
|
|
207
|
+
else if (!e.source && n.source) e.source = n.source;
|
|
208
|
+
for (const f of n.files || []) {
|
|
209
|
+
const raw = String(f).replace(/\\/g, '/').replace(/^\/+/, '');
|
|
210
|
+
const packaged = 'assets/' + c.id + '/' + raw;
|
|
211
|
+
if (!e.files.has(packaged)) {
|
|
212
|
+
e.files.set(packaged, resolve(prepared.assetsOutDir, c.id, raw));
|
|
213
|
+
}
|
|
79
214
|
}
|
|
80
|
-
}
|
|
215
|
+
}
|
|
81
216
|
}
|
|
82
|
-
|
|
217
|
+
const out = [];
|
|
218
|
+
for (const [sid, e] of merged) {
|
|
219
|
+
const files = [];
|
|
220
|
+
for (const [packaged, abs] of e.files) {
|
|
221
|
+
let hash = null;
|
|
222
|
+
try { hash = createHash('sha256').update(readFileSync(abs)).digest('hex'); }
|
|
223
|
+
catch (err) { console.warn(' ! native file not found (skipped from manifest): ' + packaged); }
|
|
224
|
+
if (hash) files.push({ [packaged]: hash });
|
|
225
|
+
}
|
|
226
|
+
if (files.length === 0) continue;
|
|
227
|
+
out.push({ serviceId: sid, files, ...(e.source ? { source: e.source } : {}) });
|
|
228
|
+
}
|
|
229
|
+
return out;
|
|
83
230
|
}
|
|
84
231
|
|
|
85
|
-
// ── esbuild 插件:
|
|
86
|
-
|
|
232
|
+
// ── esbuild 插件:yeow-dev 虚拟模块 ────────────────────────────
|
|
233
|
+
// yeow-dev 是构建期模块(发布为空包):getAssetsPath 由构建器按
|
|
234
|
+
// importer 所属依赖项注入对应命名空间 id,运行时无需任何改动。
|
|
235
|
+
export function makeAssetPlugin({ root, pkgJson, outDir, prepared }) {
|
|
87
236
|
const assetsOutDir = resolve(outDir, '.assets');
|
|
88
237
|
return {
|
|
89
238
|
name: 'yeow-assets',
|
|
90
239
|
setup(build) {
|
|
91
|
-
|
|
92
|
-
|
|
240
|
+
const p = prepared ?? prepareAssets(root, pkgJson, outDir);
|
|
241
|
+
const candidates = p.candidates;
|
|
242
|
+
const ids = p.ids;
|
|
243
|
+
const rootId = p.rootId;
|
|
244
|
+
if (!prepared) deployAll(candidates, assetsOutDir); // prepared 时已由 build.js 部署
|
|
245
|
+
|
|
246
|
+
// importer → 所属依赖项 id(最长路径前缀匹配;未匹配归主项目)
|
|
247
|
+
const idForImporter = importer => {
|
|
248
|
+
const imp = normKey(importer);
|
|
249
|
+
let best = null;
|
|
250
|
+
let bestLen = -1;
|
|
251
|
+
for (const [dir, id] of ids) {
|
|
252
|
+
if (dir === imp || imp.startsWith(dir + '/')) {
|
|
253
|
+
if (dir.length > bestLen) { best = id; bestLen = dir.length; }
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return best || rootId;
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
build.onResolve({ filter: /^yeow-dev$/ }, args => ({
|
|
260
|
+
path: 'yeow-dev?id=' + idForImporter(args.importer),
|
|
93
261
|
namespace: 'yeow-assets',
|
|
94
262
|
}));
|
|
95
|
-
build.onLoad({ filter: /.*/, namespace: 'yeow-assets' },
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
for (const src of collectSources(root, pkgJson)) {
|
|
99
|
-
deploy(src.tree, src.absSrc, assetsOutDir, map, '', '', src.overwrite, true);
|
|
100
|
-
}
|
|
263
|
+
build.onLoad({ filter: /.*/, namespace: 'yeow-assets' }, args => {
|
|
264
|
+
const m = /yeow-dev\?id=([0-9a-f]+)/.exec(args.path);
|
|
265
|
+
const id = m ? m[1] : rootId;
|
|
101
266
|
return {
|
|
102
267
|
contents:
|
|
103
|
-
'const
|
|
104
|
-
'function
|
|
105
|
-
'
|
|
106
|
-
'
|
|
107
|
-
' const
|
|
108
|
-
'
|
|
109
|
-
'
|
|
110
|
-
'
|
|
268
|
+
'const _id = ' + JSON.stringify(id) + ';\n' +
|
|
269
|
+
'export function getAssetsPath(p) {\n' +
|
|
270
|
+
' const parts = String(p).replace(/\\\\/g, "/").split("/");\n' +
|
|
271
|
+
' const out = [];\n' +
|
|
272
|
+
' for (const s of parts) {\n' +
|
|
273
|
+
' if (!s || s === ".") continue;\n' +
|
|
274
|
+
' if (s === "..") { if (out.length) out.pop(); continue; }\n' +
|
|
275
|
+
' out.push(s);\n' +
|
|
276
|
+
' }\n' +
|
|
277
|
+
' const trailing = /[\\/\\\\]$/.test(String(p)) ? "/" : "";\n' +
|
|
278
|
+
' return "assets/" + _id + (out.length ? "/" + out.join("/") : "") + trailing;\n' +
|
|
111
279
|
'}\n',
|
|
112
280
|
loader: 'js',
|
|
113
281
|
};
|
|
@@ -137,5 +305,3 @@ export function makeDedupePlugin(root) {
|
|
|
137
305
|
export function assetsOutDirFor(outDir) {
|
|
138
306
|
return resolve(outDir, '.assets');
|
|
139
307
|
}
|
|
140
|
-
|
|
141
|
-
export { buildTree };
|
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "node .yeow/build.js",
|
|
7
|
-
"dev": "node .yeow/dev-server.js"
|
|
7
|
+
"dev": "node .yeow/dev-server.js",
|
|
8
|
+
"permissions": "node .yeow/permissions.js"
|
|
8
9
|
},
|
|
9
10
|
"dependencies": {
|
|
10
|
-
"yeow-api": "^0.2.
|
|
11
|
+
"yeow-api": "^0.2.102",
|
|
11
12
|
"yeow-utils": "^0.1.14"
|
|
12
13
|
},
|
|
13
14
|
"devDependencies": {
|