@serveryyswys/opm 0.0.0-stage → 0.1.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.
@@ -0,0 +1,188 @@
1
+ 'use strict';
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OPM_PACKAGE = void 0;
4
+ exports.initProject = initProject;
5
+ /**
6
+ * opm init:把工程准备到"能 build"。
7
+ *
8
+ * 只管三件事(都是最容易写错的地方):
9
+ * 1. package.json 里补 scripts.build = "opm build",以及两个 devDependency;
10
+ * 2. 没有 manifest.json 就生成一个骨架(toolpkg 的身份靠它);
11
+ * 3. 工程里有 .ts 就补一个 tsconfig.json,且 outDir 与 manifest 的路径对得上。
12
+ *
13
+ * **不写死 registry**:依赖从哪来由使用者自己的 npm 配置决定。
14
+ * 默认会跑一次 npm install(可用 --no-install 关掉),因为没装 node.operit 就 build 不了。
15
+ */
16
+ const node_fs_1 = require("node:fs");
17
+ const node_path_1 = require("node:path");
18
+ const contract_1 = require("./contract");
19
+ const dependencies_1 = require("./dependencies");
20
+ exports.OPM_PACKAGE = '@serveryyswys/opm';
21
+ function readJson(path) {
22
+ if (!(0, node_fs_1.existsSync)(path)) {
23
+ return null;
24
+ }
25
+ try {
26
+ return JSON.parse((0, node_fs_1.readFileSync)(path, 'utf8'));
27
+ }
28
+ catch {
29
+ return null;
30
+ }
31
+ }
32
+ function writeJson(path, value) {
33
+ (0, node_fs_1.writeFileSync)(path, JSON.stringify(value, null, 2) + '\n', 'utf8');
34
+ }
35
+ /** 目录里有没有 .ts(跳过 node_modules)。 */
36
+ function hasTypeScript(projectDir) {
37
+ const stack = [projectDir];
38
+ while (stack.length > 0) {
39
+ const current = stack.pop();
40
+ let entries;
41
+ try {
42
+ entries = (0, node_fs_1.readdirSync)(current, { withFileTypes: true });
43
+ }
44
+ catch {
45
+ continue;
46
+ }
47
+ for (const entry of entries) {
48
+ if (entry.name === 'node_modules' || entry.name.startsWith('.')) {
49
+ continue;
50
+ }
51
+ if (entry.isDirectory()) {
52
+ stack.push((0, node_path_1.join)(current, entry.name));
53
+ }
54
+ else if (entry.name.endsWith('.ts')) {
55
+ return true;
56
+ }
57
+ }
58
+ }
59
+ return false;
60
+ }
61
+ function initProject(options) {
62
+ const projectDir = (0, node_path_1.resolve)(options.projectDir);
63
+ const install = options.install !== false;
64
+ const overwrite = options.overwrite === true;
65
+ const created = [];
66
+ const updated = [];
67
+ const unchanged = [];
68
+ const notes = [];
69
+ const errors = [];
70
+ const nextSteps = [];
71
+ if (!(0, node_fs_1.existsSync)(projectDir)) {
72
+ (0, node_fs_1.mkdirSync)(projectDir, { recursive: true });
73
+ created.push(projectDir + '/');
74
+ }
75
+ const projectName = (0, node_path_1.basename)(projectDir);
76
+ // ---- package.json
77
+ const packagePath = (0, node_path_1.join)(projectDir, 'package.json');
78
+ const existingPackage = readJson(packagePath);
79
+ const manifest = existingPackage === null ? {} : existingPackage;
80
+ let packageChanged = false;
81
+ const scripts = manifest.scripts === null || typeof manifest.scripts !== 'object'
82
+ ? {}
83
+ : manifest.scripts;
84
+ if (overwrite || typeof scripts.build !== 'string') {
85
+ scripts.build = 'opm build';
86
+ packageChanged = true;
87
+ }
88
+ manifest.scripts = scripts;
89
+ const devDependencies = manifest.devDependencies === null || typeof manifest.devDependencies !== 'object'
90
+ ? {}
91
+ : manifest.devDependencies;
92
+ for (const name of [exports.OPM_PACKAGE, contract_1.NODE_OPERIT_PACKAGE]) {
93
+ if (overwrite || devDependencies[name] === undefined) {
94
+ // 用 latest:版本由源上的最新决定,不在生成物里钉死一个会过期的号
95
+ devDependencies[name] = 'latest';
96
+ packageChanged = true;
97
+ }
98
+ }
99
+ manifest.devDependencies = devDependencies;
100
+ if (manifest.name === undefined) {
101
+ manifest.name = projectName;
102
+ packageChanged = true;
103
+ }
104
+ if (manifest.private === undefined) {
105
+ manifest.private = true;
106
+ packageChanged = true;
107
+ }
108
+ if (existingPackage === null) {
109
+ writeJson(packagePath, manifest);
110
+ created.push('package.json');
111
+ }
112
+ else if (packageChanged) {
113
+ writeJson(packagePath, manifest);
114
+ updated.push('package.json');
115
+ }
116
+ else {
117
+ unchanged.push('package.json');
118
+ }
119
+ // ---- manifest.json(toolpkg 身份)
120
+ const toolpkgPath = (0, node_path_1.join)(projectDir, 'manifest.json');
121
+ if (!(0, node_fs_1.existsSync)(toolpkgPath)) {
122
+ // 目录名可能以 . 或 _ 开头(.e2e-opm / _probe),直接拼会得到 "com.example..e2e-opm"
123
+ const safeName = projectName
124
+ .replace(/[^A-Za-z0-9_.-]/g, '-')
125
+ .replace(/^[^A-Za-z0-9]+/, '')
126
+ .replace(/[^A-Za-z0-9]+$/, '');
127
+ const toolpkgId = 'com.example.' + (safeName.length > 0 ? safeName : 'plugin');
128
+ const hasTs = hasTypeScript(projectDir);
129
+ writeJson(toolpkgPath, {
130
+ schema_version: 1,
131
+ toolpkg_id: toolpkgId,
132
+ version: '0.1.0',
133
+ api_version: '1.0.1',
134
+ author: [''],
135
+ main: hasTs ? 'dist/main.js' : 'main.js',
136
+ display_name: { zh: projectName, en: projectName },
137
+ description: { zh: '(待填写)', en: '(to be filled)' },
138
+ subpackages: []
139
+ });
140
+ created.push('manifest.json');
141
+ notes.push('manifest.json 里的 toolpkg_id 现在是 ' + toolpkgId + ',发布前请改成你自己的');
142
+ }
143
+ else {
144
+ unchanged.push('manifest.json');
145
+ }
146
+ // ---- tsconfig.json(只在工程确实有 .ts 时生成,免得把纯 JS 工程推进 TS 模式)
147
+ const tsconfigPath = (0, node_path_1.join)(projectDir, 'tsconfig.json');
148
+ const wantsTypeScript = (0, node_fs_1.existsSync)(tsconfigPath) || hasTypeScript(projectDir);
149
+ if (wantsTypeScript && !(0, node_fs_1.existsSync)(tsconfigPath)) {
150
+ writeJson(tsconfigPath, {
151
+ compilerOptions: {
152
+ target: 'ES2020',
153
+ module: 'node16',
154
+ moduleResolution: 'node16',
155
+ lib: ['ES2020'],
156
+ rootDir: 'src',
157
+ outDir: 'dist',
158
+ strict: true,
159
+ declaration: false,
160
+ skipLibCheck: true,
161
+ types: []
162
+ },
163
+ include: ['src/**/*.ts']
164
+ });
165
+ created.push('tsconfig.json');
166
+ notes.push('tsconfig 的 outDir 是 dist、rootDir 是 src,所以 manifest.main 要写 dist/main.js');
167
+ }
168
+ else if ((0, node_fs_1.existsSync)(tsconfigPath)) {
169
+ unchanged.push('tsconfig.json');
170
+ }
171
+ // ---- 依赖
172
+ let installOutput = '';
173
+ if (install) {
174
+ const installed = (0, dependencies_1.installDependencies)(projectDir);
175
+ installOutput = installed.output;
176
+ if (!installed.ok) {
177
+ errors.push('npm install 失败:\n' + installOutput.trim());
178
+ }
179
+ else {
180
+ notes.push('已执行 npm install(源按你自己的 npm 配置,未指定 registry)');
181
+ }
182
+ }
183
+ else {
184
+ nextSteps.push('npm install');
185
+ }
186
+ nextSteps.push('npm run build');
187
+ return { ok: errors.length === 0, created, updated, unchanged, notes, errors, nextSteps };
188
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * 从 JS 源码里找出所有 require(...) 调用点。
3
+ *
4
+ * 必须跳过注释与字符串 —— 否则文档注释里写一个 require 例子就会被当成真依赖,
5
+ * 把一个不存在的模块拖进归档、或者让构建直接失败。
6
+ *
7
+ * 状态机而不是正则:正则没法同时正确处理 \` 模板串、字符串里的引号逃逸、
8
+ * 以及 "// require('x')" 这种情况。
9
+ */
10
+ export interface RequireSite {
11
+ /** require 的实参(已去引号、已解转义) */
12
+ spec: string;
13
+ /** spec 在源码里的起止下标(不含引号),替换时用 */
14
+ start: number;
15
+ end: number;
16
+ }
17
+ export declare function findRequires(source: string): RequireSite[];
@@ -0,0 +1,115 @@
1
+ 'use strict';
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findRequires = findRequires;
4
+ const QUOTES = new Set(["'", '"', '\`']);
5
+ function findRequires(source) {
6
+ const sites = [];
7
+ let index = 0;
8
+ const length = source.length;
9
+ // 上一个有意义字符,用于区分正则字面量与除号
10
+ let previousMeaningful = '';
11
+ while (index < length) {
12
+ const char = source[index];
13
+ // 行注释
14
+ if (char === '/' && source[index + 1] === '/') {
15
+ const newline = source.indexOf('\n', index);
16
+ index = newline < 0 ? length : newline + 1;
17
+ continue;
18
+ }
19
+ // 块注释
20
+ if (char === '/' && source[index + 1] === '*') {
21
+ const close = source.indexOf('*/', index + 2);
22
+ index = close < 0 ? length : close + 2;
23
+ continue;
24
+ }
25
+ // 字符串 / 模板串
26
+ if (QUOTES.has(char)) {
27
+ const quote = char;
28
+ index += 1;
29
+ while (index < length) {
30
+ if (source[index] === '\\') {
31
+ index += 2;
32
+ continue;
33
+ }
34
+ if (source[index] === quote) {
35
+ index += 1;
36
+ break;
37
+ }
38
+ index += 1;
39
+ }
40
+ previousMeaningful = quote;
41
+ continue;
42
+ }
43
+ // 正则字面量:出现在"期待操作数"的位置时才是正则,否则是除号
44
+ if (char === '/' && (previousMeaningful === '' || '(,=:[!&|?{};+-*%~^<>'.includes(previousMeaningful))) {
45
+ index += 1;
46
+ let inClass = false;
47
+ while (index < length) {
48
+ const c = source[index];
49
+ if (c === '\\') {
50
+ index += 2;
51
+ continue;
52
+ }
53
+ if (c === '[') {
54
+ inClass = true;
55
+ }
56
+ else if (c === ']') {
57
+ inClass = false;
58
+ }
59
+ else if (c === '/' && !inClass) {
60
+ index += 1;
61
+ break;
62
+ }
63
+ else if (c === '\n') {
64
+ break;
65
+ }
66
+ index += 1;
67
+ }
68
+ previousMeaningful = '/';
69
+ continue;
70
+ }
71
+ if (char === 'r' && source.startsWith('require', index)) {
72
+ const before = index === 0 ? '' : source[index - 1];
73
+ const isIdentifierChar = /[A-Za-z0-9_$]/.test(before);
74
+ if (!isIdentifierChar) {
75
+ let cursor = index + 'require'.length;
76
+ while (cursor < length && /\s/.test(source[cursor])) {
77
+ cursor += 1;
78
+ }
79
+ if (source[cursor] === '(') {
80
+ cursor += 1;
81
+ while (cursor < length && /\s/.test(source[cursor])) {
82
+ cursor += 1;
83
+ }
84
+ const quote = source[cursor];
85
+ if (quote === "'" || quote === '"') {
86
+ cursor += 1;
87
+ const start = cursor;
88
+ let value = '';
89
+ while (cursor < length && source[cursor] !== quote) {
90
+ if (source[cursor] === '\\') {
91
+ const escaped = source[cursor + 1];
92
+ value += escaped === 'n' ? '\n' : escaped === 't' ? '\t' : escaped;
93
+ cursor += 2;
94
+ continue;
95
+ }
96
+ value += source[cursor];
97
+ cursor += 1;
98
+ }
99
+ if (cursor < length) {
100
+ sites.push({ spec: value, start: start, end: cursor });
101
+ index = cursor + 1;
102
+ previousMeaningful = quote;
103
+ continue;
104
+ }
105
+ }
106
+ }
107
+ }
108
+ }
109
+ if (!/\s/.test(char)) {
110
+ previousMeaningful = char;
111
+ }
112
+ index += 1;
113
+ }
114
+ return sites;
115
+ }
@@ -0,0 +1,24 @@
1
+ import { Contract } from './contract';
2
+ export interface Diagnostic {
3
+ file: string;
4
+ spec: string;
5
+ kind: 'builtin' | 'package' | 'relative' | 'untouched';
6
+ target: string;
7
+ }
8
+ export interface RewriteOutcome {
9
+ code: string;
10
+ diagnostics: Diagnostic[];
11
+ errors: string[];
12
+ /** 本次重写引入、需要继续处理的新文件(归档路径) */
13
+ discovered: string[];
14
+ }
15
+ export interface RewriteEnv {
16
+ /** 归档路径 -> 磁盘绝对路径;不存在返回 null */
17
+ locate(archivePath: string): string | null;
18
+ contract: Contract;
19
+ /** node.operit 在归档内的安装目录(正斜杠) */
20
+ nodeOperitArchiveDir: string;
21
+ }
22
+ /** 相对引用 -> 归档路径。越界的 .. 直接丢弃,与运行时 normalizePath 的行为一致。 */
23
+ export declare function resolveRelative(fromArchive: string, spec: string): string;
24
+ export declare function rewriteModule(env: RewriteEnv, archivePath: string, source: string): RewriteOutcome;
@@ -0,0 +1,197 @@
1
+ 'use strict';
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveRelative = resolveRelative;
4
+ exports.rewriteModule = rewriteModule;
5
+ /**
6
+ * 裸名重写:把 require 的目标改写成「归档内的显式 .js 相对路径」。
7
+ *
8
+ * 三条规则(与 node.operit 的 BUILTINS.json 契约一致):
9
+ * 1. 运行时已内建的裸名(lodash / uuid / axios)保持原样;
10
+ * 2. 其余 Node 内建 -> node.operit 的 dist 子路径;status 为 planned/unsupported 的直接构建失败;
11
+ * 3. 第三方 npm 包 -> 该包入口文件(解析 main / exports)。
12
+ *
13
+ * 为什么必须显式带 .js:运行时的 buildCandidatePaths 对命中扩展名正则的名字只试一个候选、
14
+ * 不补扩展名。包名里的 ".operit" 正好命中那个正则,所以 ".../node.operit" 会被当成完整路径而解析失败。
15
+ *
16
+ * 全程在「归档路径空间」里工作:不传项目目录,也不需要知道磁盘布局 ——
17
+ * 归档路径与磁盘路径的对应关系由 env.locate 负责,这样 TS 模式的产物目录映射能透明接进来。
18
+ */
19
+ const node_fs_1 = require("node:fs");
20
+ const node_path_1 = require("node:path");
21
+ const contract_1 = require("./contract");
22
+ const js_requires_1 = require("./js-requires");
23
+ const EXTENSION_RE = /\.[a-z0-9]+$/i;
24
+ function toPosix(value) {
25
+ return value.replace(/\\/g, '/');
26
+ }
27
+ /** 归档路径的目录部分('' 表示归档根)。 */
28
+ function dirOf(archivePath) {
29
+ const index = archivePath.lastIndexOf('/');
30
+ return index < 0 ? '' : archivePath.slice(0, index);
31
+ }
32
+ /** 相对引用 -> 归档路径。越界的 .. 直接丢弃,与运行时 normalizePath 的行为一致。 */
33
+ function resolveRelative(fromArchive, spec) {
34
+ const joined = (dirOf(fromArchive) === '' ? '' : dirOf(fromArchive) + '/') + toPosix(spec);
35
+ const stack = [];
36
+ for (const part of joined.split('/')) {
37
+ if (part === '' || part === '.')
38
+ continue;
39
+ if (part === '..') {
40
+ stack.pop();
41
+ continue;
42
+ }
43
+ stack.push(part);
44
+ }
45
+ return stack.join('/');
46
+ }
47
+ /**
48
+ * 目标文件相对当前文件的引用写法。
49
+ *
50
+ * 只有真正需要时才补 "./":已经是 "../" 开头时再补会得到 "./../x",
51
+ * 运行时的 normalizePath 虽然能吃掉它,但产出会显得很不专业。
52
+ */
53
+ function relativeSpec(fromArchive, target) {
54
+ const relative = toPosix(node_path_1.posix.relative(dirOf(fromArchive), target));
55
+ return relative.startsWith('.') ? relative : './' + relative;
56
+ }
57
+ /** 依次尝试 原样 / .js / .json / /index.js;命中已存在的则返回归档路径。 */
58
+ function withCandidates(env, archivePath) {
59
+ const attempts = EXTENSION_RE.test(archivePath)
60
+ ? [archivePath]
61
+ : [archivePath, archivePath + '.js', archivePath + '.json', archivePath + '/index.js'];
62
+ for (const attempt of attempts) {
63
+ if (env.locate(attempt) !== null) {
64
+ return attempt;
65
+ }
66
+ }
67
+ return null;
68
+ }
69
+ function readJson(path) {
70
+ try {
71
+ return JSON.parse((0, node_fs_1.readFileSync)(path, 'utf8'));
72
+ }
73
+ catch {
74
+ return null;
75
+ }
76
+ }
77
+ /** 从 exports 的某个值里挑出 CommonJS 能用的那个(require > node > default > import)。 */
78
+ function pickCondition(value) {
79
+ if (typeof value === 'string')
80
+ return value;
81
+ if (value === null || typeof value !== 'object')
82
+ return null;
83
+ const record = value;
84
+ for (const key of ['require', 'node', 'default', 'import']) {
85
+ const picked = pickCondition(record[key]);
86
+ if (picked !== null)
87
+ return picked;
88
+ }
89
+ return null;
90
+ }
91
+ function resolveExports(exportsField, subpath) {
92
+ if (exportsField === undefined || exportsField === null)
93
+ return null;
94
+ if (typeof exportsField === 'string') {
95
+ return subpath === '' ? exportsField : null;
96
+ }
97
+ if (typeof exportsField !== 'object')
98
+ return null;
99
+ const record = exportsField;
100
+ const keys = Object.keys(record);
101
+ if (!keys.some((key) => key.startsWith('.'))) {
102
+ return subpath === '' ? pickCondition(exportsField) : null;
103
+ }
104
+ const wanted = subpath === '' ? '.' : './' + subpath;
105
+ const direct = pickCondition(record[wanted]);
106
+ if (direct !== null)
107
+ return direct;
108
+ for (const key of keys) {
109
+ if (!key.includes('*'))
110
+ continue;
111
+ const starIndex = key.indexOf('*');
112
+ const prefix = key.slice(0, starIndex);
113
+ const suffix = key.slice(starIndex + 1);
114
+ if (!wanted.startsWith(prefix) || !wanted.endsWith(suffix))
115
+ continue;
116
+ const middle = wanted.slice(prefix.length, wanted.length - suffix.length);
117
+ const template = pickCondition(record[key]);
118
+ if (template !== null)
119
+ return template.replace('*', middle);
120
+ }
121
+ return null;
122
+ }
123
+ /** 裸包名 -> 归档内的入口文件。 */
124
+ function resolvePackageEntry(env, spec) {
125
+ const segments = spec.split('/');
126
+ const isScoped = spec.startsWith('@');
127
+ const packageName = isScoped ? segments.slice(0, 2).join('/') : segments[0];
128
+ const subpath = segments.slice(isScoped ? 2 : 1).join('/');
129
+ const packageDir = 'node_modules/' + packageName;
130
+ const manifestDisk = env.locate(packageDir + '/package.json');
131
+ if (manifestDisk === null) {
132
+ return null;
133
+ }
134
+ const manifest = readJson(manifestDisk);
135
+ let relative = manifest === null ? null : resolveExports(manifest.exports, subpath);
136
+ if (relative === null && subpath === '' && manifest !== null && typeof manifest.main === 'string') {
137
+ relative = manifest.main;
138
+ }
139
+ if (relative === null) {
140
+ relative = subpath === '' ? 'index.js' : subpath;
141
+ }
142
+ return withCandidates(env, packageDir + '/' + toPosix(relative));
143
+ }
144
+ function rewriteModule(env, archivePath, source) {
145
+ const sites = (0, js_requires_1.findRequires)(source);
146
+ const diagnostics = [];
147
+ const errors = [];
148
+ const discovered = [];
149
+ // 从后往前替换:前面的改动不影响后面的下标
150
+ let output = source;
151
+ for (let index = sites.length - 1; index >= 0; index -= 1) {
152
+ const site = sites[index];
153
+ const spec = site.spec;
154
+ if (contract_1.RUNTIME_BUILTINS.includes(spec)) {
155
+ diagnostics.push({ file: archivePath, spec, kind: 'untouched', target: spec });
156
+ continue;
157
+ }
158
+ if (spec.startsWith('./') || spec.startsWith('../') || spec.startsWith('/')) {
159
+ const resolved = withCandidates(env, resolveRelative(archivePath, spec));
160
+ if (resolved === null) {
161
+ errors.push(archivePath + ' 里 require(\'' + spec + '\') 解析不到文件');
162
+ continue;
163
+ }
164
+ diagnostics.push({ file: archivePath, spec, kind: 'relative', target: resolved });
165
+ discovered.push(resolved);
166
+ output = output.slice(0, site.start) + relativeSpec(archivePath, resolved) + output.slice(site.end);
167
+ continue;
168
+ }
169
+ const builtin = env.contract.builtins[spec];
170
+ if (builtin !== undefined) {
171
+ if (builtin.status === 'planned' || builtin.status === 'unsupported') {
172
+ errors.push(archivePath + ' 依赖 ' + spec + ',契约里它是 ' + builtin.status + ',构建中止。\n' +
173
+ ' 原因:' + (builtin.note === undefined ? '(契约未注明)' : builtin.note));
174
+ continue;
175
+ }
176
+ const target = env.nodeOperitArchiveDir + '/' + env.contract.distDir + '/' + builtin.subpath + '.js';
177
+ if (env.locate(target) === null) {
178
+ errors.push(archivePath + ' 依赖 ' + spec + ',但归档里找不到 ' + target + '(node.operit 装得不完整?)');
179
+ continue;
180
+ }
181
+ diagnostics.push({ file: archivePath, spec, kind: 'builtin', target });
182
+ discovered.push(target);
183
+ output = output.slice(0, site.start) + relativeSpec(archivePath, target) + output.slice(site.end);
184
+ continue;
185
+ }
186
+ const packageEntry = resolvePackageEntry(env, spec);
187
+ if (packageEntry === null) {
188
+ errors.push(archivePath + ' 里 require(\'' + spec + '\') 既不是 Node 内建、也不在 node_modules 里。\n' +
189
+ ' 契约表里没有这个名字,node_modules/' + spec + ' 也不存在。');
190
+ continue;
191
+ }
192
+ diagnostics.push({ file: archivePath, spec, kind: 'package', target: packageEntry });
193
+ discovered.push(packageEntry);
194
+ output = output.slice(0, site.start) + relativeSpec(archivePath, packageEntry) + output.slice(site.end);
195
+ }
196
+ return { code: output, diagnostics, errors, discovered };
197
+ }
@@ -0,0 +1,23 @@
1
+ import { dirname } from 'node:path';
2
+ export interface TypeScriptPlan {
3
+ /** 是否 TS 工程 */
4
+ isTypeScript: boolean;
5
+ /** tsconfig 的绝对路径(isTypeScript 为 false 时为空串) */
6
+ tsconfigPath: string;
7
+ /** 编译产物目录(绝对路径);null 表示与源码同目录 */
8
+ outDir: string | null;
9
+ }
10
+ export declare function planTypeScript(projectDir: string): TypeScriptPlan;
11
+ /**
12
+ * 调用 tsc 编译。
13
+ *
14
+ * 输出重定向到**临时文件**而不是管道:某些受限环境不允许通过管道捕获子进程输出
15
+ * (会直接 EPERM),而这里必须拿到编译日志才能报错。临时文件两边都能用。
16
+ */
17
+ export declare function compileTypeScript(projectDir: string, tsconfigPath: string): {
18
+ ok: boolean;
19
+ output: string;
20
+ };
21
+ /** 归档路径 -> 磁盘文件;按 原样 -> 产物目录 -> 逐级剥前导段 依次尝试。 */
22
+ export declare function createLocator(projectDir: string, outDir: string | null): (archivePath: string) => string | null;
23
+ export { dirname };