@gadmin2n/cli 0.0.106 → 0.0.108
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/CHANGELOG.md +17 -0
- package/actions/new.action.js +35 -0
- package/actions/update.action.d.ts +1 -0
- package/actions/update.action.js +564 -630
- package/commands/command.loader.js +0 -2
- package/commands/update.command.js +27 -37
- package/lib/template-merge/base-store.d.ts +16 -0
- package/lib/template-merge/base-store.js +127 -0
- package/lib/template-merge/binary-detect.d.ts +7 -0
- package/lib/template-merge/binary-detect.js +36 -0
- package/lib/template-merge/classify.d.ts +10 -0
- package/lib/template-merge/classify.js +61 -0
- package/lib/template-merge/config-validate.d.ts +5 -0
- package/lib/template-merge/config-validate.js +33 -0
- package/lib/template-merge/excludes.d.ts +7 -0
- package/lib/template-merge/excludes.js +109 -0
- package/lib/template-merge/index.d.ts +9 -8
- package/lib/template-merge/index.js +10 -8
- package/lib/template-merge/materialize.d.ts +18 -0
- package/lib/template-merge/materialize.js +129 -0
- package/lib/template-merge/merge3way.d.ts +15 -0
- package/lib/template-merge/merge3way.js +54 -0
- package/lib/template-merge/residual-scan.d.ts +10 -0
- package/lib/template-merge/residual-scan.js +31 -0
- package/lib/template-merge/types.d.ts +27 -0
- package/lib/template-merge/types.js +5 -0
- package/package.json +2 -2
- package/commands/build.command.d.ts +0 -5
- package/commands/build.command.js +0 -50
- package/lib/template-merge/config-loader.d.ts +0 -15
- package/lib/template-merge/config-loader.js +0 -38
- package/lib/template-merge/env-merger.d.ts +0 -5
- package/lib/template-merge/env-merger.js +0 -57
- package/lib/template-merge/glob.d.ts +0 -5
- package/lib/template-merge/glob.js +0 -78
- package/lib/template-merge/json-merger.d.ts +0 -5
- package/lib/template-merge/json-merger.js +0 -269
- package/lib/template-merge/merger.d.ts +0 -30
- package/lib/template-merge/merger.js +0 -2
- package/lib/template-merge/prisma-merger.d.ts +0 -5
- package/lib/template-merge/prisma-merger.js +0 -112
- package/lib/template-merge/registry.d.ts +0 -25
- package/lib/template-merge/registry.js +0 -42
- package/lib/template-merge/ts-module-merger.d.ts +0 -16
- package/lib/template-merge/ts-module-merger.js +0 -193
|
@@ -5,7 +5,6 @@ const chalk = require("chalk");
|
|
|
5
5
|
const actions_1 = require("../actions");
|
|
6
6
|
const ui_1 = require("../lib/ui");
|
|
7
7
|
const add_command_1 = require("./add.command");
|
|
8
|
-
const build_command_1 = require("./build.command");
|
|
9
8
|
const generate_command_1 = require("./generate.command");
|
|
10
9
|
const info_command_1 = require("./info.command");
|
|
11
10
|
const new_command_1 = require("./new.command");
|
|
@@ -15,7 +14,6 @@ const update_command_1 = require("./update.command");
|
|
|
15
14
|
class CommandLoader {
|
|
16
15
|
static load(program) {
|
|
17
16
|
new new_command_1.NewCommand(new actions_1.NewAction()).load(program);
|
|
18
|
-
new build_command_1.BuildCommand(new actions_1.BuildAction()).load(program);
|
|
19
17
|
new start_command_1.StartCommand(new actions_1.StartAction()).load(program);
|
|
20
18
|
new info_command_1.InfoCommand(new actions_1.InfoAction()).load(program);
|
|
21
19
|
new update_command_1.UpdateCommand(new actions_1.UpdateAction()).load(program);
|
|
@@ -14,55 +14,45 @@ const abstract_command_1 = require("./abstract.command");
|
|
|
14
14
|
class UpdateCommand extends abstract_command_1.AbstractCommand {
|
|
15
15
|
load(program) {
|
|
16
16
|
program
|
|
17
|
-
.command('update [subcommand]')
|
|
17
|
+
.command('update [subcommand] [arg]')
|
|
18
18
|
.alias('u')
|
|
19
|
-
.description('Update project from template
|
|
19
|
+
.description('Update project from template via git-merge-style 3-way merge,\n' +
|
|
20
|
+
'sync .claude context, and install changed deps.\n\n' +
|
|
20
21
|
' Subcommands:\n' +
|
|
21
|
-
' (none)
|
|
22
|
-
'
|
|
23
|
-
'
|
|
24
|
-
'
|
|
25
|
-
'
|
|
26
|
-
'
|
|
27
|
-
'
|
|
28
|
-
'
|
|
29
|
-
'
|
|
30
|
-
' merge Attempt auto merge via git merge-file\n' +
|
|
31
|
-
' conflict Write conflict markers for manual resolve\n')
|
|
22
|
+
' (none) Run the full pipeline: 3-way template merge → .claude sync → deps install.\n' +
|
|
23
|
+
' diff [file] Show 3-way differences (read-only). Optional file path filters output.\n' +
|
|
24
|
+
' status Report unresolved conflicts. Exit code: 0 clean / 1 conflicts / 2 meta error.\n\n' +
|
|
25
|
+
' Conflict resolution:\n' +
|
|
26
|
+
' Conflicts are written into source files as <<<<<<< INSTANCE / >>>>>>> TEMPLATE markers.\n' +
|
|
27
|
+
' Resolve them in your editor, verify with `gadmin2 update status`, then commit.\n\n' +
|
|
28
|
+
' Stage toggles:\n' +
|
|
29
|
+
' --no-template Skip template merge + auto-install.\n' +
|
|
30
|
+
' --no-coding-context Skip .claude context sync.\n')
|
|
32
31
|
.option('--no-content', '[diff] Only show file list, skip inline diff content.')
|
|
33
32
|
.option('-d, --dry-run', '[update] Show what would be updated without making changes.')
|
|
34
|
-
.option('-y, --yes', '[update] Skip
|
|
35
|
-
.option('--
|
|
33
|
+
.option('-y, --yes', '[update] Skip interactive prompts (currently used only for .claude protocol).')
|
|
34
|
+
.option('--ours', '[update] Resolve all conflicts by keeping the instance side (mutually exclusive with --theirs).')
|
|
35
|
+
.option('--theirs', '[update] Resolve all conflicts by taking the template side (mutually exclusive with --ours).')
|
|
36
36
|
.option('--no-version-check', '[update | diff] Skip the npm registry version check at startup.')
|
|
37
|
-
.option('--coding-context-repo <url>', '[update] Fully override the .claude context repo URL
|
|
38
|
-
.option('--coding-context-protocol <proto>', '[update]
|
|
39
|
-
.option('--no-coding-context', '[update] Skip the .claude context sync stage
|
|
40
|
-
.option('--no-template', '[update] Skip the template merge
|
|
41
|
-
.action((subcommand, command) => __awaiter(this, void 0, void 0, function* () {
|
|
37
|
+
.option('--coding-context-repo <url>', '[update] Fully override the .claude context repo URL.')
|
|
38
|
+
.option('--coding-context-protocol <proto>', '[update] One of: https | ssh | skip.')
|
|
39
|
+
.option('--no-coding-context', '[update] Skip the .claude context sync stage.')
|
|
40
|
+
.option('--no-template', '[update] Skip the template merge + auto-install stages.')
|
|
41
|
+
.action((subcommand, arg, command) => __awaiter(this, void 0, void 0, function* () {
|
|
42
42
|
const inputs = [];
|
|
43
43
|
inputs.push({ name: 'subcommand', value: subcommand || '' });
|
|
44
|
+
inputs.push({ name: 'subcommand-arg', value: arg || '' });
|
|
44
45
|
const options = [];
|
|
45
46
|
options.push({ name: 'no-content', value: !command.content });
|
|
46
47
|
options.push({ name: 'dry-run', value: !!command.dryRun });
|
|
47
48
|
options.push({ name: 'yes', value: !!command.yes });
|
|
48
|
-
options.push({ name: '
|
|
49
|
+
options.push({ name: 'ours', value: !!command.ours });
|
|
50
|
+
options.push({ name: 'theirs', value: !!command.theirs });
|
|
49
51
|
options.push({ name: 'version-check', value: command.versionCheck !== false });
|
|
50
|
-
options.push({
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
});
|
|
54
|
-
options.push({
|
|
55
|
-
name: 'coding-context-protocol',
|
|
56
|
-
value: command.claudeContextProtocol || '',
|
|
57
|
-
});
|
|
58
|
-
options.push({
|
|
59
|
-
name: 'coding-context',
|
|
60
|
-
value: command.claudeContext !== false,
|
|
61
|
-
});
|
|
62
|
-
options.push({
|
|
63
|
-
name: 'template',
|
|
64
|
-
value: command.template !== false,
|
|
65
|
-
});
|
|
52
|
+
options.push({ name: 'coding-context-repo', value: command.codingContextRepo || '' });
|
|
53
|
+
options.push({ name: 'coding-context-protocol', value: command.codingContextProtocol || '' });
|
|
54
|
+
options.push({ name: 'coding-context', value: command.codingContext !== false });
|
|
55
|
+
options.push({ name: 'template', value: command.template !== false });
|
|
66
56
|
yield this.action.handle(inputs, options);
|
|
67
57
|
}));
|
|
68
58
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BaseMeta, CURRENT_META_SCHEMA_VERSION } from './types';
|
|
2
|
+
export { CURRENT_META_SCHEMA_VERSION };
|
|
3
|
+
export declare const GADMIN_DIRNAME = ".gadmin";
|
|
4
|
+
export declare const META_FILENAME = ".meta.json";
|
|
5
|
+
export declare const LOCK_FILENAME = ".lock";
|
|
6
|
+
export declare const README_FILENAME = "README.md";
|
|
7
|
+
export declare const WARNING_MESSAGE = "managed by gadmin2 \u2014 do not edit";
|
|
8
|
+
export declare function gadminDir(instanceDir: string): string;
|
|
9
|
+
export declare function metaPath(instanceDir: string): string;
|
|
10
|
+
export declare function lockPath(instanceDir: string): string;
|
|
11
|
+
export declare function readmePath(instanceDir: string): string;
|
|
12
|
+
export declare function readMeta(instanceDir: string): BaseMeta;
|
|
13
|
+
export declare function writeMeta(instanceDir: string, meta: BaseMeta): void;
|
|
14
|
+
export declare function validateMetaConsistency(instanceDir: string): BaseMeta;
|
|
15
|
+
export declare function acquireLock(instanceDir: string): () => void;
|
|
16
|
+
export declare function writeReadme(instanceDir: string): void;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.writeReadme = exports.acquireLock = exports.validateMetaConsistency = exports.writeMeta = exports.readMeta = exports.readmePath = exports.lockPath = exports.metaPath = exports.gadminDir = exports.WARNING_MESSAGE = exports.README_FILENAME = exports.LOCK_FILENAME = exports.META_FILENAME = exports.GADMIN_DIRNAME = exports.CURRENT_META_SCHEMA_VERSION = void 0;
|
|
4
|
+
// cli/lib/template-merge/base-store.ts
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const types_1 = require("./types");
|
|
8
|
+
Object.defineProperty(exports, "CURRENT_META_SCHEMA_VERSION", { enumerable: true, get: function () { return types_1.CURRENT_META_SCHEMA_VERSION; } });
|
|
9
|
+
exports.GADMIN_DIRNAME = '.gadmin';
|
|
10
|
+
exports.META_FILENAME = '.meta.json';
|
|
11
|
+
exports.LOCK_FILENAME = '.lock';
|
|
12
|
+
exports.README_FILENAME = 'README.md';
|
|
13
|
+
exports.WARNING_MESSAGE = 'managed by gadmin2 — do not edit';
|
|
14
|
+
function gadminDir(instanceDir) {
|
|
15
|
+
return path.join(instanceDir, exports.GADMIN_DIRNAME);
|
|
16
|
+
}
|
|
17
|
+
exports.gadminDir = gadminDir;
|
|
18
|
+
function metaPath(instanceDir) {
|
|
19
|
+
return path.join(gadminDir(instanceDir), exports.META_FILENAME);
|
|
20
|
+
}
|
|
21
|
+
exports.metaPath = metaPath;
|
|
22
|
+
function lockPath(instanceDir) {
|
|
23
|
+
return path.join(gadminDir(instanceDir), exports.LOCK_FILENAME);
|
|
24
|
+
}
|
|
25
|
+
exports.lockPath = lockPath;
|
|
26
|
+
function readmePath(instanceDir) {
|
|
27
|
+
return path.join(gadminDir(instanceDir), exports.README_FILENAME);
|
|
28
|
+
}
|
|
29
|
+
exports.readmePath = readmePath;
|
|
30
|
+
const REQUIRED_META_FIELDS = ['schematicsVersion', 'schemaVersion'];
|
|
31
|
+
function readMeta(instanceDir) {
|
|
32
|
+
const p = metaPath(instanceDir);
|
|
33
|
+
if (!fs.existsSync(p)) {
|
|
34
|
+
throw new Error(`base meta missing at ${p} — run \`gadmin2 update\` to bootstrap`);
|
|
35
|
+
}
|
|
36
|
+
const raw = fs.readFileSync(p, 'utf-8');
|
|
37
|
+
let json;
|
|
38
|
+
try {
|
|
39
|
+
json = JSON.parse(raw);
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
throw new Error(`base meta corrupt at ${p}: ${err.message}`);
|
|
43
|
+
}
|
|
44
|
+
for (const k of REQUIRED_META_FIELDS) {
|
|
45
|
+
if (!(k in json))
|
|
46
|
+
throw new Error(`base meta missing field: ${k}`);
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
schematicsVersion: json.schematicsVersion,
|
|
50
|
+
schemaVersion: json.schemaVersion,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
exports.readMeta = readMeta;
|
|
54
|
+
function writeMeta(instanceDir, meta) {
|
|
55
|
+
fs.mkdirSync(gadminDir(instanceDir), { recursive: true });
|
|
56
|
+
// V8 preserves insertion order; @warning must be first so it renders at the top of meta.json.
|
|
57
|
+
const ordered = {
|
|
58
|
+
'@warning': exports.WARNING_MESSAGE,
|
|
59
|
+
schematicsVersion: meta.schematicsVersion,
|
|
60
|
+
schemaVersion: meta.schemaVersion,
|
|
61
|
+
};
|
|
62
|
+
fs.writeFileSync(metaPath(instanceDir), JSON.stringify(ordered, null, 2) + '\n', 'utf-8');
|
|
63
|
+
}
|
|
64
|
+
exports.writeMeta = writeMeta;
|
|
65
|
+
function validateMetaConsistency(instanceDir) {
|
|
66
|
+
const meta = readMeta(instanceDir);
|
|
67
|
+
if (meta.schemaVersion > types_1.CURRENT_META_SCHEMA_VERSION) {
|
|
68
|
+
throw new Error(`base was written by a newer gadmin2 (schema v${meta.schemaVersion}, ` +
|
|
69
|
+
`this CLI supports v${types_1.CURRENT_META_SCHEMA_VERSION}). Upgrade gadmin2.`);
|
|
70
|
+
}
|
|
71
|
+
return meta;
|
|
72
|
+
}
|
|
73
|
+
exports.validateMetaConsistency = validateMetaConsistency;
|
|
74
|
+
function acquireLock(instanceDir) {
|
|
75
|
+
fs.mkdirSync(gadminDir(instanceDir), { recursive: true });
|
|
76
|
+
const p = lockPath(instanceDir);
|
|
77
|
+
let fd;
|
|
78
|
+
try {
|
|
79
|
+
fd = fs.openSync(p, 'wx'); // O_EXCL — fails if file exists
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
if (err.code === 'EEXIST') {
|
|
83
|
+
throw new Error(`another update is running (or crashed). If sure no update is running, remove ${p} and retry.`);
|
|
84
|
+
}
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
fs.writeSync(fd, String(process.pid));
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
fs.closeSync(fd);
|
|
92
|
+
}
|
|
93
|
+
let released = false;
|
|
94
|
+
const release = () => {
|
|
95
|
+
if (released)
|
|
96
|
+
return;
|
|
97
|
+
released = true;
|
|
98
|
+
try {
|
|
99
|
+
fs.unlinkSync(p);
|
|
100
|
+
}
|
|
101
|
+
catch ( /* already gone */_a) { /* already gone */ }
|
|
102
|
+
};
|
|
103
|
+
process.on('exit', release);
|
|
104
|
+
return release;
|
|
105
|
+
}
|
|
106
|
+
exports.acquireLock = acquireLock;
|
|
107
|
+
const README_TEXT = [
|
|
108
|
+
'# .gadmin/ — managed by `gadmin2 update`',
|
|
109
|
+
'',
|
|
110
|
+
'This directory holds metadata only. The template baseline is **not** stored',
|
|
111
|
+
'here as files; it is referenced by version in `.meta.json` and materialized',
|
|
112
|
+
'on demand from the npm registry into a temp dir during `gadmin2 update`.',
|
|
113
|
+
'',
|
|
114
|
+
'Do **not** edit `.meta.json` by hand — it is the authoritative pointer to',
|
|
115
|
+
'the schematics version your 3-way merge is anchored to.',
|
|
116
|
+
'',
|
|
117
|
+
'To re-anchor the baseline, delete `.gadmin/.meta.json` and re-run',
|
|
118
|
+
'`gadmin2 update`; it will pin to the currently installed schematics version.',
|
|
119
|
+
'',
|
|
120
|
+
].join('\n');
|
|
121
|
+
function writeReadme(instanceDir) {
|
|
122
|
+
fs.mkdirSync(gadminDir(instanceDir), { recursive: true });
|
|
123
|
+
const p = readmePath(instanceDir);
|
|
124
|
+
if (!fs.existsSync(p))
|
|
125
|
+
fs.writeFileSync(p, README_TEXT, 'utf-8');
|
|
126
|
+
}
|
|
127
|
+
exports.writeReadme = writeReadme;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/** 字节缓冲:包含 \0 视为二进制。仅检查前 8KB。 */
|
|
3
|
+
export declare function isBinaryBuffer(buf: Buffer): boolean;
|
|
4
|
+
/** 读文件前 8KB,应用 isBinaryBuffer。不存在的路径返回 false。 */
|
|
5
|
+
export declare function isFileBinary(filePath: string): boolean;
|
|
6
|
+
/** 任一路径为二进制即返回 true。 */
|
|
7
|
+
export declare function isAnyBinary(...filePaths: string[]): boolean;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isAnyBinary = exports.isFileBinary = exports.isBinaryBuffer = void 0;
|
|
4
|
+
// cli/lib/template-merge/binary-detect.ts
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const PROBE_BYTES = 8192;
|
|
7
|
+
/** 字节缓冲:包含 \0 视为二进制。仅检查前 8KB。 */
|
|
8
|
+
function isBinaryBuffer(buf) {
|
|
9
|
+
const slice = buf.subarray(0, PROBE_BYTES);
|
|
10
|
+
for (let i = 0; i < slice.length; i++) {
|
|
11
|
+
if (slice[i] === 0)
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
exports.isBinaryBuffer = isBinaryBuffer;
|
|
17
|
+
/** 读文件前 8KB,应用 isBinaryBuffer。不存在的路径返回 false。 */
|
|
18
|
+
function isFileBinary(filePath) {
|
|
19
|
+
if (!fs.existsSync(filePath))
|
|
20
|
+
return false;
|
|
21
|
+
const fd = fs.openSync(filePath, 'r');
|
|
22
|
+
try {
|
|
23
|
+
const buf = Buffer.alloc(PROBE_BYTES);
|
|
24
|
+
const n = fs.readSync(fd, buf, 0, PROBE_BYTES, 0);
|
|
25
|
+
return isBinaryBuffer(buf.subarray(0, n));
|
|
26
|
+
}
|
|
27
|
+
finally {
|
|
28
|
+
fs.closeSync(fd);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.isFileBinary = isFileBinary;
|
|
32
|
+
/** 任一路径为二进制即返回 true。 */
|
|
33
|
+
function isAnyBinary(...filePaths) {
|
|
34
|
+
return filePaths.some(isFileBinary);
|
|
35
|
+
}
|
|
36
|
+
exports.isAnyBinary = isAnyBinary;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ClassifyResult, ClassifiedPlan } from './types';
|
|
2
|
+
export interface ClassifyInput {
|
|
3
|
+
baseExists: boolean;
|
|
4
|
+
oursExists: boolean;
|
|
5
|
+
theirsExists: boolean;
|
|
6
|
+
oursEqBase?: () => boolean;
|
|
7
|
+
oursEqTheirs?: () => boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function classifyFile(input: ClassifyInput): ClassifyResult;
|
|
10
|
+
export declare function classifyAll(baseDir: string, oursDir: string, theirsDir: string, userExcludes: string[]): ClassifiedPlan;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.classifyAll = exports.classifyFile = void 0;
|
|
4
|
+
// cli/lib/template-merge/classify.ts
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const excludes_1 = require("./excludes");
|
|
8
|
+
function classifyFile(input) {
|
|
9
|
+
var _a, _b, _c;
|
|
10
|
+
const { baseExists, oursExists, theirsExists } = input;
|
|
11
|
+
if (!baseExists && !oursExists && !theirsExists) {
|
|
12
|
+
throw new Error('classifyFile: impossible state — file in none of the three sides');
|
|
13
|
+
}
|
|
14
|
+
if (!baseExists && !oursExists && theirsExists)
|
|
15
|
+
return 'add';
|
|
16
|
+
if (!baseExists && oursExists && !theirsExists)
|
|
17
|
+
return 'keep';
|
|
18
|
+
if (!baseExists && oursExists && theirsExists) {
|
|
19
|
+
return ((_a = input.oursEqTheirs) === null || _a === void 0 ? void 0 : _a.call(input)) ? 'keep' : 'addAddConflict';
|
|
20
|
+
}
|
|
21
|
+
if (baseExists && !oursExists && !theirsExists)
|
|
22
|
+
return 'noop';
|
|
23
|
+
if (baseExists && !oursExists && theirsExists)
|
|
24
|
+
return 'deleteModifyConflict';
|
|
25
|
+
if (baseExists && oursExists && !theirsExists) {
|
|
26
|
+
return ((_b = input.oursEqBase) === null || _b === void 0 ? void 0 : _b.call(input)) ? 'delete' : 'modifyDeleteConflict';
|
|
27
|
+
}
|
|
28
|
+
// ✓✓✓ — all three sides exist. If ours already equals theirs, no merge needed.
|
|
29
|
+
return ((_c = input.oursEqTheirs) === null || _c === void 0 ? void 0 : _c.call(input)) ? 'keep' : 'update';
|
|
30
|
+
}
|
|
31
|
+
exports.classifyFile = classifyFile;
|
|
32
|
+
function fileBytesEqual(p, q) {
|
|
33
|
+
return fs.readFileSync(p).equals(fs.readFileSync(q));
|
|
34
|
+
}
|
|
35
|
+
function classifyAll(baseDir, oursDir, theirsDir, userExcludes) {
|
|
36
|
+
const excludes = [...excludes_1.HARDCODED_EXCLUDES, ...userExcludes];
|
|
37
|
+
const baseFiles = new Set((0, excludes_1.collectFiles)(baseDir, excludes));
|
|
38
|
+
const oursFiles = new Set((0, excludes_1.collectFiles)(oursDir, excludes));
|
|
39
|
+
const theirsFiles = new Set((0, excludes_1.collectFiles)(theirsDir, excludes));
|
|
40
|
+
const all = new Set([...baseFiles, ...oursFiles, ...theirsFiles]);
|
|
41
|
+
const plan = {
|
|
42
|
+
add: [], keep: [], noop: [],
|
|
43
|
+
delete: [], update: [],
|
|
44
|
+
addAddConflict: [], modifyDeleteConflict: [], deleteModifyConflict: [],
|
|
45
|
+
};
|
|
46
|
+
for (const rel of [...all].sort()) {
|
|
47
|
+
const baseExists = baseFiles.has(rel);
|
|
48
|
+
const oursExists = oursFiles.has(rel);
|
|
49
|
+
const theirsExists = theirsFiles.has(rel);
|
|
50
|
+
const result = classifyFile({
|
|
51
|
+
baseExists,
|
|
52
|
+
oursExists,
|
|
53
|
+
theirsExists,
|
|
54
|
+
oursEqBase: () => baseExists && oursExists && fileBytesEqual(path.join(baseDir, rel), path.join(oursDir, rel)),
|
|
55
|
+
oursEqTheirs: () => oursExists && theirsExists && fileBytesEqual(path.join(oursDir, rel), path.join(theirsDir, rel)),
|
|
56
|
+
});
|
|
57
|
+
plan[result].push(rel);
|
|
58
|
+
}
|
|
59
|
+
return plan;
|
|
60
|
+
}
|
|
61
|
+
exports.classifyAll = classifyAll;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assertNoDeprecatedFields = void 0;
|
|
4
|
+
const DEPRECATED_VERSION_PLACEHOLDER = '<deprecated-since-version>';
|
|
5
|
+
/**
|
|
6
|
+
* 抛错如果 gadmin-cli.json 含有已下线字段。
|
|
7
|
+
* `<deprecated-since-version>` 占位符在 P6 实施时由 update.command.ts 注入实际 CLI 版本。
|
|
8
|
+
*/
|
|
9
|
+
function assertNoDeprecatedFields(gadminCliJson, cliVersion = DEPRECATED_VERSION_PLACEHOLDER) {
|
|
10
|
+
const tu = gadminCliJson === null || gadminCliJson === void 0 ? void 0 : gadminCliJson.templateUpdate;
|
|
11
|
+
if (!tu || typeof tu !== 'object')
|
|
12
|
+
return;
|
|
13
|
+
const found = [];
|
|
14
|
+
if ('policies' in tu)
|
|
15
|
+
found.push('templateUpdate.policies');
|
|
16
|
+
if ('mergers' in tu)
|
|
17
|
+
found.push('templateUpdate.mergers');
|
|
18
|
+
if (found.length === 0)
|
|
19
|
+
return;
|
|
20
|
+
const lines = [
|
|
21
|
+
'gadmin-cli.json contains deprecated fields that have been removed:',
|
|
22
|
+
'',
|
|
23
|
+
...found.map((f) => ` ${f}`),
|
|
24
|
+
'',
|
|
25
|
+
` These were used by the old Smart Merger system, which has been replaced`,
|
|
26
|
+
` by git-merge-style 3-way merge as of CLI ${cliVersion}.`,
|
|
27
|
+
` Remove the fields and re-run.`,
|
|
28
|
+
'',
|
|
29
|
+
` See: usage/UPDATE.md`,
|
|
30
|
+
];
|
|
31
|
+
throw new Error(lines.join('\n'));
|
|
32
|
+
}
|
|
33
|
+
exports.assertNoDeprecatedFields = assertNoDeprecatedFields;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** 用户无法关闭的硬排除——这些目录三方扫描时永远跳过。 */
|
|
2
|
+
export declare const HARDCODED_EXCLUDES: string[];
|
|
3
|
+
/** 默认排除——可通过 gadmin-cli.json templateExcludes 追加。 */
|
|
4
|
+
export declare const DEFAULT_EXCLUDES: string[];
|
|
5
|
+
export declare function shouldExclude(filePath: string, excludes: string[]): boolean;
|
|
6
|
+
/** 递归列出 dir 下所有文件相对路径(已排序),应用 excludes。 */
|
|
7
|
+
export declare function collectFiles(dir: string, excludes: string[], base?: string): string[];
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.collectFiles = exports.shouldExclude = exports.DEFAULT_EXCLUDES = exports.HARDCODED_EXCLUDES = void 0;
|
|
4
|
+
// cli/lib/template-merge/excludes.ts
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
/** 用户无法关闭的硬排除——这些目录三方扫描时永远跳过。 */
|
|
8
|
+
exports.HARDCODED_EXCLUDES = [
|
|
9
|
+
'.gadmin',
|
|
10
|
+
'.git',
|
|
11
|
+
'node_modules',
|
|
12
|
+
'dist',
|
|
13
|
+
'generated',
|
|
14
|
+
];
|
|
15
|
+
/** 默认排除——可通过 gadmin-cli.json templateExcludes 追加。 */
|
|
16
|
+
exports.DEFAULT_EXCLUDES = [
|
|
17
|
+
'.DS_Store',
|
|
18
|
+
'.env.local',
|
|
19
|
+
'readme.md',
|
|
20
|
+
// 包管理器锁文件由 install 阶段重建,不参与 update
|
|
21
|
+
'yarn.lock',
|
|
22
|
+
'package-lock.json',
|
|
23
|
+
'pnpm-lock.yaml',
|
|
24
|
+
'.yarn',
|
|
25
|
+
'.pnp.cjs',
|
|
26
|
+
'.pnp.loader.mjs',
|
|
27
|
+
];
|
|
28
|
+
function globToRegex(pattern) {
|
|
29
|
+
let s = '^';
|
|
30
|
+
let i = 0;
|
|
31
|
+
while (i < pattern.length) {
|
|
32
|
+
const c = pattern[i];
|
|
33
|
+
if (c === '*') {
|
|
34
|
+
if (pattern[i + 1] === '*') {
|
|
35
|
+
if (pattern[i + 2] === '/') {
|
|
36
|
+
s += '(?:.+/)?';
|
|
37
|
+
i += 3;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
s += '.*';
|
|
41
|
+
i += 2;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
s += '[^/]*';
|
|
46
|
+
i++;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else if (c === '?') {
|
|
50
|
+
s += '[^/]';
|
|
51
|
+
i++;
|
|
52
|
+
}
|
|
53
|
+
else if ('.()[]{}+^$|\\'.includes(c)) {
|
|
54
|
+
s += '\\' + c;
|
|
55
|
+
i++;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
s += c;
|
|
59
|
+
i++;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return new RegExp(s + '$');
|
|
63
|
+
}
|
|
64
|
+
function matchesPattern(filePath, pattern) {
|
|
65
|
+
if (filePath === pattern)
|
|
66
|
+
return true;
|
|
67
|
+
if (filePath.startsWith(pattern + '/'))
|
|
68
|
+
return true;
|
|
69
|
+
const parts = filePath.split('/');
|
|
70
|
+
if (!pattern.includes('/') && !pattern.includes('*') && !pattern.includes('?')) {
|
|
71
|
+
if (parts.includes(pattern))
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
if (pattern.includes('*') || pattern.includes('?')) {
|
|
75
|
+
const regex = globToRegex(pattern);
|
|
76
|
+
if (regex.test(filePath))
|
|
77
|
+
return true;
|
|
78
|
+
// Patterns without '/' also match individual path segments (gitignore-style).
|
|
79
|
+
if (!pattern.includes('/')) {
|
|
80
|
+
for (const part of parts) {
|
|
81
|
+
if (regex.test(part))
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
function shouldExclude(filePath, excludes) {
|
|
89
|
+
return excludes.some((p) => matchesPattern(filePath, p));
|
|
90
|
+
}
|
|
91
|
+
exports.shouldExclude = shouldExclude;
|
|
92
|
+
/** 递归列出 dir 下所有文件相对路径(已排序),应用 excludes。 */
|
|
93
|
+
function collectFiles(dir, excludes, base = '') {
|
|
94
|
+
if (!fs.existsSync(path.join(dir, base)))
|
|
95
|
+
return [];
|
|
96
|
+
const out = [];
|
|
97
|
+
const entries = fs.readdirSync(path.join(dir, base), { withFileTypes: true });
|
|
98
|
+
for (const e of entries) {
|
|
99
|
+
const rel = base ? `${base}/${e.name}` : e.name;
|
|
100
|
+
if (shouldExclude(rel, excludes))
|
|
101
|
+
continue;
|
|
102
|
+
if (e.isDirectory())
|
|
103
|
+
out.push(...collectFiles(dir, excludes, rel));
|
|
104
|
+
else if (e.isFile())
|
|
105
|
+
out.push(rel);
|
|
106
|
+
}
|
|
107
|
+
return out.sort();
|
|
108
|
+
}
|
|
109
|
+
exports.collectFiles = collectFiles;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
export * from './
|
|
2
|
-
export * from './
|
|
3
|
-
export * from './
|
|
4
|
-
export * from './
|
|
5
|
-
export * from './
|
|
6
|
-
export * from './
|
|
7
|
-
export * from './
|
|
8
|
-
export * from './
|
|
1
|
+
export * from './types';
|
|
2
|
+
export * from './binary-detect';
|
|
3
|
+
export * from './excludes';
|
|
4
|
+
export * from './residual-scan';
|
|
5
|
+
export * from './classify';
|
|
6
|
+
export * from './merge3way';
|
|
7
|
+
export * from './base-store';
|
|
8
|
+
export * from './config-validate';
|
|
9
|
+
export * from './materialize';
|
|
@@ -14,11 +14,13 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
|
|
18
|
-
__exportStar(require("./
|
|
19
|
-
__exportStar(require("./
|
|
20
|
-
__exportStar(require("./
|
|
21
|
-
__exportStar(require("./
|
|
22
|
-
__exportStar(require("./
|
|
23
|
-
__exportStar(require("./
|
|
24
|
-
__exportStar(require("./
|
|
17
|
+
// cli/lib/template-merge/index.ts
|
|
18
|
+
__exportStar(require("./types"), exports);
|
|
19
|
+
__exportStar(require("./binary-detect"), exports);
|
|
20
|
+
__exportStar(require("./excludes"), exports);
|
|
21
|
+
__exportStar(require("./residual-scan"), exports);
|
|
22
|
+
__exportStar(require("./classify"), exports);
|
|
23
|
+
__exportStar(require("./merge3way"), exports);
|
|
24
|
+
__exportStar(require("./base-store"), exports);
|
|
25
|
+
__exportStar(require("./config-validate"), exports);
|
|
26
|
+
__exportStar(require("./materialize"), exports);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface MaterializedBase {
|
|
2
|
+
/** Absolute path to the directory containing the template's files. */
|
|
3
|
+
dir: string;
|
|
4
|
+
/** Idempotent cleanup of any temp dirs created. */
|
|
5
|
+
cleanup: () => void;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Materialize a base reference for 3-way merge.
|
|
9
|
+
*
|
|
10
|
+
* Sources, in priority order:
|
|
11
|
+
* 1. `GADMIN2_LOCAL_REGISTRY_DIR/<version>/<templateName>/` — used by tests
|
|
12
|
+
* and local development. Files are copied into a fresh tmp dir.
|
|
13
|
+
* 2. `npm pack @gadmin2n/schematics@<version>` — production. Tarball is
|
|
14
|
+
* extracted into a fresh tmp dir; the inner template subdir is returned.
|
|
15
|
+
*
|
|
16
|
+
* Returned `cleanup()` removes any tmp dir we created.
|
|
17
|
+
*/
|
|
18
|
+
export declare function materializeBase(schematicsVersion: string, templateName: string): MaterializedBase;
|