@coreframe/scripts 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/coreframe.js +35 -0
- package/config.test.ts +15 -0
- package/config.ts +9 -0
- package/coreframe.ts +272 -0
- package/db/client.ts +72 -0
- package/db/config.ts +41 -0
- package/db/drizzle-kit.test.ts +27 -0
- package/db/drizzle-kit.ts +39 -0
- package/db/ensure.ts +67 -0
- package/db/migrate-held.test.ts +81 -0
- package/db/migrate-held.ts +166 -0
- package/db/migrate.ts +39 -0
- package/db/project.ts +28 -0
- package/db/reset.ts +24 -0
- package/db/seed.ts +38 -0
- package/db/setup.ts +18 -0
- package/deploy/check-migrations.ts +31 -0
- package/deploy/checks.ts +38 -0
- package/deploy/config.test.ts +24 -0
- package/deploy/config.ts +68 -0
- package/deploy/migrations.test.ts +110 -0
- package/deploy/migrations.ts +317 -0
- package/deploy/pending-migrations.test.ts +93 -0
- package/deploy/pending-migrations.ts +45 -0
- package/deploy/tauri-release.test.ts +196 -0
- package/deploy/tauri-release.ts +816 -0
- package/deploy/version.test.ts +29 -0
- package/deploy/version.ts +65 -0
- package/deploy.test.ts +101 -0
- package/deploy.ts +346 -0
- package/dev.test.ts +153 -0
- package/dev.ts +155 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +3 -0
- package/dist/coreframe.d.ts +280 -0
- package/dist/coreframe.js +240 -0
- package/dist/db/client.d.ts +22 -0
- package/dist/db/client.js +29 -0
- package/dist/db/config.d.ts +21 -0
- package/dist/db/config.js +32 -0
- package/dist/db/drizzle-kit.d.ts +8 -0
- package/dist/db/drizzle-kit.js +22 -0
- package/dist/db/ensure.d.ts +2 -0
- package/dist/db/ensure.js +40 -0
- package/dist/db/migrate-held.d.ts +12 -0
- package/dist/db/migrate-held.js +119 -0
- package/dist/db/migrate.d.ts +2 -0
- package/dist/db/migrate.js +25 -0
- package/dist/db/project.d.ts +8 -0
- package/dist/db/project.js +9 -0
- package/dist/db/reset.d.ts +2 -0
- package/dist/db/reset.js +19 -0
- package/dist/db/seed.d.ts +3 -0
- package/dist/db/seed.js +23 -0
- package/dist/db/setup.d.ts +3 -0
- package/dist/db/setup.js +15 -0
- package/dist/deploy/check-migrations.d.ts +2 -0
- package/dist/deploy/check-migrations.js +18 -0
- package/dist/deploy/checks.d.ts +2 -0
- package/dist/deploy/checks.js +27 -0
- package/dist/deploy/config.d.ts +50 -0
- package/dist/deploy/config.js +9 -0
- package/dist/deploy/migrations.d.ts +21 -0
- package/dist/deploy/migrations.js +203 -0
- package/dist/deploy/pending-migrations.d.ts +5 -0
- package/dist/deploy/pending-migrations.js +30 -0
- package/dist/deploy/tauri-release.d.ts +85 -0
- package/dist/deploy/tauri-release.js +512 -0
- package/dist/deploy/version.d.ts +18 -0
- package/dist/deploy/version.js +41 -0
- package/dist/deploy.d.ts +17 -0
- package/dist/deploy.js +247 -0
- package/dist/dev.d.ts +13 -0
- package/dist/dev.js +96 -0
- package/dist/image-generator.d.ts +2 -0
- package/dist/image-generator.js +59 -0
- package/dist/project.d.ts +1 -0
- package/dist/project.js +13 -0
- package/dist/typecheck.d.ts +2 -0
- package/dist/typecheck.js +45 -0
- package/dist/upgrade/check-skill-current.d.ts +6 -0
- package/dist/upgrade/check-skill-current.js +88 -0
- package/dist/upgrade/ensure-template-remote.d.ts +6 -0
- package/dist/upgrade/ensure-template-remote.js +57 -0
- package/dist/upgrade/fast-forward-template-files.d.ts +7 -0
- package/dist/upgrade/fast-forward-template-files.js +280 -0
- package/image-generator.ts +84 -0
- package/package.json +48 -8
- package/project.ts +6 -0
- package/tsconfig.build.json +12 -0
- package/tsconfig.json +20 -0
- package/typecheck.ts +55 -0
- package/upgrade/check-skill-current.ts +112 -0
- package/upgrade/ensure-template-remote.ts +79 -0
- package/upgrade/fast-forward-template-files.ts +408 -0
- package/readme.md +0 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "node:child_process"
|
|
3
|
+
|
|
4
|
+
const defaultRemote = "template"
|
|
5
|
+
const defaultUrl = "https://github.com/airlock-labs/coreframe.git"
|
|
6
|
+
|
|
7
|
+
export type EnsureTemplateRemoteOptions = {
|
|
8
|
+
fetch: boolean
|
|
9
|
+
remote: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function git(args: string[]) {
|
|
13
|
+
return spawnSync("git", args, { encoding: "utf8" })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function runGit(args: string[]) {
|
|
17
|
+
const result = git(args)
|
|
18
|
+
|
|
19
|
+
if (result.status !== 0) {
|
|
20
|
+
const detail = result.stderr.trim() || result.stdout.trim()
|
|
21
|
+
throw new Error(`Git command failed: git ${args.join(" ")}${detail ? `\n${detail}` : ""}`)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return result.stdout
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getRemoteUrl(remote: string) {
|
|
28
|
+
const result = git(["remote", "get-url", remote])
|
|
29
|
+
|
|
30
|
+
if (result.status === 2) {
|
|
31
|
+
return null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (result.status !== 0) {
|
|
35
|
+
const detail = result.stderr.trim() || result.stdout.trim()
|
|
36
|
+
throw new Error(`Could not inspect remote ${remote}.${detail ? `\n${detail}` : ""}`)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return result.stdout.trim()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function ensureTemplateRemote({
|
|
43
|
+
fetch = true,
|
|
44
|
+
remote = defaultRemote,
|
|
45
|
+
}: Partial<EnsureTemplateRemoteOptions> = {}) {
|
|
46
|
+
if (remote.length === 0) {
|
|
47
|
+
throw new Error("Remote name must not be empty.")
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
runGit(["rev-parse", "--show-toplevel"])
|
|
51
|
+
|
|
52
|
+
const existingUrl = getRemoteUrl(remote)
|
|
53
|
+
|
|
54
|
+
if (existingUrl === null) {
|
|
55
|
+
runGit(["remote", "add", remote, defaultUrl])
|
|
56
|
+
console.log(`Added ${remote} remote: ${defaultUrl}`)
|
|
57
|
+
} else if (existingUrl === defaultUrl) {
|
|
58
|
+
console.log(`${remote} remote already points at ${defaultUrl}`)
|
|
59
|
+
} else {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`${remote} remote points at ${existingUrl}, not ${defaultUrl}.\n` +
|
|
62
|
+
"Choose the intended template source before continuing, then update or rename the remote manually.",
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (fetch) {
|
|
67
|
+
runGit(["fetch", remote, "--tags", "--prune"])
|
|
68
|
+
console.log(`Fetched ${remote}.`)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (import.meta.main) {
|
|
73
|
+
try {
|
|
74
|
+
ensureTemplateRemote({ fetch: !process.argv.includes("--no-fetch") })
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error(error instanceof Error ? error.message : String(error))
|
|
77
|
+
process.exit(65)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "node:child_process"
|
|
3
|
+
import {
|
|
4
|
+
chmodSync,
|
|
5
|
+
existsSync,
|
|
6
|
+
lstatSync,
|
|
7
|
+
mkdirSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
readlinkSync,
|
|
10
|
+
rmSync,
|
|
11
|
+
symlinkSync,
|
|
12
|
+
writeFileSync,
|
|
13
|
+
} from "node:fs"
|
|
14
|
+
import type { Stats } from "node:fs"
|
|
15
|
+
import { dirname, join, resolve, sep } from "node:path"
|
|
16
|
+
|
|
17
|
+
const projectOwnedPathPrefixes = ["drizzle/", "drizzle-hold/"]
|
|
18
|
+
const supportedModes = new Set(["100644", "100755", "120000"])
|
|
19
|
+
|
|
20
|
+
type GitEntry = {
|
|
21
|
+
mode: string
|
|
22
|
+
object: string
|
|
23
|
+
path: string
|
|
24
|
+
type: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type ChangedPath = {
|
|
28
|
+
path: string
|
|
29
|
+
status: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type PendingEntry = {
|
|
33
|
+
action: string
|
|
34
|
+
path: string
|
|
35
|
+
reason?: string
|
|
36
|
+
targetEntry?: GitEntry | null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type FastForwardTemplateFilesOptions = {
|
|
40
|
+
apply: boolean
|
|
41
|
+
base?: string
|
|
42
|
+
target: string
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function git(args: string[], options = {}) {
|
|
46
|
+
return spawnSync("git", args, options)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function gitText(args: string[]) {
|
|
50
|
+
const result = git(args, { encoding: "utf8" })
|
|
51
|
+
|
|
52
|
+
if (result.status !== 0) {
|
|
53
|
+
const detail = result.stderr.toString().trim() || result.stdout.toString().trim()
|
|
54
|
+
throw new Error(`Git command failed: git ${args.join(" ")}${detail ? `\n${detail}` : ""}`)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return result.stdout.toString()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function gitBuffer(args: string[]) {
|
|
61
|
+
const result = git(args)
|
|
62
|
+
|
|
63
|
+
if (result.status !== 0) {
|
|
64
|
+
const detail = result.stderr.toString("utf8").trim() || result.stdout.toString("utf8").trim()
|
|
65
|
+
throw new Error(`Git command failed: git ${args.join(" ")}${detail ? `\n${detail}` : ""}`)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return result.stdout
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getRepoRoot() {
|
|
72
|
+
return gitText(["rev-parse", "--show-toplevel"]).trim()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function readPackageCoreframeVersion(repoRoot: string) {
|
|
76
|
+
const packagePath = join(repoRoot, "package.json")
|
|
77
|
+
|
|
78
|
+
if (!existsSync(packagePath)) {
|
|
79
|
+
throw new Error("Missing --base <git-ref>, and package.json does not exist.")
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const packageJson = JSON.parse(readFileSync(packagePath, "utf8")) as {
|
|
83
|
+
engines?: { coreframe?: unknown }
|
|
84
|
+
}
|
|
85
|
+
const version = packageJson.engines?.coreframe
|
|
86
|
+
|
|
87
|
+
if (typeof version !== "string" || version.length === 0) {
|
|
88
|
+
throw new Error("Missing --base <git-ref>, and package.json engines.coreframe is not set.")
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return version
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function splitNullTerminated(buffer: Buffer) {
|
|
95
|
+
const fields = []
|
|
96
|
+
let start = 0
|
|
97
|
+
|
|
98
|
+
for (let index = 0; index < buffer.length; index += 1) {
|
|
99
|
+
if (buffer[index] !== 0) {
|
|
100
|
+
continue
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
fields.push(buffer.subarray(start, index).toString("utf8"))
|
|
104
|
+
start = index + 1
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return fields.filter((field) => field.length > 0)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function listChangedPaths(base: string, target: string) {
|
|
111
|
+
const output = gitBuffer(["diff", "--name-status", "-z", "--no-renames", base, target, "--"])
|
|
112
|
+
const fields = splitNullTerminated(output)
|
|
113
|
+
const changes: ChangedPath[] = []
|
|
114
|
+
|
|
115
|
+
for (let index = 0; index < fields.length; index += 2) {
|
|
116
|
+
const status = fields[index]
|
|
117
|
+
const path = fields[index + 1]
|
|
118
|
+
|
|
119
|
+
if (!status || !path) {
|
|
120
|
+
throw new Error("Could not parse git diff --name-status output.")
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
changes.push({ path, status })
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return changes
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function getGitEntry(ref: string, path: string): GitEntry | null {
|
|
130
|
+
const output = gitBuffer(["ls-tree", "-z", ref, "--", path])
|
|
131
|
+
|
|
132
|
+
if (output.length === 0) {
|
|
133
|
+
return null
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const record = splitNullTerminated(output)[0]
|
|
137
|
+
const tabIndex = record.indexOf("\t")
|
|
138
|
+
|
|
139
|
+
if (tabIndex === -1) {
|
|
140
|
+
throw new Error(`Could not parse git ls-tree output for ${ref}:${path}.`)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const [mode, type, object] = record.slice(0, tabIndex).split(" ")
|
|
144
|
+
|
|
145
|
+
if (!mode || !type || !object) {
|
|
146
|
+
throw new Error(`Could not parse git ls-tree metadata for ${ref}:${path}.`)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return { mode, object, path, type }
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function readGitBlob(entry: GitEntry) {
|
|
153
|
+
return gitBuffer(["cat-file", "blob", entry.object])
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function safeWorktreePath(repoRoot: string, path: string) {
|
|
157
|
+
const absolutePath = resolve(repoRoot, path)
|
|
158
|
+
const rootPrefix = repoRoot.endsWith(sep) ? repoRoot : `${repoRoot}${sep}`
|
|
159
|
+
|
|
160
|
+
if (absolutePath !== repoRoot && !absolutePath.startsWith(rootPrefix)) {
|
|
161
|
+
throw new Error(`Refusing to access path outside repository: ${path}`)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return absolutePath
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function pathExists(path: string) {
|
|
168
|
+
try {
|
|
169
|
+
lstatSync(path)
|
|
170
|
+
return true
|
|
171
|
+
} catch (error) {
|
|
172
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
173
|
+
return false
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
throw error
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function isExecutable(stat: Stats) {
|
|
181
|
+
return (stat.mode & 0o111) !== 0
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function entryIsSupported(entry: GitEntry | null) {
|
|
185
|
+
return entry === null || (entry.type === "blob" && supportedModes.has(entry.mode))
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function isProjectOwnedPath(path: string) {
|
|
189
|
+
return projectOwnedPathPrefixes.some(
|
|
190
|
+
(prefix) => path === prefix.slice(0, -1) || path.startsWith(prefix),
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function currentMatchesEntry(repoRoot: string, path: string, entry: GitEntry | null) {
|
|
195
|
+
const worktreePath = safeWorktreePath(repoRoot, path)
|
|
196
|
+
|
|
197
|
+
if (entry === null) {
|
|
198
|
+
return { matches: !pathExists(worktreePath) }
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (!entryIsSupported(entry)) {
|
|
202
|
+
return { matches: false, reason: `unsupported template entry mode ${entry.mode} ${entry.type}` }
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
let stat: Stats
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
stat = lstatSync(worktreePath)
|
|
209
|
+
} catch (error) {
|
|
210
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
211
|
+
return { matches: false, reason: "path is missing from worktree" }
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
throw error
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const expected = readGitBlob(entry)
|
|
218
|
+
|
|
219
|
+
if (entry.mode === "120000") {
|
|
220
|
+
if (!stat.isSymbolicLink()) {
|
|
221
|
+
return { matches: false, reason: "worktree path is not a symlink" }
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const linkTarget = Buffer.from(readlinkSync(worktreePath), "utf8")
|
|
225
|
+
return { matches: linkTarget.equals(expected) }
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (!stat.isFile()) {
|
|
229
|
+
return { matches: false, reason: "worktree path is not a regular file" }
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const executableMatches = entry.mode === "100755" ? isExecutable(stat) : !isExecutable(stat)
|
|
233
|
+
|
|
234
|
+
if (!executableMatches) {
|
|
235
|
+
return { matches: false, reason: "worktree executable bit differs from template" }
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return { matches: readFileSync(worktreePath).equals(expected) }
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function removeExistingPath(worktreePath: string) {
|
|
242
|
+
if (!pathExists(worktreePath)) {
|
|
243
|
+
return
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
rmSync(worktreePath, { force: true, recursive: false })
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function applyEntry(repoRoot: string, path: string, entry: GitEntry | null) {
|
|
250
|
+
const worktreePath = safeWorktreePath(repoRoot, path)
|
|
251
|
+
|
|
252
|
+
if (entry === null) {
|
|
253
|
+
removeExistingPath(worktreePath)
|
|
254
|
+
return
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
mkdirSync(dirname(worktreePath), { recursive: true })
|
|
258
|
+
|
|
259
|
+
if (entry.mode === "120000") {
|
|
260
|
+
removeExistingPath(worktreePath)
|
|
261
|
+
symlinkSync(readGitBlob(entry).toString("utf8"), worktreePath)
|
|
262
|
+
return
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const stat = pathExists(worktreePath) ? lstatSync(worktreePath) : null
|
|
266
|
+
|
|
267
|
+
if (stat && !stat.isFile()) {
|
|
268
|
+
removeExistingPath(worktreePath)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
writeFileSync(worktreePath, readGitBlob(entry))
|
|
272
|
+
chmodSync(worktreePath, entry.mode === "100755" ? 0o755 : 0o644)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function actionFor(baseEntry: GitEntry | null, targetEntry: GitEntry | null) {
|
|
276
|
+
if (baseEntry === null && targetEntry !== null) {
|
|
277
|
+
return "add"
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (baseEntry !== null && targetEntry === null) {
|
|
281
|
+
return "delete"
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return "update"
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function printEntries(title: string, entries: PendingEntry[]) {
|
|
288
|
+
if (entries.length === 0) {
|
|
289
|
+
return
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
console.log(`${title}:`)
|
|
293
|
+
|
|
294
|
+
for (const entry of entries) {
|
|
295
|
+
const detail = entry.reason ? ` (${entry.reason})` : ""
|
|
296
|
+
console.log(` ${entry.action}: ${entry.path}${detail}`)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
console.log("")
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export function fastForwardTemplateFiles(args: FastForwardTemplateFilesOptions) {
|
|
303
|
+
const repoRoot = getRepoRoot()
|
|
304
|
+
const base = args.base ?? readPackageCoreframeVersion(repoRoot)
|
|
305
|
+
const target = args.target
|
|
306
|
+
const changes = listChangedPaths(base, target)
|
|
307
|
+
const clean: PendingEntry[] = []
|
|
308
|
+
const ignored: PendingEntry[] = []
|
|
309
|
+
const skipped: PendingEntry[] = []
|
|
310
|
+
const unchanged: PendingEntry[] = []
|
|
311
|
+
|
|
312
|
+
for (const change of changes) {
|
|
313
|
+
if (isProjectOwnedPath(change.path)) {
|
|
314
|
+
ignored.push({
|
|
315
|
+
action: "ignored",
|
|
316
|
+
path: change.path,
|
|
317
|
+
reason: "project-owned migration history",
|
|
318
|
+
})
|
|
319
|
+
continue
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const baseEntry = getGitEntry(base, change.path)
|
|
323
|
+
const targetEntry = getGitEntry(target, change.path)
|
|
324
|
+
const action = actionFor(baseEntry, targetEntry)
|
|
325
|
+
|
|
326
|
+
if (!entryIsSupported(baseEntry) || !entryIsSupported(targetEntry)) {
|
|
327
|
+
skipped.push({
|
|
328
|
+
action,
|
|
329
|
+
path: change.path,
|
|
330
|
+
reason: "unsupported template entry type",
|
|
331
|
+
})
|
|
332
|
+
continue
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const targetMatch = currentMatchesEntry(repoRoot, change.path, targetEntry)
|
|
336
|
+
|
|
337
|
+
if (targetMatch.matches) {
|
|
338
|
+
unchanged.push({ action: "unchanged", path: change.path })
|
|
339
|
+
continue
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const baseMatch = currentMatchesEntry(repoRoot, change.path, baseEntry)
|
|
343
|
+
|
|
344
|
+
if (!baseMatch.matches) {
|
|
345
|
+
skipped.push({
|
|
346
|
+
action,
|
|
347
|
+
path: change.path,
|
|
348
|
+
reason: baseMatch.reason ?? "downstream differs from base template",
|
|
349
|
+
})
|
|
350
|
+
continue
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
clean.push({ action, path: change.path, targetEntry })
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (args.apply) {
|
|
357
|
+
for (const entry of clean) {
|
|
358
|
+
applyEntry(repoRoot, entry.path, entry.targetEntry ?? null)
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
console.log(`Coreframe template fast-forward: ${base} -> ${target}`)
|
|
363
|
+
console.log("")
|
|
364
|
+
|
|
365
|
+
printEntries(
|
|
366
|
+
args.apply ? "Applied clean template updates" : "Would apply clean template updates",
|
|
367
|
+
clean,
|
|
368
|
+
)
|
|
369
|
+
printEntries("Ignored project-owned paths", ignored)
|
|
370
|
+
printEntries("Skipped for agent review", skipped)
|
|
371
|
+
|
|
372
|
+
if (unchanged.length > 0) {
|
|
373
|
+
console.log(`${unchanged.length} changed template path(s) already matched the target state.`)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (changes.length === 0) {
|
|
377
|
+
console.log("No template file changes found in the requested range.")
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (!args.apply && clean.length > 0) {
|
|
381
|
+
console.log("")
|
|
382
|
+
console.log(`Run with --apply to write the ${clean.length} clean update(s).`)
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (import.meta.main) {
|
|
387
|
+
const targetIndex = process.argv.indexOf("--target")
|
|
388
|
+
const target = targetIndex === -1 ? null : process.argv[targetIndex + 1]
|
|
389
|
+
|
|
390
|
+
if (!target) {
|
|
391
|
+
console.error(
|
|
392
|
+
"Usage: coreframe upgrade fast-forward-template-files --target <git-ref> [--base <git-ref>] [--apply]",
|
|
393
|
+
)
|
|
394
|
+
process.exit(64)
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
try {
|
|
398
|
+
const baseIndex = process.argv.indexOf("--base")
|
|
399
|
+
fastForwardTemplateFiles({
|
|
400
|
+
apply: process.argv.includes("--apply"),
|
|
401
|
+
base: baseIndex === -1 ? undefined : process.argv[baseIndex + 1],
|
|
402
|
+
target,
|
|
403
|
+
})
|
|
404
|
+
} catch (error) {
|
|
405
|
+
console.error(error instanceof Error ? error.message : String(error))
|
|
406
|
+
process.exit(65)
|
|
407
|
+
}
|
|
408
|
+
}
|
package/readme.md
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
Coming soon...
|