@brickflow/cli 0.0.8 → 0.0.10

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @brickflow/cli
2
2
 
3
+ ## 0.0.10
4
+
5
+ ### Patch Changes
6
+
7
+ - Remove cli upgrade
8
+
9
+ ## 0.0.9
10
+
11
+ ### Patch Changes
12
+
13
+ - fix entry cd
14
+
3
15
  ## 0.0.8
4
16
 
5
17
  ### Patch Changes
package/index.mjs CHANGED
@@ -10,10 +10,24 @@ const commandsDir = path.join(cliDir, 'src')
10
10
  async function getAvailableCommands() {
11
11
  const entries = await readdir(commandsDir, { withFileTypes: true })
12
12
 
13
- return entries
14
- .filter((entry) => entry.isDirectory())
15
- .map((entry) => entry.name)
16
- .sort()
13
+ const commands = await Promise.all(
14
+ entries
15
+ .filter((entry) => entry.isDirectory())
16
+ .map(async (entry) => ((await hasCommandEntry(entry.name)) ? entry.name : null)),
17
+ )
18
+
19
+ return commands.filter(Boolean).sort()
20
+ }
21
+
22
+ async function hasCommandEntry(command) {
23
+ const commandEntry = path.join(commandsDir, command, 'index.js')
24
+
25
+ try {
26
+ await access(commandEntry)
27
+ return true
28
+ } catch {
29
+ return false
30
+ }
17
31
  }
18
32
 
