nx-auto-cache-deps 0.0.0 → 0.0.2
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/bin/darwin-arm64/nx-auto-cache-deps-trace +0 -0
- package/bin/linux-arm64-gnu/nx-auto-cache-deps-trace +0 -0
- package/bin/linux-x64-gnu/nx-auto-cache-deps-trace +0 -0
- package/dist/dependencies.js +64 -0
- package/dist/executor.js +48 -0
- package/dist/fingerprint-cli.js +14 -0
- package/dist/generators/init.js +17 -0
- package/dist/platform.js +30 -0
- package/dist/plugin.js +87 -0
- package/dist/run-original.js +45 -0
- package/dist/store.js +40 -0
- package/executors.json +9 -0
- package/generators.json +9 -0
- package/package.json +42 -5
- package/schemas/init.json +7 -0
- package/schemas/run.json +15 -0
- package/README.md +0 -1
- package/index.js +0 -1
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readdirSync, readFileSync, statSync } from 'node:fs';
|
|
3
|
+
import { isAbsolute, join, relative, sep } from 'node:path';
|
|
4
|
+
// Nx のワークスペースデータと Git のメタデータは、Nx 自身もタスクハッシュに含めない
|
|
5
|
+
const EXCLUDED_ROOTS = ['.nx', '.git'];
|
|
6
|
+
const toWorkspacePath = (workspaceRoot, absolutePath) => {
|
|
7
|
+
const path = relative(workspaceRoot, absolutePath);
|
|
8
|
+
if (path === '')
|
|
9
|
+
return '.';
|
|
10
|
+
if (isAbsolute(path) || path === '..' || path.startsWith(`..${sep}`))
|
|
11
|
+
return undefined;
|
|
12
|
+
if (EXCLUDED_ROOTS.some((root) => path === root || path.startsWith(`${root}${sep}`)))
|
|
13
|
+
return undefined;
|
|
14
|
+
return path.split(sep).join('/');
|
|
15
|
+
};
|
|
16
|
+
const sortedUnique = (values) => [...new Set(values)].sort();
|
|
17
|
+
export const toDependencyList = (accesses, workspaceRoot) => {
|
|
18
|
+
const inWorkspace = accesses.flatMap((access) => {
|
|
19
|
+
const path = toWorkspacePath(workspaceRoot, access.path);
|
|
20
|
+
return path === undefined ? [] : [{ kind: access.kind, path }];
|
|
21
|
+
});
|
|
22
|
+
// 自分で書いたパスを入力にすると、成果物の更新だけで毎回ミスするため除く
|
|
23
|
+
const written = new Set(inWorkspace.filter((a) => a.kind === 'write').map((a) => a.path));
|
|
24
|
+
const observed = (kind) => sortedUnique(inWorkspace.filter((a) => a.kind === kind && !written.has(a.path)).map((a) => a.path));
|
|
25
|
+
return { version: 1, paths: observed('read'), directories: observed('readDir') };
|
|
26
|
+
};
|
|
27
|
+
export const mergeDependencyLists = (a, b) => ({
|
|
28
|
+
version: 1,
|
|
29
|
+
paths: sortedUnique([...a.paths, ...b.paths]),
|
|
30
|
+
directories: sortedUnique([...a.directories, ...b.directories]),
|
|
31
|
+
});
|
|
32
|
+
const isMissingError = (error) => error instanceof Error && 'code' in error && (error.code === 'ENOENT' || error.code === 'ENOTDIR');
|
|
33
|
+
const pathState = (absolutePath) => {
|
|
34
|
+
const stats = statSync(absolutePath, { throwIfNoEntry: false });
|
|
35
|
+
if (stats === undefined)
|
|
36
|
+
return 'missing';
|
|
37
|
+
if (stats.isDirectory())
|
|
38
|
+
return 'directory';
|
|
39
|
+
if (stats.isFile())
|
|
40
|
+
return `file:${createHash('sha256').update(readFileSync(absolutePath)).digest('hex')}`;
|
|
41
|
+
return 'other';
|
|
42
|
+
};
|
|
43
|
+
const directoryState = (absolutePath) => {
|
|
44
|
+
try {
|
|
45
|
+
return readdirSync(absolutePath, { withFileTypes: true })
|
|
46
|
+
.map((entry) => `${entry.name}:${entry.isDirectory() ? 'd' : entry.isFile() ? 'f' : entry.isSymbolicLink() ? 'l' : 'o'}`)
|
|
47
|
+
.sort()
|
|
48
|
+
.join('\n');
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (isMissingError(error))
|
|
52
|
+
return 'missing';
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
/** 依存一覧の各依存を現在のファイルシステムで評価したハッシュ。 */
|
|
57
|
+
export const fingerprint = (list, workspaceRoot) => {
|
|
58
|
+
const hash = createHash('sha256');
|
|
59
|
+
for (const path of list.paths)
|
|
60
|
+
hash.update(`path\0${path}\0${pathState(join(workspaceRoot, path))}\0`);
|
|
61
|
+
for (const path of list.directories)
|
|
62
|
+
hash.update(`dir\0${path}\0${directoryState(join(workspaceRoot, path))}\0`);
|
|
63
|
+
return hash.digest('hex');
|
|
64
|
+
};
|
package/dist/executor.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { cacheDir, logger } from '@nx/devkit';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { mergeDependencyLists, toDependencyList } from './dependencies.js';
|
|
7
|
+
import { resolveTraceBinary } from './platform.js';
|
|
8
|
+
import { dependencyListPath, readDependencyList, storeDirectory, taskKey, writeDependencyList } from './store.js';
|
|
9
|
+
const runOriginalScript = fileURLToPath(new URL('./run-original.js', import.meta.url));
|
|
10
|
+
const runTraced = (args, cwd) => new Promise((resolve, reject) => {
|
|
11
|
+
const child = spawn(resolveTraceBinary(), args, { cwd, stdio: 'inherit' });
|
|
12
|
+
child.once('error', reject);
|
|
13
|
+
child.once('exit', (code) => resolve(code ?? 1));
|
|
14
|
+
});
|
|
15
|
+
export default async function run(options, context) {
|
|
16
|
+
const { projectName, targetName } = context;
|
|
17
|
+
if (projectName === undefined || targetName === undefined)
|
|
18
|
+
throw new Error('nx-auto-cache-deps: missing task target');
|
|
19
|
+
const project = context.projectsConfigurations.projects[projectName];
|
|
20
|
+
const target = project?.targets?.[targetName];
|
|
21
|
+
if (project === undefined || typeof target?.original_executor !== 'string') {
|
|
22
|
+
throw new Error(`nx-auto-cache-deps: ${projectName}:${targetName} needs "original_executor"`);
|
|
23
|
+
}
|
|
24
|
+
const runs = join(storeDirectory(cacheDir), 'runs');
|
|
25
|
+
mkdirSync(runs, { recursive: true });
|
|
26
|
+
const work = mkdtempSync(join(runs, 'run-'));
|
|
27
|
+
try {
|
|
28
|
+
const payload = join(work, 'execution.json');
|
|
29
|
+
const reportPath = join(work, 'report.json');
|
|
30
|
+
const execution = { executor: target.original_executor, options, context };
|
|
31
|
+
writeFileSync(payload, JSON.stringify(execution));
|
|
32
|
+
const code = await runTraced([reportPath, process.execPath, runOriginalScript, payload], process.cwd());
|
|
33
|
+
const report = JSON.parse(readFileSync(reportPath, 'utf8'));
|
|
34
|
+
if (report.status === 'incomplete') {
|
|
35
|
+
logger.warn(`nx-auto-cache-deps: tracing of ${projectName}:${targetName} was incomplete, dependency list not updated: ${report.reason}`);
|
|
36
|
+
}
|
|
37
|
+
else if (code === 0) {
|
|
38
|
+
const listPath = dependencyListPath(cacheDir, taskKey(project.root, targetName));
|
|
39
|
+
const observed = toDependencyList(report.accesses, context.root);
|
|
40
|
+
const stored = readDependencyList(listPath);
|
|
41
|
+
writeDependencyList(listPath, stored.status === 'found' ? mergeDependencyLists(stored.list, observed) : observed);
|
|
42
|
+
}
|
|
43
|
+
return { success: code === 0 };
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
rmSync(work, { recursive: true, force: true });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Nx の runtime input から呼ばれ、標準出力がタスクハッシュに入る。
|
|
2
|
+
import { cacheDir, workspaceRoot } from '@nx/devkit';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { fingerprint } from './dependencies.js';
|
|
5
|
+
import { dependencyListPath, readDependencyList } from './store.js';
|
|
6
|
+
const key = process.argv[2];
|
|
7
|
+
if (key === undefined)
|
|
8
|
+
throw new Error('nx-auto-cache-deps: fingerprint requires a task key');
|
|
9
|
+
const stored = readDependencyList(dependencyListPath(cacheDir, key));
|
|
10
|
+
if (stored.status === 'unreadable') {
|
|
11
|
+
process.stderr.write(`nx-auto-cache-deps: ignoring unreadable dependency list for ${key}: ${stored.reason}\n`);
|
|
12
|
+
}
|
|
13
|
+
// 依存一覧がなければ毎回違う値を返し、実行結果のキャッシュに必ずミスさせる
|
|
14
|
+
process.stdout.write(stored.status === 'found' ? fingerprint(stored.list, workspaceRoot) : `no-dependency-list:${randomUUID()}`);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { formatFiles, readNxJson, updateNxJson } from '@nx/devkit';
|
|
2
|
+
const PLUGIN = 'nx-auto-cache-deps';
|
|
3
|
+
/**
|
|
4
|
+
* `nx add nx-auto-cache-deps` から呼ばれ、nx.json の plugins に本プラグインを登録する。
|
|
5
|
+
* project.json で executor を差し替えた target は wrap なしの登録が扱うため、wrap 付きの登録だけでは登録済みとしない。
|
|
6
|
+
*/
|
|
7
|
+
export default async function init(tree) {
|
|
8
|
+
const nxJson = readNxJson(tree) ?? {};
|
|
9
|
+
const plugins = nxJson.plugins ?? [];
|
|
10
|
+
const registered = plugins.some((entry) => typeof entry === 'string'
|
|
11
|
+
? entry === PLUGIN
|
|
12
|
+
: entry.plugin === PLUGIN && entry.options?.wrap === undefined);
|
|
13
|
+
if (registered)
|
|
14
|
+
return;
|
|
15
|
+
updateNxJson(tree, { ...nxJson, plugins: [...plugins, PLUGIN] });
|
|
16
|
+
await formatFiles(tree);
|
|
17
|
+
}
|
package/dist/platform.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
export const PACKAGE_NAME = 'nx-auto-cache-deps';
|
|
4
|
+
/** トレーサーのバイナリを同梱するプラットフォーム。パッケージには bin/<id>/ として全プラットフォーム分を入れる。 */
|
|
5
|
+
export const PLATFORMS = [
|
|
6
|
+
{ id: 'linux-x64-gnu', os: 'linux', cpu: 'x64', libc: 'glibc' },
|
|
7
|
+
{ id: 'linux-arm64-gnu', os: 'linux', cpu: 'arm64', libc: 'glibc' },
|
|
8
|
+
{ id: 'darwin-arm64', os: 'darwin', cpu: 'arm64' },
|
|
9
|
+
];
|
|
10
|
+
export const traceBinaryName = (os) => `${PACKAGE_NAME}-trace${os === 'win32' ? '.exe' : ''}`;
|
|
11
|
+
/** パッケージ内でのバイナリの位置。パッケージのルートからの相対パス。 */
|
|
12
|
+
export const traceBinaryPath = (platform) => `bin/${platform.id}/${traceBinaryName(platform.os)}`;
|
|
13
|
+
const isGlibc = () => {
|
|
14
|
+
const header = process.report.getReport().header;
|
|
15
|
+
return header?.glibcVersionRuntime !== undefined;
|
|
16
|
+
};
|
|
17
|
+
export const currentPlatform = () => PLATFORMS.find((platform) => platform.os === process.platform &&
|
|
18
|
+
platform.cpu === process.arch &&
|
|
19
|
+
(platform.libc === undefined || isGlibc()));
|
|
20
|
+
/** 実行中の環境に対応する、同梱のトレーサーのバイナリを返す。 */
|
|
21
|
+
export const resolveTraceBinary = () => {
|
|
22
|
+
const platform = currentPlatform();
|
|
23
|
+
if (platform === undefined) {
|
|
24
|
+
throw new Error(`${PACKAGE_NAME}: ${process.platform}-${process.arch} is not supported`);
|
|
25
|
+
}
|
|
26
|
+
const binary = fileURLToPath(new URL(`../${traceBinaryPath(platform)}`, import.meta.url));
|
|
27
|
+
if (!existsSync(binary))
|
|
28
|
+
throw new Error(`${PACKAGE_NAME}: tracer binary is missing: ${binary}`);
|
|
29
|
+
return binary;
|
|
30
|
+
};
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { basename, dirname, join, matchesGlob, relative } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { taskKey } from './store.js';
|
|
6
|
+
export const RUN_EXECUTOR = 'nx-auto-cache-deps:run';
|
|
7
|
+
const loadWrappedPlugin = (workspaceRoot, name) => {
|
|
8
|
+
const require = createRequire(join(workspaceRoot, 'package.json'));
|
|
9
|
+
return require(require.resolve(name, { paths: [workspaceRoot] }));
|
|
10
|
+
};
|
|
11
|
+
const fingerprintCommand = (workspaceRoot, key) => {
|
|
12
|
+
// worktree ごとにタスクハッシュが変わらないよう、スクリプトはワークスペースからの相対パスで指す
|
|
13
|
+
const script = relative(workspaceRoot, fileURLToPath(new URL('./fingerprint-cli.js', import.meta.url)));
|
|
14
|
+
return { runtime: `node ${JSON.stringify(script)} ${JSON.stringify(key)}` };
|
|
15
|
+
};
|
|
16
|
+
const isFileInput = (input) => typeof input === 'string' || 'fileset' in input || 'input' in input || 'dependentTasksOutputFiles' in input;
|
|
17
|
+
export const wrapTarget = (target, fingerprintInput) => {
|
|
18
|
+
const { executor, command, inputs, options, ...rest } = target;
|
|
19
|
+
return {
|
|
20
|
+
...rest,
|
|
21
|
+
executor: RUN_EXECUTOR,
|
|
22
|
+
original_executor: executor ?? 'nx:run-commands',
|
|
23
|
+
options: command === undefined ? options : { command, ...options },
|
|
24
|
+
// ファイル入力は依存一覧で置き換え、環境変数・外部依存・runtime input は残す
|
|
25
|
+
inputs: [fingerprintInput, ...(inputs ?? []).filter((input) => !isFileInput(input))],
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
const wrapResult = (result, targetNames, workspaceRoot) => ({
|
|
29
|
+
...result,
|
|
30
|
+
projects: Object.fromEntries(Object.entries(result.projects ?? {}).map(([projectRoot, project]) => [
|
|
31
|
+
projectRoot,
|
|
32
|
+
{
|
|
33
|
+
...project,
|
|
34
|
+
targets: Object.fromEntries(Object.entries(project.targets ?? {}).map(([name, target]) => [
|
|
35
|
+
name,
|
|
36
|
+
targetNames.has(name) ? wrapTarget(target, fingerprintCommand(workspaceRoot, taskKey(projectRoot, name))) : target,
|
|
37
|
+
])),
|
|
38
|
+
},
|
|
39
|
+
])),
|
|
40
|
+
});
|
|
41
|
+
const createWrappedNodes = async (files, options, context) => {
|
|
42
|
+
const wrapped = loadWrappedPlugin(context.workspaceRoot, options.wrap.plugin);
|
|
43
|
+
const createNodes = wrapped.createNodes ?? wrapped.createNodesV2;
|
|
44
|
+
if (createNodes === undefined)
|
|
45
|
+
throw new Error(`nx-auto-cache-deps: ${options.wrap.plugin} does not export createNodes`);
|
|
46
|
+
const [pattern, create] = createNodes;
|
|
47
|
+
const matched = files.filter((file) => matchesGlob(file, pattern));
|
|
48
|
+
if (matched.length === 0)
|
|
49
|
+
return [];
|
|
50
|
+
const targetNames = new Set(options.targets ?? []);
|
|
51
|
+
const results = await create(matched, options.wrap.options, context);
|
|
52
|
+
return results.map(([file, result]) => [file, wrapResult(result, targetNames, context.workspaceRoot)]);
|
|
53
|
+
};
|
|
54
|
+
const createExplicitNodes = (files, context) => files
|
|
55
|
+
.filter((file) => basename(file) === 'project.json')
|
|
56
|
+
.flatMap((file) => {
|
|
57
|
+
const projectRoot = dirname(file);
|
|
58
|
+
const projectJson = JSON.parse(readFileSync(join(context.workspaceRoot, file), 'utf8'));
|
|
59
|
+
const targets = Object.entries(projectJson.targets ?? {})
|
|
60
|
+
.filter(([, target]) => target.executor === RUN_EXECUTOR)
|
|
61
|
+
.map(([name]) => [name, { inputs: [fingerprintCommand(context.workspaceRoot, taskKey(projectRoot, name))] }]);
|
|
62
|
+
return targets.length === 0
|
|
63
|
+
? []
|
|
64
|
+
: [[file, { projects: { [projectRoot]: { targets: Object.fromEntries(targets) } } }]];
|
|
65
|
+
});
|
|
66
|
+
// 包むプラグインの glob は登録ごとに違うため、全ファイルを受け取り内側で絞り込む
|
|
67
|
+
export const createNodes = [
|
|
68
|
+
'**/*',
|
|
69
|
+
async (files, options, context) => {
|
|
70
|
+
const wrap = options?.wrap;
|
|
71
|
+
return wrap === undefined
|
|
72
|
+
? createExplicitNodes(files, context)
|
|
73
|
+
: createWrappedNodes(files, { ...options, wrap }, context);
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
export const createDependencies = (options, context) => {
|
|
77
|
+
const wrap = options?.wrap;
|
|
78
|
+
if (wrap === undefined)
|
|
79
|
+
return [];
|
|
80
|
+
return loadWrappedPlugin(context.workspaceRoot, wrap.plugin).createDependencies?.(wrap.options, context) ?? [];
|
|
81
|
+
};
|
|
82
|
+
export const createMetadata = (graph, options, context) => {
|
|
83
|
+
const wrap = options?.wrap;
|
|
84
|
+
if (wrap === undefined)
|
|
85
|
+
return {};
|
|
86
|
+
return loadWrappedPlugin(context.workspaceRoot, wrap.plugin).createMetadata?.(graph, wrap.options, context) ?? {};
|
|
87
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
const readJson = (path) => JSON.parse(readFileSync(path, 'utf8'));
|
|
5
|
+
const resolveExecutor = (workspaceRoot, name) => {
|
|
6
|
+
const separator = name.lastIndexOf(':');
|
|
7
|
+
const packageName = name.slice(0, separator);
|
|
8
|
+
const executorName = name.slice(separator + 1);
|
|
9
|
+
const packageJsonPath = createRequire(join(workspaceRoot, 'package.json')).resolve(`${packageName}/package.json`);
|
|
10
|
+
const packageJson = readJson(packageJsonPath);
|
|
11
|
+
const executorsFile = packageJson.executors ?? packageJson.builders;
|
|
12
|
+
if (executorsFile === undefined)
|
|
13
|
+
throw new Error(`${packageName} does not declare executors`);
|
|
14
|
+
const executorsJsonPath = join(dirname(packageJsonPath), executorsFile);
|
|
15
|
+
const entry = readJson(executorsJsonPath).executors?.[executorName];
|
|
16
|
+
if (entry === undefined)
|
|
17
|
+
throw new Error(`executor ${name} not found`);
|
|
18
|
+
const base = dirname(executorsJsonPath);
|
|
19
|
+
const [modulePath = '', exportName = 'default'] = entry.implementation.split('#');
|
|
20
|
+
const module = createRequire(executorsJsonPath)(join(base, modulePath));
|
|
21
|
+
const implementation = typeof module === 'function' ? module : module[exportName];
|
|
22
|
+
if (typeof implementation !== 'function')
|
|
23
|
+
throw new Error(`executor ${name} has no implementation`);
|
|
24
|
+
return { implementation: implementation, schema: readJson(join(base, entry.schema)) };
|
|
25
|
+
};
|
|
26
|
+
// Nx は executor の schema の既定値を補ってから呼ぶ。外側の executor はそれを内側の schema で行えないため、ここで補う
|
|
27
|
+
const withDefaults = (schema, options) => ({
|
|
28
|
+
...Object.fromEntries(Object.entries(schema.properties ?? {}).flatMap(([key, property]) => property.default === undefined ? [] : [[key, property.default]])),
|
|
29
|
+
...options,
|
|
30
|
+
});
|
|
31
|
+
const lastResult = async (output) => {
|
|
32
|
+
if (!(Symbol.asyncIterator in output))
|
|
33
|
+
return output;
|
|
34
|
+
let last = { success: false };
|
|
35
|
+
for await (const result of output)
|
|
36
|
+
last = result;
|
|
37
|
+
return last;
|
|
38
|
+
};
|
|
39
|
+
const path = process.argv[2];
|
|
40
|
+
if (path === undefined)
|
|
41
|
+
throw new Error('nx-auto-cache-deps: missing execution file');
|
|
42
|
+
const { executor, options, context } = readJson(path);
|
|
43
|
+
const { implementation, schema } = resolveExecutor(context.root, executor);
|
|
44
|
+
const result = await lastResult(implementation(withDefaults(schema, options), context));
|
|
45
|
+
process.exitCode = result.success ? 0 : 1;
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
/** タスクは worktree の場所に依存しないよう、ワークスペースルートからの project root と target 名で識別する。 */
|
|
5
|
+
export const taskKey = (projectRoot, targetName) => `${projectRoot}:${targetName}`;
|
|
6
|
+
export const storeDirectory = (cacheDirectory) => join(cacheDirectory, 'nx-auto-cache-deps');
|
|
7
|
+
export const dependencyListPath = (cacheDirectory, key) => join(storeDirectory(cacheDirectory), 'dependencies', `${encodeURIComponent(key)}.json`);
|
|
8
|
+
const isDependencyList = (value) => typeof value === 'object' &&
|
|
9
|
+
value !== null &&
|
|
10
|
+
'version' in value &&
|
|
11
|
+
value.version === 1 &&
|
|
12
|
+
'paths' in value &&
|
|
13
|
+
Array.isArray(value.paths) &&
|
|
14
|
+
'directories' in value &&
|
|
15
|
+
Array.isArray(value.directories);
|
|
16
|
+
export const readDependencyList = (path) => {
|
|
17
|
+
let text;
|
|
18
|
+
try {
|
|
19
|
+
text = readFileSync(path, 'utf8');
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT')
|
|
23
|
+
return { status: 'missing' };
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const value = JSON.parse(text);
|
|
28
|
+
return isDependencyList(value) ? { status: 'found', list: value } : { status: 'unreadable', reason: 'unknown format' };
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
return { status: 'unreadable', reason: String(error) };
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
/** 別プロセスが書きかけの一覧を読まないよう、一時ファイルから rename で置き換える。 */
|
|
35
|
+
export const writeDependencyList = (path, list) => {
|
|
36
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
37
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
38
|
+
writeFileSync(temporary, JSON.stringify(list));
|
|
39
|
+
renameSync(temporary, path);
|
|
40
|
+
};
|
package/executors.json
ADDED
package/generators.json
ADDED
package/package.json
CHANGED
|
@@ -1,10 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nx-auto-cache-deps",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "Nx plugin that derives cache inputs from the files a task actually accessed",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/plugin.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./dist/plugin.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"executors": "./executors.json",
|
|
13
|
+
"generators": "./generators.json",
|
|
6
14
|
"files": [
|
|
7
|
-
"
|
|
15
|
+
"bin",
|
|
16
|
+
"dist",
|
|
17
|
+
"schemas",
|
|
18
|
+
"executors.json",
|
|
19
|
+
"generators.json"
|
|
8
20
|
],
|
|
9
|
-
"
|
|
21
|
+
"preferUnplugged": true,
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc -p tsconfig.json",
|
|
24
|
+
"build:native": "node scripts/build-native.mjs",
|
|
25
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
26
|
+
"test": "node --test --test-concurrency=1 test/*.test.ts"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"nx": ">=24.0.0 <25"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@nx/devkit": "24.0.5"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "24.19.2",
|
|
36
|
+
"nx": "24.0.5",
|
|
37
|
+
"typescript": "7.0.2"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=24"
|
|
41
|
+
},
|
|
42
|
+
"packageManager": "pnpm@12.18.0",
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "git+https://github.com/9wick/nx-auto-cache-deps.git"
|
|
46
|
+
}
|
|
10
47
|
}
|
package/schemas/run.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"title": "nx-auto-cache-deps:run",
|
|
4
|
+
"description": "Options are passed through to original_executor.",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"__unparsed__": {
|
|
8
|
+
"hidden": true,
|
|
9
|
+
"type": "array",
|
|
10
|
+
"items": { "type": "string" },
|
|
11
|
+
"$default": { "$source": "unparsed" }
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"additionalProperties": true
|
|
15
|
+
}
|
package/README.md
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
This version is not intended for use.
|
package/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
throw new Error('This version is not intended for use.');
|