@ossy/cli 1.16.10 → 1.17.3

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.
@@ -1,210 +0,0 @@
1
- import { readdir, stat, readFile } from 'fs/promises'
2
- import { existsSync } from 'fs'
3
- import path from 'path'
4
- import { logInfo } from '../log.js'
5
- import { readPublishFieldsFromWebsiteConfig } from './load-website-config.js'
6
- import {
7
- normalizeAuthorizationToken,
8
- requireCmsAuthentication,
9
- resolveApiBaseUrlForUpload,
10
- } from '../cms/upload-resource-templates.js'
11
-
12
- const MAX_FILES = 200
13
-
14
- /** @type {Record<string, string>} */
15
- const MIME_BY_EXT = {
16
- '.js': 'application/javascript',
17
- '.mjs': 'application/javascript',
18
- '.cjs': 'application/javascript',
19
- '.css': 'text/css',
20
- '.html': 'text/html',
21
- '.htm': 'text/html',
22
- '.json': 'application/json',
23
- '.map': 'application/json',
24
- '.svg': 'image/svg+xml',
25
- '.png': 'image/png',
26
- '.jpg': 'image/jpeg',
27
- '.jpeg': 'image/jpeg',
28
- '.gif': 'image/gif',
29
- '.webp': 'image/webp',
30
- '.ico': 'image/x-icon',
31
- '.woff': 'font/woff',
32
- '.woff2': 'font/woff2',
33
- '.ttf': 'font/ttf',
34
- '.txt': 'text/plain',
35
- '.xml': 'application/xml',
36
- '.webmanifest': 'application/manifest+json',
37
- }
38
-
39
- export function guessContentType (filePath) {
40
- const ext = path.extname(filePath).toLowerCase()
41
- return MIME_BY_EXT[ext] || 'application/octet-stream'
42
- }
43
-
44
- /**
45
- * @param {string} buildDir absolute path to `build/` output
46
- * @returns {Promise<{ absPath: string, relativePath: string, size: number }[]>}
47
- */
48
- export async function collectBuildFiles (buildDir) {
49
- const out = []
50
-
51
- async function walk (dir, rel = '') {
52
- const entries = await readdir(dir, { withFileTypes: true })
53
- for (const e of entries) {
54
- const abs = path.join(dir, e.name)
55
- const relPath = rel ? `${rel}/${e.name}` : e.name
56
- if (e.isDirectory()) {
57
- await walk(abs, relPath)
58
- } else {
59
- const st = await stat(abs)
60
- out.push({
61
- absPath: abs,
62
- relativePath: relPath.split(path.sep).join('/'),
63
- size: st.size,
64
- })
65
- }
66
- }
67
- }
68
-
69
- await walk(buildDir)
70
- return out
71
- }
72
-
73
- function workspaceHeaders (token, workspaceId) {
74
- return {
75
- Authorization: normalizeAuthorizationToken(token),
76
- 'Content-Type': 'application/json',
77
- workspaceId,
78
- }
79
- }
80
-
81
- /**
82
- * After deploy: upload `build/` to S3 via presigned URLs and commit a CMS resource (`@ossy/platform/site-artifact-batch`).
83
- */
84
- export async function maybeUploadSiteArtifactsAfterPublish ({
85
- configPath,
86
- cmsToken,
87
- apiUrlFlag,
88
- buildDir: buildDirOpt,
89
- }) {
90
- const config = readPublishFieldsFromWebsiteConfig(configPath)
91
- const workspaceId = config.workspaceId
92
-
93
- if (!workspaceId) {
94
- logInfo({
95
- message:
96
- '[@ossy/cli] publish: skipping site artifacts (no workspaceId in config)',
97
- })
98
- return
99
- }
100
-
101
- const packageRoot = path.join(path.dirname(configPath), '..')
102
- const buildDir = buildDirOpt
103
- ? path.resolve(buildDirOpt)
104
- : path.join(packageRoot, 'build')
105
-
106
- if (!existsSync(buildDir)) {
107
- logInfo({
108
- message: `[@ossy/cli] publish: skipping site artifacts (no build at ${buildDir}; run npm run build first)`,
109
- })
110
- return
111
- }
112
-
113
- const apiBaseUrl = resolveApiBaseUrlForUpload({
114
- flag: apiUrlFlag,
115
- envVar: process.env.OSSY_API_URL,
116
- configApiUrl: config.apiUrl,
117
- })
118
-
119
- const files = await collectBuildFiles(buildDir)
120
- if (files.length === 0) {
121
- logInfo({ message: '[@ossy/cli] publish: skipping site artifacts (build directory is empty)' })
122
- return
123
- }
124
- if (files.length > MAX_FILES) {
125
- throw new Error(
126
- `[@ossy/cli] publish: site artifact upload supports at most ${MAX_FILES} files; build has ${files.length}`
127
- )
128
- }
129
-
130
- const authToken = requireCmsAuthentication(
131
- cmsToken,
132
- 'Site artifact upload'
133
- )
134
-
135
- logInfo({ message: `[@ossy/cli] publish: uploading ${files.length} site artifact file(s) to CMS…` })
136
-
137
- const presignRes = await fetch(`${apiBaseUrl}/site-artifacts/presign-batch`, {
138
- method: 'POST',
139
- headers: workspaceHeaders(authToken, workspaceId),
140
- body: JSON.stringify({
141
- files: files.map((f) => ({
142
- path: f.relativePath,
143
- contentType: guessContentType(f.relativePath),
144
- contentLength: f.size,
145
- })),
146
- }),
147
- })
148
-
149
- if (!presignRes.ok) {
150
- const text = await presignRes.text().catch(() => '')
151
- throw new Error(
152
- `Site artifact presign failed: HTTP ${presignRes.status}${text ? ` — ${text.slice(0, 300)}` : ''}`
153
- )
154
- }
155
-
156
- /** @type {{ batchId: string, files: { path: string, key: string, uploadUrl: string, contentType: string, contentLength: number }[] }} */
157
- const presignJson = await presignRes.json()
158
- const { batchId, files: uploadSlots } = presignJson
159
-
160
- if (!batchId || !Array.isArray(uploadSlots) || uploadSlots.length !== files.length) {
161
- throw new Error('[@ossy/cli] publish: invalid presign-batch response')
162
- }
163
-
164
- const byPath = new Map(uploadSlots.map((s) => [s.path, s]))
165
-
166
- for (const local of files) {
167
- const slot = byPath.get(local.relativePath)
168
- if (!slot?.uploadUrl) {
169
- throw new Error(`[@ossy/cli] publish: presign response missing entry for ${local.relativePath}`)
170
- }
171
- const body = await readFile(local.absPath)
172
- if (body.length !== local.size) {
173
- throw new Error(`[@ossy/cli] publish: file size mismatch for ${local.relativePath}`)
174
- }
175
- const putRes = await fetch(slot.uploadUrl, {
176
- method: 'PUT',
177
- headers: {
178
- 'Content-Type': slot.contentType,
179
- 'Content-Length': String(slot.contentLength),
180
- },
181
- body,
182
- })
183
- if (!putRes.ok) {
184
- const t = await putRes.text().catch(() => '')
185
- throw new Error(
186
- `S3 PUT failed for ${local.relativePath}: HTTP ${putRes.status}${t ? ` — ${t.slice(0, 200)}` : ''}`
187
- )
188
- }
189
- }
190
-
191
-
192
- const commitRes = await fetch(`${apiBaseUrl}/site-artifacts/commit-batch`, {
193
- method: 'POST',
194
- headers: workspaceHeaders(authToken, workspaceId),
195
- body: JSON.stringify({
196
- batchId,
197
- paths: files.map((f) => f.relativePath),
198
- name: batchId,
199
- }),
200
- })
201
-
202
- if (!commitRes.ok) {
203
- const text = await commitRes.text().catch(() => '')
204
- throw new Error(
205
- `Site artifact commit failed: HTTP ${commitRes.status}${text ? ` — ${text.slice(0, 300)}` : ''}`
206
- )
207
- }
208
-
209
- logInfo({ message: '[@ossy/cli] publish: site artifacts uploaded and CMS resource created' })
210
- }