19
33
  function isValidCommandName(command) {
@@ -45,14 +59,11 @@ async function runCommand(command) {
45
59
  return false
46
60
  }
47
61
 
48
- const commandEntry = path.join(commandsDir, command, 'index.js')
49
-
50
- try {
51
- await access(commandEntry)
52
- } catch {
62
+ if (!(await hasCommandEntry(command))) {
53
63
  return false
54
64
  }
55
65
 
66
+ const commandEntry = path.join(commandsDir, command, 'index.js')
56
67
  await import(pathToFileURL(commandEntry).href)
57
68
  return true
58
69
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brickflow/cli",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "index.mjs",
@@ -1,28 +1,36 @@
1
1
  import { exec } from 'child_process'
2
2
  import fs from 'fs'
3
3
  import { globSync } from 'glob'
4
- import path from 'path'
5
- import { fileURLToPath } from 'url'
4
+
5
+ import { resolveWorkspaceRoot } from '../shared/workspace-root.js'
6
6
 
7
7
  const args = process.argv.slice(3)
8
+ const filters = args.filter((arg) => arg !== '--help' && arg !== '-h')
8
9
 
9
10
  if (args.includes('--help') || args.includes('-h')) {
10
11
  printHelp()
11
12
  process.exit(0)
12
13
  }
13
14
 
14
- if (args.length > 0) {
15
+ if (filters.some((filter) => !isValidFilter(filter))) {
15
16
  printHelp()
16
17
  process.exit(1)
17
18
  }
18
19
 
19
- const currentDir = path.dirname(fileURLToPath(import.meta.url))
20
- const workspaceRoot = path.resolve(currentDir, './')
20
+ const workspaceRoot = resolveWorkspaceRoot()
21
21
  const packageJsonPaths = globSync('**/package.json', {
22
22
  absolute: true,
23
23
  cwd: workspaceRoot,
24
24
  ignore: ['**/node_modules/**'],
25
25
  }).sort()
26
+ const latestVersions = await readLatestVersions(packageJsonPaths, filters)
27
+
28
+ if (latestVersions.size === 0) {
29
+ const scopeLabel = filters.length > 0 ? ` matching "${filters.join('", "')}"` : ''
30
+
31
+ console.log(`No packages found${scopeLabel}`)
32
+ process.exit(0)
33
+ }
26
34
 
27
35
  console.log('\x1b[32m', '\rPackage update')
28
36
 
@@ -37,7 +45,7 @@ for await (const packageJsonPath of packageJsonPaths) {
37
45
  }),
38
46
  )
39
47
 
40
- await updateJson(packageJson)
48
+ updateJson(packageJson, latestVersions)
41
49
  fs.writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`)
42
50
 
43
51
  const percent = Math.round((index * 100) / packageJsonPaths.length)
@@ -47,19 +55,66 @@ for await (const packageJsonPath of packageJsonPaths) {
47
55
  process.stdout.write('\n')
48
56
  console.log('\x1b[0m', '')
49
57
 
58
+ function collectPackageNames(filePaths, packageFilters) {
59
+ const packageNames = new Set()
60
+
61
+ for (const filePath of filePaths) {
62
+ const packageJson = JSON.parse(fs.readFileSync(filePath, 'utf-8'))
63
+
64
+ for (const [dependencyName, dependencyVersion] of Object.entries(packageJson.dependencies || {})) {
65
+ if (shouldUpdateDependency(dependencyName, dependencyVersion, packageFilters)) {
66
+ packageNames.add(dependencyName)
67
+ }
68
+ }
69
+
70
+ for (const [dependencyName, dependencyVersion] of Object.entries(packageJson.devDependencies || {})) {
71
+ if (shouldUpdateDependency(dependencyName, dependencyVersion, packageFilters)) {
72
+ packageNames.add(dependencyName)
73
+ }
74
+ }
75
+ }
76
+
77
+ return [...packageNames].sort()
78
+ }
79
+
80
+ function escapeRegex(value) {
81
+ return value.replace(/[|\\{}()[\]^$+?.]/g, '\\$&')
82
+ }
83
+
84
+ function isValidFilter(value) {
85
+ return typeof value === 'string' && value.length > 0
86
+ }
87
+
50
88
  function isWorkspaceVersion(value) {
51
89
  return value === 'workspace:*' || value === 'workspace: *'
52
90
  }
53
91
 
92
+ function matchesAnyFilter(packageName, packageFilters) {
93
+ if (packageFilters.length === 0) {
94
+ return true
95
+ }
96
+
97
+ return packageFilters.some((filter) => matchesFilter(packageName, filter))
98
+ }
99
+
100
+ function matchesFilter(packageName, filter) {
101
+ const pattern = escapeRegex(filter).replace(/\*/g, '.*').replace(/\?/g, '.')
102
+
103
+ return new RegExp(`^${pattern}$`).test(packageName)
104
+ }
105
+
54
106
  function printHelp() {
55
107
  console.log(`brick latest
56
108
 
57
109
  Usage:
58
110
  brick latest
111
+ brick latest "@brickflow/*"
112
+ brick latest "vue" "@brickflow/*"
59
113
 
60
114
  Notes:
61
115
  Scans all package.json files in the repository, including the root one
62
116
  Updates dependencies and devDependencies to the latest npm versions
117
+ Optional wildcard filters limit which package names are updated
63
118
  Skips workspace:* versions`)
64
119
  }
65
120
 
@@ -77,29 +132,45 @@ function readLatestVersion(packageName) {
77
132
  })
78
133
  }
79
134
 
80
- async function updateJson(jsonData) {
81
- await Promise.all([
82
- ...Object.entries(jsonData.dependencies || {}).map(async ([key, value]) => {
83
- if (isWorkspaceVersion(value)) {
84
- return
85
- }
135
+ async function readLatestVersions(filePaths, packageFilters) {
136
+ const packageNames = collectPackageNames(filePaths, packageFilters)
137
+ const versionsMap = new Map()
86
138
 
87
- const version = await readLatestVersion(key)
139
+ await Promise.all(
140
+ packageNames.map(async (packageName) => {
141
+ const version = await readLatestVersion(packageName)
88
142
 
89
143
  if (version) {
90
- jsonData.dependencies[key] = version
144
+ versionsMap.set(packageName, version)
91
145
  }
92
146
  }),
93
- ...Object.entries(jsonData.devDependencies || {}).map(async ([key, value]) => {
94
- if (isWorkspaceVersion(value)) {
95
- return
96
- }
147
+ )
97
148
 
98
- const version = await readLatestVersion(key)
149
+ return versionsMap
150
+ }
99
151
 
100
- if (version) {
101
- jsonData.devDependencies[key] = version
102
- }
103
- }),
104
- ])
152
+ function shouldUpdateDependency(packageName, version, packageFilters) {
153
+ return !isWorkspaceVersion(version) && matchesAnyFilter(packageName, packageFilters)
154
+ }
155
+
156
+ function updateJson(jsonData, versionsMap) {
157
+ for (const [dependencyName, version] of Object.entries(jsonData.dependencies || {})) {
158
+ if (isWorkspaceVersion(version)) {
159
+ continue
160
+ }
161
+
162
+ if (versionsMap.has(dependencyName)) {
163
+ jsonData.dependencies[dependencyName] = versionsMap.get(dependencyName)
164
+ }
165
+ }
166
+
167
+ for (const [dependencyName, version] of Object.entries(jsonData.devDependencies || {})) {
168
+ if (isWorkspaceVersion(version)) {
169
+ continue
170
+ }
171
+
172
+ if (versionsMap.has(dependencyName)) {
173
+ jsonData.devDependencies[dependencyName] = versionsMap.get(dependencyName)
174
+ }
175
+ }
105
176
  }
@@ -0,0 +1,31 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+
4
+ const WORKSPACE_MARKERS = ['.git', 'pnpm-workspace.yaml', 'lerna.json', 'turbo.json']
5
+
6
+ export function resolveWorkspaceRoot(startDir = process.cwd()) {
7
+ let currentDir = path.resolve(startDir)
8
+ let packageRoot = null
9
+
10
+ while (true) {
11
+ if (hasAnyFile(currentDir, WORKSPACE_MARKERS)) {
12
+ return currentDir
13
+ }
14
+
15
+ if (fs.existsSync(path.join(currentDir, 'package.json'))) {
16
+ packageRoot = currentDir
17
+ }
18
+
19
+ const parentDir = path.dirname(currentDir)
20
+
21
+ if (parentDir === currentDir) {
22
+ return packageRoot || startDir
23
+ }
24
+
25
+ currentDir = parentDir
26
+ }
27
+ }
28
+
29
+ function hasAnyFile(directoryPath, fileNames) {
30
+ return fileNames.some((fileName) => fs.existsSync(path.join(directoryPath, fileName)))
31
+ }
@@ -20,6 +20,10 @@ const CONTEXT_FILE_NAME = 'ai-context.json'
20
20
  const CHANGE_THRESHOLD = readFloat('TRANSLATE_CONTEXT_MIN_CHANGE', 0.3)
21
21
  const SOURCE_MAX_CHARS = readPositiveInt('TRANSLATE_CONTEXT_SOURCE_MAX_CHARS', 16000)
22
22
 
23
+ export function buildAiContextHelp(command = 'brick translate-context') {
24
+ return `${buildTranslateHelp(command)}\n\nExtra:\n --force`
25
+ }
26
+
23
27
  export function calculateChangeRatio(previousSource, nextSource) {
24
28
  const before = normalizeSource(previousSource)
25
29
  const after = normalizeSource(nextSource)
@@ -112,6 +116,52 @@ export function getContextFilePath(samplePath) {
112
116
  return join(dirname(samplePath), CONTEXT_FILE_NAME)
113
117
  }
114
118
 
119
+ export async function runAiContextCli(rawArgs = process.argv.slice(3), command = 'brick translate-context') {
120
+ const helpText = buildAiContextHelp(command)
121
+
122
+ if (rawArgs.includes('--help') || rawArgs.includes('-h')) {
123
+ console.log(helpText)
124
+ process.exit(0)
125
+ }
126
+
127
+ const force = rawArgs.includes('--force')
128
+ const filteredArgs = rawArgs.filter((arg) => arg !== '--force')
129
+ const { options, positional } = parseTranslateRuntimeArgs(filteredArgs)
130
+
131
+ try {
132
+ setTranslateRuntimeConfig(options)
133
+ } catch (error) {
134
+ console.error(error instanceof Error ? error.message : String(error))
135
+ console.error('')
136
+ console.error(helpText)
137
+ process.exit(1)
138
+ }
139
+
140
+ const targets = listTranslationTargets(workspaceRoot)
141
+ const requestedSamplePaths =
142
+ positional.length > 0 ? new Set(positional.map((samplePath) => resolve(workspaceRoot, samplePath))) : null
143
+ const targetEntries = requestedSamplePaths
144
+ ? targets.filter(({ samplePath }) => requestedSamplePaths.has(samplePath))
145
+ : targets
146
+
147
+ for (const { samplePath, sourceFilePath } of targetEntries) {
148
+ if (!sourceFilePath || !fs.existsSync(samplePath)) {
149
+ continue
150
+ }
151
+
152
+ const description = await ensureAiContext({
153
+ force,
154
+ sample: JSON.parse(fs.readFileSync(samplePath, 'utf-8')),
155
+ samplePath,
156
+ sourceFilePath,
157
+ })
158
+
159
+ if (description) {
160
+ console.log(`🧠 Context updated: ${relative(workspaceRoot, samplePath)}`)
161
+ }
162
+ }
163
+ }
164
+
115
165
  function buildContextContents({ sampleEntries, samplePath, sourceCode, sourceFilePath }) {
116
166
  return [
117
167
  `Sample path: ${samplePath}`,
@@ -251,56 +301,6 @@ function readPositiveInt(name, fallback) {
251
301
  return Number.isFinite(value) && value > 0 ? value : fallback
252
302
  }
253
303
 
254
- export function buildAiContextHelp(command = 'brick translate-context') {
255
- return `${buildTranslateHelp(command)}\n\nExtra:\n --force`
256
- }
257
-
258
- export async function runAiContextCli(rawArgs = process.argv.slice(3), command = 'brick translate-context') {
259
- const helpText = buildAiContextHelp(command)
260
-
261
- if (rawArgs.includes('--help') || rawArgs.includes('-h')) {
262
- console.log(helpText)
263
- process.exit(0)
264
- }
265
-
266
- const force = rawArgs.includes('--force')
267
- const filteredArgs = rawArgs.filter((arg) => arg !== '--force')
268
- const { options, positional } = parseTranslateRuntimeArgs(filteredArgs)
269
-
270
- try {
271
- setTranslateRuntimeConfig(options)
272
- } catch (error) {
273
- console.error(error instanceof Error ? error.message : String(error))
274
- console.error('')
275
- console.error(helpText)
276
- process.exit(1)
277
- }
278
-
279
- const targets = listTranslationTargets(workspaceRoot)
280
- const requestedSamplePaths =
281
- positional.length > 0 ? new Set(positional.map((samplePath) => resolve(workspaceRoot, samplePath))) : null
282
- const targetEntries = requestedSamplePaths
283
- ? targets.filter(({ samplePath }) => requestedSamplePaths.has(samplePath))
284
- : targets
285
-
286
- for (const { samplePath, sourceFilePath } of targetEntries) {
287
- if (!sourceFilePath || !fs.existsSync(samplePath)) {
288
- continue
289
- }
290
-
291
- const description = await ensureAiContext({
292
- force,
293
- sample: JSON.parse(fs.readFileSync(samplePath, 'utf-8')),
294
- samplePath,
295
- sourceFilePath,
296
- })
297
-
298
- if (description) {
299
- console.log(`🧠 Context updated: ${relative(workspaceRoot, samplePath)}`)
300
- }
301
- }
302
- }
303
-
304
304
  function writeTextPreservingEol(filePath, content) {
305
305
  const eol = detectEol(filePath)
306
306
  const normalized = String(content).replace(/\r?\n/g, eol)
@@ -235,28 +235,6 @@ export function getTranslationPaths(id) {
235
235
  }
236
236
  }
237
237
 
238
- export function listWorkspaceFiles(workspaceRoot) {
239
- try {
240
- const output = execFileSync('git', ['ls-files', '-co', '--exclude-standard', '-z'], {
241
- cwd: workspaceRoot,
242
- encoding: 'utf-8',
243
- })
244
-
245
- return output
246
- .split('\0')
247
- .filter(Boolean)
248
- .map((file) => resolve(workspaceRoot, file))
249
- .filter((filePath) => existsSync(filePath))
250
- } catch {
251
- return globSync('**/*', {
252
- absolute: true,
253
- cwd: workspaceRoot,
254
- ignore: IGNORED_GLOB_PATTERNS,
255
- nodir: true,
256
- })
257
- }
258
- }
259
-
260
238
  export function listTranslationSamplePaths(workspaceRoot) {
261
239
  return globSync(TRANSLATION_SAMPLE_GLOB, {
262
240
  absolute: true,
@@ -286,6 +264,28 @@ export function listTranslationTargets(workspaceRoot) {
286
264
  }))
287
265
  }
288
266
 
267
+ export function listWorkspaceFiles(workspaceRoot) {
268
+ try {
269
+ const output = execFileSync('git', ['ls-files', '-co', '--exclude-standard', '-z'], {
270
+ cwd: workspaceRoot,
271
+ encoding: 'utf-8',
272
+ })
273
+
274
+ return output
275
+ .split('\0')
276
+ .filter(Boolean)
277
+ .map((file) => resolve(workspaceRoot, file))
278
+ .filter((filePath) => existsSync(filePath))
279
+ } catch {
280
+ return globSync('**/*', {
281
+ absolute: true,
282
+ cwd: workspaceRoot,
283
+ ignore: IGNORED_GLOB_PATTERNS,
284
+ nodir: true,
285
+ })
286
+ }
287
+ }
288
+
289
289
  export function normalize(input) {
290
290
  return input.trim().toLowerCase().replace(/\s+/g, ' ')
291
291
  }
@@ -1,168 +0,0 @@
1
- import { exec } from 'child_process'
2
- import fs from 'fs'
3
- import path from 'path'
4
- import { fileURLToPath } from 'url'
5
-
6
- const args = process.argv.slice(3)
7
-
8
- if (args.includes('--help') || args.includes('-h')) {
9
- printHelp()
10
- process.exit(0)
11
- }
12
-
13
- if (args.length > 0) {
14
- printHelp()
15
- process.exit(1)
16
- }
17
-
18
- const currentDir = path.dirname(fileURLToPath(import.meta.url))
19
- const workspaceRoot = path.resolve(currentDir, '../../../..')
20
- const packageJsonPaths = findPackageJsonPaths()
21
- const localPackageNames = collectLocalBrickflowPackageNames(packageJsonPaths)
22
-
23
- if (localPackageNames.length === 0) {
24
- console.log('No local @brickflow/* packages found in the repository')
25
- process.exit(0)
26
- }
27
-
28
- const latestVersions = new Map()
29
-
30
- for (const packageName of localPackageNames) {
31
- const version = await readLatestVersion(packageName)
32
-
33
- if (version) {
34
- latestVersions.set(packageName, version)
35
- }
36
- }
37
-
38
- if (latestVersions.size === 0) {
39
- throw new Error('Could not resolve latest versions for local @brickflow/* packages')
40
- }
41
-
42
- console.log('\x1b[32m', '\rBrickflow package upgrade')
43
-
44
- let changedFiles = 0
45
- let index = 0
46
-
47
- for (const packageJsonPath of packageJsonPaths) {
48
- index += 1
49
-
50
- const packageJson = readJson(packageJsonPath)
51
- const before = JSON.stringify(packageJson)
52
-
53
- updateDependencySections(packageJson, latestVersions)
54
-
55
- if (JSON.stringify(packageJson) !== before) {
56
- writeJson(packageJsonPath, packageJson)
57
- changedFiles += 1
58
- console.log(`\nšŸ“¦ Updated ${getPackageLabel(packageJsonPath)}`)
59
- }
60
-
61
- const percent = Math.round((index * 100) / packageJsonPaths.length)
62
- process.stdout.write(`\ršŸ”© Upgrade: [${'ā–ˆ'.repeat(Math.round(percent / 10)).padEnd(10, ' ')}] ${percent}%`)
63
- }
64
-
65
- process.stdout.write('\n')
66
- console.log(`Updated ${changedFiles} package.json file(s)`)
67
- console.log('\x1b[0m', '')
68
-
69
- function collectLocalBrickflowPackageNames(filePaths) {
70
- return filePaths
71
- .map((filePath) => readJson(filePath).name)
72
- .filter((packageName) => isBrickflowPackageName(packageName))
73
- .sort()
74
- }
75
-
76
- function findPackageJsonPaths() {
77
- return walkDirectoryForPackageJson(workspaceRoot).sort()
78
- }
79
-
80
- function getDependencySections(packageJson) {
81
- return ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']
82
- .map((sectionName) => ({
83
- name: sectionName,
84
- value: packageJson[sectionName],
85
- }))
86
- .filter((section) => section.value && typeof section.value === 'object')
87
- }
88
-
89
- function getPackageLabel(packageJsonPath) {
90
- const relativePath = path.relative(workspaceRoot, packageJsonPath).replace(/\\/g, '/')
91
- const packageDir = path.dirname(relativePath)
92
-
93
- return packageDir === '.' ? 'root' : packageDir
94
- }
95
-
96
- function isBrickflowPackageName(packageName) {
97
- return typeof packageName === 'string' && packageName.startsWith('@brickflow/')
98
- }
99
-
100
- function printHelp() {
101
- console.log(`brick upgrade
102
-
103
- Usage:
104
- brick upgrade
105
-
106
- Notes:
107
- Finds local @brickflow/* package names from package.json files in the repository
108
- Fetches the latest published version for each of those packages
109
- Updates matching dependencies, devDependencies, peerDependencies and optionalDependencies in all package.json files`)
110
- }
111
-
112
- function readJson(filePath) {
113
- return JSON.parse(fs.readFileSync(filePath, 'utf8'))
114
- }
115
-
116
- function readLatestVersion(packageName) {
117
- return new Promise((resolve) => {
118
- exec(`npm view ${packageName} version`, (error, stdout) => {
119
- if (error) {
120
- console.warn(`Skipping ${packageName}: ${error.message}`)
121
- resolve(null)
122
- return
123
- }
124
-
125
- resolve(String(stdout).trim().replace('\n', ''))
126
- })
127
- })
128
- }
129
-
130
- function updateDependencySections(packageJson, latestVersionsMap) {
131
- for (const section of getDependencySections(packageJson)) {
132
- for (const dependencyName of Object.keys(section.value)) {
133
- const latestVersion = latestVersionsMap.get(dependencyName)
134
-
135
- if (latestVersion) {
136
- section.value[dependencyName] = latestVersion
137
- }
138
- }
139
- }
140
- }
141
-
142
- function writeJson(filePath, value) {
143
- fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`)
144
- }
145
-
146
- function walkDirectoryForPackageJson(directoryPath) {
147
- const entries = fs.readdirSync(directoryPath, { withFileTypes: true })
148
- const filePaths = []
149
-
150
- for (const entry of entries) {
151
- if (entry.name === 'node_modules') {
152
- continue
153
- }
154
-
155
- const entryPath = path.join(directoryPath, entry.name)
156
-
157
- if (entry.isDirectory()) {
158
- filePaths.push(...walkDirectoryForPackageJson(entryPath))
159
- continue
160
- }
161
-
162
- if (entry.isFile() && entry.name === 'package.json') {
163
- filePaths.push(entryPath)
164
- }
165
- }
166
-
167
- return filePaths
168
- }