@artinstack/migrator 0.1.17 → 0.1.18
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 +10 -1
- package/dist/{chunk-RLMYUUNK.js → chunk-PZADAGBA.js} +157 -9
- package/dist/chunk-PZADAGBA.js.map +1 -0
- package/dist/cli/index.js +1 -1
- package/dist/index.d.ts +93 -4
- package/dist/index.js +13 -1
- package/package.json +2 -2
- package/dist/chunk-RLMYUUNK.js.map +0 -1
package/README.md
CHANGED
|
@@ -4,6 +4,15 @@ Stateless content normalizer and migration framework for transforming **WordPres
|
|
|
4
4
|
|
|
5
5
|
Portable parsers and JSON export are useful without any specific host. Job orchestration, credentials, and UI are implemented separately via `MigrationSink`.
|
|
6
6
|
|
|
7
|
+
## Used by [ArtInStack](https://www.artinstack.com) (as an optional host)
|
|
8
|
+
|
|
9
|
+
**MIT-licensed OSS** — use the CLI, export normalized JSON, or implement `MigrationSink` for **any** CMS or product. No ArtInStack account required.
|
|
10
|
+
|
|
11
|
+
[ArtInStack](https://www.artinstack.com) is one production integration: the creative operating system for photographers and studios (websites, portfolios, client delivery, commerce, and courses). The platform’s import UI runs this package under the hood.
|
|
12
|
+
|
|
13
|
+
- **Any host** — parsers, normalizer DTOs, conflict analysis, `MigrationSink` contract, and `artinstack-migrate` CLI (dry-run, filesystem export, custom sinks).
|
|
14
|
+
- **ArtInStack product** — workspace owners use **Settings → Import Site**: scan WordPress WXR, preview entities, confirm import, review reports. [Customer docs](https://www.artinstack.com/docs/platform-migration/platform-migration-overview) cover export formats, SEO redirects, and cutover.
|
|
15
|
+
|
|
7
16
|
See [docs/architecture.md](./docs/architecture.md) for the high-level blueprint: data flow, DTOs, sink contract, and source mappings.
|
|
8
17
|
|
|
9
18
|
## Package layout
|
|
@@ -192,7 +201,7 @@ pnpm dev # watch build
|
|
|
192
201
|
| CLI + filesystem JSON export | Yes | No |
|
|
193
202
|
| `MigrationSink` interface | Yes | Implementation |
|
|
194
203
|
| Map `data-wp-widget` stubs → platform blocks; populate slider heroes from hints | No | Yes |
|
|
195
|
-
| Jobs, worker, credentials, UI | No |
|
|
204
|
+
| Jobs, worker, credentials, UI | No | Your host — [example: ArtInStack Import Site](https://www.artinstack.com/docs/platform-migration/platform-migration-overview) |
|
|
196
205
|
|
|
197
206
|
## License
|
|
198
207
|
|
|
@@ -607,9 +607,11 @@ var SMUGMUG_OAUTH_ENDPOINTS = {
|
|
|
607
607
|
authorize: "https://api.smugmug.com/services/oauth/1.0a/authorize",
|
|
608
608
|
accessToken: "https://api.smugmug.com/services/oauth/1.0a/getAccessToken"
|
|
609
609
|
};
|
|
610
|
-
var
|
|
610
|
+
var smugMugConsumerCredentialsSchema = z.object({
|
|
611
611
|
consumerKey: z.string().min(1),
|
|
612
|
-
consumerSecret: z.string().min(1)
|
|
612
|
+
consumerSecret: z.string().min(1)
|
|
613
|
+
});
|
|
614
|
+
var smugMugCredentialsSchema = smugMugConsumerCredentialsSchema.extend({
|
|
613
615
|
accessToken: z.string().min(1),
|
|
614
616
|
accessTokenSecret: z.string().min(1)
|
|
615
617
|
});
|
|
@@ -680,23 +682,28 @@ function signSmugMugOAuthRequest(input) {
|
|
|
680
682
|
oauthPercentEncode(normalizeRequestUrl(url)),
|
|
681
683
|
oauthPercentEncode(parameterString)
|
|
682
684
|
].join("&");
|
|
683
|
-
const
|
|
685
|
+
const tokenSecret = input.credentials.accessTokenSecret ?? "";
|
|
686
|
+
const signingKey = `${oauthPercentEncode(input.credentials.consumerSecret)}&${oauthPercentEncode(tokenSecret)}`;
|
|
684
687
|
return createHmac("sha1", signingKey).update(signatureBase).digest("base64");
|
|
685
688
|
}
|
|
686
|
-
function buildOAuthParams(credentials, nonce, timestamp) {
|
|
687
|
-
|
|
689
|
+
function buildOAuthParams(credentials, nonce, timestamp, extras) {
|
|
690
|
+
const params = {
|
|
688
691
|
oauth_consumer_key: credentials.consumerKey,
|
|
689
|
-
oauth_token: credentials.accessToken,
|
|
690
692
|
oauth_signature_method: "HMAC-SHA1",
|
|
691
693
|
oauth_timestamp: timestamp,
|
|
692
694
|
oauth_nonce: nonce,
|
|
693
|
-
oauth_version: "1.0"
|
|
695
|
+
oauth_version: "1.0",
|
|
696
|
+
...extras ?? {}
|
|
694
697
|
};
|
|
698
|
+
if (credentials.accessToken) {
|
|
699
|
+
params.oauth_token = credentials.accessToken;
|
|
700
|
+
}
|
|
701
|
+
return params;
|
|
695
702
|
}
|
|
696
703
|
function buildSmugMugAuthorizationHeader(input) {
|
|
697
704
|
const nonce = input.nonce ?? randomBytes(16).toString("hex");
|
|
698
705
|
const timestamp = input.timestamp ?? String(Math.floor(Date.now() / 1e3));
|
|
699
|
-
const oauthParams = buildOAuthParams(input.credentials, nonce, timestamp);
|
|
706
|
+
const oauthParams = buildOAuthParams(input.credentials, nonce, timestamp, input.oauthExtras);
|
|
700
707
|
const signature = signSmugMugOAuthRequest({
|
|
701
708
|
method: input.method,
|
|
702
709
|
url: input.url,
|
|
@@ -708,6 +715,141 @@ function buildSmugMugAuthorizationHeader(input) {
|
|
|
708
715
|
const headerValue = Object.keys(headerParams).sort().map((key) => `${oauthPercentEncode(key)}="${oauthPercentEncode(headerParams[key])}"`).join(", ");
|
|
709
716
|
return `OAuth ${headerValue}`;
|
|
710
717
|
}
|
|
718
|
+
function parseSmugMugOAuthFormBody(body) {
|
|
719
|
+
const params = new URLSearchParams(body.trim());
|
|
720
|
+
const out = {};
|
|
721
|
+
params.forEach((value, key) => {
|
|
722
|
+
out[key] = value;
|
|
723
|
+
});
|
|
724
|
+
return out;
|
|
725
|
+
}
|
|
726
|
+
function requireOAuthFormField(fields, name) {
|
|
727
|
+
const value = fields[name]?.trim();
|
|
728
|
+
if (!value) {
|
|
729
|
+
throw new Error(`SmugMug OAuth response missing ${name}`);
|
|
730
|
+
}
|
|
731
|
+
return value;
|
|
732
|
+
}
|
|
733
|
+
async function postSmugMugOAuthTokenEndpoint(input) {
|
|
734
|
+
const fetchImpl = input.fetchImpl ?? fetch;
|
|
735
|
+
const authorization = buildSmugMugAuthorizationHeader({
|
|
736
|
+
method: "POST",
|
|
737
|
+
url: input.url,
|
|
738
|
+
credentials: input.credentials,
|
|
739
|
+
oauthExtras: input.oauthExtras,
|
|
740
|
+
nonce: input.nonce,
|
|
741
|
+
timestamp: input.timestamp
|
|
742
|
+
});
|
|
743
|
+
const response = await fetchImpl(input.url, {
|
|
744
|
+
method: "POST",
|
|
745
|
+
headers: {
|
|
746
|
+
Accept: "application/x-www-form-urlencoded",
|
|
747
|
+
Authorization: authorization
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
const body = await response.text();
|
|
751
|
+
if (!response.ok) {
|
|
752
|
+
throw new Error(
|
|
753
|
+
`SmugMug OAuth HTTP ${response.status}${body ? `: ${body.slice(0, 200)}` : ""}`
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
return parseSmugMugOAuthFormBody(body);
|
|
757
|
+
}
|
|
758
|
+
async function getSmugMugRequestToken(input) {
|
|
759
|
+
const consumer = smugMugConsumerCredentialsSchema.parse(input.consumer);
|
|
760
|
+
const callbackUrl = input.callbackUrl.trim();
|
|
761
|
+
if (!callbackUrl) {
|
|
762
|
+
throw new Error('SmugMug OAuth callbackUrl is required (use a URL or "oob")');
|
|
763
|
+
}
|
|
764
|
+
const fields = await postSmugMugOAuthTokenEndpoint({
|
|
765
|
+
url: SMUGMUG_OAUTH_ENDPOINTS.requestToken,
|
|
766
|
+
credentials: {
|
|
767
|
+
consumerKey: consumer.consumerKey,
|
|
768
|
+
consumerSecret: consumer.consumerSecret,
|
|
769
|
+
accessTokenSecret: ""
|
|
770
|
+
},
|
|
771
|
+
oauthExtras: { oauth_callback: callbackUrl },
|
|
772
|
+
fetchImpl: input.fetchImpl,
|
|
773
|
+
nonce: input.nonce,
|
|
774
|
+
timestamp: input.timestamp
|
|
775
|
+
});
|
|
776
|
+
return {
|
|
777
|
+
token: requireOAuthFormField(fields, "oauth_token"),
|
|
778
|
+
tokenSecret: requireOAuthFormField(fields, "oauth_token_secret"),
|
|
779
|
+
callbackConfirmed: fields.oauth_callback_confirmed === "true"
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
function buildSmugMugAuthorizeUrl(options) {
|
|
783
|
+
const requestToken = options.requestToken.trim();
|
|
784
|
+
if (!requestToken) {
|
|
785
|
+
throw new Error("SmugMug authorize URL requires requestToken");
|
|
786
|
+
}
|
|
787
|
+
const url = new URL(SMUGMUG_OAUTH_ENDPOINTS.authorize);
|
|
788
|
+
url.searchParams.set("oauth_token", requestToken);
|
|
789
|
+
url.searchParams.set("Access", options.access ?? "Full");
|
|
790
|
+
url.searchParams.set("Permissions", options.permissions ?? "Modify");
|
|
791
|
+
if (options.allowThirdPartyLogin !== void 0) {
|
|
792
|
+
url.searchParams.set("allowThirdPartyLogin", options.allowThirdPartyLogin ? "1" : "0");
|
|
793
|
+
}
|
|
794
|
+
if (options.showSignUpButton !== void 0) {
|
|
795
|
+
url.searchParams.set("showSignUpButton", options.showSignUpButton ? "true" : "false");
|
|
796
|
+
}
|
|
797
|
+
if (options.username?.trim()) {
|
|
798
|
+
url.searchParams.set("username", options.username.trim());
|
|
799
|
+
}
|
|
800
|
+
return url.toString();
|
|
801
|
+
}
|
|
802
|
+
async function getSmugMugAccessToken(input) {
|
|
803
|
+
const consumer = smugMugConsumerCredentialsSchema.parse(input.consumer);
|
|
804
|
+
const token = input.requestToken.token.trim();
|
|
805
|
+
const tokenSecret = input.requestToken.tokenSecret;
|
|
806
|
+
const verifier = input.verifier.trim();
|
|
807
|
+
if (!token || tokenSecret === void 0 || tokenSecret === null) {
|
|
808
|
+
throw new Error("SmugMug access-token exchange requires request token + secret");
|
|
809
|
+
}
|
|
810
|
+
if (!verifier) {
|
|
811
|
+
throw new Error("SmugMug access-token exchange requires oauth_verifier");
|
|
812
|
+
}
|
|
813
|
+
const fields = await postSmugMugOAuthTokenEndpoint({
|
|
814
|
+
url: SMUGMUG_OAUTH_ENDPOINTS.accessToken,
|
|
815
|
+
credentials: {
|
|
816
|
+
consumerKey: consumer.consumerKey,
|
|
817
|
+
consumerSecret: consumer.consumerSecret,
|
|
818
|
+
accessToken: token,
|
|
819
|
+
accessTokenSecret: tokenSecret
|
|
820
|
+
},
|
|
821
|
+
oauthExtras: { oauth_verifier: verifier },
|
|
822
|
+
fetchImpl: input.fetchImpl,
|
|
823
|
+
nonce: input.nonce,
|
|
824
|
+
timestamp: input.timestamp
|
|
825
|
+
});
|
|
826
|
+
return {
|
|
827
|
+
token: requireOAuthFormField(fields, "oauth_token"),
|
|
828
|
+
tokenSecret: requireOAuthFormField(fields, "oauth_token_secret")
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
async function createSmugMugOAuthSession(input) {
|
|
832
|
+
const request = await getSmugMugRequestToken({
|
|
833
|
+
consumer: {
|
|
834
|
+
consumerKey: input.consumerKey,
|
|
835
|
+
consumerSecret: input.consumerSecret
|
|
836
|
+
},
|
|
837
|
+
callbackUrl: input.callbackUrl,
|
|
838
|
+
fetchImpl: input.fetchImpl,
|
|
839
|
+
nonce: input.nonce,
|
|
840
|
+
timestamp: input.timestamp
|
|
841
|
+
});
|
|
842
|
+
return {
|
|
843
|
+
requestToken: request.token,
|
|
844
|
+
requestTokenSecret: request.tokenSecret,
|
|
845
|
+
authorizeUrl: buildSmugMugAuthorizeUrl({
|
|
846
|
+
requestToken: request.token,
|
|
847
|
+
access: input.access,
|
|
848
|
+
permissions: input.permissions
|
|
849
|
+
}),
|
|
850
|
+
callbackConfirmed: request.callbackConfirmed
|
|
851
|
+
};
|
|
852
|
+
}
|
|
711
853
|
function readSmugMugCredentialsFromEnv(env = process.env) {
|
|
712
854
|
return smugMugCredentialsSchema.parse({
|
|
713
855
|
consumerKey: env.SMUGMUG_CONSUMER_KEY,
|
|
@@ -2557,9 +2699,15 @@ export {
|
|
|
2557
2699
|
wordpressAdapter,
|
|
2558
2700
|
SMUGMUG_API_BASE,
|
|
2559
2701
|
SMUGMUG_OAUTH_ENDPOINTS,
|
|
2702
|
+
smugMugConsumerCredentialsSchema,
|
|
2560
2703
|
smugMugCredentialsSchema,
|
|
2561
2704
|
signSmugMugOAuthRequest,
|
|
2562
2705
|
buildSmugMugAuthorizationHeader,
|
|
2706
|
+
parseSmugMugOAuthFormBody,
|
|
2707
|
+
getSmugMugRequestToken,
|
|
2708
|
+
buildSmugMugAuthorizeUrl,
|
|
2709
|
+
getSmugMugAccessToken,
|
|
2710
|
+
createSmugMugOAuthSession,
|
|
2563
2711
|
readSmugMugCredentialsFromEnv,
|
|
2564
2712
|
SmugMugApiClient,
|
|
2565
2713
|
smugmugAdapter,
|
|
@@ -2569,4 +2717,4 @@ export {
|
|
|
2569
2717
|
wixAdapter,
|
|
2570
2718
|
getAdapter
|
|
2571
2719
|
};
|
|
2572
|
-
//# sourceMappingURL=chunk-
|
|
2720
|
+
//# sourceMappingURL=chunk-PZADAGBA.js.map
|