@noir-ai/create 1.17.0 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +10 -15
- package/dist/index.js +118 -77
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/templates/noir-readme.md.tmpl +4 -2
- package/templates/router.md.tmpl +6 -0
package/dist/index.d.ts
CHANGED
|
@@ -239,13 +239,8 @@ interface WriteOutcome {
|
|
|
239
239
|
* Parent directories are NOT created here — the orchestrator does that once
|
|
240
240
|
* for the whole manifest so a missing dir is a single, attributable failure
|
|
241
241
|
* rather than N silent ones inside the writer.
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
* target, so an entry executed directly (the SessionStart hook runner) lands
|
|
245
|
-
* with its exec bit on a first write and a rewrite alike. As with core's
|
|
246
|
-
* `atomicWriteFile`, the requested bits are an upper bound (the process umask
|
|
247
|
-
* can narrow them) and Windows ignores them (ACL-based permissions). */
|
|
248
|
-
declare function regenerate(absPath: string, content: string, mode?: number): WriteOutcome;
|
|
242
|
+
*/
|
|
243
|
+
declare function regenerate(absPath: string, content: string): WriteOutcome;
|
|
249
244
|
/** Re-emit a managed region, delegating to core's `writeManagedRegion`.
|
|
250
245
|
* `regionText` MUST already include the begin/end markers (matches the shape
|
|
251
246
|
* `writeManagedRegion` expects and that `IGNORE_BLOCK`/`CONTEXT_BLOCK`
|
|
@@ -581,11 +576,11 @@ declare function mergeThreeWay(base: string, ours: string, theirs: string, style
|
|
|
581
576
|
* `result.conflicts` so the caller (doctor/CI) can fail loudly with a
|
|
582
577
|
* reviewable artifact rather than hang on a prompt.
|
|
583
578
|
*
|
|
584
|
-
* The registry ({@link MIGRATIONS}) carries
|
|
585
|
-
* `1.0.0 → 1.0.0` smoke test that exercises the runner end-to-end, and
|
|
586
|
-
* real version-to-version migrations, `1.0.0 → 1.1.0`, `1.1.0 → 1.2.0
|
|
587
|
-
* `1.2.0 → 1.3.0`. The synthetic one is the copy-pasteable
|
|
588
|
-
* next contributor.
|
|
579
|
+
* The registry ({@link MIGRATIONS}) carries five entries: the synthetic
|
|
580
|
+
* `1.0.0 → 1.0.0` smoke test that exercises the runner end-to-end, and four
|
|
581
|
+
* real version-to-version migrations, `1.0.0 → 1.1.0`, `1.1.0 → 1.2.0`,
|
|
582
|
+
* `1.2.0 → 1.3.0` and `1.3.0 → 1.4.0`. The synthetic one is the copy-pasteable
|
|
583
|
+
* template for the next contributor.
|
|
589
584
|
*/
|
|
590
585
|
interface MigrationContext {
|
|
591
586
|
/** Absolute repo root the migration operates on. */
|
|
@@ -888,9 +883,9 @@ declare function scaffold(opts: ScaffoldOptions): Promise<ScaffoldResult>;
|
|
|
888
883
|
/** The scaffold version this build of @noir-ai/create ships. Bumped atomically
|
|
889
884
|
* whenever a manifest entry, template, or migration changes shape. 1.3.0 adds
|
|
890
885
|
* the migration that points a joined repo's MCP entry back at the workspace
|
|
891
|
-
* bridge
|
|
892
|
-
*
|
|
893
|
-
declare const CURRENT_SCAFFOLD_VERSION = "1.
|
|
886
|
+
* bridge; 1.4.0 adds the migration that moves the SessionStart hook command off
|
|
887
|
+
* the `.mjs` runner and onto `noir hook` (see migrations/index.ts). */
|
|
888
|
+
declare const CURRENT_SCAFFOLD_VERSION = "1.4.0";
|
|
894
889
|
/** Path to the stamp file under `root`. Exposed for tests + doctor. */
|
|
895
890
|
declare function scaffoldVersionPath(root: string): string;
|
|
896
891
|
/** Read the applied scaffold version, or `null` if the stamp is absent/unparseable.
|
package/dist/index.js
CHANGED
|
@@ -316,7 +316,7 @@ function buildHostArtifacts(adapter, ctx) {
|
|
|
316
316
|
});
|
|
317
317
|
}
|
|
318
318
|
if (host === "claude") {
|
|
319
|
-
const HOOK_DEDUP = "
|
|
319
|
+
const HOOK_DEDUP = " hook";
|
|
320
320
|
const hookEntry = {
|
|
321
321
|
hooks: {
|
|
322
322
|
SessionStart: [
|
|
@@ -324,7 +324,7 @@ function buildHostArtifacts(adapter, ctx) {
|
|
|
324
324
|
hooks: [
|
|
325
325
|
{
|
|
326
326
|
type: "command",
|
|
327
|
-
command: `"${ctx.
|
|
327
|
+
command: `"${ctx.command}" hook`
|
|
328
328
|
}
|
|
329
329
|
]
|
|
330
330
|
}
|
|
@@ -334,7 +334,7 @@ function buildHostArtifacts(adapter, ctx) {
|
|
|
334
334
|
hooks: [
|
|
335
335
|
{
|
|
336
336
|
type: "command",
|
|
337
|
-
command: `"${ctx.
|
|
337
|
+
command: `"${ctx.command}" hook`
|
|
338
338
|
}
|
|
339
339
|
]
|
|
340
340
|
}
|
|
@@ -349,14 +349,6 @@ function buildHostArtifacts(adapter, ctx) {
|
|
|
349
349
|
dedupSubstring: HOOK_DEDUP,
|
|
350
350
|
description: "SessionStart + SubagentStart hook entry (user-owned, written once)"
|
|
351
351
|
});
|
|
352
|
-
entries.push({
|
|
353
|
-
path: ".noir/hooks/noir-session-start.mjs",
|
|
354
|
-
mode: "regenerate",
|
|
355
|
-
fileMode: 493,
|
|
356
|
-
host,
|
|
357
|
-
content: SESSION_START_HOOK_SCRIPT,
|
|
358
|
-
description: "SessionStart hook runner (Noir-owned, re-emitted)"
|
|
359
|
-
});
|
|
360
352
|
entries.push({
|
|
361
353
|
path: ".noir/router.md",
|
|
362
354
|
mode: "managedBlock",
|
|
@@ -377,40 +369,6 @@ function buildHostArtifacts(adapter, ctx) {
|
|
|
377
369
|
}
|
|
378
370
|
return entries;
|
|
379
371
|
}
|
|
380
|
-
var SESSION_START_HOOK_SCRIPT = `#!/usr/bin/env node
|
|
381
|
-
// Noir SessionStart hook \u2014 injects the skill router contract + anti-slop ruleset
|
|
382
|
-
// at session start. The mutable contract lives in .noir/router.md (a Noir
|
|
383
|
-
// managed block, so user edits outside the markers survive noir sync) and the
|
|
384
|
-
// ruleset in .noir/rules/anti-slop.md (regenerated). This script is a pure runner.
|
|
385
|
-
import { readFileSync, existsSync } from 'node:fs';
|
|
386
|
-
import { join, dirname } from 'node:path';
|
|
387
|
-
import { fileURLToPath } from 'node:url';
|
|
388
|
-
|
|
389
|
-
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
390
|
-
const ROOT = join(HERE, '..', '..');
|
|
391
|
-
const ROUTER = join(ROOT, '.noir', 'router.md');
|
|
392
|
-
const RULES = join(ROOT, '.noir', 'rules', 'anti-slop.md');
|
|
393
|
-
|
|
394
|
-
function readIfPresent(path) {
|
|
395
|
-
if (!existsSync(path)) return '';
|
|
396
|
-
return readFileSync(path, 'utf8').trim();
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
function main() {
|
|
400
|
-
const parts = [readIfPresent(ROUTER), readIfPresent(RULES)].filter((part) => part !== '');
|
|
401
|
-
if (parts.length === 0) {
|
|
402
|
-
// Neither contract present (project not initialized / both removed) \u2014 silent.
|
|
403
|
-
process.stdout.write(JSON.stringify({ hookSpecificOutput: {} }));
|
|
404
|
-
process.exit(0);
|
|
405
|
-
}
|
|
406
|
-
const context = parts.join('\\n\\n');
|
|
407
|
-
process.stdout.write(
|
|
408
|
-
JSON.stringify({ hookSpecificOutput: { additionalContext: context } }),
|
|
409
|
-
);
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
main();
|
|
413
|
-
`;
|
|
414
372
|
function hostRel(abs, root) {
|
|
415
373
|
const rel = relative(root, abs);
|
|
416
374
|
if (rel.length === 0 || rel.startsWith("..") || rel.startsWith("/")) {
|
|
@@ -567,7 +525,7 @@ function mergeThreeWay(base, ours, theirs, style = "minimal") {
|
|
|
567
525
|
}
|
|
568
526
|
|
|
569
527
|
// src/migrations/index.ts
|
|
570
|
-
import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
528
|
+
import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync4, rmdirSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
571
529
|
import { join as join5, relative as relative2, sep } from "path";
|
|
572
530
|
import { mcpConfigPathFor, noirStdioArgs, TRANSPORT_KEYS } from "@noir-ai/adapters";
|
|
573
531
|
import {
|
|
@@ -829,7 +787,7 @@ import {
|
|
|
829
787
|
import { basename, dirname as dirname3, join as join4 } from "path";
|
|
830
788
|
import { atomicWriteFile, stripManagedBlock, writeManagedRegion } from "@noir-ai/core";
|
|
831
789
|
import { ensureOwnerOnly } from "@noir-ai/core";
|
|
832
|
-
function regenerate(absPath, content
|
|
790
|
+
function regenerate(absPath, content) {
|
|
833
791
|
const dir = dirname3(absPath);
|
|
834
792
|
const tmp = join4(
|
|
835
793
|
dir,
|
|
@@ -837,7 +795,7 @@ function regenerate(absPath, content, mode) {
|
|
|
837
795
|
);
|
|
838
796
|
let fd;
|
|
839
797
|
try {
|
|
840
|
-
fd = openSync(tmp, "w"
|
|
798
|
+
fd = openSync(tmp, "w");
|
|
841
799
|
writeSync(fd, content, 0, "utf8");
|
|
842
800
|
closeSync(fd);
|
|
843
801
|
fd = void 0;
|
|
@@ -949,6 +907,17 @@ function mergeJson(absPath, patch, dedupSubstring) {
|
|
|
949
907
|
regenerate(absPath, content);
|
|
950
908
|
return { path: absPath, mode: "mergeJson", written: true };
|
|
951
909
|
}
|
|
910
|
+
function entryHasCommand(entry, marker) {
|
|
911
|
+
if (typeof entry !== "object" || entry === null) return false;
|
|
912
|
+
const obj = entry;
|
|
913
|
+
if (typeof obj.command === "string" && obj.command.includes(marker)) return true;
|
|
914
|
+
if (!Array.isArray(obj.hooks)) return false;
|
|
915
|
+
return obj.hooks.some((h) => {
|
|
916
|
+
if (typeof h !== "object" || h === null) return false;
|
|
917
|
+
const command = h.command;
|
|
918
|
+
return typeof command === "string" && command.includes(marker);
|
|
919
|
+
});
|
|
920
|
+
}
|
|
952
921
|
function deepMergePreservingHooks(existing, patch, dedupSubstring) {
|
|
953
922
|
const out = { ...existing };
|
|
954
923
|
for (const [key, value] of Object.entries(patch)) {
|
|
@@ -958,9 +927,7 @@ function deepMergePreservingHooks(existing, patch, dedupSubstring) {
|
|
|
958
927
|
for (const [event, entries] of Object.entries(value)) {
|
|
959
928
|
const existingEntries = Array.isArray(mergedHooks[event]) ? mergedHooks[event] : [];
|
|
960
929
|
const patchEntries = Array.isArray(entries) ? entries : [];
|
|
961
|
-
const alreadyRegistered = dedupSubstring !== void 0 && existingEntries.some(
|
|
962
|
-
(x) => x?.command?.includes(dedupSubstring) ?? false
|
|
963
|
-
);
|
|
930
|
+
const alreadyRegistered = dedupSubstring !== void 0 && existingEntries.some((x) => entryHasCommand(x, dedupSubstring));
|
|
964
931
|
const toAdd = alreadyRegistered ? [] : patchEntries;
|
|
965
932
|
mergedHooks[event] = [...existingEntries, ...toAdd];
|
|
966
933
|
}
|
|
@@ -1234,11 +1201,98 @@ var workspaceBridge = {
|
|
|
1234
1201
|
return result;
|
|
1235
1202
|
}
|
|
1236
1203
|
};
|
|
1204
|
+
var OLD_HOOK_MARKER = "noir-session-start";
|
|
1205
|
+
var hookCommand = {
|
|
1206
|
+
from: "1.3.0",
|
|
1207
|
+
to: "1.4.0",
|
|
1208
|
+
description: "route the SessionStart hook through `noir hook` (drop the .mjs runner)",
|
|
1209
|
+
run: (ctx) => {
|
|
1210
|
+
const result = { changed: [], conflicts: [], notes: [] };
|
|
1211
|
+
const settingsRel = ".claude/settings.local.json";
|
|
1212
|
+
const settingsPath = join5(ctx.root, ".claude", "settings.local.json");
|
|
1213
|
+
if (existsSync4(settingsPath)) {
|
|
1214
|
+
let settings;
|
|
1215
|
+
try {
|
|
1216
|
+
settings = JSON.parse(readFileSync4(settingsPath, "utf8"));
|
|
1217
|
+
} catch (err) {
|
|
1218
|
+
result.conflicts.push(settingsRel);
|
|
1219
|
+
result.notes.push(
|
|
1220
|
+
`${settingsRel}: unreadable (${errorMessage(err)}) \u2014 command left untouched`
|
|
1221
|
+
);
|
|
1222
|
+
settings = {};
|
|
1223
|
+
}
|
|
1224
|
+
let rewritten = false;
|
|
1225
|
+
const hooks = settings.hooks;
|
|
1226
|
+
if (typeof hooks === "object" && hooks !== null && !Array.isArray(hooks)) {
|
|
1227
|
+
for (const eventEntries of Object.values(hooks)) {
|
|
1228
|
+
if (!Array.isArray(eventEntries)) continue;
|
|
1229
|
+
for (const group of eventEntries) {
|
|
1230
|
+
if (typeof group !== "object" || group === null) continue;
|
|
1231
|
+
const inner = group.hooks;
|
|
1232
|
+
if (!Array.isArray(inner)) continue;
|
|
1233
|
+
for (const entry of inner) {
|
|
1234
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
1235
|
+
const hookObj = entry;
|
|
1236
|
+
if (typeof hookObj.command === "string" && hookObj.command.includes(OLD_HOOK_MARKER)) {
|
|
1237
|
+
hookObj.command = `"${resolveNoirCommand()}" hook`;
|
|
1238
|
+
rewritten = true;
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
if (!rewritten) {
|
|
1245
|
+
result.notes.push(`${settingsRel}: no .mjs hook command to migrate`);
|
|
1246
|
+
} else if (ctx.dryRun) {
|
|
1247
|
+
result.changed.push(settingsRel);
|
|
1248
|
+
result.notes.push(`${settingsRel}: would route the hook through \`noir hook\``);
|
|
1249
|
+
} else {
|
|
1250
|
+
try {
|
|
1251
|
+
atomicWriteFile2(settingsPath, `${JSON.stringify(settings, null, 2)}
|
|
1252
|
+
`);
|
|
1253
|
+
result.changed.push(settingsRel);
|
|
1254
|
+
result.notes.push(`${settingsRel}: hook now routes through \`noir hook\``);
|
|
1255
|
+
} catch (err) {
|
|
1256
|
+
result.conflicts.push(settingsRel);
|
|
1257
|
+
result.notes.push(`${settingsRel}: write failed (${errorMessage(err)}) \u2014 left untouched`);
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
} else {
|
|
1261
|
+
result.notes.push(`${settingsRel}: absent \u2014 nothing to migrate`);
|
|
1262
|
+
}
|
|
1263
|
+
const runnerRel = ".noir/hooks/noir-session-start.mjs";
|
|
1264
|
+
const runnerPath = join5(ctx.root, ".noir", "hooks", "noir-session-start.mjs");
|
|
1265
|
+
if (!existsSync4(runnerPath)) {
|
|
1266
|
+
return result;
|
|
1267
|
+
}
|
|
1268
|
+
if (ctx.dryRun) {
|
|
1269
|
+
result.changed.push(runnerRel);
|
|
1270
|
+
result.notes.push(`${runnerRel}: would remove the orphaned runner`);
|
|
1271
|
+
return result;
|
|
1272
|
+
}
|
|
1273
|
+
try {
|
|
1274
|
+
rmSync2(runnerPath, { force: true });
|
|
1275
|
+
} catch (err) {
|
|
1276
|
+
result.conflicts.push(runnerRel);
|
|
1277
|
+
result.notes.push(`${runnerRel}: remove failed (${errorMessage(err)})`);
|
|
1278
|
+
return result;
|
|
1279
|
+
}
|
|
1280
|
+
result.changed.push(runnerRel);
|
|
1281
|
+
result.notes.push(`${runnerRel}: removed (orphaned runner)`);
|
|
1282
|
+
const hooksDir = join5(ctx.root, ".noir", "hooks");
|
|
1283
|
+
try {
|
|
1284
|
+
if (readdirSync(hooksDir).length === 0) rmdirSync(hooksDir);
|
|
1285
|
+
} catch {
|
|
1286
|
+
}
|
|
1287
|
+
return result;
|
|
1288
|
+
}
|
|
1289
|
+
};
|
|
1237
1290
|
var MIGRATIONS = [
|
|
1238
1291
|
synthetic,
|
|
1239
1292
|
envPointer,
|
|
1240
1293
|
envTemplates,
|
|
1241
|
-
workspaceBridge
|
|
1294
|
+
workspaceBridge,
|
|
1295
|
+
hookCommand
|
|
1242
1296
|
];
|
|
1243
1297
|
function errorMessage(err) {
|
|
1244
1298
|
return err instanceof Error ? err.message : String(err);
|
|
@@ -1258,15 +1312,7 @@ function applyWithConflict(ours, theirs, path) {
|
|
|
1258
1312
|
}
|
|
1259
1313
|
|
|
1260
1314
|
// src/scaffold.ts
|
|
1261
|
-
import {
|
|
1262
|
-
chmodSync,
|
|
1263
|
-
existsSync as existsSync6,
|
|
1264
|
-
mkdirSync as mkdirSync3,
|
|
1265
|
-
readFileSync as readFileSync7,
|
|
1266
|
-
renameSync as renameSync2,
|
|
1267
|
-
rmSync as rmSync2,
|
|
1268
|
-
statSync
|
|
1269
|
-
} from "fs";
|
|
1315
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync7, renameSync as renameSync2, rmSync as rmSync3 } from "fs";
|
|
1270
1316
|
import { basename as basename2, dirname as dirname5, join as join8 } from "path";
|
|
1271
1317
|
import {
|
|
1272
1318
|
createProjectId,
|
|
@@ -1282,7 +1328,7 @@ import {
|
|
|
1282
1328
|
import { mkdirSync as mkdirSync2, readFileSync as readFileSync5 } from "fs";
|
|
1283
1329
|
import { dirname as dirname4, join as join6 } from "path";
|
|
1284
1330
|
import { NOIR_DIR as NOIR_DIR2 } from "@noir-ai/core";
|
|
1285
|
-
var CURRENT_SCAFFOLD_VERSION = "1.
|
|
1331
|
+
var CURRENT_SCAFFOLD_VERSION = "1.4.0";
|
|
1286
1332
|
var PREFIX = "noir-scaffold=";
|
|
1287
1333
|
function scaffoldVersionPath(root) {
|
|
1288
1334
|
return join6(root, NOIR_DIR2, "scaffold-version");
|
|
@@ -1311,7 +1357,7 @@ function writeScaffoldVersion(root, version) {
|
|
|
1311
1357
|
}
|
|
1312
1358
|
|
|
1313
1359
|
// src/stack-detect.ts
|
|
1314
|
-
import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync6 } from "fs";
|
|
1360
|
+
import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync6 } from "fs";
|
|
1315
1361
|
import { join as join7 } from "path";
|
|
1316
1362
|
var NODE_FRAMEWORKS = [
|
|
1317
1363
|
["next", "next"],
|
|
@@ -1461,7 +1507,7 @@ function detectStack(root) {
|
|
|
1461
1507
|
const cursorRulesDir = join7(root, ".cursor", "rules");
|
|
1462
1508
|
try {
|
|
1463
1509
|
if (existsSync5(cursorRulesDir)) {
|
|
1464
|
-
const entries =
|
|
1510
|
+
const entries = readdirSync2(cursorRulesDir);
|
|
1465
1511
|
for (const e of entries) {
|
|
1466
1512
|
if (e.endsWith(".md"))
|
|
1467
1513
|
existingAiFiles.push({ path: `.cursor/rules/${e}`, kind: "cursor-rules" });
|
|
@@ -1542,7 +1588,7 @@ async function scaffold(opts) {
|
|
|
1542
1588
|
const idFile = readProjectIdFile(opts.root);
|
|
1543
1589
|
const projectId = resolveProjectId(opts, idFile);
|
|
1544
1590
|
if (!opts.dryRun && idFile.state === "corrupt") {
|
|
1545
|
-
|
|
1591
|
+
rmSync3(paths2.projectId(opts.root), { force: true });
|
|
1546
1592
|
}
|
|
1547
1593
|
const fromVersion = readScaffoldVersion(opts.root);
|
|
1548
1594
|
const stack = detectStack(opts.root);
|
|
@@ -1715,8 +1761,7 @@ async function scaffold(opts) {
|
|
|
1715
1761
|
if (entry.mode === "regenerate") {
|
|
1716
1762
|
const out = await writeWithConflict(abs, entry.path, body, opts, {
|
|
1717
1763
|
memory: conflictMemory,
|
|
1718
|
-
record: recordConflict
|
|
1719
|
-
mode: entry.fileMode
|
|
1764
|
+
record: recordConflict
|
|
1720
1765
|
});
|
|
1721
1766
|
written.push(...out.written);
|
|
1722
1767
|
skipped.push(...out.skipped);
|
|
@@ -1931,14 +1976,10 @@ async function writeWithConflict(abs, relPath, proposed, opts, internals) {
|
|
|
1931
1976
|
existing = void 0;
|
|
1932
1977
|
}
|
|
1933
1978
|
if (existing === void 0) {
|
|
1934
|
-
regenerate(abs, proposed
|
|
1979
|
+
regenerate(abs, proposed);
|
|
1935
1980
|
return { written: [relPath], skipped: [], identical: [] };
|
|
1936
1981
|
}
|
|
1937
1982
|
if (existing === proposed) {
|
|
1938
|
-
if (internals.mode !== void 0 && (statSync(abs).mode & 511) !== internals.mode) {
|
|
1939
|
-
chmodSync(abs, internals.mode);
|
|
1940
|
-
return { written: [relPath], skipped: [], identical: [] };
|
|
1941
|
-
}
|
|
1942
1983
|
return { written: [], skipped: [], identical: [relPath] };
|
|
1943
1984
|
}
|
|
1944
1985
|
const MODE = internals.artifactClass ?? "regenerate";
|
|
@@ -1960,21 +2001,21 @@ async function writeWithConflict(abs, relPath, proposed, opts, internals) {
|
|
|
1960
2001
|
internals.record(relPath, MODE, existing, proposed, resolution, sim);
|
|
1961
2002
|
switch (resolution) {
|
|
1962
2003
|
case "replace":
|
|
1963
|
-
regenerate(abs, proposed
|
|
2004
|
+
regenerate(abs, proposed);
|
|
1964
2005
|
return { written: [relPath], skipped: [], identical: [] };
|
|
1965
2006
|
case "merge": {
|
|
1966
|
-
regenerate(abs, proposed
|
|
2007
|
+
regenerate(abs, proposed);
|
|
1967
2008
|
return { written: [relPath], skipped: [], identical: [] };
|
|
1968
2009
|
}
|
|
1969
2010
|
case "rename": {
|
|
1970
2011
|
const aside = uniqueAside(abs, relPath, ".local");
|
|
1971
2012
|
renameSync2(abs, aside.abs);
|
|
1972
|
-
regenerate(abs, proposed
|
|
2013
|
+
regenerate(abs, proposed);
|
|
1973
2014
|
return { written: [relPath], skipped: [aside.rel], identical: [] };
|
|
1974
2015
|
}
|
|
1975
2016
|
case "duplicate": {
|
|
1976
2017
|
const aside = uniqueAside(abs, relPath, ".noir");
|
|
1977
|
-
regenerate(aside.abs, proposed
|
|
2018
|
+
regenerate(aside.abs, proposed);
|
|
1978
2019
|
return { written: [aside.rel], skipped: [relPath], identical: [] };
|
|
1979
2020
|
}
|
|
1980
2021
|
case "preserve":
|
|
@@ -2016,7 +2057,7 @@ function healLegacyNoirMd(absPath) {
|
|
|
2016
2057
|
return;
|
|
2017
2058
|
}
|
|
2018
2059
|
if (/<!-- noir:[a-z]+ begin -->/.test(content)) return;
|
|
2019
|
-
|
|
2060
|
+
rmSync3(absPath, { force: true });
|
|
2020
2061
|
}
|
|
2021
2062
|
function renderEntry(entry, vars) {
|
|
2022
2063
|
if (entry.template !== void 0) {
|