@adobedjangir/commerce-admin-snapshots 0.1.0 → 0.1.1

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 CHANGED
@@ -1,20 +1,40 @@
1
1
  {
2
2
  "name": "@adobedjangir/commerce-admin-snapshots",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Snapshot + rollback add-on for @adobedjangir/commerce-admin-management. Capture point-in-time copies of schema + values, restore wholesale (auto pre-restore backup included).",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Adobe Inc.",
7
- "keywords": ["adobe-io", "aio", "app-builder", "commerce-admin", "snapshot", "rollback"],
7
+ "keywords": [
8
+ "adobe-io",
9
+ "aio",
10
+ "app-builder",
11
+ "commerce-admin",
12
+ "snapshot",
13
+ "rollback"
14
+ ],
8
15
  "main": "./src/index.js",
9
16
  "exports": {
10
17
  ".": "./src/index.js",
11
- "./web": "./web/index.js"
18
+ "./web": "./web/dist/index.js"
12
19
  },
13
20
  "scripts": {
21
+ "build": "node ./scripts/build-web.js",
22
+ "prepublishOnly": "node ./scripts/build-web.js",
14
23
  "postinstall": "node ./scripts/setup.js"
15
24
  },
16
- "files": ["src", "actions", "web", "scripts", "README.md"],
17
- "engines": { "node": ">=18" },
25
+ "files": [
26
+ "src",
27
+ "actions",
28
+ "web",
29
+ "scripts",
30
+ "README.md"
31
+ ],
32
+ "engines": {
33
+ "node": ">=18"
34
+ },
35
+ "dependencies": {
36
+ "esbuild": "^0.25.0"
37
+ },
18
38
  "peerDependencies": {
19
39
  "@adobedjangir/commerce-admin-management": ">=0.3.0",
20
40
  "@adobedjangir/commerce-admin-get-config": ">=0.0.7"
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ Copyright 2025 Adobe. All rights reserved.
4
+ Licensed under the Apache License, Version 2.0
5
+ */
6
+
7
+ // Pre-bundle this add-on's web entry so the host's Parcel build can
8
+ // consume a single JS file without having to transform JSX from
9
+ // node_modules. Mirrors core's build-web.js.
10
+
11
+ const fs = require('fs')
12
+ const path = require('path')
13
+
14
+ async function main () {
15
+ let esbuild
16
+ try { esbuild = require('esbuild') } catch {
17
+ console.error('[@adobedjangir/commerce-admin-snapshots] esbuild missing — run `npm install` in the package directory.')
18
+ process.exit(1)
19
+ }
20
+ const pkgRoot = path.join(__dirname, '..')
21
+ const entry = path.join(pkgRoot, 'web', 'index.js')
22
+ const outdir = path.join(pkgRoot, 'web', 'dist')
23
+ fs.mkdirSync(outdir, { recursive: true })
24
+ await esbuild.build({
25
+ entryPoints: [entry],
26
+ bundle: true,
27
+ format: 'esm',
28
+ platform: 'browser',
29
+ outfile: path.join(outdir, 'index.js'),
30
+ packages: 'external',
31
+ jsx: 'automatic',
32
+ loader: { '.js': 'jsx', '.css': 'css' },
33
+ target: ['chrome79', 'firefox85', 'safari13'],
34
+ logLevel: 'info'
35
+ })
36
+ console.log('[@adobedjangir/commerce-admin-snapshots] built web/dist/index.js')
37
+ }
38
+
39
+ if (require.main === module) main().catch((e) => { console.error(e.message); process.exit(1) })
package/scripts/setup.js CHANGED
@@ -35,8 +35,11 @@ function resolveProjectRoot () {
35
35
  */
36
36
  function patchAppConfig (content) {
37
37
  // Look for our own marker to detect prior installs.
38
+ // Escape marker special chars before interpolating into the regex —
39
+ // `(auto-linked on npm install)` previously broke the detection.
40
+ const ESC = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
38
41
  const blockRe = new RegExp(
39
- String.raw`^[ \t]*${MARKER}\n([ \t]+${RUNTIME_KEY}:[ \t]*\n[ \t]+\$include:[^\n]*\n)`, 'm'
42
+ "^[ \\t]*" + ESC(MARKER) + "\\n[ \\t]+" + RUNTIME_KEY + ":[ \\t]*\\n[ \\t]+\\$include:[^\\n]*\\n", "m"
40
43
  )
41
44
  const desiredBody = ` ${MARKER}\n ${RUNTIME_KEY}:\n $include: ${INCLUDE_REL}\n`
42
45
 
@@ -0,0 +1,239 @@
1
+ // web/index.js
2
+ import { configureWeb } from "@adobedjangir/commerce-admin-management/web";
3
+
4
+ // web/src/SnapshotHistory.js
5
+ import React, { useCallback, useEffect, useState } from "react";
6
+ import {
7
+ View,
8
+ Flex,
9
+ Heading,
10
+ Text,
11
+ Button,
12
+ TextField,
13
+ ProgressCircle,
14
+ StatusLight,
15
+ Well,
16
+ DialogTrigger,
17
+ Dialog,
18
+ Content,
19
+ Header,
20
+ ButtonGroup,
21
+ Divider,
22
+ Switch
23
+ } from "@adobe/react-spectrum";
24
+ import { callAction, resolveActor } from "@adobedjangir/commerce-admin-management/web";
25
+ import { getActionKey } from "@adobedjangir/commerce-admin-management/web";
26
+ import { PALETTE, RADIUS, SHADOW } from "@adobedjangir/commerce-admin-management/web";
27
+ import { jsx, jsxs } from "react/jsx-runtime";
28
+ function SnapshotHistory({ runtime, ims }) {
29
+ var _a, _b, _c, _d;
30
+ const [items, setItems] = useState([]);
31
+ const [loading, setLoading] = useState(false);
32
+ const [error, setError] = useState(null);
33
+ const [status, setStatus] = useState({ tone: "neutral", message: "" });
34
+ const [newLabel, setNewLabel] = useState("");
35
+ const [creating, setCreating] = useState(false);
36
+ const [confirmRow, setConfirmRow] = useState(null);
37
+ const [restoreSchema, setRestoreSchema] = useState(true);
38
+ const [restoring, setRestoring] = useState(false);
39
+ const fetchList = useCallback(async () => {
40
+ setLoading(true);
41
+ setError(null);
42
+ try {
43
+ const res = await callAction({ runtime, ims }, getActionKey("systemConfigSnapshotList"), "", { limit: 100 });
44
+ const body = (res == null ? void 0 : res.body) || res;
45
+ setItems(Array.isArray(body == null ? void 0 : body.items) ? body.items : []);
46
+ } catch (e) {
47
+ setError(e.message || "Failed to load snapshots");
48
+ } finally {
49
+ setLoading(false);
50
+ }
51
+ }, [runtime, ims]);
52
+ useEffect(() => {
53
+ fetchList();
54
+ }, [fetchList]);
55
+ const doCreate = async () => {
56
+ setCreating(true);
57
+ setStatus({ tone: "notice", message: "Creating snapshot\u2026" });
58
+ try {
59
+ const res = await callAction({ runtime, ims }, getActionKey("systemConfigSnapshotCreate"), "", {
60
+ label: newLabel.trim() || void 0,
61
+ actor: resolveActor(ims)
62
+ });
63
+ const body = (res == null ? void 0 : res.body) || res;
64
+ if (body == null ? void 0 : body.ok) {
65
+ setStatus({ tone: "positive", message: `Snapshot saved: ${body.snapshot.label}` });
66
+ setNewLabel("");
67
+ await fetchList();
68
+ } else {
69
+ setStatus({ tone: "negative", message: (body == null ? void 0 : body.error) || "Snapshot failed" });
70
+ }
71
+ } catch (e) {
72
+ setStatus({ tone: "negative", message: e.message || "Snapshot failed" });
73
+ } finally {
74
+ setCreating(false);
75
+ }
76
+ };
77
+ const doRestore = async () => {
78
+ if (!confirmRow) return;
79
+ setRestoring(true);
80
+ setStatus({ tone: "notice", message: "Restoring\u2026" });
81
+ try {
82
+ const res = await callAction({ runtime, ims }, getActionKey("systemConfigSnapshotRestore"), "", {
83
+ snapshotId: confirmRow._id,
84
+ restoreSchema,
85
+ actor: resolveActor(ims)
86
+ });
87
+ const body = (res == null ? void 0 : res.body) || res;
88
+ if (body == null ? void 0 : body.ok) {
89
+ setStatus({
90
+ tone: "positive",
91
+ message: `Restored from ${confirmRow.label}. Pre-restore backup saved as ${body.preRestoreSnapshot}.`
92
+ });
93
+ await fetchList();
94
+ } else {
95
+ setStatus({ tone: "negative", message: (body == null ? void 0 : body.error) || "Restore failed" });
96
+ }
97
+ } catch (e) {
98
+ setStatus({ tone: "negative", message: e.message || "Restore failed" });
99
+ } finally {
100
+ setRestoring(false);
101
+ setConfirmRow(null);
102
+ }
103
+ };
104
+ return /* @__PURE__ */ jsxs(View, { padding: "size-400", UNSAFE_style: { background: PALETTE.bg, minHeight: "100vh" }, children: [
105
+ /* @__PURE__ */ jsx(Heading, { level: 2, marginTop: 0, children: "Snapshots" }),
106
+ /* @__PURE__ */ jsx(Text, { UNSAFE_style: { color: PALETTE.textMuted }, children: "Each snapshot captures the entire schema + every value row. Restore replays a snapshot wholesale \u2014 the current state is automatically backed up first so a restore is itself reversible." }),
107
+ status.message && /* @__PURE__ */ jsx(View, { marginTop: "size-150", children: /* @__PURE__ */ jsx(StatusLight, { variant: status.tone, children: status.message }) }),
108
+ error && /* @__PURE__ */ jsx(Well, { marginTop: "size-150", UNSAFE_style: { borderColor: PALETTE.danger }, children: /* @__PURE__ */ jsx(Text, { UNSAFE_style: { color: PALETTE.danger }, children: error }) }),
109
+ /* @__PURE__ */ jsx(
110
+ View,
111
+ {
112
+ marginTop: "size-200",
113
+ padding: "size-200",
114
+ UNSAFE_style: { background: PALETTE.surface, border: `1px solid ${PALETTE.border}`, borderRadius: RADIUS.lg, boxShadow: SHADOW.xs },
115
+ children: /* @__PURE__ */ jsxs(Flex, { gap: "size-150", alignItems: "end", wrap: true, children: [
116
+ /* @__PURE__ */ jsx(
117
+ TextField,
118
+ {
119
+ label: "Snapshot label (optional)",
120
+ value: newLabel,
121
+ onChange: setNewLabel,
122
+ width: "size-4600",
123
+ placeholder: "e.g. before-2026-Q2-release"
124
+ }
125
+ ),
126
+ /* @__PURE__ */ jsx(Button, { variant: "cta", onPress: doCreate, isDisabled: creating || loading, children: creating ? "Saving\u2026" : "Save snapshot now" }),
127
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onPress: fetchList, isDisabled: loading, children: "Reload" })
128
+ ] })
129
+ }
130
+ ),
131
+ /* @__PURE__ */ jsxs(
132
+ View,
133
+ {
134
+ marginTop: "size-200",
135
+ UNSAFE_style: { background: PALETTE.surface, border: `1px solid ${PALETTE.border}`, borderRadius: RADIUS.lg, boxShadow: SHADOW.xs, overflow: "hidden" },
136
+ children: [
137
+ /* @__PURE__ */ jsxs("div", { style: {
138
+ display: "grid",
139
+ gridTemplateColumns: "minmax(180px, 1fr) minmax(140px, 200px) minmax(160px, 220px) 110px 110px 120px",
140
+ padding: "12px 16px",
141
+ gap: 12,
142
+ background: PALETTE.surfaceMuted,
143
+ fontSize: 11,
144
+ fontWeight: 700,
145
+ letterSpacing: 0.6,
146
+ textTransform: "uppercase",
147
+ color: PALETTE.textMuted,
148
+ borderBottom: `1px solid ${PALETTE.border}`
149
+ }, children: [
150
+ /* @__PURE__ */ jsx("div", { children: "Label" }),
151
+ /* @__PURE__ */ jsx("div", { children: "Created at" }),
152
+ /* @__PURE__ */ jsx("div", { children: "Created by" }),
153
+ /* @__PURE__ */ jsx("div", { children: "Values" }),
154
+ /* @__PURE__ */ jsx("div", { children: "Sections" }),
155
+ /* @__PURE__ */ jsx("div", { children: "Action" })
156
+ ] }),
157
+ loading && items.length === 0 ? /* @__PURE__ */ jsx(Flex, { justifyContent: "center", margin: "size-400", children: /* @__PURE__ */ jsx(ProgressCircle, { "aria-label": "Loading", isIndeterminate: true }) }) : items.length === 0 ? /* @__PURE__ */ jsx(View, { padding: "size-400", children: /* @__PURE__ */ jsx(Text, { UNSAFE_style: { color: PALETTE.textMuted }, children: "No snapshots yet." }) }) : items.map((row, i) => {
158
+ var _a2, _b2, _c2, _d2;
159
+ return /* @__PURE__ */ jsxs(
160
+ "div",
161
+ {
162
+ style: {
163
+ display: "grid",
164
+ gridTemplateColumns: "minmax(180px, 1fr) minmax(140px, 200px) minmax(160px, 220px) 110px 110px 120px",
165
+ padding: "12px 16px",
166
+ gap: 12,
167
+ borderBottom: `1px solid ${PALETTE.border}`,
168
+ fontSize: 13,
169
+ background: i % 2 === 0 ? PALETTE.surface : PALETTE.surfaceSubtle,
170
+ alignItems: "center"
171
+ },
172
+ children: [
173
+ /* @__PURE__ */ jsxs("div", { style: { wordBreak: "break-word" }, children: [
174
+ /* @__PURE__ */ jsx("div", { style: { fontWeight: 600 }, children: row.label }),
175
+ /* @__PURE__ */ jsx("div", { style: { color: PALETTE.textMuted, fontSize: 11, fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" }, children: row._id })
176
+ ] }),
177
+ /* @__PURE__ */ jsx("div", { style: { color: PALETTE.textMuted, fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 }, children: row.createdAt ? new Date(row.createdAt).toLocaleString() : "" }),
178
+ /* @__PURE__ */ jsx("div", { style: { color: PALETTE.textMuted, wordBreak: "break-word" }, children: row.createdBy || "system" }),
179
+ /* @__PURE__ */ jsx("div", { children: (_b2 = (_a2 = row.counts) == null ? void 0 : _a2.values) != null ? _b2 : "?" }),
180
+ /* @__PURE__ */ jsx("div", { children: (_d2 = (_c2 = row.counts) == null ? void 0 : _c2.sections) != null ? _d2 : "?" }),
181
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, { variant: "secondary", onPress: () => setConfirmRow(row), isDisabled: restoring, children: "Restore" }) })
182
+ ]
183
+ },
184
+ row._id
185
+ );
186
+ })
187
+ ]
188
+ }
189
+ ),
190
+ /* @__PURE__ */ jsxs(DialogTrigger, { isOpen: !!confirmRow, onOpenChange: (o) => {
191
+ if (!o) setConfirmRow(null);
192
+ }, children: [
193
+ /* @__PURE__ */ jsx("div", { style: { display: "none" }, "aria-hidden": "true", children: "trigger" }),
194
+ /* @__PURE__ */ jsxs(Dialog, { children: [
195
+ /* @__PURE__ */ jsx(Heading, { children: "Restore this snapshot?" }),
196
+ /* @__PURE__ */ jsx(Header, { children: /* @__PURE__ */ jsx(Text, { children: confirmRow == null ? void 0 : confirmRow.label }) }),
197
+ /* @__PURE__ */ jsx(Divider, {}),
198
+ /* @__PURE__ */ jsxs(Content, { children: [
199
+ /* @__PURE__ */ jsxs(Text, { children: [
200
+ "The current state will be saved as a backup snapshot, then every value row will be replaced with the snapshot's contents.",
201
+ " ",
202
+ (_b = (_a = confirmRow == null ? void 0 : confirmRow.counts) == null ? void 0 : _a.values) != null ? _b : "?",
203
+ " value rows and",
204
+ " ",
205
+ (_d = (_c = confirmRow == null ? void 0 : confirmRow.counts) == null ? void 0 : _c.sections) != null ? _d : "?",
206
+ " schema sections."
207
+ ] }),
208
+ /* @__PURE__ */ jsx(View, { marginTop: "size-200", children: /* @__PURE__ */ jsx(Switch, { isSelected: restoreSchema, onChange: setRestoreSchema, children: "Restore schema too (uncheck to keep current schema, restore values only)" }) })
209
+ ] }),
210
+ /* @__PURE__ */ jsxs(ButtonGroup, { children: [
211
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onPress: () => setConfirmRow(null), isDisabled: restoring, children: "Cancel" }),
212
+ /* @__PURE__ */ jsx(Button, { variant: "cta", onPress: doRestore, isDisabled: restoring, children: restoring ? "Restoring\u2026" : "Restore" })
213
+ ] })
214
+ ] })
215
+ ] })
216
+ ] });
217
+ }
218
+
219
+ // web/index.js
220
+ function registerSnapshots() {
221
+ configureWeb({
222
+ actionKeys: {
223
+ systemConfigSnapshotCreate: "Snapshots/system-config-snapshot-create",
224
+ systemConfigSnapshotList: "Snapshots/system-config-snapshot-list",
225
+ systemConfigSnapshotRestore: "Snapshots/system-config-snapshot-restore"
226
+ },
227
+ extraNav: [{
228
+ id: "snapshots",
229
+ path: "/snapshots",
230
+ label: "Snapshots",
231
+ icon: "Box"
232
+ }],
233
+ extraPages: { snapshots: SnapshotHistory }
234
+ });
235
+ }
236
+ export {
237
+ SnapshotHistory,
238
+ registerSnapshots as default
239
+ };