@goliapkg/sentori-cli 1.6.0 → 1.8.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/README.md +65 -7
- package/lib/apps.d.ts +24 -0
- package/lib/apps.d.ts.map +1 -0
- package/lib/apps.js +17 -0
- package/lib/apps.js.map +1 -0
- package/lib/index.js +58 -5
- package/lib/index.js.map +1 -1
- package/lib/issue.d.ts +4 -0
- package/lib/issue.d.ts.map +1 -1
- package/lib/issue.js +8 -0
- package/lib/issue.js.map +1 -1
- package/lib/web.d.ts +3 -0
- package/lib/web.d.ts.map +1 -0
- package/lib/web.js +121 -0
- package/lib/web.js.map +1 -0
- package/lib/workflow.d.ts +2 -0
- package/lib/workflow.d.ts.map +1 -1
- package/lib/workflow.js +16 -2
- package/lib/workflow.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/apps.test.ts +48 -0
- package/src/__tests__/web.test.ts +48 -0
- package/src/__tests__/workflow.test.ts +36 -1
- package/src/apps.ts +31 -0
- package/src/index.ts +44 -5
- package/src/issue.ts +5 -1
- package/src/web.ts +88 -0
- package/src/workflow.ts +17 -2
package/src/workflow.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { existsSync, lstatSync, mkdirSync, readFileSync, writeFileSync } from 'n
|
|
|
3
3
|
import { basename, dirname, resolve } from 'node:path'
|
|
4
4
|
import { gunzipSync } from 'node:zlib'
|
|
5
5
|
import { parseArgs } from 'node:util'
|
|
6
|
+
import { showProject } from './apps.js'
|
|
6
7
|
import { fetchArtifacts } from './artifacts.js'
|
|
7
8
|
import { checkCompatibility } from './compatibility.js'
|
|
8
9
|
import { dwarfBinariesIn } from './native-artifacts.js'
|
|
@@ -14,6 +15,7 @@ export type UploadConfig = {
|
|
|
14
15
|
version: 1
|
|
15
16
|
apiUrl: string
|
|
16
17
|
tokenEnv: string
|
|
18
|
+
projectId?: string
|
|
17
19
|
release?: string
|
|
18
20
|
artifacts: { sourcemap?: string[]; dsym?: string[]; mapping?: string[] }
|
|
19
21
|
sources?: { root?: string; include: string[]; exclude?: string[]; gitTag?: string }
|
|
@@ -23,6 +25,7 @@ export type UploadPlan = {
|
|
|
23
25
|
release: string
|
|
24
26
|
apiUrl: string
|
|
25
27
|
tokenEnv: string
|
|
28
|
+
projectId?: string
|
|
26
29
|
files: { kind: UploadOpts['kind']; name: string; path: string; hash: string; bytes: number }[]
|
|
27
30
|
source?: { files: string[]; tag?: string; commit?: string }
|
|
28
31
|
}
|
|
@@ -58,12 +61,13 @@ export function loadUploadConfig(path: string): UploadConfig {
|
|
|
58
61
|
const value: unknown = JSON.parse(readFileSync(path, 'utf8'))
|
|
59
62
|
if (!object(value) || value.version !== 1 || typeof value.apiUrl !== 'string' || !object(value.artifacts)) throw new Error('config requires version: 1, apiUrl, and artifacts; run sentori init')
|
|
60
63
|
if (Object.hasOwn(value, 'token')) throw new Error('store the API token in the environment and set tokenEnv, not token in the config')
|
|
61
|
-
const allowed = new Set(['version', 'apiUrl', 'tokenEnv', 'release', 'artifacts', 'sources', 'sourceArchive'])
|
|
64
|
+
const allowed = new Set(['version', 'apiUrl', 'tokenEnv', 'projectId', 'release', 'artifacts', 'sources', 'sourceArchive'])
|
|
62
65
|
for (const key of Object.keys(value)) if (!allowed.has(key)) throw new Error(`unknown config field: ${key}`)
|
|
63
66
|
for (const [kind, paths] of Object.entries(value.artifacts)) {
|
|
64
67
|
if (!['sourcemap', 'dsym', 'mapping'].includes(kind)) throw new Error(`unknown artifact kind ${kind}; use sourcemap, dsym, or mapping`)
|
|
65
68
|
strings(paths, `artifacts.${kind}`)
|
|
66
69
|
}
|
|
70
|
+
if (value.projectId !== undefined && (typeof value.projectId !== 'string' || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value.projectId))) throw new Error('projectId must be the expected Project UUID')
|
|
67
71
|
if (value.release !== undefined && (typeof value.release !== 'string' || !value.release.trim())) throw new Error('release must be a non-empty string')
|
|
68
72
|
if (value.sourceArchive !== undefined && typeof value.sourceArchive !== 'string') throw new Error('sourceArchive must be a ZIP path')
|
|
69
73
|
if (value.sources !== undefined) {
|
|
@@ -97,7 +101,7 @@ export function prepareUploadPlan(configPath: string, overrides: { release?: str
|
|
|
97
101
|
const base = dirname(resolve(configPath))
|
|
98
102
|
const release = overrides.release ?? process.env.SENTORI_RELEASE ?? config.release
|
|
99
103
|
if (!release?.trim()) throw new Error('set --release, SENTORI_RELEASE, or config.release to the exact SDK release')
|
|
100
|
-
const plan: UploadPlan = { release, apiUrl: apiUrl(overrides.apiUrl ?? process.env.SENTORI_API_URL ?? config.apiUrl), tokenEnv: config.tokenEnv, files: [] }
|
|
104
|
+
const plan: UploadPlan = { release, apiUrl: apiUrl(overrides.apiUrl ?? process.env.SENTORI_API_URL ?? config.apiUrl), tokenEnv: config.tokenEnv, ...(config.projectId ? { projectId: config.projectId } : {}), files: [] }
|
|
101
105
|
const add = (kind: UploadOpts['kind'], path: string, name = basename(path)): void => {
|
|
102
106
|
let bytes = readFileSync(path)
|
|
103
107
|
if (bytes[0] === 0x1f && bytes[1] === 0x8b) { bytes = gunzipSync(bytes, { maxOutputLength: 512 * 1024 * 1024 }); name = name.replace(/\.gz$/, '') }
|
|
@@ -183,6 +187,7 @@ export async function runWorkflow(command: 'init' | 'validate' | 'upload' | 'ver
|
|
|
183
187
|
const config = loadUploadConfig(configPath)
|
|
184
188
|
const token = process.env[config.tokenEnv]
|
|
185
189
|
if (!token) throw new Error(`set ${config.tokenEnv} to an API-scope token`)
|
|
190
|
+
await verifyProject({ apiUrl: apiUrl(parsed.values['api-url'] ?? process.env.SENTORI_API_URL ?? config.apiUrl), projectId: config.projectId }, token)
|
|
186
191
|
const result = await fetchArtifacts({
|
|
187
192
|
apiUrl: apiUrl(parsed.values['api-url'] ?? process.env.SENTORI_API_URL ?? config.apiUrl), token,
|
|
188
193
|
release: parsed.values.release ?? process.env.SENTORI_RELEASE ?? config.release ?? '__sentori_compatibility_check__',
|
|
@@ -196,6 +201,7 @@ export async function runWorkflow(command: 'init' | 'validate' | 'upload' | 'ver
|
|
|
196
201
|
if (command === 'validate' || parsed.values['dry-run']) { console.log('Local files checked; no upload or server verification performed.'); return 0 }
|
|
197
202
|
const token = process.env[plan.tokenEnv]
|
|
198
203
|
if (!token) throw new Error(`set ${plan.tokenEnv} to an API-scope token`)
|
|
204
|
+
await verifyProject(plan, token)
|
|
199
205
|
// Check credentials before the first write. An unknown release is allowed.
|
|
200
206
|
console.log(checkCompatibility(await fetchArtifacts({ ...plan, token }), plan.files.some(file => file.kind === 'srcbundle')))
|
|
201
207
|
const failures: string[] = []
|
|
@@ -238,3 +244,12 @@ export async function runSourcesPack(argv: string[]): Promise<number> {
|
|
|
238
244
|
return 0
|
|
239
245
|
} catch (error) { console.error(`[sentori] ${error instanceof Error ? error.message : String(error)}`); return 1 }
|
|
240
246
|
}
|
|
247
|
+
|
|
248
|
+
async function verifyProject(config: { apiUrl: string; projectId?: string }, token: string): Promise<void> {
|
|
249
|
+
if (!config.projectId) return
|
|
250
|
+
const { project } = await showProject({ apiUrl: config.apiUrl, token })
|
|
251
|
+
if (project.id.toLowerCase() !== config.projectId.toLowerCase()) {
|
|
252
|
+
throw new Error(`Project mismatch: expected ${config.projectId}, token belongs to ${project.id}. No files uploaded.`)
|
|
253
|
+
}
|
|
254
|
+
console.log(`Project verified: ${project.name} (${project.id})`)
|
|
255
|
+
}
|