@conduction/nextcloud-vue 2.2.0-vue3.13 → 2.2.0-vue3.15
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/package.json +3 -1
- package/scripts/.jsdoc-baselines.json +254 -0
- package/scripts/build-validators.js +174 -0
- package/scripts/check-css-entry.js +118 -0
- package/scripts/check-docs.js +724 -0
- package/scripts/check-integration-build.js +180 -0
- package/scripts/check-integration-parity.js +540 -0
- package/scripts/check-jsdoc.js +230 -0
- package/scripts/check-peer-consistency.js +161 -0
- package/scripts/generate-nl-icons.mjs +98 -0
- package/scripts/precommit-regenerate-features.sh +33 -0
- package/scripts/precommit-regenerate-partials.sh +68 -0
- package/scripts/sync-l10n-ellipsis.js +46 -0
- package/scripts/update-fleet-manifest-fixtures.js +116 -0
- package/scripts/vue3-compile-sweep.cjs +77 -0
- package/stylelint/index.js +61 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@conduction/nextcloud-vue",
|
|
3
|
-
"version": "2.2.0-vue3.
|
|
3
|
+
"version": "2.2.0-vue3.15",
|
|
4
4
|
"description": "Shared Vue component library for Conduction Nextcloud apps — complements @nextcloud/vue with higher-level components, OpenRegister integration, and NL Design System support",
|
|
5
5
|
"license": "EUPL-1.2",
|
|
6
6
|
"author": "Conduction B.V. <info@conduction.nl>",
|
|
@@ -24,7 +24,9 @@
|
|
|
24
24
|
"src/",
|
|
25
25
|
"css/",
|
|
26
26
|
"eslint/",
|
|
27
|
+
"stylelint/",
|
|
27
28
|
"l10n/",
|
|
29
|
+
"scripts/",
|
|
28
30
|
"testing/",
|
|
29
31
|
"webpack/"
|
|
30
32
|
],
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
{
|
|
2
|
+
"CnActionButtons": 1,
|
|
3
|
+
"CnActionsBar": 1,
|
|
4
|
+
"CnActionsMenu": 1,
|
|
5
|
+
"CnAdminSettingsShell": 1,
|
|
6
|
+
"CnAdvancedFormDialog": 0.65,
|
|
7
|
+
"CnAiCompanion": 1,
|
|
8
|
+
"CnAnalyticsReportCreate": 1,
|
|
9
|
+
"CnAnalyticsReportPicker": 1,
|
|
10
|
+
"CnAppLoading": 0.67,
|
|
11
|
+
"CnAppNav": 1,
|
|
12
|
+
"CnAppRoot": 0.92,
|
|
13
|
+
"CnAuditTrailCard": 0.92,
|
|
14
|
+
"CnAuditTrailWidget": 1,
|
|
15
|
+
"CnAuditTrailWidgetForm": 1,
|
|
16
|
+
"CnBannerWidget": 1,
|
|
17
|
+
"CnBannerWidgetForm": 1,
|
|
18
|
+
"CnBodySections": 1,
|
|
19
|
+
"CnBookmarkCreate": 1,
|
|
20
|
+
"CnBookmarkPicker": 1,
|
|
21
|
+
"CnCalendarEventCreate": 1,
|
|
22
|
+
"CnCalendarEventPicker": 1,
|
|
23
|
+
"CnCalendarWidget": 1,
|
|
24
|
+
"CnCalendarWidgetForm": 1,
|
|
25
|
+
"CnCard": 0.59,
|
|
26
|
+
"CnCardGrid": 0.54,
|
|
27
|
+
"CnCellRenderer": 1,
|
|
28
|
+
"CnChartWidget": 1,
|
|
29
|
+
"CnChartWidgetForm": 1,
|
|
30
|
+
"CnChatPage": 0.69,
|
|
31
|
+
"CnCollectivePageCreate": 1,
|
|
32
|
+
"CnCollectivePagePicker": 1,
|
|
33
|
+
"CnColorPicker": 1,
|
|
34
|
+
"CnCommandPalette": 1,
|
|
35
|
+
"CnConfigurationCard": 0.17,
|
|
36
|
+
"CnContactCreate": 1,
|
|
37
|
+
"CnContactPicker": 1,
|
|
38
|
+
"CnContainerWidget": 1,
|
|
39
|
+
"CnContainerWidgetForm": 1,
|
|
40
|
+
"CnContextMenu": 1,
|
|
41
|
+
"CnCopyDialog": 0.82,
|
|
42
|
+
"CnCospendCreate": 1,
|
|
43
|
+
"CnCospendPicker": 1,
|
|
44
|
+
"CnCredentials": 1,
|
|
45
|
+
"CnDashboardGrid": 0.94,
|
|
46
|
+
"CnDashboardPage": 0.94,
|
|
47
|
+
"CnDashTileWidget": 1,
|
|
48
|
+
"CnDashTileWidgetForm": 1,
|
|
49
|
+
"CnDataMatrix": 1,
|
|
50
|
+
"CnDataTable": 1,
|
|
51
|
+
"CnDateRangePicker": 1,
|
|
52
|
+
"CnDeckCardCreate": 1,
|
|
53
|
+
"CnDeckCardPicker": 1,
|
|
54
|
+
"CnDeleteDialog": 0.82,
|
|
55
|
+
"CnDeltaWidget": 1,
|
|
56
|
+
"CnDeltaWidgetForm": 1,
|
|
57
|
+
"CnDependencyMissing": 1,
|
|
58
|
+
"CnDetailCard": 0.5,
|
|
59
|
+
"CnDetailGrid": 1,
|
|
60
|
+
"CnDetailPage": 1,
|
|
61
|
+
"CnDividerWidget": 1,
|
|
62
|
+
"CnDividerWidgetForm": 1,
|
|
63
|
+
"CnEmailPicker": 1,
|
|
64
|
+
"CnExportWizard": 1,
|
|
65
|
+
"CnFacetSidebar": 0.78,
|
|
66
|
+
"CnFeaturesAndRoadmapLink": 1,
|
|
67
|
+
"CnFeaturesAndRoadmapPage": 1,
|
|
68
|
+
"CnFeaturesAndRoadmapSidebar": 1,
|
|
69
|
+
"CnFeaturesAndRoadmapView": 1,
|
|
70
|
+
"CnFeaturesTab": 1,
|
|
71
|
+
"CnFederationStatus": 1,
|
|
72
|
+
"CnFieldPicker": 1,
|
|
73
|
+
"CnFileManager": 1,
|
|
74
|
+
"CnFilesCard": 1,
|
|
75
|
+
"CnFilesPage": 0.72,
|
|
76
|
+
"CnFilesWidget": 1,
|
|
77
|
+
"CnFilesWidgetForm": 1,
|
|
78
|
+
"CnFilterBar": 0.75,
|
|
79
|
+
"CnFilterRowsEditor": 1,
|
|
80
|
+
"CnFkResolveCell": 1,
|
|
81
|
+
"CnFlowCanvas": 1,
|
|
82
|
+
"CnFlowOperationPicker": 1,
|
|
83
|
+
"CnFlowRunsWidget": 1,
|
|
84
|
+
"CnFlowRunsWidgetForm": 1,
|
|
85
|
+
"CnFolderSidebar": 1,
|
|
86
|
+
"CnFolderTree": 1,
|
|
87
|
+
"CnFormBuilder": 1,
|
|
88
|
+
"CnFormDialog": 0.72,
|
|
89
|
+
"CnFormPage": 1,
|
|
90
|
+
"CnGaugeWidget": 1,
|
|
91
|
+
"CnGaugeWidgetForm": 1,
|
|
92
|
+
"CnGraphCanvas": 1,
|
|
93
|
+
"CnHeaderWidget": 1,
|
|
94
|
+
"CnHeaderWidgetForm": 1,
|
|
95
|
+
"CnIcon": 1,
|
|
96
|
+
"CnIconBrowser": 1,
|
|
97
|
+
"CnIconPicker": 1,
|
|
98
|
+
"CnImageWidget": 1,
|
|
99
|
+
"CnImageWidgetForm": 1,
|
|
100
|
+
"CnIndexPage": 0.72,
|
|
101
|
+
"CnIndexSidebar": 0.69,
|
|
102
|
+
"CnInfoWidget": 1,
|
|
103
|
+
"CnIntegrationCard": 1,
|
|
104
|
+
"CnIntegrationTab": 1,
|
|
105
|
+
"CnIntegrationWidget": 1,
|
|
106
|
+
"CnIntegrationWidgetGrid": 1,
|
|
107
|
+
"CnInteractionFormWidget": 1,
|
|
108
|
+
"CnItemCard": 0.5,
|
|
109
|
+
"CnJsonViewer": 1,
|
|
110
|
+
"CnKbSearchWidget": 1,
|
|
111
|
+
"CnKpiGrid": 0.67,
|
|
112
|
+
"CnLabelWidget": 1,
|
|
113
|
+
"CnLabelWidgetForm": 1,
|
|
114
|
+
"CnLeafMountHost": 1,
|
|
115
|
+
"CnLifecycleActions": 1,
|
|
116
|
+
"CnLinkButtonWidget": 1,
|
|
117
|
+
"CnLinkButtonWidgetForm": 1,
|
|
118
|
+
"CnLinksWidget": 1,
|
|
119
|
+
"CnLinksWidgetForm": 1,
|
|
120
|
+
"CnLockedBanner": 1,
|
|
121
|
+
"CnLogsPage": 0.63,
|
|
122
|
+
"CnMapPage": 1,
|
|
123
|
+
"CnMapPoiCreate": 1,
|
|
124
|
+
"CnMapPoiPicker": 1,
|
|
125
|
+
"CnMapWidget": 1,
|
|
126
|
+
"CnMapWidgetForm": 1,
|
|
127
|
+
"CnMarkdownEditor": 1,
|
|
128
|
+
"CnMassActionBar": 1,
|
|
129
|
+
"CnMassCopyDialog": 0.92,
|
|
130
|
+
"CnMassDeleteDialog": 0.92,
|
|
131
|
+
"CnMassExportDialog": 0.93,
|
|
132
|
+
"CnMassImportDialog": 0.87,
|
|
133
|
+
"CnMenuItemEditor": 1,
|
|
134
|
+
"CnMenuTreeNode": 1,
|
|
135
|
+
"CnMenuWidget": 1,
|
|
136
|
+
"CnMenuWidgetForm": 1,
|
|
137
|
+
"CnNcDashboardWidgetForm": 1,
|
|
138
|
+
"CnNcWidgetGridPicker": 1,
|
|
139
|
+
"CnNcWidgetWidget": 1,
|
|
140
|
+
"CnNewsWidget": 1,
|
|
141
|
+
"CnNewsWidgetForm": 1,
|
|
142
|
+
"CnNoteCard": 0.67,
|
|
143
|
+
"CnNotesCard": 0.8,
|
|
144
|
+
"CnNotificationPreferences": 1,
|
|
145
|
+
"CnObjectCalendar": 1,
|
|
146
|
+
"CnObjectCard": 0.7,
|
|
147
|
+
"CnObjectDataWidget": 0.73,
|
|
148
|
+
"CnObjectDataWidgetForm": 1,
|
|
149
|
+
"CnObjectGeoWidget": 1,
|
|
150
|
+
"CnObjectGeoWidgetForm": 1,
|
|
151
|
+
"CnObjectKanban": 1,
|
|
152
|
+
"CnObjectList": 1,
|
|
153
|
+
"CnObjectListWidget": 1,
|
|
154
|
+
"CnObjectListWidgetForm": 1,
|
|
155
|
+
"CnObjectMetadataModal": 1,
|
|
156
|
+
"CnObjectMetadataWidget": 0.92,
|
|
157
|
+
"CnObjectRow": 1,
|
|
158
|
+
"CnObjectSidebar": 0.75,
|
|
159
|
+
"CnOpenBuildEditButton": 1,
|
|
160
|
+
"CnOpenProjectCreate": 1,
|
|
161
|
+
"CnOpenProjectPicker": 1,
|
|
162
|
+
"CnPageHeader": 0.67,
|
|
163
|
+
"CnPageRenderer": 1,
|
|
164
|
+
"CnPageTreeNode": 1,
|
|
165
|
+
"CnPagination": 1,
|
|
166
|
+
"CnPeopleWidget": 1,
|
|
167
|
+
"CnPeopleWidgetForm": 1,
|
|
168
|
+
"CnPhotoAlbumCreate": 1,
|
|
169
|
+
"CnPhotoAlbumPicker": 1,
|
|
170
|
+
"CnPollCreate": 1,
|
|
171
|
+
"CnPollPicker": 1,
|
|
172
|
+
"CnProgressBar": 0.71,
|
|
173
|
+
"CnQuickFilterBar": 0.9,
|
|
174
|
+
"CnQuicklinksWidget": 1,
|
|
175
|
+
"CnQuicklinksWidgetForm": 1,
|
|
176
|
+
"CnRegisterMapping": 0.68,
|
|
177
|
+
"CnRegisterSchemaSelect": 1,
|
|
178
|
+
"CnRelatedCollections": 1,
|
|
179
|
+
"CnRelatedFiles": 1,
|
|
180
|
+
"CnRelatedObjectsWidget": 1,
|
|
181
|
+
"CnRelatedObjectsWidgetForm": 1,
|
|
182
|
+
"CnRelationshipGraph": 1,
|
|
183
|
+
"CnResourceSelect": 1,
|
|
184
|
+
"CnRichSubmitDialog": 1,
|
|
185
|
+
"CnRoadmapItem": 1,
|
|
186
|
+
"CnRoadmapTab": 1,
|
|
187
|
+
"CnRowActions": 0.8,
|
|
188
|
+
"CnSavedViewsControl": 1,
|
|
189
|
+
"CnSaveViewDialog": 1,
|
|
190
|
+
"CnSchemaFormDialog": 0.79,
|
|
191
|
+
"CnSearchPage": 1,
|
|
192
|
+
"CnSettingsCard": 0.83,
|
|
193
|
+
"CnSettingsPage": 0.64,
|
|
194
|
+
"CnSettingsSection": 0.71,
|
|
195
|
+
"CnSetupWizard": 1,
|
|
196
|
+
"CnShareCreate": 1,
|
|
197
|
+
"CnSignatureCapture": 1,
|
|
198
|
+
"CnSpendAnalyticsWidget": 1,
|
|
199
|
+
"CnSpendAnalyticsWidgetForm": 1,
|
|
200
|
+
"CnStatsBlock": 0.88,
|
|
201
|
+
"CnStatsBlockWidget": 1,
|
|
202
|
+
"CnStatsBlockWidgetForm": 1,
|
|
203
|
+
"CnStatsPanel": 0.33,
|
|
204
|
+
"CnStatusBadge": 0.71,
|
|
205
|
+
"CnStatWidget": 1,
|
|
206
|
+
"CnStatWidgetForm": 1,
|
|
207
|
+
"CnStructuredDocReview": 1,
|
|
208
|
+
"CnSuggestFeatureModal": 1,
|
|
209
|
+
"CnSummaryAggregates": 1,
|
|
210
|
+
"CnSupportDialog": 1,
|
|
211
|
+
"CnTabbedFormDialog": 0.7,
|
|
212
|
+
"CnTableWidget": 1,
|
|
213
|
+
"CnTabs": 1,
|
|
214
|
+
"CnTagsCard": 1,
|
|
215
|
+
"CnTalkRoomCreate": 1,
|
|
216
|
+
"CnTalkRoomPicker": 1,
|
|
217
|
+
"CnTasksCard": 0.91,
|
|
218
|
+
"CnTenantBadge": 1,
|
|
219
|
+
"CnTextTableEditor": 1,
|
|
220
|
+
"CnTextWidget": 1,
|
|
221
|
+
"CnTextWidgetForm": 1,
|
|
222
|
+
"CnThemePreview": 1,
|
|
223
|
+
"CnTileWidget": 1,
|
|
224
|
+
"CnTimelineStages": 0.78,
|
|
225
|
+
"CnTimelineView": 1,
|
|
226
|
+
"CnTimeTrackerCreate": 1,
|
|
227
|
+
"CnTimeTrackerPicker": 1,
|
|
228
|
+
"CnTranslatedBadge": 1,
|
|
229
|
+
"CnTreeView": 1,
|
|
230
|
+
"CnUserActionMenu": 1,
|
|
231
|
+
"CnVersionHistory": 1,
|
|
232
|
+
"CnVersionInfoCard": 0.72,
|
|
233
|
+
"CnVideoWidget": 1,
|
|
234
|
+
"CnVideoWidgetForm": 1,
|
|
235
|
+
"CnWalkthrough": 1,
|
|
236
|
+
"CnWidgetCardGrid": 1,
|
|
237
|
+
"CnWidgetEditCog": 1,
|
|
238
|
+
"CnWidgetFormRenderer": 1,
|
|
239
|
+
"CnWidgetGrid": 1,
|
|
240
|
+
"CnWidgetMapViewer": 1,
|
|
241
|
+
"CnWidgetObjectTable": 1,
|
|
242
|
+
"CnWidgetRefItem": 1,
|
|
243
|
+
"CnWidgetRenderer": 1,
|
|
244
|
+
"CnWidgetWrapper": 0.84,
|
|
245
|
+
"CnWikiPage": 1,
|
|
246
|
+
"CnWizardDialog": 1,
|
|
247
|
+
"CnWorkspaceFilterWidget": 1,
|
|
248
|
+
"CnXwikiPageCreate": 1,
|
|
249
|
+
"CnXwikiPagePicker": 1,
|
|
250
|
+
"__schema__": {
|
|
251
|
+
"generatedBy": "scripts/check-jsdoc.js --update",
|
|
252
|
+
"rule": "CI fails if any component score < baseline. New components require 1.0."
|
|
253
|
+
}
|
|
254
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
3
|
+
// Copyright (C) 2026 Conduction B.V.
|
|
4
|
+
//
|
|
5
|
+
// Pre-compiles the v2 manifest JSON Schema into a standalone JS module
|
|
6
|
+
// that doesn't require runtime `new Function()`. Nextcloud's CSP blocks
|
|
7
|
+
// unsafe-eval, so Ajv's default JIT compilation breaks Vue mount in v2
|
|
8
|
+
// apps (EvalError on every app boot). This generator runs at build time
|
|
9
|
+
// as a `prebuild` step to produce
|
|
10
|
+
// src/utils/validateManifestV2.compiled.js — a regular CJS module that
|
|
11
|
+
// imports Ajv's runtime helpers but never calls new Function().
|
|
12
|
+
//
|
|
13
|
+
// See ADR-036 and docs/utilities/validate-manifest-v2.md for rationale.
|
|
14
|
+
|
|
15
|
+
'use strict'
|
|
16
|
+
|
|
17
|
+
const fs = require('fs')
|
|
18
|
+
const path = require('path')
|
|
19
|
+
// eslint-disable-next-line n/no-unpublished-require
|
|
20
|
+
const Ajv2020 = require('ajv/dist/2020')
|
|
21
|
+
// eslint-disable-next-line n/no-unpublished-require
|
|
22
|
+
const addFormats = require('ajv-formats')
|
|
23
|
+
// eslint-disable-next-line n/no-unpublished-require
|
|
24
|
+
const standaloneCode = require('ajv/dist/standalone')
|
|
25
|
+
|
|
26
|
+
// ajv and ajv-formats export default via CJS interop differently depending on
|
|
27
|
+
// bundler / Node version — unwrap .default when present.
|
|
28
|
+
const AjvClass = Ajv2020.default || Ajv2020
|
|
29
|
+
const addFormatsFunc = addFormats.default || addFormats
|
|
30
|
+
const standaloneCodeFunc = standaloneCode.default || standaloneCode
|
|
31
|
+
|
|
32
|
+
const schemaPath = path.resolve(__dirname, '../src/schemas/app-manifest-v2.schema.json')
|
|
33
|
+
const outPath = path.resolve(__dirname, '../src/utils/validateManifestV2.compiled.js')
|
|
34
|
+
|
|
35
|
+
const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf-8'))
|
|
36
|
+
|
|
37
|
+
const ajv = new AjvClass({
|
|
38
|
+
code: { source: true, esm: false }, // CJS output for broadest bundler compat
|
|
39
|
+
useDefaults: true,
|
|
40
|
+
allErrors: true,
|
|
41
|
+
strict: false,
|
|
42
|
+
})
|
|
43
|
+
addFormatsFunc(ajv)
|
|
44
|
+
|
|
45
|
+
const validate = ajv.compile(schema)
|
|
46
|
+
let moduleCode = standaloneCodeFunc(ajv, validate)
|
|
47
|
+
|
|
48
|
+
// Inline ajv-formats so the standalone module has ZERO runtime dependency on
|
|
49
|
+
// ajv-formats. Ajv's standalone output emits lines like
|
|
50
|
+
// const formatN = require("ajv-formats/dist/formats").fullFormats.uri;
|
|
51
|
+
// That runtime require survives Node/Jest fine, but the rollup→webpack
|
|
52
|
+
// double-bundle every consumer app runs tree-shakes ajv-formats'
|
|
53
|
+
// `exports.fullFormats = {…}` assignment down to `{}`, so at runtime
|
|
54
|
+
// `{}.fullFormats.uri` throws `Cannot read properties of undefined (reading
|
|
55
|
+
// 'uri')` during app boot and white-screens the whole SPA (observed
|
|
56
|
+
// 2026-07-11 on openbuild). Replacing each reference with a self-contained
|
|
57
|
+
// inline definition, extracted from the installed ajv-formats at build time,
|
|
58
|
+
// removes the fragile require while preserving identical validation. The
|
|
59
|
+
// formats file is re-read on every regeneration, so this stays in sync.
|
|
60
|
+
const formatsSrc = fs.readFileSync(
|
|
61
|
+
require.resolve('ajv-formats/dist/formats'),
|
|
62
|
+
'utf-8',
|
|
63
|
+
)
|
|
64
|
+
const fullFormats = require('ajv-formats/dist/formats').fullFormats
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Read a full JavaScript regex literal (including flags) from source, starting
|
|
68
|
+
* at the opening `/`. Correctly skips escapes and `[…]` character classes so a
|
|
69
|
+
* bare `/` inside a class does not prematurely terminate the literal.
|
|
70
|
+
*
|
|
71
|
+
* @param {string} src The source text.
|
|
72
|
+
* @param {number} start Index of the opening `/`.
|
|
73
|
+
* @return {string|null} The literal `/…/flags`, or null if unterminated.
|
|
74
|
+
*/
|
|
75
|
+
function readRegexLiteral(src, start) {
|
|
76
|
+
let i = start + 1
|
|
77
|
+
let inClass = false
|
|
78
|
+
for (; i < src.length; i++) {
|
|
79
|
+
const ch = src[i]
|
|
80
|
+
if (ch === '\\') {
|
|
81
|
+
i++
|
|
82
|
+
continue
|
|
83
|
+
}
|
|
84
|
+
if (ch === '[') {
|
|
85
|
+
inClass = true
|
|
86
|
+
} else if (ch === ']') {
|
|
87
|
+
inClass = false
|
|
88
|
+
} else if (ch === '/' && !inClass) {
|
|
89
|
+
break
|
|
90
|
+
} else if (ch === '\n') {
|
|
91
|
+
return null
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (i >= src.length) {
|
|
95
|
+
return null
|
|
96
|
+
}
|
|
97
|
+
let end = i + 1
|
|
98
|
+
while (end < src.length && /[a-z]/i.test(src[end])) {
|
|
99
|
+
end++
|
|
100
|
+
}
|
|
101
|
+
return src.slice(start, end)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Build a self-contained JS expression reproducing one ajv-formats format,
|
|
106
|
+
* with no runtime dependency on the ajv-formats package.
|
|
107
|
+
*
|
|
108
|
+
* @param {string} name The format name (e.g. "uri").
|
|
109
|
+
* @return {string} A JS expression that evaluates to the format value.
|
|
110
|
+
*/
|
|
111
|
+
function inlineFormat(name) {
|
|
112
|
+
const value = fullFormats[name]
|
|
113
|
+
if (value === undefined) {
|
|
114
|
+
throw new Error(`build-validators: ajv-formats has no format "${name}" to inline`)
|
|
115
|
+
}
|
|
116
|
+
if (value instanceof RegExp) {
|
|
117
|
+
return value.toString()
|
|
118
|
+
}
|
|
119
|
+
if (typeof value === 'function') {
|
|
120
|
+
// A function format (e.g. uri) closes over module-level `const X = /…/;`
|
|
121
|
+
// regexes in formats.js. Inline exactly the ones it references inside an
|
|
122
|
+
// IIFE so the returned function is self-contained. Regex literals are
|
|
123
|
+
// read with a lexer-correct scanner (escapes + `[…]` classes) — a plain
|
|
124
|
+
// regex can't parse a regex literal that contains bare `/` inside a
|
|
125
|
+
// character class (ajv-formats' URI regex does).
|
|
126
|
+
const body = value.toString()
|
|
127
|
+
const deps = []
|
|
128
|
+
const declRe = /const ([A-Z0-9_]+) = \//g
|
|
129
|
+
let m
|
|
130
|
+
while ((m = declRe.exec(formatsSrc)) !== null) {
|
|
131
|
+
const name = m[1]
|
|
132
|
+
if (!new RegExp(`\\b${name}\\b`).test(body)) {
|
|
133
|
+
continue
|
|
134
|
+
}
|
|
135
|
+
const literal = readRegexLiteral(formatsSrc, m.index + m[0].length - 1)
|
|
136
|
+
if (literal) {
|
|
137
|
+
deps.push(`const ${name} = ${literal};`)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return `(function(){${deps.join('')}return ${body};})()`
|
|
141
|
+
}
|
|
142
|
+
throw new Error(`build-validators: cannot inline ajv-formats format "${name}" of type ${typeof value}`)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
moduleCode = moduleCode.replace(
|
|
146
|
+
/require\("ajv-formats\/dist\/formats"\)\.fullFormats\.([A-Za-z0-9_-]+)/g,
|
|
147
|
+
(_match, name) => inlineFormat(name),
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
if (/require\("ajv-formats/.test(moduleCode)) {
|
|
151
|
+
throw new Error('build-validators: a require("ajv-formats…") reference survived inlining — the standalone validator is not self-contained')
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const banner = `// SPDX-License-Identifier: EUPL-1.2
|
|
155
|
+
// Copyright (C) 2026 Conduction B.V.
|
|
156
|
+
//
|
|
157
|
+
// AUTOGENERATED by scripts/build-validators.js — DO NOT EDIT BY HAND.
|
|
158
|
+
// Run \`npm run build:validators\` to regenerate after editing
|
|
159
|
+
// src/schemas/app-manifest-v2.schema.json.
|
|
160
|
+
//
|
|
161
|
+
// This is a standalone Ajv-compiled validator. It does NOT use
|
|
162
|
+
// new Function() at runtime — required for Nextcloud's CSP which
|
|
163
|
+
// blocks unsafe-eval. The validator function is exported as the
|
|
164
|
+
// module's default export (CJS: module.exports).
|
|
165
|
+
//
|
|
166
|
+
// See ADR-036 and docs/utilities/validate-manifest-v2.md.
|
|
167
|
+
|
|
168
|
+
`
|
|
169
|
+
|
|
170
|
+
fs.writeFileSync(outPath, banner + moduleCode)
|
|
171
|
+
|
|
172
|
+
const kb = (moduleCode.length / 1024).toFixed(1)
|
|
173
|
+
// eslint-disable-next-line no-console
|
|
174
|
+
console.log(`build-validators: wrote ${path.relative(process.cwd(), outPath)} (${kb} KB)`)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Published-CSS-entry gate.
|
|
5
|
+
*
|
|
6
|
+
* THE BUG THIS GUARDS AGAINST
|
|
7
|
+
* ---------------------------
|
|
8
|
+
* `css/index.css` — the entry every consumer app imports — used to re-export
|
|
9
|
+
* `../src/css/index.css`, which holds only the UNSCOPED globals. Every
|
|
10
|
+
* component's scoped SFC style block lives exclusively in the built
|
|
11
|
+
* `dist/nextcloud-vue.css`, and the only reference to that file was the
|
|
12
|
+
* `import './nextcloud-vue.css'` banner atop `dist/esm/index.js`. webpack
|
|
13
|
+
* treats that barrel as side-effect-free (it is absent from
|
|
14
|
+
* package.json#sideEffects), so it skips the module — and the CSS import with
|
|
15
|
+
* it — whenever a consumer imports named components instead of the namespace.
|
|
16
|
+
*
|
|
17
|
+
* Net effect: dist-consuming apps rendered components carrying `data-v-*`
|
|
18
|
+
* scope attributes for which no rule was ever shipped. Nothing errored; the
|
|
19
|
+
* elements just silently lost every scoped declaration, so flex containers
|
|
20
|
+
* fell back to `display: block`. Observed on pipelinq, where the dashboard
|
|
21
|
+
* header stacked its action buttons underneath the title and 11 nc-vue
|
|
22
|
+
* components rendered entirely unstyled.
|
|
23
|
+
*
|
|
24
|
+
* WHAT THIS DOES
|
|
25
|
+
* --------------
|
|
26
|
+
* Follows the @import chain out of `css/index.css` exactly as a consumer's
|
|
27
|
+
* css-loader would, then asserts the invariant that was violated: EVERY
|
|
28
|
+
* `data-v-*` scope id baked into the built JS has at least one matching rule
|
|
29
|
+
* in the CSS reachable from that entry. Vue only emits a scope id for a
|
|
30
|
+
* component that actually has a scoped <style> block, so an id with no rules
|
|
31
|
+
* always means its styles were dropped somewhere between rollup and the
|
|
32
|
+
* published entry point.
|
|
33
|
+
*
|
|
34
|
+
* Runs after `npm run build` (dist/ must exist).
|
|
35
|
+
*
|
|
36
|
+
* Exit codes:
|
|
37
|
+
* 0 — every scope id rendered by the built JS is styled by the CSS entry
|
|
38
|
+
* 1 — at least one scope id would ship unstyled
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
const fs = require('fs')
|
|
42
|
+
const path = require('path')
|
|
43
|
+
|
|
44
|
+
const root = path.resolve(__dirname, '..')
|
|
45
|
+
const entry = path.join(root, 'css', 'index.css')
|
|
46
|
+
|
|
47
|
+
const SCOPE_ID = /data-v-[0-9a-f]{8}/g
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Resolve a stylesheet and everything it @imports, css-loader style.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} file Absolute path to a CSS file.
|
|
53
|
+
* @param {Set<string>} seen Guard against @import cycles.
|
|
54
|
+
* @return {string} The concatenated CSS of `file` and its transitive imports.
|
|
55
|
+
*/
|
|
56
|
+
function readWithImports(file, seen = new Set()) {
|
|
57
|
+
if (seen.has(file)) {
|
|
58
|
+
return ''
|
|
59
|
+
}
|
|
60
|
+
seen.add(file)
|
|
61
|
+
|
|
62
|
+
if (!fs.existsSync(file)) {
|
|
63
|
+
console.error(`✗ CSS entry points at a file that does not exist: ${path.relative(root, file)}`)
|
|
64
|
+
console.error(' Did `npm run build` run first? dist/ is gitignored and built by rollup.')
|
|
65
|
+
process.exit(1)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const css = fs.readFileSync(file, 'utf8')
|
|
69
|
+
const imports = [...css.matchAll(/@import\s+['"]([^'"]+)['"]/g)].map((m) => m[1])
|
|
70
|
+
|
|
71
|
+
return imports.reduce(
|
|
72
|
+
(acc, spec) => acc + readWithImports(path.resolve(path.dirname(file), spec), seen),
|
|
73
|
+
css,
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Collect every scope id the built JS will stamp onto rendered elements.
|
|
79
|
+
*
|
|
80
|
+
* @return {Set<string>} Scope ids such as `data-v-5266112e`.
|
|
81
|
+
*/
|
|
82
|
+
function scopeIdsInBuiltJs() {
|
|
83
|
+
const ids = new Set()
|
|
84
|
+
const walk = (dir) => {
|
|
85
|
+
for (const dirent of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
86
|
+
const full = path.join(dir, dirent.name)
|
|
87
|
+
if (dirent.isDirectory()) {
|
|
88
|
+
walk(full)
|
|
89
|
+
} else if (dirent.name.endsWith('.js')) {
|
|
90
|
+
for (const id of fs.readFileSync(full, 'utf8').match(SCOPE_ID) || []) {
|
|
91
|
+
ids.add(id)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
walk(path.join(root, 'dist'))
|
|
97
|
+
return ids
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const css = readWithImports(entry)
|
|
101
|
+
const styled = new Set(css.match(SCOPE_ID) || [])
|
|
102
|
+
const rendered = scopeIdsInBuiltJs()
|
|
103
|
+
const unstyled = [...rendered].filter((id) => !styled.has(id)).sort()
|
|
104
|
+
|
|
105
|
+
if (unstyled.length > 0) {
|
|
106
|
+
console.error(`✗ ${unstyled.length} scope id(s) are rendered by the built JS but carry no rule in css/index.css:`)
|
|
107
|
+
for (const id of unstyled) {
|
|
108
|
+
console.error(` ${id}`)
|
|
109
|
+
}
|
|
110
|
+
console.error('')
|
|
111
|
+
console.error(' Consumers importing @conduction/nextcloud-vue/css/index.css would render')
|
|
112
|
+
console.error(' these components with every scoped declaration missing (silently — no error).')
|
|
113
|
+
console.error(' css/index.css must @import the BUILT stylesheet (dist/nextcloud-vue.css),')
|
|
114
|
+
console.error(' which carries the scoped SFC styles as well as the unscoped globals.')
|
|
115
|
+
process.exit(1)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
console.log(`✓ css/index.css styles all ${rendered.size} scope ids rendered by the built JS`)
|