@noir-ai/create 1.13.0-beta.1 → 1.14.0-beta.1
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 +48 -10
- package/dist/index.js +126 -28
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/templates/config.env.tmpl +75 -0
- package/templates/config.yml.tmpl +6 -0
- package/templates/env.example.tmpl +73 -5
- package/templates/gitignore.tmpl +0 -3
- package/templates/noir-readme.md.tmpl +57 -0
package/dist/index.d.ts
CHANGED
|
@@ -123,8 +123,22 @@ declare function managedBlock(absPath: string, block: ManagedBlock, regionText:
|
|
|
123
123
|
* S-T2 relies on for a diff-free refactor. */
|
|
124
124
|
declare function buildRegion(block: ManagedBlock, body: string): string;
|
|
125
125
|
/** Write `content` to `absPath` only if no file exists there. Returns whether
|
|
126
|
-
* bytes were written. Parent dirs are
|
|
127
|
-
|
|
126
|
+
* bytes were written. Parent dirs are created by {@link atomicWriteFile}.
|
|
127
|
+
*
|
|
128
|
+
* `fileMode` (slice E, §8.1) is the permission for the NEWLY created file —
|
|
129
|
+
* `0o600` for the `.noir/.env` credential seed. It is applied to the temp file
|
|
130
|
+
* BEFORE the rename (see `AtomicWriteOptions.mode`), so the file never exists
|
|
131
|
+
* at its final path with a laxer mode. It is deliberately ignored when the
|
|
132
|
+
* target already exists: this writer's whole contract is "never touch an
|
|
133
|
+
* existing file", and that includes its permissions. POSIX-only — Windows
|
|
134
|
+
* permissions are ACL-based and the mode is dropped there.
|
|
135
|
+
*
|
|
136
|
+
* The write goes through `atomicWriteFile` (tmp + rename) rather than a bare
|
|
137
|
+
* `writeFileSync`: a requested mode must land on the temp, and an interrupted
|
|
138
|
+
* seed write should not leave a half-written `.noir/.env` behind. Byte output
|
|
139
|
+
* is unchanged for every existing caller (same content, umask default 0o644
|
|
140
|
+
* when no mode is requested). */
|
|
141
|
+
declare function skipIfExists(absPath: string, content: string, fileMode?: number): WriteOutcome;
|
|
128
142
|
|
|
129
143
|
/**
|
|
130
144
|
* Declarative scaffold manifest — the single source of truth for what
|
|
@@ -172,6 +186,14 @@ interface ManifestEntry {
|
|
|
172
186
|
template?: string;
|
|
173
187
|
/** One-line human description for `noir doctor` + logs. */
|
|
174
188
|
description?: string;
|
|
189
|
+
/** Permission for a NEWLY created file (e.g. `0o600` for a credential seed).
|
|
190
|
+
* Applied only on creation; an existing file's mode is never changed — the
|
|
191
|
+
* `skipIfExists` contract means an existing file is not even opened. The
|
|
192
|
+
* orchestrator forwards this to the writer and reports it on the result as
|
|
193
|
+
* {@link ScaffoldResult.fileModes}. Ignored by every other write mode, whose
|
|
194
|
+
* targets are regenerated rather than seeded.
|
|
195
|
+
* NOTE: POSIX-only. Windows permissions are ACL-based and ignore it. */
|
|
196
|
+
fileMode?: number;
|
|
175
197
|
/** Required for `mergeJson` mode: the JSON patch object (or a template that
|
|
176
198
|
* renders to one). Merged into the existing file, preserving user keys. */
|
|
177
199
|
patch?: Record<string, unknown>;
|
|
@@ -232,6 +254,8 @@ declare const MANIFEST_PATH_PARITY: ReadonlyArray<[
|
|
|
232
254
|
* chosen host for `noir sync` to read back.)
|
|
233
255
|
* - `NOIR.md` → managedBlock (BRIEF_BLOCK). Auto-brief is co-owned.
|
|
234
256
|
* - `RULES.md` → skipIfExists. User-owned working-contract seed.
|
|
257
|
+
* - `README.md` → managedBlock (README_BLOCK). The `.noir/` runtime map is
|
|
258
|
+
* regenerated as the layout grows; user notes outside the markers survive.
|
|
235
259
|
* - ignore files → managedBlock (IGNORE_BLOCK). Matches syncIgnores.
|
|
236
260
|
* - host entries → SEE {@link buildHostArtifacts} (regenerate / managedBlock).
|
|
237
261
|
*/
|
|
@@ -361,10 +385,10 @@ declare function mergeThreeWay(base: string, ours: string, theirs: string, style
|
|
|
361
385
|
* `result.conflicts` so the caller (doctor/CI) can fail loudly with a
|
|
362
386
|
* reviewable artifact rather than hang on a prompt.
|
|
363
387
|
*
|
|
364
|
-
* The registry ({@link MIGRATIONS})
|
|
365
|
-
*
|
|
366
|
-
*
|
|
367
|
-
* for the
|
|
388
|
+
* The registry ({@link MIGRATIONS}) carries two entries: the synthetic
|
|
389
|
+
* `1.0.0 → 1.0.0` smoke test that exercises the runner end-to-end, and the
|
|
390
|
+
* first real version-to-version migration, `1.0.0 → 1.1.0`. The synthetic one
|
|
391
|
+
* is the copy-pasteable template for the next contributor.
|
|
368
392
|
*/
|
|
369
393
|
interface MigrationContext {
|
|
370
394
|
/** Absolute repo root the migration operates on. */
|
|
@@ -419,6 +443,8 @@ declare function runMigrations(root: string, from: string | null, to: string, op
|
|
|
419
443
|
ran: string[];
|
|
420
444
|
};
|
|
421
445
|
|
|
446
|
+
/** The registry. The runner sorts the selected window by `to`, so declaration
|
|
447
|
+
* order is documentation only — oldest step first. */
|
|
422
448
|
declare const MIGRATIONS: readonly MigrationScript[];
|
|
423
449
|
/** Write git-style inline conflict markers around `theirs`/`ours` so a human
|
|
424
450
|
* or AI agent can resolve later. This is the CI-safe fallback the spec locks
|
|
@@ -467,9 +493,12 @@ interface ScaffoldOptions {
|
|
|
467
493
|
/** Explicit project id; bypasses generate/read. Mainly for tests + `create`
|
|
468
494
|
* flows that want deterministic ids. */
|
|
469
495
|
projectId?: string;
|
|
470
|
-
/** `noir init --upgrade`: run migrations
|
|
471
|
-
*
|
|
472
|
-
*
|
|
496
|
+
/** `noir init --upgrade`: run migrations, then re-emit every manifest mode
|
|
497
|
+
* EXCEPT `mergeJson` (`.claude/settings.local.json` is user-owned and
|
|
498
|
+
* write-once at init/create). `skipIfExists` keeps its
|
|
499
|
+
* create-only-if-absent semantics, so an upgrade backfills seeds added since
|
|
500
|
+
* initialization without ever touching a file the user owns. Only meaningful
|
|
501
|
+
* with mode `'init'`. */
|
|
473
502
|
upgrade?: boolean;
|
|
474
503
|
/** SP-A: re-scaffold even when the target is already initialized (bypasses
|
|
475
504
|
* the already-initialized no-op guard). Does NOT bypass `assertSafeRoot`
|
|
@@ -535,6 +564,15 @@ interface ScaffoldResult {
|
|
|
535
564
|
* grepping stderr. Empty when no conflicts occurred (also on first-run +
|
|
536
565
|
* no-op). */
|
|
537
566
|
conflicts: ConflictRecord[];
|
|
567
|
+
/** Permission planned for each file this run CREATED at a non-default mode
|
|
568
|
+
* (repo-relative path → mode), i.e. the {@link ManifestEntry.fileMode} of a
|
|
569
|
+
* `skipIfExists` entry that had no file on disk. A real run reports the mode
|
|
570
|
+
* it applied; a `dryRun` reports the mode it WOULD apply (the predictor's
|
|
571
|
+
* job — the caller can preview `.noir/.env` at 0600 without writing it).
|
|
572
|
+
* Paths the entry skipped (already present) are absent: an existing file's
|
|
573
|
+
* mode is never changed. Optional so existing external constructors of this
|
|
574
|
+
* type (test stubs) stay valid; `scaffold()` always populates it. */
|
|
575
|
+
fileModes?: Record<string, number>;
|
|
538
576
|
}
|
|
539
577
|
/** One entry in {@link ScaffoldResult.conflicts}. */
|
|
540
578
|
interface ConflictRecord {
|
|
@@ -614,7 +652,7 @@ declare function scaffold(opts: ScaffoldOptions): Promise<ScaffoldResult>;
|
|
|
614
652
|
*/
|
|
615
653
|
/** The scaffold version this build of @noir-ai/create ships. Bumped atomically
|
|
616
654
|
* whenever a manifest entry, template, or migration changes shape. */
|
|
617
|
-
declare const CURRENT_SCAFFOLD_VERSION = "1.
|
|
655
|
+
declare const CURRENT_SCAFFOLD_VERSION = "1.1.0";
|
|
618
656
|
/** Path to the stamp file under `root`. Exposed for tests + doctor. */
|
|
619
657
|
declare function scaffoldVersionPath(root: string): string;
|
|
620
658
|
/** Read the applied scaffold version, or `null` if the stamp is absent/unparseable.
|
package/dist/index.js
CHANGED
|
@@ -47,12 +47,15 @@ import {
|
|
|
47
47
|
RULES_BLOCK
|
|
48
48
|
} from "@noir-ai/core";
|
|
49
49
|
var BRIEF_BLOCK = managedBlock("brief", "html");
|
|
50
|
+
var README_BLOCK = managedBlock("readme", "html");
|
|
50
51
|
var P = {
|
|
51
52
|
projectId: `${NOIR_DIR}/project.id`,
|
|
52
53
|
config: `${NOIR_DIR}/config.yml`,
|
|
53
54
|
envExample: `${NOIR_DIR}/.env.example`,
|
|
55
|
+
env: `${NOIR_DIR}/.env`,
|
|
54
56
|
noirMd: `${NOIR_DIR}/NOIR.md`,
|
|
55
|
-
rulesMd: `${NOIR_DIR}/rules/RULES.md
|
|
57
|
+
rulesMd: `${NOIR_DIR}/rules/RULES.md`,
|
|
58
|
+
readme: `${NOIR_DIR}/README.md`
|
|
56
59
|
};
|
|
57
60
|
var MANIFEST_PATH_PARITY = [
|
|
58
61
|
[P.projectId, paths.projectId],
|
|
@@ -82,7 +85,20 @@ function hostAgnosticEntries(ctx) {
|
|
|
82
85
|
path: P.envExample,
|
|
83
86
|
mode: "skipIfExists",
|
|
84
87
|
template: "env.example.tmpl",
|
|
85
|
-
description: ".noir/.env
|
|
88
|
+
description: ".noir/.env.example committable documentation (never loaded)"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
// §8.1: init creates the REAL env file, not just the example. The body is
|
|
92
|
+
// all-comment, so the file parses to an EMPTY overlay and creating it
|
|
93
|
+
// changes no behaviour — it exists so the user never has to copy the
|
|
94
|
+
// example by hand. `fileMode: 0o600` because the file holds tokens the
|
|
95
|
+
// moment the user edits it (set on creation only — skipIfExists never
|
|
96
|
+
// touches an existing file, so a user's own chmod stands).
|
|
97
|
+
path: P.env,
|
|
98
|
+
mode: "skipIfExists",
|
|
99
|
+
template: "config.env.tmpl",
|
|
100
|
+
fileMode: 384,
|
|
101
|
+
description: "project-scoped env file (gitignored, 0600)"
|
|
86
102
|
},
|
|
87
103
|
{
|
|
88
104
|
path: P.noirMd,
|
|
@@ -97,6 +113,18 @@ function hostAgnosticEntries(ctx) {
|
|
|
97
113
|
template: "rules-seed.md.tmpl",
|
|
98
114
|
description: "AI working-rules seed"
|
|
99
115
|
},
|
|
116
|
+
{
|
|
117
|
+
// Slice E (spec §9): the `.noir/` runtime map — what init just wrote,
|
|
118
|
+
// what appears later (and which command creates it), and where to go
|
|
119
|
+
// next. Host-agnostic: it describes the canonical store, which every
|
|
120
|
+
// host shares. Co-owned (README_BLOCK) so user notes survive while the
|
|
121
|
+
// map stays current through `noir sync` / `init --upgrade`.
|
|
122
|
+
path: P.readme,
|
|
123
|
+
mode: "managedBlock",
|
|
124
|
+
block: README_BLOCK,
|
|
125
|
+
template: "noir-readme.md.tmpl",
|
|
126
|
+
description: ".noir/ runtime map (what exists now / what appears later)"
|
|
127
|
+
},
|
|
100
128
|
// --- ignore files (host-agnostic; co-owned via IGNORE_BLOCK) ------------
|
|
101
129
|
{
|
|
102
130
|
path: ".gitignore",
|
|
@@ -505,7 +533,68 @@ var synthetic = {
|
|
|
505
533
|
return result;
|
|
506
534
|
}
|
|
507
535
|
};
|
|
508
|
-
var
|
|
536
|
+
var ENV_POINTER_MARKER = "# noir:env-pointer";
|
|
537
|
+
var ENV_POINTER_BLOCK = [
|
|
538
|
+
ENV_POINTER_MARKER,
|
|
539
|
+
"# This file is safe to commit: it holds no secrets.",
|
|
540
|
+
"# Project-scoped values and secrets belong in .noir/.env (gitignored, mode",
|
|
541
|
+
"# 0600). .noir/.env.example documents the full variable set; `noir env`",
|
|
542
|
+
"# reports which source wins for each key.",
|
|
543
|
+
""
|
|
544
|
+
].join("\n");
|
|
545
|
+
function appendSeparated(prev, block) {
|
|
546
|
+
if (prev.length === 0) return block;
|
|
547
|
+
if (prev.endsWith("\n\n")) return `${prev}${block}`;
|
|
548
|
+
return prev.endsWith("\n") ? `${prev}
|
|
549
|
+
${block}` : `${prev}
|
|
550
|
+
|
|
551
|
+
${block}`;
|
|
552
|
+
}
|
|
553
|
+
var envPointer = {
|
|
554
|
+
from: "1.0.0",
|
|
555
|
+
to: "1.1.0",
|
|
556
|
+
description: "point .noir/config.yml at .noir/.env (the secrets home)",
|
|
557
|
+
run: (ctx) => {
|
|
558
|
+
const result = { changed: [], conflicts: [], notes: [] };
|
|
559
|
+
const rel = ".noir/config.yml";
|
|
560
|
+
const abs = join3(ctx.root, ".noir", "config.yml");
|
|
561
|
+
if (!existsSync3(abs)) {
|
|
562
|
+
result.notes.push(`${rel}: absent \u2014 the template seed carries the pointer`);
|
|
563
|
+
return result;
|
|
564
|
+
}
|
|
565
|
+
let prev;
|
|
566
|
+
try {
|
|
567
|
+
prev = readFileSync2(abs, "utf8");
|
|
568
|
+
} catch (err) {
|
|
569
|
+
result.conflicts.push(rel);
|
|
570
|
+
result.notes.push(`${rel}: unreadable (${errorMessage(err)}) \u2014 left untouched`);
|
|
571
|
+
return result;
|
|
572
|
+
}
|
|
573
|
+
if (prev.includes(ENV_POINTER_MARKER)) {
|
|
574
|
+
result.notes.push(`${rel}: already carries ${ENV_POINTER_MARKER}`);
|
|
575
|
+
return result;
|
|
576
|
+
}
|
|
577
|
+
if (ctx.dryRun) {
|
|
578
|
+
result.changed.push(rel);
|
|
579
|
+
result.notes.push(`${rel}: would append the .noir/.env pointer`);
|
|
580
|
+
return result;
|
|
581
|
+
}
|
|
582
|
+
try {
|
|
583
|
+
writeFileSync2(abs, appendSeparated(prev, ENV_POINTER_BLOCK), "utf8");
|
|
584
|
+
} catch (err) {
|
|
585
|
+
result.conflicts.push(rel);
|
|
586
|
+
result.notes.push(`${rel}: write failed (${errorMessage(err)}) \u2014 left untouched`);
|
|
587
|
+
return result;
|
|
588
|
+
}
|
|
589
|
+
result.changed.push(rel);
|
|
590
|
+
result.notes.push(`${rel}: appended the .noir/.env pointer`);
|
|
591
|
+
return result;
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
var MIGRATIONS = [synthetic, envPointer];
|
|
595
|
+
function errorMessage(err) {
|
|
596
|
+
return err instanceof Error ? err.message : String(err);
|
|
597
|
+
}
|
|
509
598
|
function applyInlineConflict(ours, theirs, oursLabel = "ours", theirsLabel = "theirs") {
|
|
510
599
|
return `<<<<<<< ${oursLabel}
|
|
511
600
|
${ours}=======
|
|
@@ -548,7 +637,7 @@ import {
|
|
|
548
637
|
writeSync
|
|
549
638
|
} from "fs";
|
|
550
639
|
import { basename, dirname as dirname2, join as join4 } from "path";
|
|
551
|
-
import { stripManagedBlock, writeManagedRegion } from "@noir-ai/core";
|
|
640
|
+
import { atomicWriteFile, stripManagedBlock, writeManagedRegion } from "@noir-ai/core";
|
|
552
641
|
function regenerate(absPath, content) {
|
|
553
642
|
const dir = dirname2(absPath);
|
|
554
643
|
const tmp = join4(
|
|
@@ -635,11 +724,11 @@ function predictManagedBlocks(currentContent, regions) {
|
|
|
635
724
|
|
|
636
725
|
${regionsJoined}` : regionsJoined;
|
|
637
726
|
}
|
|
638
|
-
function skipIfExists(absPath, content) {
|
|
727
|
+
function skipIfExists(absPath, content, fileMode) {
|
|
639
728
|
if (existsSync4(absPath)) {
|
|
640
729
|
return { path: absPath, mode: "skipIfExists", written: false };
|
|
641
730
|
}
|
|
642
|
-
|
|
731
|
+
atomicWriteFile(absPath, content, fileMode !== void 0 ? { mode: fileMode } : {});
|
|
643
732
|
return { path: absPath, mode: "skipIfExists", written: true };
|
|
644
733
|
}
|
|
645
734
|
function mergeJson(absPath, patch, dedupSubstring) {
|
|
@@ -689,7 +778,7 @@ function deepMergePreservingHooks(existing, patch, dedupSubstring) {
|
|
|
689
778
|
}
|
|
690
779
|
|
|
691
780
|
// src/scaffold-version.ts
|
|
692
|
-
var CURRENT_SCAFFOLD_VERSION = "1.
|
|
781
|
+
var CURRENT_SCAFFOLD_VERSION = "1.1.0";
|
|
693
782
|
var PREFIX = "noir-scaffold=";
|
|
694
783
|
function scaffoldVersionPath(root) {
|
|
695
784
|
return join5(root, NOIR_DIR2, "scaffold-version");
|
|
@@ -931,17 +1020,11 @@ function templatesDir() {
|
|
|
931
1020
|
}
|
|
932
1021
|
|
|
933
1022
|
// src/scaffold.ts
|
|
934
|
-
var
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
// written once at init/create; sync re-emits only the script + router
|
|
940
|
-
// contract (regenerate + managedBlock), never the user-owned entry.
|
|
941
|
-
regenerate: "runtime",
|
|
942
|
-
managedBlock: "runtime",
|
|
943
|
-
skipIfExists: "all",
|
|
944
|
-
mergeJson: "all"
|
|
1023
|
+
var EMITTED_IN = {
|
|
1024
|
+
regenerate: { sync: true, upgrade: true },
|
|
1025
|
+
managedBlock: { sync: true, upgrade: true },
|
|
1026
|
+
skipIfExists: { sync: false, upgrade: true },
|
|
1027
|
+
mergeJson: { sync: false, upgrade: false }
|
|
945
1028
|
};
|
|
946
1029
|
function assertSafeRoot(root) {
|
|
947
1030
|
let cur = root;
|
|
@@ -989,6 +1072,7 @@ async function scaffold(opts) {
|
|
|
989
1072
|
skipped: [],
|
|
990
1073
|
identical: [],
|
|
991
1074
|
conflicts: [],
|
|
1075
|
+
fileModes: {},
|
|
992
1076
|
noop: true,
|
|
993
1077
|
migrationsRan: [],
|
|
994
1078
|
migrationConflicts: [],
|
|
@@ -1018,7 +1102,7 @@ async function scaffold(opts) {
|
|
|
1018
1102
|
command,
|
|
1019
1103
|
stack
|
|
1020
1104
|
});
|
|
1021
|
-
const
|
|
1105
|
+
const emitContext = opts.mode === "sync" ? "sync" : opts.mode === "init" && opts.upgrade === true ? "upgrade" : null;
|
|
1022
1106
|
const vars = {
|
|
1023
1107
|
root: opts.root,
|
|
1024
1108
|
projectId,
|
|
@@ -1030,6 +1114,7 @@ async function scaffold(opts) {
|
|
|
1030
1114
|
const written = [];
|
|
1031
1115
|
const skipped = [];
|
|
1032
1116
|
const identical = [];
|
|
1117
|
+
const fileModes = {};
|
|
1033
1118
|
const conflictMemory = /* @__PURE__ */ new Map();
|
|
1034
1119
|
const conflictRecords = [];
|
|
1035
1120
|
const recordConflict = (relPath, mode, existing, proposed, resolution, similarity2) => {
|
|
@@ -1042,7 +1127,7 @@ async function scaffold(opts) {
|
|
|
1042
1127
|
resolution
|
|
1043
1128
|
});
|
|
1044
1129
|
};
|
|
1045
|
-
const groups = groupApplicableByPath(manifest, host,
|
|
1130
|
+
const groups = groupApplicableByPath(manifest, host, emitContext);
|
|
1046
1131
|
for (const [relPath, entries] of groups) {
|
|
1047
1132
|
const abs = join8(opts.root, relPath);
|
|
1048
1133
|
const managed = entries.filter((e) => e.mode === "managedBlock");
|
|
@@ -1095,8 +1180,16 @@ async function scaffold(opts) {
|
|
|
1095
1180
|
if (!opts.dryRun) mkdirSync3(dirname5(abs), { recursive: true });
|
|
1096
1181
|
for (const entry of entries) {
|
|
1097
1182
|
if (opts.dryRun) {
|
|
1098
|
-
if (entry.mode === "skipIfExists"
|
|
1099
|
-
|
|
1183
|
+
if (entry.mode === "skipIfExists") {
|
|
1184
|
+
if (existsSync6(abs)) {
|
|
1185
|
+
skipped.push(entry.path);
|
|
1186
|
+
} else {
|
|
1187
|
+
written.push(entry.path);
|
|
1188
|
+
if (entry.fileMode !== void 0) fileModes[entry.path] = entry.fileMode;
|
|
1189
|
+
}
|
|
1190
|
+
} else {
|
|
1191
|
+
written.push(entry.path);
|
|
1192
|
+
}
|
|
1100
1193
|
continue;
|
|
1101
1194
|
}
|
|
1102
1195
|
const body = renderEntry(entry, vars);
|
|
@@ -1156,13 +1249,17 @@ async function scaffold(opts) {
|
|
|
1156
1249
|
if (out.written) written.push(entry.path);
|
|
1157
1250
|
else identical.push(entry.path);
|
|
1158
1251
|
} else {
|
|
1159
|
-
const out = skipIfExists(abs, body);
|
|
1160
|
-
if (out.written)
|
|
1161
|
-
|
|
1252
|
+
const out = skipIfExists(abs, body, entry.fileMode);
|
|
1253
|
+
if (out.written) {
|
|
1254
|
+
written.push(entry.path);
|
|
1255
|
+
if (entry.fileMode !== void 0) fileModes[entry.path] = entry.fileMode;
|
|
1256
|
+
} else {
|
|
1257
|
+
skipped.push(entry.path);
|
|
1258
|
+
}
|
|
1162
1259
|
}
|
|
1163
1260
|
}
|
|
1164
1261
|
}
|
|
1165
|
-
if ((opts.mode === "init" || opts.mode === "create") && !opts.dryRun) {
|
|
1262
|
+
if ((opts.mode === "init" || opts.mode === "create") && !opts.dryRun && migrationConflicts.length === 0) {
|
|
1166
1263
|
writeScaffoldVersion(opts.root, CURRENT_SCAFFOLD_VERSION);
|
|
1167
1264
|
}
|
|
1168
1265
|
if (!opts.dryRun) {
|
|
@@ -1173,6 +1270,7 @@ async function scaffold(opts) {
|
|
|
1173
1270
|
skipped,
|
|
1174
1271
|
identical,
|
|
1175
1272
|
conflicts: conflictRecords,
|
|
1273
|
+
fileModes,
|
|
1176
1274
|
noop: false,
|
|
1177
1275
|
migrationsRan,
|
|
1178
1276
|
migrationConflicts,
|
|
@@ -1316,11 +1414,11 @@ function uniqueAside(abs, relPath, suffix) {
|
|
|
1316
1414
|
for (let n = 1; existsSync6(candidate.abs); n++) candidate = make(`${suffix}.${n}`);
|
|
1317
1415
|
return candidate;
|
|
1318
1416
|
}
|
|
1319
|
-
function groupApplicableByPath(manifest, host,
|
|
1417
|
+
function groupApplicableByPath(manifest, host, emitContext) {
|
|
1320
1418
|
const groups = /* @__PURE__ */ new Map();
|
|
1321
1419
|
for (const entry of manifest) {
|
|
1322
1420
|
if (entry.host !== void 0 && entry.host !== host) continue;
|
|
1323
|
-
if (
|
|
1421
|
+
if (emitContext !== null && !EMITTED_IN[entry.mode][emitContext]) continue;
|
|
1324
1422
|
const list = groups.get(entry.path);
|
|
1325
1423
|
if (list) list.push(entry);
|
|
1326
1424
|
else groups.set(entry.path, [entry]);
|