@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.
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-License-Identifier: AGPL-3.0-or-later
3
+ //
4
+ // update-fleet-manifest-fixtures.js — refresh the vendored fleet-manifest
5
+ // corpus used by tests/schemas/fleet-manifest-regression.spec.js.
6
+ //
7
+ // Why (2026-07-06 manifest fleet audit, item 15): a schema change in this
8
+ // library can silently invalidate a manifest already deployed in a fleet app
9
+ // — the exact drift the audit chased (installed schema generations spanned
10
+ // beta.30..beta.155). The regression spec validates every deployed manifest
11
+ // against the CURRENT candidate schema on every PR, so a breaking schema
12
+ // change fails HERE, before release, instead of surfacing as install drift.
13
+ //
14
+ // The corpus is a vendored snapshot (CI has no fleet checkout). This script
15
+ // refreshes it from the sibling app checkouts. Run it after a deliberate,
16
+ // reviewed manifest change lands in a fleet app — never to make a failing
17
+ // regression test pass (that would defeat the guard).
18
+ //
19
+ // Usage:
20
+ // node scripts/update-fleet-manifest-fixtures.js [fleet-root]
21
+ // fleet-root — dir whose children are the app checkouts
22
+ // (default: the parent of this library's checkout).
23
+ //
24
+ // Output: writes tests/fixtures/fleet-manifests/<app>.json for every app
25
+ // that ships src/manifest.json, plus an index.json manifest (app -> bytes,
26
+ // $schema, sha256) so the spec can report drift precisely.
27
+
28
+ 'use strict'
29
+
30
+ const fs = require('fs')
31
+ const path = require('path')
32
+ const crypto = require('crypto')
33
+ const { execFileSync } = require('child_process')
34
+
35
+ const REPO_ROOT = path.resolve(__dirname, '..')
36
+ const FLEET_ROOT = process.argv[2]
37
+ ? path.resolve(process.argv[2])
38
+ : path.resolve(REPO_ROOT, '..')
39
+ const OUT_DIR = path.join(REPO_ROOT, 'tests', 'fixtures', 'fleet-manifests')
40
+
41
+ // Non-app sibling dirs that may carry a src/manifest.json but are not fleet
42
+ // apps we want to gate on (this library's own examples, etc.).
43
+ const SKIP = new Set(['nextcloud-vue'])
44
+
45
+ function sha256(buf) {
46
+ return crypto.createHash('sha256').update(buf).digest('hex')
47
+ }
48
+
49
+ function main() {
50
+ if (fs.existsSync(OUT_DIR) === false) {
51
+ fs.mkdirSync(OUT_DIR, { recursive: true })
52
+ }
53
+
54
+ const index = {}
55
+ let written = 0
56
+
57
+ for (const app of fs.readdirSync(FLEET_ROOT).sort()) {
58
+ if (SKIP.has(app)) {
59
+ continue
60
+ }
61
+ const appDir = path.join(FLEET_ROOT, app)
62
+ const manifestPath = path.join(appDir, 'src', 'manifest.json')
63
+ if (fs.existsSync(manifestPath) === false) {
64
+ continue
65
+ }
66
+
67
+ // The corpus must reflect the DEPLOYED manifest — what is merged to the
68
+ // app's integration branch — not whatever a local checkout happens to
69
+ // have on a feature branch. Prefer the committed origin/development (or
70
+ // origin/main) blob; fall back to the working tree only if git can't
71
+ // resolve one (e.g. a shallow CI checkout).
72
+ let raw = null
73
+ for (const ref of ['origin/development', 'origin/main']) {
74
+ try {
75
+ raw = execFileSync('git', ['-C', appDir, 'show', `${ref}:src/manifest.json`], { maxBuffer: 32 * 1024 * 1024 })
76
+ break
77
+ } catch (_) { /* try next ref */ }
78
+ }
79
+ if (raw === null) {
80
+ try {
81
+ raw = fs.readFileSync(manifestPath)
82
+ console.error(` (note: ${app} — no origin/development|main blob, snapshotting working tree)`)
83
+ } catch (_) {
84
+ continue
85
+ }
86
+ }
87
+
88
+ // Validate it is parseable JSON before vendoring — never snapshot
89
+ // a corrupt file into the corpus.
90
+ let parsed
91
+ try {
92
+ parsed = JSON.parse(raw)
93
+ } catch (e) {
94
+ console.error(`SKIP ${app}: manifest.json is not valid JSON (${e.message})`)
95
+ continue
96
+ }
97
+
98
+ const pretty = JSON.stringify(parsed, null, '\t') + '\n'
99
+ fs.writeFileSync(path.join(OUT_DIR, `${app}.json`), pretty)
100
+ index[app] = {
101
+ bytes: Buffer.byteLength(pretty),
102
+ $schema: typeof parsed.$schema === 'string' ? parsed.$schema : null,
103
+ sha256: sha256(pretty),
104
+ }
105
+ written += 1
106
+ console.log(` ${app} (${index[app].bytes} bytes)`)
107
+ }
108
+
109
+ fs.writeFileSync(
110
+ path.join(OUT_DIR, 'index.json'),
111
+ JSON.stringify(index, null, '\t') + '\n',
112
+ )
113
+ console.log(`\nWrote ${written} fleet manifests + index.json to ${path.relative(REPO_ROOT, OUT_DIR)}`)
114
+ }
115
+
116
+ main()
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Vue 3 compile-readiness sweep (ADR-066, openspec vue-3-migration).
3
+ *
4
+ * Compiles every SFC template + script under src/ with @vue/compiler-sfc in
5
+ * @vue/compat MODE 2, and reports which components fail to COMPILE on Vue 3.
6
+ * This is the fastest signal for the migration: compile failures are hard
7
+ * blockers, and this needs no bundle/install of the runtime deps.
8
+ *
9
+ * Requires the Vue 3 toolchain (`npm i -D @vue/compiler-sfc@^3.5`). Run:
10
+ * node scripts/vue3-compile-sweep.cjs (or: npm run check:vue3-compile)
11
+ *
12
+ * Exit code is the number of failing components (0 = all clean).
13
+ *
14
+ * NOTE: a clean sweep does NOT mean runtime-correct. Plain Vue 3 silently
15
+ * mis-compiles `.sync` and `{{x|f}}` (see BUILD-VUE3.md); compat MODE 2 keeps
16
+ * them correct here, but every such site must still be rewritten (tasks 2.2/2.6)
17
+ * before the compat flags come off.
18
+ */
19
+ const fs = require('fs')
20
+ const path = require('path')
21
+
22
+ let sfc
23
+ try {
24
+ sfc = require('@vue/compiler-sfc')
25
+ } catch (e) {
26
+ console.error('[vue3-compile-sweep] @vue/compiler-sfc not found — install the Vue 3 toolchain first (npm i -D @vue/compiler-sfc@^3.5).')
27
+ process.exit(2)
28
+ }
29
+
30
+ const root = path.resolve(__dirname, '..', 'src')
31
+ const compat = { compatConfig: { MODE: 2, COMPILER_FILTERS: true } }
32
+
33
+ function walk(dir, out = []) {
34
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
35
+ const p = path.join(dir, e.name)
36
+ if (e.isDirectory()) walk(p, out)
37
+ else if (e.name.endsWith('.vue')) out.push(p)
38
+ }
39
+ return out
40
+ }
41
+
42
+ const files = walk(root)
43
+ let clean = 0
44
+ const failed = []
45
+
46
+ for (const f of files) {
47
+ const src = fs.readFileSync(f, 'utf8')
48
+ const errs = []
49
+ try {
50
+ const { descriptor, errors } = sfc.parse(src, { filename: f })
51
+ errs.push(...(errors || []).map((e) => 'parse: ' + (e.message || e)))
52
+ if (descriptor.template) {
53
+ const t = sfc.compileTemplate({ source: descriptor.template.content, filename: f, id: 'x', compilerOptions: compat })
54
+ errs.push(...(t.errors || []).map((e) => 'tmpl: ' + (e.message || String(e)).slice(0, 100)))
55
+ }
56
+ if (descriptor.script || descriptor.scriptSetup) {
57
+ try {
58
+ sfc.compileScript(descriptor, { id: 'x', templateOptions: { compilerOptions: compat } })
59
+ } catch (e) {
60
+ errs.push('script: ' + (e.message || String(e)).slice(0, 100))
61
+ }
62
+ }
63
+ } catch (e) {
64
+ errs.push('fatal: ' + (e.message || String(e)).slice(0, 100))
65
+ }
66
+ if (errs.length === 0) clean++
67
+ else failed.push({ f: path.relative(root, f), errs })
68
+ }
69
+
70
+ console.log(`Vue 3 compile sweep (compat MODE 2) — ${files.length} components`)
71
+ console.log(` clean : ${clean}`)
72
+ console.log(` failed: ${failed.length}`)
73
+ if (failed.length) {
74
+ console.log('')
75
+ for (const x of failed) console.log(` ✗ ${x.f}\n ${x.errs[0]}`)
76
+ }
77
+ process.exit(failed.length)
@@ -0,0 +1,61 @@
1
+ /**
2
+ * SPDX-License-Identifier: EUPL-1.2
3
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
4
+ *
5
+ * `@conduction/nextcloud-vue/stylelint` — the shared Stylelint preset for every
6
+ * Conduction Nextcloud app.
7
+ *
8
+ * WHY THIS LIVES HERE
9
+ * -------------------
10
+ * The same reason the ESLint preset does, and the same reason the PHP ruleset
11
+ * moved to `conduction/hydra-gates`: a copied config is not a shared config.
12
+ *
13
+ * Measured across the 18 core apps on 2026-08-12, `stylelint.config.js` existed
14
+ * in six variants. Ten apps were byte-identical to this file; the other eight
15
+ * had each drifted separately. None of that drift was a decision.
16
+ *
17
+ * Stylelint cannot be homed the way PHPCS was, through a path into `vendor/`,
18
+ * because a Stylelint config resolves `extends` against `node_modules` relative
19
+ * to itself. An npm package is the only channel that works — and every app
20
+ * already depends on this one.
21
+ *
22
+ * WHAT IT IS
23
+ * ----------
24
+ * `@nextcloud/stylelint-config`, plus exactly one addition.
25
+ *
26
+ * That is deliberate and it mirrors `conduction/coding-standard` on the PHP
27
+ * side: Conduction code must pass Nextcloud's own checks unchanged. We may be
28
+ * STRICTER than Nextcloud; we may not be DIFFERENT from it. Anything here that
29
+ * contradicted `@nextcloud/stylelint-config` would put an app in the position
30
+ * the PHP toolchain was in until this week — two tools with overlapping
31
+ * jurisdiction demanding opposite things, and no way to satisfy both.
32
+ *
33
+ * THE ONE ADDITION
34
+ * ----------------
35
+ * `::v-deep` is a Vue SFC scoped-style selector, not a CSS pseudo-element.
36
+ * Stylelint's `selector-pseudo-element-no-unknown` does not know it and flags
37
+ * every use. Nextcloud's config does not carry the exception because Nextcloud
38
+ * core does not use `::v-deep`; this fleet does, in every app that restyles a
39
+ * child component's internals.
40
+ *
41
+ * This is additive in the strict sense — it relaxes a rule on a token Nextcloud
42
+ * never emits — so a file that satisfies this preset still satisfies theirs.
43
+ *
44
+ * USAGE
45
+ * -----
46
+ * // stylelint.config.js
47
+ * module.exports = require('@conduction/nextcloud-vue/stylelint')
48
+ *
49
+ * To add an app-specific rule, spread it — do not redefine `extends`:
50
+ *
51
+ * const base = require('@conduction/nextcloud-vue/stylelint')
52
+ * module.exports = { ...base, rules: { ...base.rules, 'my/rule': true } }
53
+ */
54
+ module.exports = {
55
+ extends: '@nextcloud/stylelint-config',
56
+ rules: {
57
+ 'selector-pseudo-element-no-unknown': [true, {
58
+ ignorePseudoElements: ['v-deep'],
59
+ }],
60
+ },
61
+ }