code-foundry 1.2.0 → 1.3.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.
@@ -13,7 +13,7 @@ export function discoverRepositories(root) {
13
13
  /** @type {FleetRepository[]} */
14
14
  const result = []
15
15
  const candidates = [root, ...children(root), ...children(join(root, 'NiftyLeague'))]
16
- for (const candidate of [...new Set(candidates)]) {
16
+ for (const candidate of new Set(candidates)) {
17
17
  if (!existsSync(join(candidate, '.git'))) continue
18
18
  const configured = existsSync(join(candidate, '.github/code-foundry.yml'))
19
19
  const config = readConfig(join(candidate, '.github/code-foundry.yml'))
@@ -35,12 +35,23 @@ export function upgradeFleet(root, source, options) {
35
35
  const repositories = discoverRepositories(root)
36
36
  const report = []
37
37
  for (const repository of repositories) {
38
- if ((options.exclude ?? []).some((value) => repository.path === value || repository.repository === value || repository.path.endsWith(`/${value}`))) {
38
+ if (
39
+ (options.exclude ?? []).some(
40
+ (value) =>
41
+ repository.path === value ||
42
+ repository.repository === value ||
43
+ repository.path.endsWith(`/${value}`)
44
+ )
45
+ ) {
39
46
  report.push({ path: repository.path, status: 'skipped', reason: 'excluded by fleet policy' })
40
47
  continue
41
48
  }
42
49
  if (!repository.configured) {
43
- report.push({ path: repository.path, status: 'skipped', reason: 'missing .github/code-foundry.yml' })
50
+ report.push({
51
+ path: repository.path,
52
+ status: 'skipped',
53
+ reason: 'missing .github/code-foundry.yml',
54
+ })
44
55
  continue
45
56
  }
46
57
  if (repository.dirty && !options.force) {
@@ -48,12 +59,25 @@ export function upgradeFleet(root, source, options) {
48
59
  continue
49
60
  }
50
61
  if (!options.createPr || options.dryRun) {
51
- const result = syncRepository({ target: repository.path, source, dryRun: options.dryRun, force: false })
52
- report.push({ path: repository.path, status: options.dryRun ? 'preview' : 'synced', changed: result.changed })
62
+ const result = syncRepository({
63
+ target: repository.path,
64
+ source,
65
+ dryRun: options.dryRun,
66
+ force: false,
67
+ })
68
+ report.push({
69
+ path: repository.path,
70
+ status: options.dryRun ? 'preview' : 'synced',
71
+ changed: result.changed,
72
+ })
53
73
  continue
54
74
  }
55
75
  if (!repository.repository) {
56
- report.push({ path: repository.path, status: 'skipped', reason: 'origin is not a GitHub remote' })
76
+ report.push({
77
+ path: repository.path,
78
+ status: 'skipped',
79
+ reason: 'origin is not a GitHub remote',
80
+ })
57
81
  continue
58
82
  }
59
83
  report.push(upgradeRepository(repository, source, options.version))
@@ -68,12 +92,35 @@ function upgradeRepository(repository, source, version) {
68
92
  const base = repository.gitWorkflow === 'staging-release' ? 'staging' : 'main'
69
93
  const temporary = mkdtempSync(join(tmpdir(), 'code-foundry-fleet-'))
70
94
  try {
71
- const fetch = spawnSync('git', ['-C', repository.path, 'fetch', 'origin', base, '--quiet'], { encoding: 'utf8' })
72
- if (fetch.status !== 0) return { path: repository.path, status: 'skipped', reason: fetch.stderr.trim() || `unable to refresh ${base} baseline` }
73
- const baseline = spawnSync('git', ['-C', repository.path, 'rev-parse', `origin/${base}`], { encoding: 'utf8' })
74
- if (baseline.status !== 0) return { path: repository.path, status: 'skipped', reason: `remote ${base} branch is unavailable` }
75
- const add = spawnSync('git', ['-C', repository.path, 'worktree', 'add', '-b', branch, temporary, baseline.stdout.trim()], { encoding: 'utf8' })
76
- if (add.status !== 0) return { path: repository.path, status: 'skipped', reason: add.stderr.trim() || 'unable to create isolated worktree' }
95
+ const fetch = spawnSync('git', ['-C', repository.path, 'fetch', 'origin', base, '--quiet'], {
96
+ encoding: 'utf8',
97
+ })
98
+ if (fetch.status !== 0)
99
+ return {
100
+ path: repository.path,
101
+ status: 'skipped',
102
+ reason: fetch.stderr.trim() || `unable to refresh ${base} baseline`,
103
+ }
104
+ const baseline = spawnSync('git', ['-C', repository.path, 'rev-parse', `origin/${base}`], {
105
+ encoding: 'utf8',
106
+ })
107
+ if (baseline.status !== 0)
108
+ return {
109
+ path: repository.path,
110
+ status: 'skipped',
111
+ reason: `remote ${base} branch is unavailable`,
112
+ }
113
+ const add = spawnSync(
114
+ 'git',
115
+ ['-C', repository.path, 'worktree', 'add', '-b', branch, temporary, baseline.stdout.trim()],
116
+ { encoding: 'utf8' }
117
+ )
118
+ if (add.status !== 0)
119
+ return {
120
+ path: repository.path,
121
+ status: 'skipped',
122
+ reason: add.stderr.trim() || 'unable to create isolated worktree',
123
+ }
77
124
  const result = syncRepository({ target: temporary, source, force: false })
78
125
  const configFile = join(temporary, '.github/code-foundry.yml')
79
126
  if (existsSync(configFile)) {
@@ -83,21 +130,62 @@ function upgradeRepository(repository, source, version) {
83
130
  : `${current.trimEnd()}\nruntime_ref: ${version}\n`
84
131
  if (updated !== current) {
85
132
  writeFileSync(configFile, updated)
86
- if (!result.changed.includes('.github/code-foundry.yml')) result.changed.push('.github/code-foundry.yml')
133
+ if (!result.changed.includes('.github/code-foundry.yml'))
134
+ result.changed.push('.github/code-foundry.yml')
87
135
  }
88
136
  }
89
137
  if (!result.changed.length) return { path: repository.path, status: 'unchanged', branch }
90
138
  spawnSync('git', ['-C', temporary, 'add', '-A'], { stdio: 'ignore' })
91
- const commit = spawnSync('git', ['-C', temporary, 'commit', '-m', `chore(code-foundry): upgrade runtime to ${version}`], { encoding: 'utf8' })
92
- if (commit.status !== 0) return { path: repository.path, status: 'failed', reason: commit.stderr.trim() || 'commit failed' }
93
- const push = spawnSync('git', ['-C', temporary, 'push', '-u', 'origin', branch], { encoding: 'utf8' })
94
- if (push.status !== 0) return { path: repository.path, status: 'failed', reason: push.stderr.trim() || 'push failed' }
95
- const pr = spawnSync('gh', ['pr', 'create', '--repo', repository.repository, '--base', base, '--head', branch, '--title', `chore(code-foundry): upgrade to ${version}`, '--body', `Automated isolated Code Foundry runtime upgrade to ${version}.\n\nThe sync preserved protected repository-owned documents and custom workflows.`], { encoding: 'utf8' })
139
+ const commit = spawnSync(
140
+ 'git',
141
+ ['-C', temporary, 'commit', '-m', `chore(code-foundry): upgrade runtime to ${version}`],
142
+ { encoding: 'utf8' }
143
+ )
144
+ if (commit.status !== 0)
145
+ return {
146
+ path: repository.path,
147
+ status: 'failed',
148
+ reason: commit.stderr.trim() || 'commit failed',
149
+ }
150
+ const push = spawnSync('git', ['-C', temporary, 'push', '-u', 'origin', branch], {
151
+ encoding: 'utf8',
152
+ })
153
+ if (push.status !== 0)
154
+ return {
155
+ path: repository.path,
156
+ status: 'failed',
157
+ reason: push.stderr.trim() || 'push failed',
158
+ }
159
+ const pr = spawnSync(
160
+ 'gh',
161
+ [
162
+ 'pr',
163
+ 'create',
164
+ '--repo',
165
+ repository.repository,
166
+ '--base',
167
+ base,
168
+ '--head',
169
+ branch,
170
+ '--title',
171
+ `chore(code-foundry): upgrade to ${version}`,
172
+ '--body',
173
+ `Automated isolated Code Foundry runtime upgrade to ${version}.\n\nThe sync preserved protected repository-owned documents and custom workflows.`,
174
+ ],
175
+ { encoding: 'utf8' }
176
+ )
96
177
  return pr.status === 0
97
178
  ? { path: repository.path, status: 'pr-created', branch, pullRequest: pr.stdout.trim() }
98
- : { path: repository.path, status: 'failed', branch, reason: pr.stderr.trim() || 'pull request creation failed' }
179
+ : {
180
+ path: repository.path,
181
+ status: 'failed',
182
+ branch,
183
+ reason: pr.stderr.trim() || 'pull request creation failed',
184
+ }
99
185
  } finally {
100
- spawnSync('git', ['-C', repository.path, 'worktree', 'remove', '--force', temporary], { stdio: 'ignore' })
186
+ spawnSync('git', ['-C', repository.path, 'worktree', 'remove', '--force', temporary], {
187
+ stdio: 'ignore',
188
+ })
101
189
  }
102
190
  }
103
191
 
@@ -105,19 +193,34 @@ function upgradeRepository(repository, source, version) {
105
193
  function children(root) {
106
194
  try {
107
195
  return readdirSync(root, { withFileTypes: true })
108
- .filter((entry) => entry.isDirectory() && !entry.name.startsWith('.') && !['node_modules', 'target'].includes(entry.name))
196
+ .filter(
197
+ (entry) =>
198
+ entry.isDirectory() &&
199
+ !entry.name.startsWith('.') &&
200
+ !['node_modules', 'target'].includes(entry.name)
201
+ )
109
202
  .map((entry) => join(root, entry.name))
110
- } catch { return [] }
203
+ } catch {
204
+ return []
205
+ }
111
206
  }
112
207
 
113
208
  /** @param {string} file @returns {Record<string, string>} */
114
209
  function readConfig(file) {
115
210
  try {
116
- return Object.fromEntries(requireText(file).split(/\r?\n/).flatMap((line) => {
117
- const match = line.match(/^([A-Za-z0-9_-]+):\s*(.*?)\s*$/)
118
- return match ? [[match[1], match[2].replace(/\s+#.*$/, '').replace(/^['"]|['"]$/g, '')]] : []
119
- }))
120
- } catch { return {} }
211
+ return Object.fromEntries(
212
+ requireText(file)
213
+ .split(/\r?\n/)
214
+ .flatMap((line) => {
215
+ const match = line.match(/^([A-Za-z0-9_-]+):\s*(.*?)\s*$/)
216
+ return match
217
+ ? [[match[1], match[2].replace(/\s+#.*$/, '').replace(/^['"]|['"]$/g, '')]]
218
+ : []
219
+ })
220
+ )
221
+ } catch {
222
+ return {}
223
+ }
121
224
  }
122
225
 
123
226
  /** @param {string} file */
@@ -126,7 +229,14 @@ function requireText(file) {
126
229
  }
127
230
 
128
231
  /** @param {string} root @param {string[]} args */
129
- function git(root, args) { return spawnSync('git', args, { cwd: root, encoding: 'utf8' }).stdout?.trim() ?? '' }
232
+ function git(root, args) {
233
+ return spawnSync('git', args, { cwd: root, encoding: 'utf8' }).stdout?.trim() ?? ''
234
+ }
130
235
 
131
236
  /** @param {string} remote */
132
- function normalizeRemote(remote) { return remote.replace(/^git@github\.com:/, '').replace(/^https?:\/\/github\.com\//, '').replace(/\.git$/, '') }
237
+ function normalizeRemote(remote) {
238
+ return remote
239
+ .replace(/^git@github\.com:/, '')
240
+ .replace(/^https?:\/\/github\.com\//, '')
241
+ .replace(/\.git$/, '')
242
+ }