@woodsportal/hubspot-kit 1.0.31 → 1.0.34
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/CHANGELOG.md +45 -0
- package/dist/cli.js +798 -281
- package/dist/cli.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/examples/fixtures/adopted-module-hybrid/woodscli.json +1 -1
- package/examples/fixtures/adopted-module-monolith/woodscli.json +1 -1
- package/examples/greenfield/module-hybrid-cdn/public/fields.json +26 -20
- package/examples/greenfield/module-hybrid-cdn/public/meta.json +9 -0
- package/examples/greenfield/module-hybrid-cdn/public/module.html +3 -4
- package/examples/greenfield/module-hybrid-cdn/src/assets/css/main.prod.css +6 -0
- package/examples/greenfield/module-hybrid-cdn/src/cdn/app-entry.ts +5 -0
- package/examples/greenfield/module-hybrid-cdn/src/hubspot/bootstrap.ts +6 -0
- package/examples/greenfield/module-hybrid-cdn/src/portal/ensure-portal-cdn-stylesheet.ts +58 -0
- package/examples/greenfield/module-hybrid-cdn/woodscli.json +1 -1
- package/examples/greenfield/module-monolith/woodscli.json +1 -1
- package/package.json +2 -2
- package/scripts/build-cdn.mjs +6 -1
- package/scripts/bump-module-build-id.mjs +28 -0
- package/scripts/hubspot-module-postbuild.mjs +35 -1
- package/scripts/load-portal-cdn-config.mjs +21 -3
- package/scripts/portal-build-id.mjs +346 -0
- package/scripts/portal-build-id.test.mjs +132 -0
- package/scripts/publish-cdn-github.mjs +60 -15
- package/scripts/resolve-cdn-base-url.mjs +2 -1
- package/scripts/resolve-module-build-id-meta.mjs +7 -0
- package/scripts/resolve-portal-build-id.mjs +16 -56
- package/scripts/tailwind/prefix-plugin.js +46 -8
- package/scripts/tailwind/tailwind-content-plugin.js +0 -1
- package/scripts/vite/portal-build-shared.js +3 -1
- package/src/dev-module-config/__tests__/flatten-visible-fields.test.ts +107 -0
- package/src/dev-module-config/runtime/module-config-bridge.ts +10 -2
- package/src/dev-module-config/schema/types.ts +10 -0
- package/src/dev-module-config/schema/visibility.ts +42 -4
- package/src/dev-module-config/ui/ModuleConfigShell.tsx +10 -9
- package/src/dev-module-config/ui/hubspot/HsOccurrenceGroupEditor.tsx +3 -3
- package/templates/module-hybrid-cdn/public/fields.json +52 -41
- package/templates/module-hybrid-cdn/public/meta.json +9 -0
- package/templates/module-hybrid-cdn/public/module.html +18 -4
- package/templates/module-hybrid-cdn/src/assets/css/main.dev.css +8 -0
- package/templates/module-hybrid-cdn/src/assets/css/main.prod.css +12 -0
- package/templates/module-hybrid-cdn/src/assets/css/tailwind.css.config.css +7 -0
- package/templates/module-hybrid-cdn/src/cdn/app-entry.ts +14 -3
- package/templates/module-hybrid-cdn/src/features/starter/components/starter-home.tsx +9 -11
- package/templates/module-hybrid-cdn/src/features/starter/hooks/starter-preview-values.ts +23 -5
- package/templates/module-hybrid-cdn/src/features/starter/hooks/use-starter-preview.dev.ts +1 -0
- package/templates/module-hybrid-cdn/src/hubspot/bootstrap.ts +6 -0
- package/templates/module-hybrid-cdn/src/portal/ensure-portal-cdn-stylesheet.ts +115 -0
- package/templates/module-hybrid-cdn/src/portal/mount-portal.tsx +35 -5
- package/templates/module-hybrid-cdn/woodscli.json +1 -1
- package/templates/module-monolith/public/fields.json +23 -16
- package/templates/module-monolith/public/meta.json +9 -0
- package/templates/module-monolith/src/features/starter/components/starter-home.tsx +9 -11
- package/templates/module-monolith/src/features/starter/hooks/starter-preview-values.ts +23 -5
- package/templates/module-monolith/src/features/starter/hooks/use-starter-preview.dev.ts +1 -0
- package/templates/module-monolith/woodscli.json +1 -1
- package/vite/vite.config.dev.mjs +16 -10
package/dist/cli.js
CHANGED
|
@@ -11,8 +11,8 @@ import {
|
|
|
11
11
|
} from "./chunk-4GOJPOW3.js";
|
|
12
12
|
|
|
13
13
|
// src/cli/program.ts
|
|
14
|
-
import { readFileSync as
|
|
15
|
-
import { resolve as
|
|
14
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
15
|
+
import { resolve as resolve24 } from "path";
|
|
16
16
|
import { Command } from "commander";
|
|
17
17
|
|
|
18
18
|
// src/lib/config.ts
|
|
@@ -90,7 +90,7 @@ var WoodsPortalHsSchema = z.object({
|
|
|
90
90
|
}).optional(),
|
|
91
91
|
cdn: z.object({
|
|
92
92
|
mode: z.enum(["jsdelivr", "custom", "local", "none"]).default("jsdelivr"),
|
|
93
|
-
buildIdFile: z.string().default(".
|
|
93
|
+
buildIdFile: z.string().default(".module-build-id"),
|
|
94
94
|
mirror: z.string().default("portal.cdn.json")
|
|
95
95
|
}).default({}),
|
|
96
96
|
entries: z.object({
|
|
@@ -125,7 +125,7 @@ var DEFAULT_WOODSPORTAL_HS = {
|
|
|
125
125
|
},
|
|
126
126
|
cdn: {
|
|
127
127
|
mode: "jsdelivr",
|
|
128
|
-
buildIdFile: ".
|
|
128
|
+
buildIdFile: ".module-build-id",
|
|
129
129
|
mirror: "portal.cdn.json"
|
|
130
130
|
},
|
|
131
131
|
entries: {
|
|
@@ -302,8 +302,8 @@ function defaultBaselineDir(projectRoot, config) {
|
|
|
302
302
|
}
|
|
303
303
|
|
|
304
304
|
// src/cli/commands/doctor.ts
|
|
305
|
-
import { existsSync as
|
|
306
|
-
import { join, resolve as
|
|
305
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
|
|
306
|
+
import { join, resolve as resolve8 } from "path";
|
|
307
307
|
|
|
308
308
|
// src/lib/cli-spinner.ts
|
|
309
309
|
import * as p from "@clack/prompts";
|
|
@@ -321,11 +321,11 @@ function isVerboseStepLog(ctx) {
|
|
|
321
321
|
return ctx.global.verbose === true && !ctx.global.quiet && !ctx.global.json;
|
|
322
322
|
}
|
|
323
323
|
async function yieldForSpinnerPaint() {
|
|
324
|
-
await new Promise((
|
|
325
|
-
setImmediate(
|
|
324
|
+
await new Promise((resolve25) => {
|
|
325
|
+
setImmediate(resolve25);
|
|
326
326
|
});
|
|
327
|
-
await new Promise((
|
|
328
|
-
setTimeout(
|
|
327
|
+
await new Promise((resolve25) => {
|
|
328
|
+
setTimeout(resolve25, 48);
|
|
329
329
|
});
|
|
330
330
|
}
|
|
331
331
|
async function runCliTasks(ctx, tasks, options = {}) {
|
|
@@ -653,6 +653,13 @@ function validateFieldNode(node, path, issues) {
|
|
|
653
653
|
message: type === "textarea" ? `'textarea' is not a HubSpot field type \u2014 use "type": "text" with "display": "textarea"` : `'${type}' is not a valid HubSpot field type`
|
|
654
654
|
});
|
|
655
655
|
}
|
|
656
|
+
const tab = node.tab;
|
|
657
|
+
if (tab === "STYLE" && type !== "group") {
|
|
658
|
+
issues.push({
|
|
659
|
+
path: `${path}.tab`,
|
|
660
|
+
message: 'STYLE tab fields must live inside a "type": "group" with "tab": "STYLE" (HubSpot page editor Styles tab)'
|
|
661
|
+
});
|
|
662
|
+
}
|
|
656
663
|
if (type !== "group") {
|
|
657
664
|
const name = node.name;
|
|
658
665
|
if (typeof name !== "string" || name.trim().length === 0) {
|
|
@@ -684,14 +691,148 @@ function validateHubSpotFieldsJson(fields) {
|
|
|
684
691
|
return issues;
|
|
685
692
|
}
|
|
686
693
|
|
|
687
|
-
// src/lib/
|
|
688
|
-
import { existsSync as existsSync4 } from "fs";
|
|
694
|
+
// src/lib/developer-slug.ts
|
|
695
|
+
import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync4, writeFileSync } from "fs";
|
|
689
696
|
import { resolve as resolve5 } from "path";
|
|
697
|
+
import * as p2 from "@clack/prompts";
|
|
698
|
+
var SLUG_MAX_LEN = 12;
|
|
699
|
+
var SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,10}[a-z0-9])?$/;
|
|
700
|
+
var DEVELOPER_CONFIG_FILE = "developer.json";
|
|
701
|
+
function developerConfigPath(projectRoot) {
|
|
702
|
+
return resolve5(projectRoot, ".wphs", "config", DEVELOPER_CONFIG_FILE);
|
|
703
|
+
}
|
|
704
|
+
function sanitizeDevSlug(input) {
|
|
705
|
+
if (!input?.trim()) return void 0;
|
|
706
|
+
const slug = input.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, SLUG_MAX_LEN);
|
|
707
|
+
if (!slug || !SLUG_PATTERN.test(slug)) return void 0;
|
|
708
|
+
return slug;
|
|
709
|
+
}
|
|
710
|
+
function readDeveloperSlug(projectRoot) {
|
|
711
|
+
const fromEnv = sanitizeDevSlug(process.env.PORTAL_CDN_DEV_SLUG) ?? sanitizeDevSlug(process.env.MODULE_CDN_DEV_SLUG);
|
|
712
|
+
if (fromEnv) return fromEnv;
|
|
713
|
+
const path = developerConfigPath(projectRoot);
|
|
714
|
+
if (!existsSync4(path)) return void 0;
|
|
715
|
+
try {
|
|
716
|
+
const raw = JSON.parse(readFileSync4(path, "utf8"));
|
|
717
|
+
return sanitizeDevSlug(raw.slug);
|
|
718
|
+
} catch {
|
|
719
|
+
return void 0;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
function writeDeveloperSlug(projectRoot, slug) {
|
|
723
|
+
const normalized = sanitizeDevSlug(slug);
|
|
724
|
+
if (!normalized) {
|
|
725
|
+
throw new Error("Invalid CDN dev slug \u2014 use lowercase letters, numbers, and hyphens (max 12 chars)");
|
|
726
|
+
}
|
|
727
|
+
const path = developerConfigPath(projectRoot);
|
|
728
|
+
mkdirSync(resolve5(path, ".."), { recursive: true });
|
|
729
|
+
writeFileSync(path, `${JSON.stringify({ slug: normalized }, null, 2)}
|
|
730
|
+
`);
|
|
731
|
+
}
|
|
732
|
+
function needsDeveloperSlugSetup(projectRoot) {
|
|
733
|
+
return !readDeveloperSlug(projectRoot);
|
|
734
|
+
}
|
|
735
|
+
async function promptDeveloperSlug(projectRoot) {
|
|
736
|
+
if (!needsDeveloperSlugSetup(projectRoot)) {
|
|
737
|
+
return readDeveloperSlug(projectRoot);
|
|
738
|
+
}
|
|
739
|
+
const slug = await p2.text({
|
|
740
|
+
message: "CDN dev slug (unique per developer for shared CDN mirror \u2014 e.g. your first name)",
|
|
741
|
+
placeholder: "manab",
|
|
742
|
+
validate: (value) => {
|
|
743
|
+
if (!sanitizeDevSlug(value)) {
|
|
744
|
+
return "Use 1\u201312 lowercase letters, numbers, or hyphens";
|
|
745
|
+
}
|
|
746
|
+
return void 0;
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
if (p2.isCancel(slug) || typeof slug !== "string") {
|
|
750
|
+
return void 0;
|
|
751
|
+
}
|
|
752
|
+
writeDeveloperSlug(projectRoot, slug);
|
|
753
|
+
return sanitizeDevSlug(slug);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// src/lib/module-build-id.ts
|
|
757
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
758
|
+
function runKitScript(script, args, options) {
|
|
759
|
+
const result = spawnSync2(process.execPath, [kitPath("scripts", script), ...args], {
|
|
760
|
+
cwd: options.cwd,
|
|
761
|
+
env: {
|
|
762
|
+
...options.env,
|
|
763
|
+
WP_HS_PROJECT_ROOT: options.cwd
|
|
764
|
+
},
|
|
765
|
+
encoding: "utf8"
|
|
766
|
+
});
|
|
767
|
+
if (result.status !== 0) {
|
|
768
|
+
const stderr = result.stderr?.trim() || result.stdout?.trim();
|
|
769
|
+
throw new Error(stderr || `Failed running ${script}`);
|
|
770
|
+
}
|
|
771
|
+
return (result.stdout ?? "").trim();
|
|
772
|
+
}
|
|
773
|
+
function bumpModuleBuildIdForCli(projectRoot, options = {}) {
|
|
774
|
+
const args = [...options.tier ? [`--tier=${options.tier}`] : [], ...options.dryRun ? ["--dry-run"] : []];
|
|
775
|
+
const stdout = runKitScript("bump-module-build-id.mjs", args, {
|
|
776
|
+
cwd: projectRoot,
|
|
777
|
+
env: options.env ?? process.env,
|
|
778
|
+
dryRun: options.dryRun
|
|
779
|
+
});
|
|
780
|
+
return JSON.parse(stdout);
|
|
781
|
+
}
|
|
782
|
+
function resolveModuleBuildIdMetaForCli(projectRoot, env = process.env) {
|
|
783
|
+
const stdout = runKitScript("resolve-module-build-id-meta.mjs", [], {
|
|
784
|
+
cwd: projectRoot,
|
|
785
|
+
env
|
|
786
|
+
});
|
|
787
|
+
return JSON.parse(stdout);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// src/lib/upload-only-module.ts
|
|
791
|
+
import { existsSync as existsSync5 } from "fs";
|
|
792
|
+
import { resolve as resolve6 } from "path";
|
|
690
793
|
function isUploadOnlyPreexistingProject(projectRoot, moduleArtifactDirs) {
|
|
691
794
|
if (moduleArtifactDirs.length === 0) return false;
|
|
692
|
-
if (
|
|
693
|
-
if (
|
|
694
|
-
return moduleArtifactDirs.some((dir) =>
|
|
795
|
+
if (existsSync5(resolve6(projectRoot, "package.json"))) return false;
|
|
796
|
+
if (existsSync5(resolve6(projectRoot, "src"))) return false;
|
|
797
|
+
return moduleArtifactDirs.some((dir) => existsSync5(resolve6(dir, "fields.json")));
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// src/vite/resolve-dev-config.ts
|
|
801
|
+
import { existsSync as existsSync6 } from "fs";
|
|
802
|
+
import { platform } from "os";
|
|
803
|
+
import { resolve as resolve7 } from "path";
|
|
804
|
+
var CONSUMER_DEV_CONFIG = "vite.config.dev.mjs";
|
|
805
|
+
var VITE_JS_SEGMENTS = ["node_modules", "vite", "bin", "vite.js"];
|
|
806
|
+
function resolveDevViteConfigPath(projectRoot) {
|
|
807
|
+
const consumerConfig = resolve7(projectRoot, CONSUMER_DEV_CONFIG);
|
|
808
|
+
if (existsSync6(consumerConfig)) {
|
|
809
|
+
return consumerConfig;
|
|
810
|
+
}
|
|
811
|
+
return kitPath("vite", "vite.config.dev.mjs");
|
|
812
|
+
}
|
|
813
|
+
function isProjectViteInstalled(projectRoot) {
|
|
814
|
+
return existsSync6(resolve7(projectRoot, ...VITE_JS_SEGMENTS));
|
|
815
|
+
}
|
|
816
|
+
function resolveProjectViteSpawn(projectRoot) {
|
|
817
|
+
const viteJs = resolve7(projectRoot, ...VITE_JS_SEGMENTS);
|
|
818
|
+
if (existsSync6(viteJs)) {
|
|
819
|
+
return { command: process.execPath, prefixArgs: [viteJs] };
|
|
820
|
+
}
|
|
821
|
+
const binDir = resolve7(projectRoot, "node_modules", ".bin");
|
|
822
|
+
if (platform() === "win32") {
|
|
823
|
+
const winCmd = resolve7(binDir, "vite.cmd");
|
|
824
|
+
if (existsSync6(winCmd)) {
|
|
825
|
+
return { command: winCmd, prefixArgs: [] };
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
const posixShim = resolve7(binDir, "vite");
|
|
829
|
+
if (existsSync6(posixShim)) {
|
|
830
|
+
return { command: posixShim, prefixArgs: [] };
|
|
831
|
+
}
|
|
832
|
+
return null;
|
|
833
|
+
}
|
|
834
|
+
function formatProjectViteSpawn(spawn2) {
|
|
835
|
+
return [spawn2.command, ...spawn2.prefixArgs].join(" ");
|
|
695
836
|
}
|
|
696
837
|
|
|
697
838
|
// src/cli/commands/doctor.ts
|
|
@@ -759,8 +900,8 @@ function checkConfig(ctx) {
|
|
|
759
900
|
function checkEnvFiles(ctx) {
|
|
760
901
|
return ctx.config.env.tiers.map((tier) => {
|
|
761
902
|
const file = tier === "dev" ? ".env.dev" : tier === "stg" ? ".env.stg" : ".env.prod";
|
|
762
|
-
const path =
|
|
763
|
-
const ok =
|
|
903
|
+
const path = resolve8(ctx.projectRoot, file);
|
|
904
|
+
const ok = existsSync7(path);
|
|
764
905
|
return {
|
|
765
906
|
name: `env-${tier}`,
|
|
766
907
|
ok,
|
|
@@ -770,20 +911,76 @@ function checkEnvFiles(ctx) {
|
|
|
770
911
|
};
|
|
771
912
|
});
|
|
772
913
|
}
|
|
914
|
+
function checkLegacyBuildIdFile(ctx) {
|
|
915
|
+
if (isThemeProject(ctx.config) || !usesCdnPipeline(ctx.config)) return null;
|
|
916
|
+
const legacy = resolve8(ctx.projectRoot, ".portal-build-id");
|
|
917
|
+
if (!existsSync7(legacy)) return null;
|
|
918
|
+
return {
|
|
919
|
+
name: "legacy-build-id",
|
|
920
|
+
ok: false,
|
|
921
|
+
message: "Legacy .portal-build-id present \u2014 use .module-build-id",
|
|
922
|
+
remediation: "Run wp migrate --yes",
|
|
923
|
+
severity: "warn"
|
|
924
|
+
};
|
|
925
|
+
}
|
|
773
926
|
function checkBuildId(ctx) {
|
|
774
927
|
if (isThemeProject(ctx.config) || !usesCdnPipeline(ctx.config)) return null;
|
|
775
|
-
const file =
|
|
776
|
-
const ok =
|
|
928
|
+
const file = resolve8(ctx.projectRoot, ctx.config.cdn.buildIdFile);
|
|
929
|
+
const ok = existsSync7(file);
|
|
777
930
|
return {
|
|
778
931
|
name: "build-id",
|
|
779
932
|
ok,
|
|
780
933
|
message: ok ? `${ctx.config.cdn.buildIdFile} present` : `${ctx.config.cdn.buildIdFile} missing`,
|
|
781
|
-
remediation: ok ? void 0 : "wp setup (creates
|
|
934
|
+
remediation: ok ? void 0 : "wp setup (creates channel baseline e.g. v0.0.1-dev)",
|
|
935
|
+
severity: "warn"
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
function checkModuleBuildIdResolved(ctx) {
|
|
939
|
+
if (isThemeProject(ctx.config) || !usesCdnPipeline(ctx.config)) return null;
|
|
940
|
+
try {
|
|
941
|
+
const meta = resolveModuleBuildIdMetaForCli(ctx.projectRoot, process.env);
|
|
942
|
+
const detail = meta.source === "state" ? `${meta.id} (state: ${meta.slug} #${meta.seq})` : `${meta.id} (source: ${meta.source})`;
|
|
943
|
+
return {
|
|
944
|
+
name: "module-build-id",
|
|
945
|
+
ok: true,
|
|
946
|
+
message: `Effective module build id: ${detail}`,
|
|
947
|
+
severity: "warn"
|
|
948
|
+
};
|
|
949
|
+
} catch (error) {
|
|
950
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
951
|
+
return {
|
|
952
|
+
name: "module-build-id",
|
|
953
|
+
ok: false,
|
|
954
|
+
message: `Could not resolve module build id \u2014 ${message}`,
|
|
955
|
+
remediation: "Run wp setup and set a CDN dev slug (or PORTAL_CDN_DEV_SLUG)",
|
|
956
|
+
severity: "warn"
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
function checkDevSlug(ctx) {
|
|
961
|
+
if (isThemeProject(ctx.config) || !usesCdnPipeline(ctx.config)) return null;
|
|
962
|
+
if (process.env.PORTAL_BUILD_ID?.trim() || process.env.MODULE_BUILD_ID?.trim()) {
|
|
963
|
+
return null;
|
|
964
|
+
}
|
|
965
|
+
const slug = readDeveloperSlug(ctx.projectRoot);
|
|
966
|
+
if (slug) {
|
|
967
|
+
return {
|
|
968
|
+
name: "cdn-dev-slug",
|
|
969
|
+
ok: true,
|
|
970
|
+
message: `CDN dev slug: ${slug}`,
|
|
971
|
+
severity: "warn"
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
return {
|
|
975
|
+
name: "cdn-dev-slug",
|
|
976
|
+
ok: false,
|
|
977
|
+
message: "CDN dev slug not configured (shared CDN mirror needs per-developer ids)",
|
|
978
|
+
remediation: "Run wp setup, set PORTAL_CDN_DEV_SLUG, or configure git user.name for automatic slug",
|
|
782
979
|
severity: "warn"
|
|
783
980
|
};
|
|
784
981
|
}
|
|
785
982
|
function isUploadOnlyProject(ctx) {
|
|
786
|
-
const moduleDirs = [
|
|
983
|
+
const moduleDirs = [resolve8(ctx.projectRoot, ctx.config.module.uploadPath)];
|
|
787
984
|
return isUploadOnlyPreexistingProject(ctx.projectRoot, moduleDirs);
|
|
788
985
|
}
|
|
789
986
|
function checkUploadOnlyNote(ctx) {
|
|
@@ -797,8 +994,8 @@ function checkUploadOnlyNote(ctx) {
|
|
|
797
994
|
};
|
|
798
995
|
}
|
|
799
996
|
function checkDevViteConfig(ctx) {
|
|
800
|
-
const path =
|
|
801
|
-
const ok =
|
|
997
|
+
const path = resolve8(ctx.projectRoot, "vite.config.dev.mjs");
|
|
998
|
+
const ok = existsSync7(path);
|
|
802
999
|
return {
|
|
803
1000
|
name: "vite-dev-config",
|
|
804
1001
|
ok,
|
|
@@ -807,10 +1004,154 @@ function checkDevViteConfig(ctx) {
|
|
|
807
1004
|
severity: "error"
|
|
808
1005
|
};
|
|
809
1006
|
}
|
|
1007
|
+
function checkMetaJson(ctx) {
|
|
1008
|
+
if (isThemeProject(ctx.config)) return null;
|
|
1009
|
+
const path = resolve8(ctx.projectRoot, "public/meta.json");
|
|
1010
|
+
if (!existsSync7(path)) {
|
|
1011
|
+
return {
|
|
1012
|
+
name: "meta-json",
|
|
1013
|
+
ok: false,
|
|
1014
|
+
message: "public/meta.json missing",
|
|
1015
|
+
remediation: "Copy meta.json from @woodsportal/hubspot-kit templates (content_types + module label for page editor)",
|
|
1016
|
+
severity: "warn"
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
try {
|
|
1020
|
+
const meta = JSON.parse(readFileSync5(path, "utf8"));
|
|
1021
|
+
const hasContentTypes = Array.isArray(meta.content_types) && meta.content_types.length > 0;
|
|
1022
|
+
return {
|
|
1023
|
+
name: "meta-json",
|
|
1024
|
+
ok: hasContentTypes,
|
|
1025
|
+
message: hasContentTypes ? "public/meta.json includes content_types" : "public/meta.json missing content_types (module may not appear on site pages)",
|
|
1026
|
+
remediation: hasContentTypes ? void 0 : 'Add content_types: ["SITE_PAGE", "LANDING_PAGE", ...] \u2014 see templates/module-hybrid-cdn/public/meta.json',
|
|
1027
|
+
severity: "warn"
|
|
1028
|
+
};
|
|
1029
|
+
} catch (error) {
|
|
1030
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1031
|
+
return {
|
|
1032
|
+
name: "meta-json",
|
|
1033
|
+
ok: false,
|
|
1034
|
+
message: `public/meta.json is invalid JSON \u2014 ${message}`,
|
|
1035
|
+
remediation: "Fix JSON syntax in public/meta.json",
|
|
1036
|
+
severity: "warn"
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
async function checkCdnMirrorPublic(ctx) {
|
|
1041
|
+
if (isThemeProject(ctx.config) || !usesCdnPipeline(ctx.config)) return null;
|
|
1042
|
+
const mirrorPath = resolve8(ctx.projectRoot, ctx.config.cdn?.mirror ?? "portal.cdn.json");
|
|
1043
|
+
if (!existsSync7(mirrorPath)) {
|
|
1044
|
+
return {
|
|
1045
|
+
name: "cdn-mirror-public",
|
|
1046
|
+
ok: false,
|
|
1047
|
+
message: "portal.cdn.json missing (hybrid CDN publish config)",
|
|
1048
|
+
remediation: "Run wp setup or wp init to configure the public GitHub CDN mirror",
|
|
1049
|
+
severity: "error"
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
let github;
|
|
1053
|
+
try {
|
|
1054
|
+
const mirror = JSON.parse(readFileSync5(mirrorPath, "utf8"));
|
|
1055
|
+
github = mirror.github?.trim();
|
|
1056
|
+
} catch {
|
|
1057
|
+
return {
|
|
1058
|
+
name: "cdn-mirror-public",
|
|
1059
|
+
ok: false,
|
|
1060
|
+
message: "portal.cdn.json is invalid JSON",
|
|
1061
|
+
remediation: "Fix portal.cdn.json or re-run wp setup",
|
|
1062
|
+
severity: "error"
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
if (!github) {
|
|
1066
|
+
return {
|
|
1067
|
+
name: "cdn-mirror-public",
|
|
1068
|
+
ok: false,
|
|
1069
|
+
message: "portal.cdn.json missing github (org/repo)",
|
|
1070
|
+
remediation: "Set github to a public repo for jsDelivr (e.g. Digital-Woods/woodsportal-client-frontend-cdn)",
|
|
1071
|
+
severity: "error"
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
try {
|
|
1075
|
+
const res = await fetch(`https://api.github.com/repos/${github}`, {
|
|
1076
|
+
headers: { Accept: "application/vnd.github+json" }
|
|
1077
|
+
});
|
|
1078
|
+
if (res.status === 404) {
|
|
1079
|
+
return {
|
|
1080
|
+
name: "cdn-mirror-public",
|
|
1081
|
+
ok: false,
|
|
1082
|
+
message: `GitHub CDN repo ${github} not found`,
|
|
1083
|
+
remediation: "Create the repo or fix portal.cdn.json github slug",
|
|
1084
|
+
severity: "error"
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
const data = await res.json();
|
|
1088
|
+
if (data.private === true) {
|
|
1089
|
+
return {
|
|
1090
|
+
name: "cdn-mirror-public",
|
|
1091
|
+
ok: false,
|
|
1092
|
+
message: `CDN mirror ${github} is private \u2014 jsDelivr returns 404 (blank HubSpot preview)`,
|
|
1093
|
+
remediation: "Make the GitHub repo public (Settings \u2192 Change visibility) or point portal.cdn.json at a public mirror",
|
|
1094
|
+
severity: "error"
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
} catch (error) {
|
|
1098
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1099
|
+
return {
|
|
1100
|
+
name: "cdn-mirror-public",
|
|
1101
|
+
ok: false,
|
|
1102
|
+
message: `Could not verify CDN repo ${github} \u2014 ${message}`,
|
|
1103
|
+
severity: "warn"
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
const manifestPath = resolve8(ctx.projectRoot, "dist/cdn/manifest.json");
|
|
1107
|
+
if (!existsSync7(manifestPath)) {
|
|
1108
|
+
return {
|
|
1109
|
+
name: "cdn-mirror-public",
|
|
1110
|
+
ok: true,
|
|
1111
|
+
message: `CDN mirror ${github} is public (run wp build to verify jsDelivr URLs)`,
|
|
1112
|
+
severity: "error"
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
try {
|
|
1116
|
+
const manifest = JSON.parse(readFileSync5(manifestPath, "utf8"));
|
|
1117
|
+
const appUrl = manifest.scripts?.find((s) => s.name === "app")?.url;
|
|
1118
|
+
if (!appUrl) {
|
|
1119
|
+
return {
|
|
1120
|
+
name: "cdn-mirror-public",
|
|
1121
|
+
ok: true,
|
|
1122
|
+
message: `CDN mirror ${github} is public`,
|
|
1123
|
+
severity: "error"
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
const head = await fetch(appUrl, { method: "HEAD" });
|
|
1127
|
+
if (!head.ok) {
|
|
1128
|
+
return {
|
|
1129
|
+
name: "cdn-mirror-public",
|
|
1130
|
+
ok: false,
|
|
1131
|
+
message: `jsDelivr app chunk not reachable (HTTP ${head.status}) \u2014 HubSpot preview will be blank`,
|
|
1132
|
+
remediation: "Run wp deploy (publish CDN + upload module) after making the mirror repo public",
|
|
1133
|
+
severity: "error"
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
return {
|
|
1137
|
+
name: "cdn-mirror-public",
|
|
1138
|
+
ok: true,
|
|
1139
|
+
message: `CDN mirror ${github} is public and jsDelivr app chunk is reachable`,
|
|
1140
|
+
severity: "error"
|
|
1141
|
+
};
|
|
1142
|
+
} catch {
|
|
1143
|
+
return {
|
|
1144
|
+
name: "cdn-mirror-public",
|
|
1145
|
+
ok: true,
|
|
1146
|
+
message: `CDN mirror ${github} is public`,
|
|
1147
|
+
severity: "error"
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
810
1151
|
function checkFieldsJson(ctx) {
|
|
811
1152
|
if (isThemeProject(ctx.config)) return null;
|
|
812
|
-
const path =
|
|
813
|
-
if (!
|
|
1153
|
+
const path = resolve8(ctx.projectRoot, "public/fields.json");
|
|
1154
|
+
if (!existsSync7(path)) {
|
|
814
1155
|
return {
|
|
815
1156
|
name: "fields-json",
|
|
816
1157
|
ok: false,
|
|
@@ -820,7 +1161,7 @@ function checkFieldsJson(ctx) {
|
|
|
820
1161
|
};
|
|
821
1162
|
}
|
|
822
1163
|
try {
|
|
823
|
-
const fields = JSON.parse(
|
|
1164
|
+
const fields = JSON.parse(readFileSync5(path, "utf8"));
|
|
824
1165
|
const issues = validateHubSpotFieldsJson(fields);
|
|
825
1166
|
if (issues.length === 0) {
|
|
826
1167
|
return {
|
|
@@ -849,10 +1190,97 @@ function checkFieldsJson(ctx) {
|
|
|
849
1190
|
};
|
|
850
1191
|
}
|
|
851
1192
|
}
|
|
1193
|
+
function readProjectFile(projectRoot, relativePath) {
|
|
1194
|
+
const path = resolve8(projectRoot, relativePath);
|
|
1195
|
+
if (!existsSync7(path)) {
|
|
1196
|
+
return null;
|
|
1197
|
+
}
|
|
1198
|
+
return readFileSync5(path, "utf8");
|
|
1199
|
+
}
|
|
1200
|
+
function checkCssPipeline(ctx) {
|
|
1201
|
+
if (isThemeProject(ctx.config) || isUploadOnlyProject(ctx)) {
|
|
1202
|
+
return [];
|
|
1203
|
+
}
|
|
1204
|
+
const checks = [];
|
|
1205
|
+
const mainDev = readProjectFile(ctx.projectRoot, "src/assets/css/main.dev.css");
|
|
1206
|
+
const mainProd = readProjectFile(ctx.projectRoot, "src/assets/css/main.prod.css");
|
|
1207
|
+
const appEntry = readProjectFile(ctx.projectRoot, "src/cdn/app-entry.ts");
|
|
1208
|
+
if (mainDev) {
|
|
1209
|
+
const withoutComments = mainDev.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
|
|
1210
|
+
const hasPrefix = /@import\s+['"]tailwindcss['"]\s+prefix\s*\(\s*dw\s*\)/i.test(
|
|
1211
|
+
withoutComments
|
|
1212
|
+
);
|
|
1213
|
+
checks.push({
|
|
1214
|
+
name: "css-main-dev-unprefixed",
|
|
1215
|
+
ok: !hasPrefix,
|
|
1216
|
+
message: hasPrefix ? "main.dev.css uses prefix(dw) \u2014 local wp dev will not match HubSpot builds" : "main.dev.css is unprefixed (local dev)",
|
|
1217
|
+
remediation: hasPrefix ? "Remove prefix(dw) from main.dev.css; use main.prod.css for HubSpot/CDN builds only" : void 0,
|
|
1218
|
+
severity: "error"
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
if (mainProd) {
|
|
1222
|
+
const withoutComments = mainProd.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
|
|
1223
|
+
const hasPrefix = /@import\s+['"]tailwindcss['"]\s+prefix\s*\(\s*dw\s*\)/i.test(
|
|
1224
|
+
withoutComments
|
|
1225
|
+
);
|
|
1226
|
+
const scansTemp = /@source\s+['"][^'"]*\.temp/.test(mainProd);
|
|
1227
|
+
checks.push({
|
|
1228
|
+
name: "css-main-prod-prefixed",
|
|
1229
|
+
ok: hasPrefix,
|
|
1230
|
+
message: hasPrefix ? "main.prod.css uses prefix(dw) (HubSpot/CDN)" : "main.prod.css missing prefix(dw)",
|
|
1231
|
+
remediation: hasPrefix ? void 0 : "Add @import 'tailwindcss' prefix(dw) to main.prod.css",
|
|
1232
|
+
severity: "error"
|
|
1233
|
+
});
|
|
1234
|
+
checks.push({
|
|
1235
|
+
name: "css-main-prod-temp-source",
|
|
1236
|
+
ok: scansTemp,
|
|
1237
|
+
message: scansTemp ? "main.prod.css scans .temp/ for prefixed utilities" : "main.prod.css missing @source for .temp/",
|
|
1238
|
+
remediation: scansTemp ? void 0 : "Add @source '../../../.temp/**/*.{js,jsx,ts,tsx}' to main.prod.css",
|
|
1239
|
+
severity: "error"
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
if (usesCdnPipeline(ctx.config) && appEntry) {
|
|
1243
|
+
const skipsRuntimeCss = /skipRuntimeCss\s*:\s*true/.test(appEntry) || /mountPortal\s*\(\s*\{[^}]*skipRuntimeCss\s*:\s*true/.test(appEntry);
|
|
1244
|
+
checks.push({
|
|
1245
|
+
name: "css-app-entry-skip-runtime",
|
|
1246
|
+
ok: skipsRuntimeCss,
|
|
1247
|
+
message: skipsRuntimeCss ? "cdn/app-entry.ts skips runtime CSS (relies on module.css)" : "cdn/app-entry.ts should call mountPortal({ skipRuntimeCss: true })",
|
|
1248
|
+
remediation: skipsRuntimeCss ? void 0 : "Update src/cdn/app-entry.ts \u2014 see templates/module-hybrid-cdn/src/cdn/app-entry.ts",
|
|
1249
|
+
severity: "error"
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
const moduleHtml = readProjectFile(ctx.projectRoot, "public/module.html");
|
|
1253
|
+
if (moduleHtml && usesCdnPipeline(ctx.config)) {
|
|
1254
|
+
const loadsCdnAppCss = /require_css\s*\([^)]*portal_app_css/i.test(moduleHtml) || /require_css\s*\([^)]*PORTAL_APP_CSS/i.test(moduleHtml);
|
|
1255
|
+
checks.push({
|
|
1256
|
+
name: "css-module-html-no-cdn-require",
|
|
1257
|
+
ok: !loadsCdnAppCss,
|
|
1258
|
+
message: loadsCdnAppCss ? "module.html require_css for CDN app CSS may double-load with merged module.css" : "module.html relies on merged module.css (no CDN require_css)",
|
|
1259
|
+
remediation: loadsCdnAppCss ? "Remove require_css for app.*.css; wp build merges app CSS into module.css" : void 0,
|
|
1260
|
+
severity: "warn"
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
const moduleOutDir = ctx.config.module?.outDir;
|
|
1264
|
+
if (moduleOutDir && usesCdnPipeline(ctx.config)) {
|
|
1265
|
+
const moduleCssPath = resolve8(ctx.projectRoot, moduleOutDir, "module.css");
|
|
1266
|
+
if (existsSync7(moduleCssPath)) {
|
|
1267
|
+
const css = readFileSync5(moduleCssPath, "utf8");
|
|
1268
|
+
const hasDwUtilities = /dw\\:/.test(css) || /\.dw:/.test(css);
|
|
1269
|
+
checks.push({
|
|
1270
|
+
name: "css-module-dist-prefixed",
|
|
1271
|
+
ok: hasDwUtilities,
|
|
1272
|
+
message: hasDwUtilities ? `${moduleOutDir}/module.css contains dw-prefixed utilities` : `${moduleOutDir}/module.css missing dw: utilities \u2014 run wp build`,
|
|
1273
|
+
remediation: hasDwUtilities ? void 0 : "Run wp build --tier dev (or stg/prod) before upload",
|
|
1274
|
+
severity: "warn"
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
return checks;
|
|
1279
|
+
}
|
|
852
1280
|
function checkHybridModuleHtml(ctx) {
|
|
853
1281
|
if (isThemeProject(ctx.config) || !usesCdnPipeline(ctx.config)) return null;
|
|
854
|
-
const path =
|
|
855
|
-
if (!
|
|
1282
|
+
const path = resolve8(ctx.projectRoot, "public/module.html");
|
|
1283
|
+
if (!existsSync7(path)) {
|
|
856
1284
|
return {
|
|
857
1285
|
name: "module-html",
|
|
858
1286
|
ok: false,
|
|
@@ -861,8 +1289,8 @@ function checkHybridModuleHtml(ctx) {
|
|
|
861
1289
|
severity: "error"
|
|
862
1290
|
};
|
|
863
1291
|
}
|
|
864
|
-
const html =
|
|
865
|
-
const needs = ["__PORTAL_CDN_BASE__", "require_js", "
|
|
1292
|
+
const html = readFileSync5(path, "utf8");
|
|
1293
|
+
const needs = ["__PORTAL_CDN_BASE__", "require_js", "hubSpotData", 'id="app"'];
|
|
866
1294
|
const missing = needs.filter((token) => !html.includes(token));
|
|
867
1295
|
const usesUndeclaredCdnFields = /module\.portal_(cdn_base_url|vendor_script|app_script|app_css)/.test(
|
|
868
1296
|
html
|
|
@@ -879,8 +1307,8 @@ function checkHybridModuleHtml(ctx) {
|
|
|
879
1307
|
function checkCdnViteConfigs(ctx) {
|
|
880
1308
|
if (isThemeProject(ctx.config) || !usesCdnPipeline(ctx.config)) return [];
|
|
881
1309
|
return ["vite.config.cdn-vendor.mjs", "vite.config.cdn-app.mjs"].map((file) => {
|
|
882
|
-
const path =
|
|
883
|
-
const ok =
|
|
1310
|
+
const path = resolve8(ctx.projectRoot, file);
|
|
1311
|
+
const ok = existsSync7(path);
|
|
884
1312
|
return {
|
|
885
1313
|
name: file.replace(/\./g, "-"),
|
|
886
1314
|
ok,
|
|
@@ -892,8 +1320,8 @@ function checkCdnViteConfigs(ctx) {
|
|
|
892
1320
|
}
|
|
893
1321
|
function checkDevEntry(ctx) {
|
|
894
1322
|
const checks = [];
|
|
895
|
-
const indexHtml =
|
|
896
|
-
const hasIndex =
|
|
1323
|
+
const indexHtml = resolve8(ctx.projectRoot, "index.html");
|
|
1324
|
+
const hasIndex = existsSync7(indexHtml);
|
|
897
1325
|
checks.push({
|
|
898
1326
|
name: "index-html",
|
|
899
1327
|
ok: hasIndex,
|
|
@@ -901,8 +1329,8 @@ function checkDevEntry(ctx) {
|
|
|
901
1329
|
remediation: hasIndex ? void 0 : "Re-run wp init or copy index.html from kit templates",
|
|
902
1330
|
severity: "error"
|
|
903
1331
|
});
|
|
904
|
-
const localDev =
|
|
905
|
-
const hasEntry =
|
|
1332
|
+
const localDev = resolve8(ctx.projectRoot, ctx.config.entries.localDev);
|
|
1333
|
+
const hasEntry = existsSync7(localDev);
|
|
906
1334
|
checks.push({
|
|
907
1335
|
name: "local-dev-entry",
|
|
908
1336
|
ok: hasEntry,
|
|
@@ -912,7 +1340,7 @@ function checkDevEntry(ctx) {
|
|
|
912
1340
|
});
|
|
913
1341
|
if (hasEntry) {
|
|
914
1342
|
try {
|
|
915
|
-
const source =
|
|
1343
|
+
const source = readFileSync5(localDev, "utf8");
|
|
916
1344
|
const usesKitNodeBootstrap = source.includes("@woodsportal/hubspot-kit/dev/bootstrap-greenfield") || source.includes("@woodsportal/hubspot-kit/dev/bootstrap");
|
|
917
1345
|
const usesWphsShim = source.includes(".wphs/.kit-bootstrap-greenfield") || source.includes(".wphs/.kit-bootstrap") || source.includes("@wphs/bootstrap-greenfield") || source.includes("@wphs/bootstrap");
|
|
918
1346
|
checks.push({
|
|
@@ -929,7 +1357,7 @@ function checkDevEntry(ctx) {
|
|
|
929
1357
|
}
|
|
930
1358
|
function checkKitScripts() {
|
|
931
1359
|
const script = kitPath("scripts", "build-cdn.mjs");
|
|
932
|
-
const ok =
|
|
1360
|
+
const ok = existsSync7(script);
|
|
933
1361
|
return {
|
|
934
1362
|
name: "kit-scripts",
|
|
935
1363
|
ok,
|
|
@@ -942,8 +1370,8 @@ function checkWphsWorkspace(ctx) {
|
|
|
942
1370
|
const workspace = ctx.config.dev.moduleConfigDir;
|
|
943
1371
|
const checks = [];
|
|
944
1372
|
for (const legacy of [".dev", ".woodsportal"]) {
|
|
945
|
-
const legacyPath =
|
|
946
|
-
if (
|
|
1373
|
+
const legacyPath = resolve8(ctx.projectRoot, legacy);
|
|
1374
|
+
if (existsSync7(legacyPath)) {
|
|
947
1375
|
checks.push({
|
|
948
1376
|
name: `legacy-${legacy}`,
|
|
949
1377
|
ok: false,
|
|
@@ -953,7 +1381,7 @@ function checkWphsWorkspace(ctx) {
|
|
|
953
1381
|
});
|
|
954
1382
|
}
|
|
955
1383
|
}
|
|
956
|
-
if (
|
|
1384
|
+
if (existsSync7(resolve8(ctx.projectRoot, ".wp-hs-baseline"))) {
|
|
957
1385
|
checks.push({
|
|
958
1386
|
name: "legacy-baseline",
|
|
959
1387
|
ok: false,
|
|
@@ -972,10 +1400,10 @@ function checkWphsWorkspace(ctx) {
|
|
|
972
1400
|
});
|
|
973
1401
|
return checks;
|
|
974
1402
|
}
|
|
975
|
-
const wphsRoot =
|
|
1403
|
+
const wphsRoot = resolve8(ctx.projectRoot, workspace);
|
|
976
1404
|
const required = ["config", "assets/branding", "fixtures", "cache", "audit/baseline", "state"];
|
|
977
1405
|
for (const sub of required) {
|
|
978
|
-
const ok =
|
|
1406
|
+
const ok = existsSync7(join(wphsRoot, sub));
|
|
979
1407
|
checks.push({
|
|
980
1408
|
name: `wphs-${sub.replace(/\//g, "-")}`,
|
|
981
1409
|
ok,
|
|
@@ -988,8 +1416,8 @@ function checkWphsWorkspace(ctx) {
|
|
|
988
1416
|
}
|
|
989
1417
|
function checkAdapter(ctx) {
|
|
990
1418
|
if (isThemeProject(ctx.config)) return null;
|
|
991
|
-
const adapterPath =
|
|
992
|
-
const ok =
|
|
1419
|
+
const adapterPath = resolve8(ctx.projectRoot, ctx.config.dev.adapter);
|
|
1420
|
+
const ok = existsSync7(adapterPath);
|
|
993
1421
|
return {
|
|
994
1422
|
name: "wphs-adapter",
|
|
995
1423
|
ok,
|
|
@@ -1000,11 +1428,11 @@ function checkAdapter(ctx) {
|
|
|
1000
1428
|
}
|
|
1001
1429
|
function checkModuleConfigUiDeps(ctx) {
|
|
1002
1430
|
if (isThemeProject(ctx.config)) return [];
|
|
1003
|
-
const pkgPath =
|
|
1004
|
-
if (!
|
|
1431
|
+
const pkgPath = resolve8(ctx.projectRoot, "package.json");
|
|
1432
|
+
if (!existsSync7(pkgPath)) return [];
|
|
1005
1433
|
let deps = {};
|
|
1006
1434
|
try {
|
|
1007
|
-
const pkg = JSON.parse(
|
|
1435
|
+
const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
|
|
1008
1436
|
deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1009
1437
|
} catch {
|
|
1010
1438
|
return [];
|
|
@@ -1021,11 +1449,10 @@ function checkModuleConfigUiDeps(ctx) {
|
|
|
1021
1449
|
});
|
|
1022
1450
|
}
|
|
1023
1451
|
function checkViteInstalled(ctx) {
|
|
1024
|
-
if (isThemeProject(ctx.config) && !
|
|
1452
|
+
if (isThemeProject(ctx.config) && !existsSync7(resolve8(ctx.projectRoot, "vite.config.dev.mjs"))) {
|
|
1025
1453
|
return null;
|
|
1026
1454
|
}
|
|
1027
|
-
const
|
|
1028
|
-
const ok = existsSync5(localBin);
|
|
1455
|
+
const ok = isProjectViteInstalled(ctx.projectRoot);
|
|
1029
1456
|
return {
|
|
1030
1457
|
name: "vite",
|
|
1031
1458
|
ok,
|
|
@@ -1060,6 +1487,7 @@ async function gatherDoctorChecks(ctx) {
|
|
|
1060
1487
|
checks.push(uploadOnlyNote);
|
|
1061
1488
|
} else {
|
|
1062
1489
|
checks.push(checkDevViteConfig(ctx), ...checkCdnViteConfigs(ctx), ...checkDevEntry(ctx));
|
|
1490
|
+
checks.push(...checkCssPipeline(ctx));
|
|
1063
1491
|
const hybridHtml = checkHybridModuleHtml(ctx);
|
|
1064
1492
|
if (hybridHtml) checks.push(hybridHtml);
|
|
1065
1493
|
const viteCheck = checkViteInstalled(ctx);
|
|
@@ -1074,6 +1502,8 @@ async function gatherDoctorChecks(ctx) {
|
|
|
1074
1502
|
checks.push(...checkConfig(ctx), ...checkWphsWorkspace(ctx));
|
|
1075
1503
|
const fieldsCheck = checkFieldsJson(ctx);
|
|
1076
1504
|
if (fieldsCheck) checks.push(fieldsCheck);
|
|
1505
|
+
const metaCheck = checkMetaJson(ctx);
|
|
1506
|
+
if (metaCheck) checks.push(metaCheck);
|
|
1077
1507
|
const adapterCheck = checkAdapter(ctx);
|
|
1078
1508
|
if (adapterCheck) checks.push(adapterCheck);
|
|
1079
1509
|
}
|
|
@@ -1087,9 +1517,17 @@ async function gatherDoctorChecks(ctx) {
|
|
|
1087
1517
|
{
|
|
1088
1518
|
title: "Checking environment and CDN files",
|
|
1089
1519
|
task: async () => {
|
|
1520
|
+
const legacyBuildId = checkLegacyBuildIdFile(ctx);
|
|
1521
|
+
if (legacyBuildId) checks.push(legacyBuildId);
|
|
1090
1522
|
const buildIdCheck = checkBuildId(ctx);
|
|
1091
1523
|
if (buildIdCheck) checks.push(buildIdCheck);
|
|
1524
|
+
const resolvedBuildId = checkModuleBuildIdResolved(ctx);
|
|
1525
|
+
if (resolvedBuildId) checks.push(resolvedBuildId);
|
|
1526
|
+
const devSlugCheck = checkDevSlug(ctx);
|
|
1527
|
+
if (devSlugCheck) checks.push(devSlugCheck);
|
|
1092
1528
|
checks.push(...checkEnvFiles(ctx));
|
|
1529
|
+
const cdnMirrorCheck = await checkCdnMirrorPublic(ctx);
|
|
1530
|
+
if (cdnMirrorCheck) checks.push(cdnMirrorCheck);
|
|
1093
1531
|
}
|
|
1094
1532
|
}
|
|
1095
1533
|
]);
|
|
@@ -1168,21 +1606,21 @@ async function runConfigValidate(ctx) {
|
|
|
1168
1606
|
}
|
|
1169
1607
|
|
|
1170
1608
|
// src/cli/commands/build.ts
|
|
1171
|
-
import { resolve as
|
|
1609
|
+
import { resolve as resolve10 } from "path";
|
|
1172
1610
|
|
|
1173
1611
|
// src/lib/cli-banner.ts
|
|
1174
1612
|
import pc3 from "picocolors";
|
|
1175
1613
|
|
|
1176
1614
|
// src/lib/kit-version.ts
|
|
1177
|
-
import { readFileSync as
|
|
1178
|
-
import { resolve as
|
|
1615
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
1616
|
+
import { resolve as resolve9 } from "path";
|
|
1179
1617
|
var cachedVersion = null;
|
|
1180
1618
|
function readKitVersion() {
|
|
1181
1619
|
if (cachedVersion) {
|
|
1182
1620
|
return cachedVersion;
|
|
1183
1621
|
}
|
|
1184
1622
|
try {
|
|
1185
|
-
const pkg = JSON.parse(
|
|
1623
|
+
const pkg = JSON.parse(readFileSync6(resolve9(getKitRoot(), "package.json"), "utf8"));
|
|
1186
1624
|
cachedVersion = pkg.version ?? "0.0.0";
|
|
1187
1625
|
} catch {
|
|
1188
1626
|
cachedVersion = "0.0.0";
|
|
@@ -1220,7 +1658,7 @@ function formatProjectLabel(config) {
|
|
|
1220
1658
|
return `${config.module.name} \xB7 ${config.projectType}${routerNote}`;
|
|
1221
1659
|
}
|
|
1222
1660
|
function section(color, title, body) {
|
|
1223
|
-
const bold = (
|
|
1661
|
+
const bold = (text4) => color ? pc3.bold(text4) : text4;
|
|
1224
1662
|
return ` ${bold(title)}
|
|
1225
1663
|
${body}`;
|
|
1226
1664
|
}
|
|
@@ -1229,10 +1667,10 @@ function printWoodsCliDevBanner(options) {
|
|
|
1229
1667
|
return;
|
|
1230
1668
|
}
|
|
1231
1669
|
const color = useCliColor();
|
|
1232
|
-
const cyan = (
|
|
1233
|
-
const dim = (
|
|
1234
|
-
const bold = (
|
|
1235
|
-
const yellow = (
|
|
1670
|
+
const cyan = (text4) => color ? pc3.cyan(text4) : text4;
|
|
1671
|
+
const dim = (text4) => color ? pc3.dim(text4) : text4;
|
|
1672
|
+
const bold = (text4) => color ? pc3.bold(text4) : text4;
|
|
1673
|
+
const yellow = (text4) => color ? pc3.yellow(text4) : text4;
|
|
1236
1674
|
const version = options.version ?? readKitVersion();
|
|
1237
1675
|
const project = formatProjectLabel(options.config);
|
|
1238
1676
|
const isTheme = isThemeProject(options.config);
|
|
@@ -1314,7 +1752,7 @@ function buildTasks(ctx, options, run) {
|
|
|
1314
1752
|
title: `Building monolith module (${tier})`,
|
|
1315
1753
|
task: async () => {
|
|
1316
1754
|
await run("run-vite-build.mjs", [
|
|
1317
|
-
|
|
1755
|
+
resolve10(ctx.projectRoot, "vite.config.monolith.mjs"),
|
|
1318
1756
|
...tierToStgFlag(tier)
|
|
1319
1757
|
]);
|
|
1320
1758
|
await run("run-hubspot-module-postbuild.mjs");
|
|
@@ -1328,7 +1766,7 @@ function buildTasks(ctx, options, run) {
|
|
|
1328
1766
|
task: async () => {
|
|
1329
1767
|
if (options.cdnEsm) {
|
|
1330
1768
|
await run("run-vite-build.mjs", [
|
|
1331
|
-
|
|
1769
|
+
resolve10(ctx.projectRoot, "vite.config.cdn-esm.mjs"),
|
|
1332
1770
|
...tierToStgFlag(tier)
|
|
1333
1771
|
]);
|
|
1334
1772
|
} else {
|
|
@@ -1343,7 +1781,7 @@ function buildTasks(ctx, options, run) {
|
|
|
1343
1781
|
title: `Building HubSpot module (${tier})`,
|
|
1344
1782
|
task: async () => {
|
|
1345
1783
|
await run("run-vite-build.mjs", [
|
|
1346
|
-
|
|
1784
|
+
resolve10(ctx.projectRoot, "vite.config.hubspot.mjs"),
|
|
1347
1785
|
...tierToStgFlag(tier)
|
|
1348
1786
|
]);
|
|
1349
1787
|
await run("run-hubspot-module-postbuild.mjs");
|
|
@@ -1371,14 +1809,35 @@ function buildIntroLine(ctx, tier, options) {
|
|
|
1371
1809
|
if (parts.length === 0) return void 0;
|
|
1372
1810
|
return `${formatProjectLabel(ctx.config)} \xB7 ${parts.join(" + ")} (${tier})`;
|
|
1373
1811
|
}
|
|
1812
|
+
async function maybeBumpModuleBuildId(ctx, tier, options, env) {
|
|
1813
|
+
const useCdn = usesCdnPipeline(ctx.config) && !isThemeProject(ctx.config);
|
|
1814
|
+
const shouldBump = useCdn && !options.hubspotOnly && !ctx.global.noBump && !options.buildIdBumped && !env.PORTAL_BUILD_ID?.trim() && !env.MODULE_BUILD_ID?.trim() && !env.GITHUB_REF_NAME?.trim();
|
|
1815
|
+
if (!shouldBump) {
|
|
1816
|
+
return env;
|
|
1817
|
+
}
|
|
1818
|
+
const result = bumpModuleBuildIdForCli(ctx.projectRoot, {
|
|
1819
|
+
tier,
|
|
1820
|
+
dryRun: ctx.global.dryRun,
|
|
1821
|
+
env
|
|
1822
|
+
});
|
|
1823
|
+
if (!ctx.global.quiet && !ctx.global.json && result.bumped) {
|
|
1824
|
+
ctx.logger.info(`Module build id \u2192 ${result.id}`);
|
|
1825
|
+
}
|
|
1826
|
+
return {
|
|
1827
|
+
...env,
|
|
1828
|
+
PORTAL_BUILD_ID: result.id,
|
|
1829
|
+
MODULE_BUILD_ID: result.id
|
|
1830
|
+
};
|
|
1831
|
+
}
|
|
1374
1832
|
async function runBuild(ctx, options) {
|
|
1375
1833
|
const tier = options.tier ?? DEFAULT_BUILD_TIER;
|
|
1376
1834
|
const quietSubprocess = shouldQuietSubprocess(ctx, options);
|
|
1377
|
-
|
|
1378
|
-
...process.env,
|
|
1835
|
+
let env = {
|
|
1836
|
+
...options.env ?? process.env,
|
|
1379
1837
|
WP_HS_PROJECT_ROOT: ctx.projectRoot,
|
|
1380
1838
|
...quietSubprocess ? { WP_HS_QUIET_BUILD: "1" } : {}
|
|
1381
1839
|
};
|
|
1840
|
+
env = await maybeBumpModuleBuildId(ctx, tier, options, env);
|
|
1382
1841
|
const isTheme = isThemeProject(ctx.config);
|
|
1383
1842
|
const execOpts = {
|
|
1384
1843
|
cwd: ctx.projectRoot,
|
|
@@ -1419,7 +1878,7 @@ async function runBuild(ctx, options) {
|
|
|
1419
1878
|
command: "build",
|
|
1420
1879
|
tier,
|
|
1421
1880
|
ok: true,
|
|
1422
|
-
outDir:
|
|
1881
|
+
outDir: resolve10(ctx.projectRoot, ctx.config.module.outDir)
|
|
1423
1882
|
});
|
|
1424
1883
|
} else if (!options.quiet) {
|
|
1425
1884
|
printCommandDone(ctx, "Build complete");
|
|
@@ -1433,30 +1892,9 @@ async function runBuild(ctx, options) {
|
|
|
1433
1892
|
}
|
|
1434
1893
|
|
|
1435
1894
|
// src/cli/commands/dev.ts
|
|
1436
|
-
import { existsSync as
|
|
1437
|
-
import { resolve as
|
|
1895
|
+
import { existsSync as existsSync8 } from "fs";
|
|
1896
|
+
import { resolve as resolve11 } from "path";
|
|
1438
1897
|
import { spawn } from "child_process";
|
|
1439
|
-
|
|
1440
|
-
// src/vite/resolve-dev-config.ts
|
|
1441
|
-
import { existsSync as existsSync6 } from "fs";
|
|
1442
|
-
import { resolve as resolve9 } from "path";
|
|
1443
|
-
var CONSUMER_DEV_CONFIG = "vite.config.dev.mjs";
|
|
1444
|
-
function resolveDevViteConfigPath(projectRoot) {
|
|
1445
|
-
const consumerConfig = resolve9(projectRoot, CONSUMER_DEV_CONFIG);
|
|
1446
|
-
if (existsSync6(consumerConfig)) {
|
|
1447
|
-
return consumerConfig;
|
|
1448
|
-
}
|
|
1449
|
-
return kitPath("vite", "vite.config.dev.mjs");
|
|
1450
|
-
}
|
|
1451
|
-
function resolveProjectViteBin(projectRoot) {
|
|
1452
|
-
const localBin = resolve9(projectRoot, "node_modules", ".bin", "vite");
|
|
1453
|
-
if (existsSync6(localBin)) {
|
|
1454
|
-
return localBin;
|
|
1455
|
-
}
|
|
1456
|
-
return "vite";
|
|
1457
|
-
}
|
|
1458
|
-
|
|
1459
|
-
// src/cli/commands/dev.ts
|
|
1460
1898
|
async function runDev(ctx, options) {
|
|
1461
1899
|
const port = options.port ?? 3e3;
|
|
1462
1900
|
const env = {
|
|
@@ -1464,19 +1902,20 @@ async function runDev(ctx, options) {
|
|
|
1464
1902
|
WP_HS_PROJECT_ROOT: ctx.projectRoot
|
|
1465
1903
|
};
|
|
1466
1904
|
const configPath = resolveDevViteConfigPath(ctx.projectRoot);
|
|
1467
|
-
const
|
|
1905
|
+
const viteSpawn = resolveProjectViteSpawn(ctx.projectRoot);
|
|
1468
1906
|
if (ctx.global.dryRun) {
|
|
1469
|
-
|
|
1907
|
+
const dryRunTarget = viteSpawn ? formatProjectViteSpawn(viteSpawn) : "vite";
|
|
1908
|
+
ctx.logger.info(`[dry-run] ${dryRunTarget} --config ${configPath} --port ${port}`);
|
|
1470
1909
|
return ExitCode.SUCCESS;
|
|
1471
1910
|
}
|
|
1472
|
-
if (
|
|
1911
|
+
if (!viteSpawn) {
|
|
1473
1912
|
throw new WpHsError("vite is not installed in this project.", {
|
|
1474
1913
|
exitCode: ExitCode.PREREQUISITE,
|
|
1475
1914
|
remediation: "Run yarn install or npm install. If this is an older wp init project, add vite and peers to devDependencies (run wp init again or see @woodsportal/hubspot-kit templates)."
|
|
1476
1915
|
});
|
|
1477
1916
|
}
|
|
1478
|
-
const consumerConfig =
|
|
1479
|
-
if (!
|
|
1917
|
+
const consumerConfig = resolve11(ctx.projectRoot, "vite.config.dev.mjs");
|
|
1918
|
+
if (!existsSync8(consumerConfig)) {
|
|
1480
1919
|
throw new WpHsError("vite.config.dev.mjs is missing in the project root.", {
|
|
1481
1920
|
exitCode: ExitCode.USER_ERROR,
|
|
1482
1921
|
remediation: "Add vite.config.dev.mjs (see @woodsportal/hubspot-kit templates/module-hybrid-cdn)."
|
|
@@ -1489,8 +1928,16 @@ async function runDev(ctx, options) {
|
|
|
1489
1928
|
});
|
|
1490
1929
|
return new Promise((resolvePromise) => {
|
|
1491
1930
|
const child = spawn(
|
|
1492
|
-
|
|
1493
|
-
[
|
|
1931
|
+
viteSpawn.command,
|
|
1932
|
+
[
|
|
1933
|
+
...viteSpawn.prefixArgs,
|
|
1934
|
+
"--config",
|
|
1935
|
+
configPath,
|
|
1936
|
+
"--port",
|
|
1937
|
+
String(port),
|
|
1938
|
+
"--mode",
|
|
1939
|
+
"dev"
|
|
1940
|
+
],
|
|
1494
1941
|
{
|
|
1495
1942
|
cwd: ctx.projectRoot,
|
|
1496
1943
|
env,
|
|
@@ -1505,11 +1952,11 @@ async function runDev(ctx, options) {
|
|
|
1505
1952
|
}
|
|
1506
1953
|
|
|
1507
1954
|
// src/cli/commands/deploy.ts
|
|
1508
|
-
import { resolve as
|
|
1955
|
+
import { resolve as resolve14 } from "path";
|
|
1509
1956
|
|
|
1510
1957
|
// src/lib/project-env-files.ts
|
|
1511
|
-
import { existsSync as
|
|
1512
|
-
import { resolve as
|
|
1958
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "fs";
|
|
1959
|
+
import { resolve as resolve12 } from "path";
|
|
1513
1960
|
var ENV_LOCAL_FILE = ".env.local";
|
|
1514
1961
|
var ENV_GITIGNORE_LINES = [".env", ".env.*", "!.env*.example", ENV_LOCAL_FILE];
|
|
1515
1962
|
function parseEnvFile(content) {
|
|
@@ -1529,9 +1976,9 @@ function parseEnvFile(content) {
|
|
|
1529
1976
|
return result;
|
|
1530
1977
|
}
|
|
1531
1978
|
function readEnvLocal(projectRoot) {
|
|
1532
|
-
const path =
|
|
1533
|
-
if (!
|
|
1534
|
-
return parseEnvFile(
|
|
1979
|
+
const path = resolve12(projectRoot, ENV_LOCAL_FILE);
|
|
1980
|
+
if (!existsSync9(path)) return {};
|
|
1981
|
+
return parseEnvFile(readFileSync7(path, "utf8"));
|
|
1535
1982
|
}
|
|
1536
1983
|
function formatEnvLocal(entries) {
|
|
1537
1984
|
const lines = [
|
|
@@ -1550,9 +1997,9 @@ function formatEnvLocal(entries) {
|
|
|
1550
1997
|
`;
|
|
1551
1998
|
}
|
|
1552
1999
|
function writeEnvLocal(projectRoot, entries) {
|
|
1553
|
-
const path =
|
|
1554
|
-
const existing =
|
|
1555
|
-
|
|
2000
|
+
const path = resolve12(projectRoot, ENV_LOCAL_FILE);
|
|
2001
|
+
const existing = existsSync9(path) ? parseEnvFile(readFileSync7(path, "utf8")) : {};
|
|
2002
|
+
writeFileSync2(path, formatEnvLocal({ ...existing, ...entries }));
|
|
1556
2003
|
}
|
|
1557
2004
|
function mergeProjectEnv(projectRoot, base = process.env) {
|
|
1558
2005
|
const local = readEnvLocal(projectRoot);
|
|
@@ -1565,11 +2012,11 @@ function mergeProjectEnv(projectRoot, base = process.env) {
|
|
|
1565
2012
|
return merged;
|
|
1566
2013
|
}
|
|
1567
2014
|
function ensureEnvFilesGitignore(projectRoot) {
|
|
1568
|
-
const gitignorePath =
|
|
1569
|
-
const existing =
|
|
2015
|
+
const gitignorePath = resolve12(projectRoot, ".gitignore");
|
|
2016
|
+
const existing = existsSync9(gitignorePath) ? readFileSync7(gitignorePath, "utf8") : "";
|
|
1570
2017
|
const toAppend = ENV_GITIGNORE_LINES.filter((line) => !existing.includes(line)).join("\n");
|
|
1571
2018
|
if (!toAppend) return;
|
|
1572
|
-
|
|
2019
|
+
writeFileSync2(gitignorePath, `${existing.trimEnd()}
|
|
1573
2020
|
|
|
1574
2021
|
# Env files (secrets)
|
|
1575
2022
|
${toAppend}
|
|
@@ -1577,14 +2024,14 @@ ${toAppend}
|
|
|
1577
2024
|
}
|
|
1578
2025
|
|
|
1579
2026
|
// src/cli/commands/upload.ts
|
|
1580
|
-
import { resolve as
|
|
2027
|
+
import { resolve as resolve13 } from "path";
|
|
1581
2028
|
async function uploadModuleOrTheme(ctx) {
|
|
1582
2029
|
if (isThemeProject(ctx.config)) {
|
|
1583
2030
|
const theme = ctx.config.theme;
|
|
1584
2031
|
if (!theme) {
|
|
1585
2032
|
throw new WpHsError("theme block missing in woodscli.json", { exitCode: ExitCode.USER_ERROR });
|
|
1586
2033
|
}
|
|
1587
|
-
const srcDir =
|
|
2034
|
+
const srcDir = resolve13(ctx.projectRoot, theme.srcDir);
|
|
1588
2035
|
await runCommandAsync("hs", ["cms", "upload", srcDir, theme.uploadPath], {
|
|
1589
2036
|
cwd: ctx.projectRoot,
|
|
1590
2037
|
projectRoot: ctx.projectRoot,
|
|
@@ -1594,7 +2041,7 @@ async function uploadModuleOrTheme(ctx) {
|
|
|
1594
2041
|
});
|
|
1595
2042
|
return `Uploaded theme ${srcDir} \u2192 ${theme.uploadPath}`;
|
|
1596
2043
|
}
|
|
1597
|
-
const outDir =
|
|
2044
|
+
const outDir = resolve13(ctx.projectRoot, ctx.config.module.outDir);
|
|
1598
2045
|
const uploadPath = ctx.config.module.uploadPath;
|
|
1599
2046
|
await runCommandAsync("hs", ["cms", "upload", outDir, uploadPath], {
|
|
1600
2047
|
cwd: ctx.projectRoot,
|
|
@@ -1661,11 +2108,29 @@ async function runDeploy(ctx, options) {
|
|
|
1661
2108
|
quiet: true,
|
|
1662
2109
|
quietSubprocess: true
|
|
1663
2110
|
};
|
|
1664
|
-
const
|
|
2111
|
+
const useCdn = usesCdnPipeline(ctx.config) && !isThemeProject(ctx.config);
|
|
2112
|
+
let baseEnv = mergeProjectEnv(ctx.projectRoot, {
|
|
1665
2113
|
...process.env,
|
|
1666
2114
|
WP_HS_PROJECT_ROOT: ctx.projectRoot,
|
|
1667
2115
|
...quietSubprocess ? { WP_HS_QUIET_BUILD: "1" } : {}
|
|
1668
2116
|
});
|
|
2117
|
+
let buildIdBumped = false;
|
|
2118
|
+
if (useCdn && !ctx.global.noBump && !baseEnv.PORTAL_BUILD_ID?.trim() && !baseEnv.MODULE_BUILD_ID?.trim() && !baseEnv.GITHUB_REF_NAME?.trim()) {
|
|
2119
|
+
const bump = bumpModuleBuildIdForCli(ctx.projectRoot, {
|
|
2120
|
+
tier,
|
|
2121
|
+
dryRun: ctx.global.dryRun,
|
|
2122
|
+
env: baseEnv
|
|
2123
|
+
});
|
|
2124
|
+
baseEnv = {
|
|
2125
|
+
...baseEnv,
|
|
2126
|
+
PORTAL_BUILD_ID: bump.id,
|
|
2127
|
+
MODULE_BUILD_ID: bump.id
|
|
2128
|
+
};
|
|
2129
|
+
buildIdBumped = bump.bumped;
|
|
2130
|
+
if (!ctx.global.quiet && !ctx.global.json && bump.bumped) {
|
|
2131
|
+
ctx.logger.info(`Module build id \u2192 ${bump.id}`);
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
1669
2134
|
const execOpts = {
|
|
1670
2135
|
cwd: ctx.projectRoot,
|
|
1671
2136
|
projectRoot: ctx.projectRoot,
|
|
@@ -1674,7 +2139,6 @@ async function runDeploy(ctx, options) {
|
|
|
1674
2139
|
dryRun: ctx.global.dryRun,
|
|
1675
2140
|
streamOutput: ctx.global.verbose
|
|
1676
2141
|
};
|
|
1677
|
-
const useCdn = usesCdnPipeline(ctx.config) && !isThemeProject(ctx.config);
|
|
1678
2142
|
const verifyFlag = tier === "prod" ? ["--prod"] : ["--stg"];
|
|
1679
2143
|
const tasks = [
|
|
1680
2144
|
{
|
|
@@ -1688,7 +2152,7 @@ async function runDeploy(ctx, options) {
|
|
|
1688
2152
|
tasks.push({
|
|
1689
2153
|
title: `Building CDN (${tier})`,
|
|
1690
2154
|
task: async () => {
|
|
1691
|
-
await runBuild(ctx, { tier, cdnOnly: true, ...nested });
|
|
2155
|
+
await runBuild(ctx, { tier, cdnOnly: true, buildIdBumped, env: baseEnv, ...nested });
|
|
1692
2156
|
}
|
|
1693
2157
|
});
|
|
1694
2158
|
if (!options.skipPublish) {
|
|
@@ -1708,7 +2172,7 @@ async function runDeploy(ctx, options) {
|
|
|
1708
2172
|
tasks.push({
|
|
1709
2173
|
title: `Building HubSpot module (${tier})`,
|
|
1710
2174
|
task: async () => {
|
|
1711
|
-
await runBuild(ctx, { tier, hubspotOnly: true, ...nested });
|
|
2175
|
+
await runBuild(ctx, { tier, hubspotOnly: true, env: baseEnv, ...nested });
|
|
1712
2176
|
}
|
|
1713
2177
|
});
|
|
1714
2178
|
} else {
|
|
@@ -1745,7 +2209,7 @@ async function runDeploy(ctx, options) {
|
|
|
1745
2209
|
if (!ctx.global.json) {
|
|
1746
2210
|
printCommandDone(
|
|
1747
2211
|
ctx,
|
|
1748
|
-
`Deploy complete (${
|
|
2212
|
+
`Deploy complete (${resolve14(ctx.projectRoot, ctx.config.module.outDir)} \u2192 ${ctx.config.module.uploadPath})`
|
|
1749
2213
|
);
|
|
1750
2214
|
}
|
|
1751
2215
|
return ExitCode.SUCCESS;
|
|
@@ -1818,8 +2282,8 @@ async function runPublish(ctx, options) {
|
|
|
1818
2282
|
|
|
1819
2283
|
// src/cli/commands/audit.ts
|
|
1820
2284
|
import { createHash } from "crypto";
|
|
1821
|
-
import { cpSync, existsSync as
|
|
1822
|
-
import { resolve as
|
|
2285
|
+
import { cpSync, existsSync as existsSync10, mkdirSync as mkdirSync2, readFileSync as readFileSync8, readdirSync, rmSync, statSync } from "fs";
|
|
2286
|
+
import { resolve as resolve15, join as join2 } from "path";
|
|
1823
2287
|
var FORBIDDEN_SHIP_TOKENS = [
|
|
1824
2288
|
"ModuleConfigOverlay",
|
|
1825
2289
|
"module-config-store",
|
|
@@ -1828,10 +2292,10 @@ var FORBIDDEN_SHIP_TOKENS = [
|
|
|
1828
2292
|
"dev.module-config"
|
|
1829
2293
|
];
|
|
1830
2294
|
function hashFile(path) {
|
|
1831
|
-
return createHash("sha256").update(
|
|
2295
|
+
return createHash("sha256").update(readFileSync8(path)).digest("hex");
|
|
1832
2296
|
}
|
|
1833
2297
|
function walkFiles(dir) {
|
|
1834
|
-
if (!
|
|
2298
|
+
if (!existsSync10(dir)) return [];
|
|
1835
2299
|
const entries = readdirSync(dir);
|
|
1836
2300
|
const files = [];
|
|
1837
2301
|
for (const entry of entries) {
|
|
@@ -1847,14 +2311,14 @@ function walkFiles(dir) {
|
|
|
1847
2311
|
async function runAuditShipBundle(ctx) {
|
|
1848
2312
|
const root = ctx.projectRoot;
|
|
1849
2313
|
const targets = [
|
|
1850
|
-
|
|
1851
|
-
|
|
2314
|
+
resolve15(root, "dist/cdn"),
|
|
2315
|
+
resolve15(root, ctx.config.module.outDir)
|
|
1852
2316
|
];
|
|
1853
2317
|
const violations = [];
|
|
1854
2318
|
for (const dir of targets) {
|
|
1855
2319
|
for (const file of walkFiles(dir)) {
|
|
1856
2320
|
if (!/\.(js|css|html)$/i.test(file)) continue;
|
|
1857
|
-
const content =
|
|
2321
|
+
const content = readFileSync8(file, "utf8");
|
|
1858
2322
|
for (const token of FORBIDDEN_SHIP_TOKENS) {
|
|
1859
2323
|
if (content.includes(token)) {
|
|
1860
2324
|
violations.push(`${file}: contains forbidden token "${token}"`);
|
|
@@ -1881,23 +2345,23 @@ async function runAuditBaselineCapture(ctx, options) {
|
|
|
1881
2345
|
const root = ctx.projectRoot;
|
|
1882
2346
|
const baseline = options.baselineDir ?? defaultBaselineDir(root, ctx.config);
|
|
1883
2347
|
const sources = [
|
|
1884
|
-
{ label: "cdn", path:
|
|
1885
|
-
{ label: "hubspot", path:
|
|
2348
|
+
{ label: "cdn", path: resolve15(root, "dist/cdn") },
|
|
2349
|
+
{ label: "hubspot", path: resolve15(root, ctx.config.module.outDir) }
|
|
1886
2350
|
];
|
|
1887
2351
|
if (ctx.global.dryRun) {
|
|
1888
2352
|
ctx.logger.info(`[dry-run] Would capture baseline to ${baseline}`);
|
|
1889
2353
|
return ExitCode.SUCCESS;
|
|
1890
2354
|
}
|
|
1891
2355
|
for (const { label, path } of sources) {
|
|
1892
|
-
if (!
|
|
2356
|
+
if (!existsSync10(path)) {
|
|
1893
2357
|
throw new WpHsError(`Cannot capture baseline \u2014 missing ${path}`, {
|
|
1894
2358
|
exitCode: ExitCode.USER_ERROR,
|
|
1895
2359
|
remediation: "Run wp build before wp audit baseline --capture."
|
|
1896
2360
|
});
|
|
1897
2361
|
}
|
|
1898
|
-
const target =
|
|
2362
|
+
const target = resolve15(baseline, label);
|
|
1899
2363
|
rmSync(target, { recursive: true, force: true });
|
|
1900
|
-
|
|
2364
|
+
mkdirSync2(baseline, { recursive: true });
|
|
1901
2365
|
cpSync(path, target, { recursive: true });
|
|
1902
2366
|
}
|
|
1903
2367
|
if (ctx.global.json) {
|
|
@@ -1911,13 +2375,13 @@ async function runAuditParity(ctx, options) {
|
|
|
1911
2375
|
const root = ctx.projectRoot;
|
|
1912
2376
|
const baseline = options.baselineDir ?? defaultBaselineDir(root, ctx.config);
|
|
1913
2377
|
const currentDirs = [
|
|
1914
|
-
{ label: "cdn", path:
|
|
1915
|
-
{ label: "hubspot", path:
|
|
2378
|
+
{ label: "cdn", path: resolve15(root, "dist/cdn") },
|
|
2379
|
+
{ label: "hubspot", path: resolve15(root, ctx.config.module.outDir) }
|
|
1916
2380
|
];
|
|
1917
2381
|
const mismatches = [];
|
|
1918
2382
|
for (const { label, path } of currentDirs) {
|
|
1919
|
-
const baselinePath =
|
|
1920
|
-
if (!
|
|
2383
|
+
const baselinePath = resolve15(baseline, label);
|
|
2384
|
+
if (!existsSync10(baselinePath)) {
|
|
1921
2385
|
ctx.logger.warn(`No baseline at ${baselinePath} \u2014 skipping ${label} parity`);
|
|
1922
2386
|
continue;
|
|
1923
2387
|
}
|
|
@@ -1932,8 +2396,8 @@ async function runAuditParity(ctx, options) {
|
|
|
1932
2396
|
}
|
|
1933
2397
|
for (const file of baselineFiles) {
|
|
1934
2398
|
const rel = file.replace(baselinePath, "");
|
|
1935
|
-
const current =
|
|
1936
|
-
if (!
|
|
2399
|
+
const current = resolve15(path, rel.slice(1));
|
|
2400
|
+
if (!existsSync10(current)) continue;
|
|
1937
2401
|
const bh = hashFile(file);
|
|
1938
2402
|
const ch = hashFile(current);
|
|
1939
2403
|
if (bh !== ch) {
|
|
@@ -1960,13 +2424,13 @@ var SECRET_PATTERNS = [
|
|
|
1960
2424
|
];
|
|
1961
2425
|
async function runAuditSecrets(ctx) {
|
|
1962
2426
|
const root = ctx.projectRoot;
|
|
1963
|
-
const scanRoots = ["src", "public", ".wphs/config"].map((d) =>
|
|
2427
|
+
const scanRoots = ["src", "public", ".wphs/config"].map((d) => resolve15(root, d));
|
|
1964
2428
|
const findings = [];
|
|
1965
2429
|
for (const dir of scanRoots) {
|
|
1966
2430
|
for (const file of walkFiles(dir)) {
|
|
1967
2431
|
if (!/\.(ts|tsx|js|jsx|json|env|yml|yaml|html)$/i.test(file)) continue;
|
|
1968
2432
|
if (file.includes("node_modules")) continue;
|
|
1969
|
-
const content =
|
|
2433
|
+
const content = readFileSync8(file, "utf8");
|
|
1970
2434
|
for (const pattern of SECRET_PATTERNS) {
|
|
1971
2435
|
pattern.lastIndex = 0;
|
|
1972
2436
|
if (pattern.test(content)) {
|
|
@@ -1992,13 +2456,13 @@ async function runAuditSecrets(ctx) {
|
|
|
1992
2456
|
}
|
|
1993
2457
|
|
|
1994
2458
|
// src/cli/commands/init.ts
|
|
1995
|
-
import { cpSync as cpSync2, existsSync as
|
|
1996
|
-
import { basename as basename2, resolve as
|
|
1997
|
-
import * as
|
|
2459
|
+
import { cpSync as cpSync2, existsSync as existsSync14, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "fs";
|
|
2460
|
+
import { basename as basename2, resolve as resolve19 } from "path";
|
|
2461
|
+
import * as p4 from "@clack/prompts";
|
|
1998
2462
|
|
|
1999
2463
|
// src/lib/init-router-scaffold.ts
|
|
2000
|
-
import { existsSync as
|
|
2001
|
-
import { resolve as
|
|
2464
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync3, readFileSync as readFileSync9, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
2465
|
+
import { resolve as resolve16 } from "path";
|
|
2002
2466
|
var ROUTER_FILES = [
|
|
2003
2467
|
"src/routes",
|
|
2004
2468
|
"src/routeTree.gen.ts",
|
|
@@ -2012,10 +2476,10 @@ function isModuleTemplate(template) {
|
|
|
2012
2476
|
return template === "module-hybrid-cdn" || template === "module-monolith";
|
|
2013
2477
|
}
|
|
2014
2478
|
function writeRouterConfig(targetDir, mode) {
|
|
2015
|
-
const configDir =
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2479
|
+
const configDir = resolve16(targetDir, "src/config");
|
|
2480
|
+
mkdirSync3(configDir, { recursive: true });
|
|
2481
|
+
writeFileSync3(
|
|
2482
|
+
resolve16(configDir, "router.ts"),
|
|
2019
2483
|
`/** Router history \u2014 set by \`wp init\`. Hash matches woodsportal-client-frontend (HubSpot CMS embeds). */
|
|
2020
2484
|
export type RouterHistoryMode = 'hash' | 'browser'
|
|
2021
2485
|
|
|
@@ -2024,8 +2488,8 @@ export const ROUTER_HISTORY: RouterHistoryMode = '${mode}'
|
|
|
2024
2488
|
);
|
|
2025
2489
|
}
|
|
2026
2490
|
function writeMountDevPreviewRouter(targetDir) {
|
|
2027
|
-
|
|
2028
|
-
|
|
2491
|
+
writeFileSync3(
|
|
2492
|
+
resolve16(targetDir, "src/portal/mount-dev-preview.tsx"),
|
|
2029
2493
|
`import { mountGreenfieldDev } from '../../.wphs/.kit-bootstrap-greenfield.tsx'
|
|
2030
2494
|
|
|
2031
2495
|
import { App } from '@/app'
|
|
@@ -2038,8 +2502,8 @@ export async function mountDevPreview() {
|
|
|
2038
2502
|
);
|
|
2039
2503
|
}
|
|
2040
2504
|
function writeMountDevPreviewSinglePage(targetDir) {
|
|
2041
|
-
|
|
2042
|
-
|
|
2505
|
+
writeFileSync3(
|
|
2506
|
+
resolve16(targetDir, "src/portal/mount-dev-preview.tsx"),
|
|
2043
2507
|
`import { mountGreenfieldDev } from '../../.wphs/.kit-bootstrap-greenfield.tsx'
|
|
2044
2508
|
|
|
2045
2509
|
import { StarterSinglePage } from '@/features/starter'
|
|
@@ -2052,8 +2516,8 @@ export async function mountDevPreview() {
|
|
|
2052
2516
|
);
|
|
2053
2517
|
}
|
|
2054
2518
|
function writeMountPortalRouter(targetDir) {
|
|
2055
|
-
|
|
2056
|
-
|
|
2519
|
+
writeFileSync3(
|
|
2520
|
+
resolve16(targetDir, "src/portal/mount-portal.tsx"),
|
|
2057
2521
|
`import ReactDOM from 'react-dom/client'
|
|
2058
2522
|
|
|
2059
2523
|
import { App } from '@/app'
|
|
@@ -2070,8 +2534,8 @@ export async function mountPortal() {
|
|
|
2070
2534
|
);
|
|
2071
2535
|
}
|
|
2072
2536
|
function writeMountPortalSinglePage(targetDir) {
|
|
2073
|
-
|
|
2074
|
-
|
|
2537
|
+
writeFileSync3(
|
|
2538
|
+
resolve16(targetDir, "src/portal/mount-portal.tsx"),
|
|
2075
2539
|
`import ReactDOM from 'react-dom/client'
|
|
2076
2540
|
|
|
2077
2541
|
import { StarterSinglePage } from '@/features/starter'
|
|
@@ -2088,11 +2552,11 @@ export async function mountPortal() {
|
|
|
2088
2552
|
);
|
|
2089
2553
|
}
|
|
2090
2554
|
function writeStarterSinglePage(targetDir) {
|
|
2091
|
-
const file =
|
|
2092
|
-
if (
|
|
2555
|
+
const file = resolve16(targetDir, "src/features/starter/components/starter-single-page.tsx");
|
|
2556
|
+
if (existsSync11(file)) {
|
|
2093
2557
|
return;
|
|
2094
2558
|
}
|
|
2095
|
-
|
|
2559
|
+
writeFileSync3(
|
|
2096
2560
|
file,
|
|
2097
2561
|
`import { StarterShell } from '@/components/layout/starter-shell'
|
|
2098
2562
|
|
|
@@ -2115,25 +2579,25 @@ export function StarterSinglePage() {
|
|
|
2115
2579
|
);
|
|
2116
2580
|
}
|
|
2117
2581
|
function removePath(targetDir, relativePath) {
|
|
2118
|
-
const full =
|
|
2119
|
-
if (!
|
|
2582
|
+
const full = resolve16(targetDir, relativePath);
|
|
2583
|
+
if (!existsSync11(full)) {
|
|
2120
2584
|
return;
|
|
2121
2585
|
}
|
|
2122
2586
|
rmSync2(full, { recursive: true, force: true });
|
|
2123
2587
|
}
|
|
2124
2588
|
function stripTanStackFromPackage(targetDir) {
|
|
2125
|
-
const pkgPath =
|
|
2126
|
-
if (!
|
|
2589
|
+
const pkgPath = resolve16(targetDir, "package.json");
|
|
2590
|
+
if (!existsSync11(pkgPath)) {
|
|
2127
2591
|
return;
|
|
2128
2592
|
}
|
|
2129
|
-
const pkg = JSON.parse(
|
|
2593
|
+
const pkg = JSON.parse(readFileSync9(pkgPath, "utf8"));
|
|
2130
2594
|
if (!pkg.devDependencies) {
|
|
2131
2595
|
return;
|
|
2132
2596
|
}
|
|
2133
2597
|
for (const dep of TANSTACK_DEPS) {
|
|
2134
2598
|
delete pkg.devDependencies[dep];
|
|
2135
2599
|
}
|
|
2136
|
-
|
|
2600
|
+
writeFileSync3(pkgPath, `${JSON.stringify(pkg, null, 2)}
|
|
2137
2601
|
`);
|
|
2138
2602
|
}
|
|
2139
2603
|
function applyRouterScaffold(targetDir, mode, template) {
|
|
@@ -2153,30 +2617,30 @@ function applyRouterScaffold(targetDir, mode, template) {
|
|
|
2153
2617
|
writeRouterConfig(targetDir, mode);
|
|
2154
2618
|
writeMountDevPreviewRouter(targetDir);
|
|
2155
2619
|
writeMountPortalRouter(targetDir);
|
|
2156
|
-
const featuresIndex =
|
|
2157
|
-
if (
|
|
2158
|
-
let content =
|
|
2620
|
+
const featuresIndex = resolve16(targetDir, "src/features/starter/index.ts");
|
|
2621
|
+
if (existsSync11(featuresIndex)) {
|
|
2622
|
+
let content = readFileSync9(featuresIndex, "utf8");
|
|
2159
2623
|
if (!content.includes("StarterSinglePage")) {
|
|
2160
2624
|
content = content.replace(
|
|
2161
2625
|
"export type { StarterPreviewValues } from './hooks/use-starter-preview'\n",
|
|
2162
2626
|
"export type { StarterPreviewValues } from './hooks/use-starter-preview'\nexport { StarterSinglePage } from './components/starter-single-page'\n"
|
|
2163
2627
|
);
|
|
2164
|
-
|
|
2628
|
+
writeFileSync3(featuresIndex, content);
|
|
2165
2629
|
}
|
|
2166
2630
|
}
|
|
2167
2631
|
}
|
|
2168
2632
|
|
|
2169
2633
|
// src/lib/cdn-github-setup.ts
|
|
2170
|
-
import { existsSync as
|
|
2171
|
-
import { resolve as
|
|
2172
|
-
import * as
|
|
2634
|
+
import { existsSync as existsSync12, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
|
|
2635
|
+
import { resolve as resolve17 } from "path";
|
|
2636
|
+
import * as p3 from "@clack/prompts";
|
|
2173
2637
|
var PORTAL_CDN_PUBLISH_TOKEN_ENV = "PORTAL_CDN_PUBLISH_TOKEN";
|
|
2174
2638
|
var PORTAL_CDN_JSON = "portal.cdn.json";
|
|
2175
2639
|
var PLACEHOLDER_GITHUB = /your-org|your-portal-cdn-mirror/i;
|
|
2176
2640
|
function defaultPortalCdnJson() {
|
|
2177
2641
|
const examplePath = kitPath("templates", "shared", "portal.cdn.json.example");
|
|
2178
|
-
if (
|
|
2179
|
-
return JSON.parse(
|
|
2642
|
+
if (existsSync12(examplePath)) {
|
|
2643
|
+
return JSON.parse(readFileSync10(examplePath, "utf8"));
|
|
2180
2644
|
}
|
|
2181
2645
|
return {
|
|
2182
2646
|
publishMode: "jsdelivr",
|
|
@@ -2187,10 +2651,10 @@ function defaultPortalCdnJson() {
|
|
|
2187
2651
|
};
|
|
2188
2652
|
}
|
|
2189
2653
|
function readPortalCdnJson(projectRoot) {
|
|
2190
|
-
const path =
|
|
2191
|
-
if (!
|
|
2654
|
+
const path = resolve17(projectRoot, PORTAL_CDN_JSON);
|
|
2655
|
+
if (!existsSync12(path)) return null;
|
|
2192
2656
|
try {
|
|
2193
|
-
return JSON.parse(
|
|
2657
|
+
return JSON.parse(readFileSync10(path, "utf8"));
|
|
2194
2658
|
} catch {
|
|
2195
2659
|
return null;
|
|
2196
2660
|
}
|
|
@@ -2237,7 +2701,7 @@ function needsCdnGitHubSetup(projectRoot) {
|
|
|
2237
2701
|
function applyCdnGitHubSetup(projectRoot, input) {
|
|
2238
2702
|
const existing = readPortalCdnJson(projectRoot) ?? defaultPortalCdnJson();
|
|
2239
2703
|
const updated = portalCdnJsonFromGithub(input.github, existing);
|
|
2240
|
-
|
|
2704
|
+
writeFileSync4(resolve17(projectRoot, PORTAL_CDN_JSON), `${JSON.stringify(updated, null, 2)}
|
|
2241
2705
|
`);
|
|
2242
2706
|
if (input.token?.trim()) {
|
|
2243
2707
|
writeEnvLocal(projectRoot, { [PORTAL_CDN_PUBLISH_TOKEN_ENV]: input.token.trim() });
|
|
@@ -2255,7 +2719,7 @@ async function promptCdnGitHubSetup(options = {}) {
|
|
|
2255
2719
|
github = readPortalCdnJson(projectRoot)?.github;
|
|
2256
2720
|
}
|
|
2257
2721
|
if (!github) {
|
|
2258
|
-
const answer = await
|
|
2722
|
+
const answer = await p3.text({
|
|
2259
2723
|
message: "Public GitHub CDN mirror (org/repo)",
|
|
2260
2724
|
placeholder: options.githubDefault ?? "your-org/your-portal-cdn-mirror",
|
|
2261
2725
|
initialValue: options.githubDefault,
|
|
@@ -2264,21 +2728,21 @@ async function promptCdnGitHubSetup(options = {}) {
|
|
|
2264
2728
|
return slug ? void 0 : "Enter a public repo as org/repo (jsDelivr serves from it)";
|
|
2265
2729
|
}
|
|
2266
2730
|
});
|
|
2267
|
-
if (
|
|
2731
|
+
if (p3.isCancel(answer)) return null;
|
|
2268
2732
|
github = normalizeGithubSlug(answer) ?? void 0;
|
|
2269
2733
|
}
|
|
2270
2734
|
if (!github) return null;
|
|
2271
2735
|
if (hasCdnPublishToken(projectRoot)) {
|
|
2272
2736
|
return { github };
|
|
2273
2737
|
}
|
|
2274
|
-
const tokenAnswer = await
|
|
2738
|
+
const tokenAnswer = await p3.password({
|
|
2275
2739
|
message: "GitHub token (repo push on CDN mirror)",
|
|
2276
2740
|
validate: (value) => {
|
|
2277
2741
|
if (!value?.trim()) return "Required for wp publish \u2014 create a fine-grained PAT with Contents: Read and write";
|
|
2278
2742
|
return void 0;
|
|
2279
2743
|
}
|
|
2280
2744
|
});
|
|
2281
|
-
if (
|
|
2745
|
+
if (p3.isCancel(tokenAnswer)) return null;
|
|
2282
2746
|
return { github, token: tokenAnswer.trim() };
|
|
2283
2747
|
}
|
|
2284
2748
|
function formatCdnSetupNote(_projectRoot) {
|
|
@@ -2286,8 +2750,8 @@ function formatCdnSetupNote(_projectRoot) {
|
|
|
2286
2750
|
}
|
|
2287
2751
|
|
|
2288
2752
|
// src/lib/project-setup.ts
|
|
2289
|
-
import { copyFileSync, existsSync as
|
|
2290
|
-
import { basename, resolve as
|
|
2753
|
+
import { copyFileSync, existsSync as existsSync13, writeFileSync as writeFileSync5 } from "fs";
|
|
2754
|
+
import { basename, resolve as resolve18 } from "path";
|
|
2291
2755
|
function envFileForTier(tier) {
|
|
2292
2756
|
return tier === "dev" ? ".env.dev" : tier === "stg" ? ".env.stg" : ".env.prod";
|
|
2293
2757
|
}
|
|
@@ -2297,13 +2761,13 @@ function tiersToSetup(config, tier) {
|
|
|
2297
2761
|
}
|
|
2298
2762
|
function firstExisting(paths) {
|
|
2299
2763
|
for (const path of paths) {
|
|
2300
|
-
if (
|
|
2764
|
+
if (existsSync13(path)) return path;
|
|
2301
2765
|
}
|
|
2302
2766
|
return null;
|
|
2303
2767
|
}
|
|
2304
2768
|
function copyMissingFile(projectRoot, targetName, sourceCandidates, dryRun) {
|
|
2305
|
-
const target =
|
|
2306
|
-
if (
|
|
2769
|
+
const target = resolve18(projectRoot, targetName);
|
|
2770
|
+
if (existsSync13(target)) {
|
|
2307
2771
|
return { action: "skipped", path: targetName, detail: "already present" };
|
|
2308
2772
|
}
|
|
2309
2773
|
const source = firstExisting(sourceCandidates);
|
|
@@ -2325,12 +2789,12 @@ function copyMissingFile(projectRoot, targetName, sourceCandidates, dryRun) {
|
|
|
2325
2789
|
};
|
|
2326
2790
|
}
|
|
2327
2791
|
function writeMissingFile(projectRoot, targetName, content, dryRun) {
|
|
2328
|
-
const target =
|
|
2329
|
-
if (
|
|
2792
|
+
const target = resolve18(projectRoot, targetName);
|
|
2793
|
+
if (existsSync13(target)) {
|
|
2330
2794
|
return { action: "skipped", path: targetName, detail: "already present" };
|
|
2331
2795
|
}
|
|
2332
2796
|
if (!dryRun) {
|
|
2333
|
-
|
|
2797
|
+
writeFileSync5(target, content);
|
|
2334
2798
|
}
|
|
2335
2799
|
return {
|
|
2336
2800
|
action: "copied",
|
|
@@ -2356,8 +2820,8 @@ function applyProjectSetup(ctx, options = {}) {
|
|
|
2356
2820
|
const templateDir = kitTemplateDir(config);
|
|
2357
2821
|
results.push(
|
|
2358
2822
|
copyMissingFile(projectRoot, HUBSPOT_CONFIG_FILE, [
|
|
2359
|
-
|
|
2360
|
-
|
|
2823
|
+
resolve18(projectRoot, HUBSPOT_CONFIG_EXAMPLE),
|
|
2824
|
+
resolve18(templateDir, HUBSPOT_CONFIG_EXAMPLE),
|
|
2361
2825
|
kitPath("templates", HUBSPOT_CONFIG_EXAMPLE)
|
|
2362
2826
|
], dryRun)
|
|
2363
2827
|
);
|
|
@@ -2366,8 +2830,8 @@ function applyProjectSetup(ctx, options = {}) {
|
|
|
2366
2830
|
const envExample = `${envFile}.example`;
|
|
2367
2831
|
results.push(
|
|
2368
2832
|
copyMissingFile(projectRoot, envFile, [
|
|
2369
|
-
|
|
2370
|
-
|
|
2833
|
+
resolve18(projectRoot, envExample),
|
|
2834
|
+
resolve18(sharedDir, envExample)
|
|
2371
2835
|
], dryRun)
|
|
2372
2836
|
);
|
|
2373
2837
|
}
|
|
@@ -2375,26 +2839,31 @@ function applyProjectSetup(ctx, options = {}) {
|
|
|
2375
2839
|
const mirror = config.cdn.mirror;
|
|
2376
2840
|
results.push(
|
|
2377
2841
|
copyMissingFile(projectRoot, mirror, [
|
|
2378
|
-
|
|
2379
|
-
|
|
2842
|
+
resolve18(projectRoot, `${mirror}.example`),
|
|
2843
|
+
resolve18(sharedDir, "portal.cdn.json.example")
|
|
2380
2844
|
], dryRun)
|
|
2381
2845
|
);
|
|
2382
2846
|
const buildIdFile = config.cdn.buildIdFile;
|
|
2383
2847
|
results.push(
|
|
2384
|
-
writeMissingFile(
|
|
2848
|
+
writeMissingFile(
|
|
2849
|
+
projectRoot,
|
|
2850
|
+
buildIdFile,
|
|
2851
|
+
"# Channel baseline for local CDN deploys (wp deploy auto-bumps per developer).\n# Example resolved id: v0.0.1-dev.you.1\nv0.0.1-dev\n",
|
|
2852
|
+
dryRun
|
|
2853
|
+
)
|
|
2385
2854
|
);
|
|
2386
2855
|
}
|
|
2387
2856
|
for (const file of templateViteFiles(config)) {
|
|
2388
2857
|
results.push(
|
|
2389
|
-
copyMissingFile(projectRoot, file, [
|
|
2858
|
+
copyMissingFile(projectRoot, file, [resolve18(templateDir, file)], dryRun)
|
|
2390
2859
|
);
|
|
2391
2860
|
}
|
|
2392
2861
|
if (isModuleProject(config) && !isThemeProject(config)) {
|
|
2393
2862
|
const adapter = config.dev.adapter;
|
|
2394
|
-
const adapterPath =
|
|
2395
|
-
if (!
|
|
2396
|
-
const templateAdapter =
|
|
2397
|
-
if (
|
|
2863
|
+
const adapterPath = resolve18(projectRoot, adapter);
|
|
2864
|
+
if (!existsSync13(adapterPath)) {
|
|
2865
|
+
const templateAdapter = resolve18(templateDir, "wphs.adapter.ts");
|
|
2866
|
+
if (existsSync13(templateAdapter)) {
|
|
2398
2867
|
results.push(copyMissingFile(projectRoot, adapter, [templateAdapter], dryRun));
|
|
2399
2868
|
} else {
|
|
2400
2869
|
results.push({
|
|
@@ -2411,26 +2880,26 @@ function applyProjectSetup(ctx, options = {}) {
|
|
|
2411
2880
|
}
|
|
2412
2881
|
function copySharedSetupExamples(targetDir) {
|
|
2413
2882
|
const sharedDir = kitPath("templates", "shared");
|
|
2414
|
-
if (!
|
|
2883
|
+
if (!existsSync13(sharedDir)) return;
|
|
2415
2884
|
for (const name of [
|
|
2416
2885
|
".env.dev.example",
|
|
2417
2886
|
".env.stg.example",
|
|
2418
2887
|
".env.prod.example",
|
|
2419
2888
|
"portal.cdn.json.example"
|
|
2420
2889
|
]) {
|
|
2421
|
-
const src =
|
|
2422
|
-
const dest =
|
|
2423
|
-
if (
|
|
2890
|
+
const src = resolve18(sharedDir, name);
|
|
2891
|
+
const dest = resolve18(targetDir, name);
|
|
2892
|
+
if (existsSync13(src) && !existsSync13(dest)) {
|
|
2424
2893
|
copyFileSync(src, dest);
|
|
2425
2894
|
}
|
|
2426
2895
|
}
|
|
2427
2896
|
const hubspotExample = firstExisting([
|
|
2428
|
-
|
|
2897
|
+
resolve18(sharedDir, HUBSPOT_CONFIG_EXAMPLE),
|
|
2429
2898
|
kitPath("templates", HUBSPOT_CONFIG_EXAMPLE)
|
|
2430
2899
|
]);
|
|
2431
2900
|
if (!hubspotExample) return;
|
|
2432
|
-
const destHubspot =
|
|
2433
|
-
if (!
|
|
2901
|
+
const destHubspot = resolve18(targetDir, HUBSPOT_CONFIG_EXAMPLE);
|
|
2902
|
+
if (!existsSync13(destHubspot) && !existsSync13(resolve18(targetDir, HUBSPOT_CONFIG_FILE))) {
|
|
2434
2903
|
copyFileSync(hubspotExample, destHubspot);
|
|
2435
2904
|
}
|
|
2436
2905
|
}
|
|
@@ -2528,7 +2997,7 @@ function buildConfigForProject(template, projectName, router) {
|
|
|
2528
2997
|
return config;
|
|
2529
2998
|
}
|
|
2530
2999
|
function assertTargetDirIsScaffoldable(targetDir) {
|
|
2531
|
-
if (!
|
|
3000
|
+
if (!existsSync14(targetDir)) return;
|
|
2532
3001
|
const entries = readdirSync2(targetDir).filter((entry) => entry !== ".DS_Store");
|
|
2533
3002
|
if (entries.length > 0) {
|
|
2534
3003
|
throw new WpHsError(`Target directory is not empty: ${targetDir}`, {
|
|
@@ -2539,7 +3008,7 @@ function assertTargetDirIsScaffoldable(targetDir) {
|
|
|
2539
3008
|
}
|
|
2540
3009
|
function resolveInitTargetDir(options, interactiveName) {
|
|
2541
3010
|
if (options.directory) {
|
|
2542
|
-
return
|
|
3011
|
+
return resolve19(options.directory);
|
|
2543
3012
|
}
|
|
2544
3013
|
const parentDir = process.cwd();
|
|
2545
3014
|
const slug = options.name ?? interactiveName;
|
|
@@ -2553,7 +3022,7 @@ function resolveInitTargetDir(options, interactiveName) {
|
|
|
2553
3022
|
if (error) {
|
|
2554
3023
|
throw new WpHsError(error, { exitCode: ExitCode.USER_ERROR });
|
|
2555
3024
|
}
|
|
2556
|
-
return
|
|
3025
|
+
return resolve19(parentDir, slug);
|
|
2557
3026
|
}
|
|
2558
3027
|
async function runInit(ctx, options) {
|
|
2559
3028
|
const nonInteractive = ctx.global.ci || ctx.global.yes || ctx.global.json;
|
|
@@ -2562,7 +3031,7 @@ async function runInit(ctx, options) {
|
|
|
2562
3031
|
let router = options.router;
|
|
2563
3032
|
if (!nonInteractive) {
|
|
2564
3033
|
if (!template) {
|
|
2565
|
-
const type = await
|
|
3034
|
+
const type = await p4.select({
|
|
2566
3035
|
message: "Project type",
|
|
2567
3036
|
options: [
|
|
2568
3037
|
{
|
|
@@ -2574,22 +3043,22 @@ async function runInit(ctx, options) {
|
|
|
2574
3043
|
{ value: "theme-starter", label: "Theme starter", hint: "CMS theme templates" }
|
|
2575
3044
|
]
|
|
2576
3045
|
});
|
|
2577
|
-
if (
|
|
3046
|
+
if (p4.isCancel(type)) throw new WpHsError("Init cancelled", { exitCode: ExitCode.USER_ERROR });
|
|
2578
3047
|
template = type;
|
|
2579
3048
|
}
|
|
2580
3049
|
if (!options.directory && !projectName) {
|
|
2581
|
-
const nameAnswer = await
|
|
3050
|
+
const nameAnswer = await p4.text({
|
|
2582
3051
|
message: "Project name (folder name)",
|
|
2583
3052
|
placeholder: "my-portal-module",
|
|
2584
3053
|
validate: (value) => validateProjectName(normalizeProjectName(value ?? ""))
|
|
2585
3054
|
});
|
|
2586
|
-
if (
|
|
3055
|
+
if (p4.isCancel(nameAnswer)) {
|
|
2587
3056
|
throw new WpHsError("Init cancelled", { exitCode: ExitCode.USER_ERROR });
|
|
2588
3057
|
}
|
|
2589
3058
|
projectName = normalizeProjectName(nameAnswer);
|
|
2590
3059
|
}
|
|
2591
3060
|
if (!router && template && isModuleTemplate(template)) {
|
|
2592
|
-
const routerAnswer = await
|
|
3061
|
+
const routerAnswer = await p4.select({
|
|
2593
3062
|
message: "Client-side routing",
|
|
2594
3063
|
initialValue: "hash",
|
|
2595
3064
|
options: [
|
|
@@ -2610,7 +3079,7 @@ async function runInit(ctx, options) {
|
|
|
2610
3079
|
}
|
|
2611
3080
|
]
|
|
2612
3081
|
});
|
|
2613
|
-
if (
|
|
3082
|
+
if (p4.isCancel(routerAnswer)) {
|
|
2614
3083
|
throw new WpHsError("Init cancelled", { exitCode: ExitCode.USER_ERROR });
|
|
2615
3084
|
}
|
|
2616
3085
|
router = routerAnswer;
|
|
@@ -2626,29 +3095,29 @@ async function runInit(ctx, options) {
|
|
|
2626
3095
|
const slug = projectName ?? basename2(targetDir);
|
|
2627
3096
|
assertTargetDirIsScaffoldable(targetDir);
|
|
2628
3097
|
const templateDir = kitPath("templates", template);
|
|
2629
|
-
if (!
|
|
3098
|
+
if (!existsSync14(templateDir)) {
|
|
2630
3099
|
throw new WpHsError(`Template not found: ${template}`, { exitCode: ExitCode.USER_ERROR });
|
|
2631
3100
|
}
|
|
2632
3101
|
if (ctx.global.dryRun) {
|
|
2633
3102
|
ctx.logger.info(`[dry-run] Would scaffold ${template} in ${targetDir}`);
|
|
2634
3103
|
return ExitCode.SUCCESS;
|
|
2635
3104
|
}
|
|
2636
|
-
|
|
3105
|
+
mkdirSync4(targetDir, { recursive: true });
|
|
2637
3106
|
cpSync2(templateDir, targetDir, { recursive: true });
|
|
2638
3107
|
copySharedSetupExamples(targetDir);
|
|
2639
3108
|
const config = buildConfigForProject(template, slug, router);
|
|
2640
|
-
|
|
3109
|
+
writeFileSync6(resolve19(targetDir, WOODSCLI_CONFIG_FILE), `${JSON.stringify(config, null, 2)}
|
|
2641
3110
|
`);
|
|
2642
|
-
const pkgPath =
|
|
2643
|
-
if (
|
|
2644
|
-
const pkg = JSON.parse(
|
|
3111
|
+
const pkgPath = resolve19(targetDir, "package.json");
|
|
3112
|
+
if (existsSync14(pkgPath)) {
|
|
3113
|
+
const pkg = JSON.parse(readFileSync11(pkgPath, "utf8"));
|
|
2645
3114
|
pkg.name = slug;
|
|
2646
3115
|
pkg.scripts = { ...pkg.scripts, ...TEMPLATE_SCRIPTS[template] };
|
|
2647
3116
|
pkg.devDependencies = {
|
|
2648
3117
|
...pkg.devDependencies,
|
|
2649
3118
|
...templateDevDependencies()
|
|
2650
3119
|
};
|
|
2651
|
-
|
|
3120
|
+
writeFileSync6(pkgPath, `${JSON.stringify(pkg, null, 2)}
|
|
2652
3121
|
`);
|
|
2653
3122
|
}
|
|
2654
3123
|
if (router && isModuleTemplate(template)) {
|
|
@@ -2659,7 +3128,7 @@ async function runInit(ctx, options) {
|
|
|
2659
3128
|
logger: ctx.logger,
|
|
2660
3129
|
dryRun: false
|
|
2661
3130
|
});
|
|
2662
|
-
const gitignorePath =
|
|
3131
|
+
const gitignorePath = resolve19(targetDir, ".gitignore");
|
|
2663
3132
|
const gitignoreExtras = [
|
|
2664
3133
|
"hubspot.config.yml",
|
|
2665
3134
|
".wphs/config/module-config.local.json",
|
|
@@ -2672,10 +3141,10 @@ async function runInit(ctx, options) {
|
|
|
2672
3141
|
".wphs/.kit-bootstrap.tsx",
|
|
2673
3142
|
".wphs/.kit-bootstrap-greenfield.tsx"
|
|
2674
3143
|
];
|
|
2675
|
-
const existing =
|
|
3144
|
+
const existing = existsSync14(gitignorePath) ? readFileSync11(gitignorePath, "utf8") : "";
|
|
2676
3145
|
const toAppend = gitignoreExtras.filter((line) => !existing.includes(line)).join("\n");
|
|
2677
3146
|
if (toAppend) {
|
|
2678
|
-
|
|
3147
|
+
writeFileSync6(gitignorePath, `${existing.trimEnd()}
|
|
2679
3148
|
${toAppend}
|
|
2680
3149
|
`);
|
|
2681
3150
|
}
|
|
@@ -2687,7 +3156,7 @@ ${toAppend}
|
|
|
2687
3156
|
cdnSetup = { github: options.cdnGithub };
|
|
2688
3157
|
}
|
|
2689
3158
|
} else {
|
|
2690
|
-
|
|
3159
|
+
p4.log.step("Hybrid CDN \u2014 GitHub mirror (public repo for jsDelivr)");
|
|
2691
3160
|
cdnSetup = await promptCdnGitHubSetup({
|
|
2692
3161
|
projectRoot: targetDir,
|
|
2693
3162
|
githubDefault,
|
|
@@ -2697,15 +3166,15 @@ ${toAppend}
|
|
|
2697
3166
|
if (cdnSetup) {
|
|
2698
3167
|
applyCdnGitHubSetup(targetDir, cdnSetup);
|
|
2699
3168
|
if (!nonInteractive) {
|
|
2700
|
-
|
|
3169
|
+
p4.log.success(formatCdnSetupNote(targetDir));
|
|
2701
3170
|
} else {
|
|
2702
3171
|
ctx.logger.success(formatCdnSetupNote(targetDir));
|
|
2703
3172
|
}
|
|
2704
3173
|
}
|
|
2705
3174
|
}
|
|
2706
3175
|
if (!nonInteractive) {
|
|
2707
|
-
|
|
2708
|
-
|
|
3176
|
+
p4.log.success(`Scaffolded ${template} in ${targetDir}`);
|
|
3177
|
+
p4.note(`cd ${slug} && yarn install && wp setup && wp doctor && wp dev`, "Getting started");
|
|
2709
3178
|
} else {
|
|
2710
3179
|
ctx.logger.success(`Scaffolded ${template} in ${targetDir}`);
|
|
2711
3180
|
}
|
|
@@ -2713,70 +3182,112 @@ ${toAppend}
|
|
|
2713
3182
|
}
|
|
2714
3183
|
|
|
2715
3184
|
// src/cli/commands/migrate.ts
|
|
2716
|
-
import { cpSync as cpSync3, existsSync as
|
|
2717
|
-
import { join as join3, resolve as
|
|
3185
|
+
import { cpSync as cpSync3, existsSync as existsSync15, mkdirSync as mkdirSync5, readFileSync as readFileSync12, readdirSync as readdirSync3, renameSync, rmSync as rmSync3, writeFileSync as writeFileSync7 } from "fs";
|
|
3186
|
+
import { join as join3, resolve as resolve20 } from "path";
|
|
2718
3187
|
var LEGACY_DIRS = [".dev", ".woodsportal"];
|
|
2719
3188
|
var LEGACY_BASELINE = ".wp-hs-baseline";
|
|
3189
|
+
var LEGACY_BUILD_ID_FILE = ".portal-build-id";
|
|
3190
|
+
var MODULE_BUILD_ID_FILE = ".module-build-id";
|
|
3191
|
+
function readFirstNonCommentLine(filePath) {
|
|
3192
|
+
if (!existsSync15(filePath)) return void 0;
|
|
3193
|
+
const line = readFileSync12(filePath, "utf8").split("\n").map((l) => l.trim()).find((l) => l.length > 0 && !l.startsWith("#"));
|
|
3194
|
+
return line || void 0;
|
|
3195
|
+
}
|
|
3196
|
+
function stripToBaseline(raw) {
|
|
3197
|
+
const line = raw?.trim();
|
|
3198
|
+
if (!line) return void 0;
|
|
3199
|
+
const perDev = line.match(/^(v\d+\.\d+\.\d+(?:-[a-z0-9]+)?)\.[a-z0-9-]{1,12}\.\d+$/i);
|
|
3200
|
+
if (perDev) return perDev[1];
|
|
3201
|
+
const numericSuffix = line.match(/^(v\d+\.\d+\.\d+(?:-[a-z0-9]+)?)\.\d+$/);
|
|
3202
|
+
if (numericSuffix) return numericSuffix[1];
|
|
3203
|
+
return line;
|
|
3204
|
+
}
|
|
2720
3205
|
async function runMigrate(ctx, _options = {}) {
|
|
2721
3206
|
const root = ctx.projectRoot;
|
|
2722
|
-
const wphsRoot =
|
|
3207
|
+
const wphsRoot = resolve20(root, WPHS_WORKSPACE_DIR);
|
|
2723
3208
|
const actions = [];
|
|
2724
3209
|
for (const legacy of LEGACY_DIRS) {
|
|
2725
|
-
const legacyPath =
|
|
2726
|
-
if (!
|
|
3210
|
+
const legacyPath = resolve20(root, legacy);
|
|
3211
|
+
if (!existsSync15(legacyPath)) continue;
|
|
2727
3212
|
actions.push(`Move ${legacy}/ \u2192 .wphs/`);
|
|
2728
3213
|
if (ctx.global.dryRun) continue;
|
|
2729
|
-
|
|
2730
|
-
|
|
3214
|
+
mkdirSync5(join3(wphsRoot, "config"), { recursive: true });
|
|
3215
|
+
mkdirSync5(join3(wphsRoot, "assets", "branding"), { recursive: true });
|
|
2731
3216
|
const localConfig = join3(legacyPath, "module-config.local.json");
|
|
2732
3217
|
const exampleConfig = join3(legacyPath, "module-config.example.json");
|
|
2733
|
-
if (
|
|
3218
|
+
if (existsSync15(localConfig)) {
|
|
2734
3219
|
cpSync3(localConfig, join3(wphsRoot, "config", "module-config.local.json"));
|
|
2735
3220
|
}
|
|
2736
|
-
if (
|
|
3221
|
+
if (existsSync15(exampleConfig)) {
|
|
2737
3222
|
cpSync3(exampleConfig, join3(wphsRoot, "config", "module-config.example.json"));
|
|
2738
3223
|
}
|
|
2739
3224
|
const branding = join3(legacyPath, "branding-assets");
|
|
2740
|
-
if (
|
|
3225
|
+
if (existsSync15(branding)) {
|
|
2741
3226
|
for (const file of readdirSync3(branding)) {
|
|
2742
3227
|
cpSync3(join3(branding, file), join3(wphsRoot, "assets", "branding", file));
|
|
2743
3228
|
}
|
|
2744
3229
|
}
|
|
2745
3230
|
rmSync3(legacyPath, { recursive: true, force: true });
|
|
2746
3231
|
}
|
|
2747
|
-
const legacyBaseline =
|
|
3232
|
+
const legacyBaseline = resolve20(root, LEGACY_BASELINE);
|
|
2748
3233
|
const targetBaseline = join3(wphsRoot, "audit", "baseline");
|
|
2749
|
-
if (
|
|
3234
|
+
if (existsSync15(legacyBaseline)) {
|
|
2750
3235
|
actions.push(`Move ${LEGACY_BASELINE}/ \u2192 .wphs/audit/baseline/`);
|
|
2751
3236
|
if (!ctx.global.dryRun) {
|
|
2752
|
-
|
|
3237
|
+
mkdirSync5(targetBaseline, { recursive: true });
|
|
2753
3238
|
cpSync3(legacyBaseline, targetBaseline, { recursive: true });
|
|
2754
3239
|
rmSync3(legacyBaseline, { recursive: true, force: true });
|
|
2755
3240
|
}
|
|
2756
3241
|
}
|
|
2757
|
-
const legacyConfigPath2 =
|
|
2758
|
-
const woodscliPath =
|
|
2759
|
-
if (
|
|
3242
|
+
const legacyConfigPath2 = resolve20(root, LEGACY_WOODSPORTAL_HS_FILE);
|
|
3243
|
+
const woodscliPath = resolve20(root, WOODSCLI_CONFIG_FILE);
|
|
3244
|
+
if (existsSync15(legacyConfigPath2) && !existsSync15(woodscliPath)) {
|
|
2760
3245
|
actions.push(`Rename ${LEGACY_WOODSPORTAL_HS_FILE} \u2192 ${WOODSCLI_CONFIG_FILE}`);
|
|
2761
3246
|
if (!ctx.global.dryRun) {
|
|
2762
3247
|
renameSync(legacyConfigPath2, woodscliPath);
|
|
2763
3248
|
}
|
|
2764
|
-
} else if (
|
|
3249
|
+
} else if (existsSync15(legacyConfigPath2) && existsSync15(woodscliPath)) {
|
|
2765
3250
|
actions.push(`Remove duplicate ${LEGACY_WOODSPORTAL_HS_FILE}`);
|
|
2766
3251
|
if (!ctx.global.dryRun) {
|
|
2767
3252
|
rmSync3(legacyConfigPath2, { force: true });
|
|
2768
3253
|
}
|
|
2769
3254
|
}
|
|
2770
|
-
|
|
2771
|
-
|
|
3255
|
+
const legacyBuildIdPath = resolve20(root, LEGACY_BUILD_ID_FILE);
|
|
3256
|
+
const moduleBuildIdPath = resolve20(root, MODULE_BUILD_ID_FILE);
|
|
3257
|
+
if (existsSync15(legacyBuildIdPath) && !existsSync15(moduleBuildIdPath)) {
|
|
3258
|
+
const baseline = stripToBaseline(readFirstNonCommentLine(legacyBuildIdPath)) ?? "v0.0.1-dev";
|
|
3259
|
+
actions.push(`Migrate ${LEGACY_BUILD_ID_FILE} \u2192 ${MODULE_BUILD_ID_FILE} (baseline ${baseline})`);
|
|
3260
|
+
if (!ctx.global.dryRun) {
|
|
3261
|
+
writeFileSync7(
|
|
3262
|
+
moduleBuildIdPath,
|
|
3263
|
+
`# Channel baseline for local CDN deploys (wp deploy auto-bumps per developer).
|
|
3264
|
+
${baseline}
|
|
3265
|
+
`
|
|
3266
|
+
);
|
|
3267
|
+
rmSync3(legacyBuildIdPath, { force: true });
|
|
3268
|
+
}
|
|
3269
|
+
}
|
|
3270
|
+
if (existsSync15(woodscliPath)) {
|
|
3271
|
+
const raw = JSON.parse(readFileSync12(woodscliPath, "utf8"));
|
|
3272
|
+
let configChanged = false;
|
|
2772
3273
|
if (raw?.dev?.moduleConfigDir && raw.dev.moduleConfigDir !== WPHS_WORKSPACE_DIR) {
|
|
2773
3274
|
actions.push(`Update dev.moduleConfigDir \u2192 .wphs in project config`);
|
|
2774
3275
|
if (!ctx.global.dryRun) {
|
|
2775
3276
|
raw.dev.moduleConfigDir = WPHS_WORKSPACE_DIR;
|
|
2776
|
-
|
|
2777
|
-
|
|
3277
|
+
configChanged = true;
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
if (raw?.cdn?.buildIdFile === LEGACY_BUILD_ID_FILE) {
|
|
3281
|
+
actions.push(`Update cdn.buildIdFile \u2192 ${MODULE_BUILD_ID_FILE}`);
|
|
3282
|
+
if (!ctx.global.dryRun) {
|
|
3283
|
+
raw.cdn.buildIdFile = MODULE_BUILD_ID_FILE;
|
|
3284
|
+
configChanged = true;
|
|
2778
3285
|
}
|
|
2779
3286
|
}
|
|
3287
|
+
if (configChanged && !ctx.global.dryRun) {
|
|
3288
|
+
writeFileSync7(woodscliPath, `${JSON.stringify(raw, null, 2)}
|
|
3289
|
+
`);
|
|
3290
|
+
}
|
|
2780
3291
|
}
|
|
2781
3292
|
if (actions.length === 0) {
|
|
2782
3293
|
ctx.logger.success("Nothing to migrate \u2014 already on .wphs / woodscli.json");
|
|
@@ -2802,10 +3313,10 @@ async function runMigrate(ctx, _options = {}) {
|
|
|
2802
3313
|
}
|
|
2803
3314
|
|
|
2804
3315
|
// src/cli/commands/examples.ts
|
|
2805
|
-
import { readFileSync as
|
|
3316
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
2806
3317
|
function loadManifest() {
|
|
2807
3318
|
const manifestPath = kitPath("examples", "manifest.json");
|
|
2808
|
-
const raw = JSON.parse(
|
|
3319
|
+
const raw = JSON.parse(readFileSync13(manifestPath, "utf8"));
|
|
2809
3320
|
return raw.scenarios;
|
|
2810
3321
|
}
|
|
2811
3322
|
async function runExamplesList(ctx, options = {}) {
|
|
@@ -2838,15 +3349,15 @@ async function runExamplesSmoke(ctx, options = {}) {
|
|
|
2838
3349
|
}
|
|
2839
3350
|
|
|
2840
3351
|
// src/cli/commands/fields.ts
|
|
2841
|
-
import { cpSync as cpSync4, existsSync as
|
|
2842
|
-
import { resolve as
|
|
3352
|
+
import { cpSync as cpSync4, existsSync as existsSync16, readFileSync as readFileSync14, writeFileSync as writeFileSync8 } from "fs";
|
|
3353
|
+
import { resolve as resolve21 } from "path";
|
|
2843
3354
|
async function runFieldsSync(ctx, options = {}) {
|
|
2844
3355
|
const direction = options.direction ?? "pull";
|
|
2845
|
-
const fieldsPath =
|
|
2846
|
-
const cacheDir =
|
|
2847
|
-
const cachePath =
|
|
3356
|
+
const fieldsPath = resolve21(ctx.projectRoot, ctx.config.dev.fieldsJson);
|
|
3357
|
+
const cacheDir = resolve21(ctx.projectRoot, ctx.config.dev.moduleConfigDir, "cache", "fields-sync");
|
|
3358
|
+
const cachePath = resolve21(cacheDir, "fields.json");
|
|
2848
3359
|
if (direction === "pull") {
|
|
2849
|
-
if (!
|
|
3360
|
+
if (!existsSync16(fieldsPath)) {
|
|
2850
3361
|
throw new WpHsError(`Missing ${ctx.config.dev.fieldsJson}`, {
|
|
2851
3362
|
exitCode: ExitCode.USER_ERROR,
|
|
2852
3363
|
remediation: "Add fields.json or run wp init."
|
|
@@ -2856,13 +3367,13 @@ async function runFieldsSync(ctx, options = {}) {
|
|
|
2856
3367
|
ctx.logger.info(`[dry-run] Would cache ${fieldsPath} \u2192 ${cachePath}`);
|
|
2857
3368
|
return ExitCode.SUCCESS;
|
|
2858
3369
|
}
|
|
2859
|
-
const { mkdirSync:
|
|
2860
|
-
|
|
3370
|
+
const { mkdirSync: mkdirSync6 } = await import("fs");
|
|
3371
|
+
mkdirSync6(cacheDir, { recursive: true });
|
|
2861
3372
|
cpSync4(fieldsPath, cachePath);
|
|
2862
3373
|
ctx.logger.success(`Cached fields \u2192 ${cachePath}`);
|
|
2863
3374
|
return ExitCode.SUCCESS;
|
|
2864
3375
|
}
|
|
2865
|
-
if (!
|
|
3376
|
+
if (!existsSync16(cachePath)) {
|
|
2866
3377
|
throw new WpHsError("No cached fields \u2014 run wp fields sync pull first.", {
|
|
2867
3378
|
exitCode: ExitCode.USER_ERROR
|
|
2868
3379
|
});
|
|
@@ -2871,14 +3382,14 @@ async function runFieldsSync(ctx, options = {}) {
|
|
|
2871
3382
|
ctx.logger.info(`[dry-run] Would write ${cachePath} \u2192 ${fieldsPath}`);
|
|
2872
3383
|
return ExitCode.SUCCESS;
|
|
2873
3384
|
}
|
|
2874
|
-
|
|
3385
|
+
writeFileSync8(fieldsPath, readFileSync14(cachePath, "utf8"));
|
|
2875
3386
|
ctx.logger.success(`Restored fields from cache \u2192 ${fieldsPath}`);
|
|
2876
3387
|
return ExitCode.SUCCESS;
|
|
2877
3388
|
}
|
|
2878
3389
|
|
|
2879
3390
|
// src/cli/commands/regression.ts
|
|
2880
|
-
import { existsSync as
|
|
2881
|
-
import { resolve as
|
|
3391
|
+
import { existsSync as existsSync17 } from "fs";
|
|
3392
|
+
import { resolve as resolve22 } from "path";
|
|
2882
3393
|
var LEGACY_SHIM_SCRIPTS = [
|
|
2883
3394
|
"build-cdn.mjs",
|
|
2884
3395
|
"run-vite-build.mjs",
|
|
@@ -2901,21 +3412,21 @@ async function runRegressionSmoke(ctx, options = {}) {
|
|
|
2901
3412
|
if (!validateOk) failures.push("wp config validate failed");
|
|
2902
3413
|
if (options.checkLegacyShims !== false) {
|
|
2903
3414
|
for (const script of LEGACY_SHIM_SCRIPTS) {
|
|
2904
|
-
const shim =
|
|
3415
|
+
const shim = resolve22(ctx.projectRoot, "scripts", script);
|
|
2905
3416
|
const kitScript = kitPath("scripts", script);
|
|
2906
|
-
const shimOk =
|
|
3417
|
+
const shimOk = existsSync17(shim) && existsSync17(kitScript);
|
|
2907
3418
|
checks.push({ name: `shim:${script}`, ok: shimOk });
|
|
2908
3419
|
if (!shimOk) failures.push(`Missing legacy shim or kit script: ${script}`);
|
|
2909
3420
|
}
|
|
2910
3421
|
}
|
|
2911
3422
|
for (const rel of FORBIDDEN_CONSUMER_PATHS) {
|
|
2912
|
-
const forbidden =
|
|
2913
|
-
const absent = !
|
|
3423
|
+
const forbidden = resolve22(ctx.projectRoot, rel);
|
|
3424
|
+
const absent = !existsSync17(forbidden);
|
|
2914
3425
|
checks.push({ name: `no-${rel}`, ok: absent });
|
|
2915
3426
|
if (!absent) failures.push(`Forbidden dev path still present: ${rel}`);
|
|
2916
3427
|
}
|
|
2917
|
-
const woodscliConfig =
|
|
2918
|
-
const configOk =
|
|
3428
|
+
const woodscliConfig = resolve22(ctx.projectRoot, WOODSCLI_CONFIG_FILE);
|
|
3429
|
+
const configOk = existsSync17(woodscliConfig);
|
|
2919
3430
|
checks.push({ name: WOODSCLI_CONFIG_FILE, ok: configOk });
|
|
2920
3431
|
if (!configOk) failures.push(`${WOODSCLI_CONFIG_FILE} missing`);
|
|
2921
3432
|
if (ctx.global.json) {
|
|
@@ -2974,6 +3485,12 @@ async function runSetup(ctx, options = {}) {
|
|
|
2974
3485
|
const copied = results.filter((r) => r.action === "copied");
|
|
2975
3486
|
const manual = results.filter((r) => r.action === "manual");
|
|
2976
3487
|
const nonInteractive = ctx.global.ci || ctx.global.yes || ctx.global.json || ctx.global.dryRun;
|
|
3488
|
+
if (usesCdnPipeline(ctx.config) && !nonInteractive && !ctx.global.dryRun) {
|
|
3489
|
+
const slug = await promptDeveloperSlug(ctx.projectRoot);
|
|
3490
|
+
if (slug) {
|
|
3491
|
+
ctx.logger.success(`CDN dev slug \u2192 ${slug} (.wphs/config/developer.json)`);
|
|
3492
|
+
}
|
|
3493
|
+
}
|
|
2977
3494
|
if (usesCdnPipeline(ctx.config) && needsCdnGitHubSetup(ctx.projectRoot) && !nonInteractive) {
|
|
2978
3495
|
const cdnSetup = await promptCdnGitHubSetup({
|
|
2979
3496
|
projectRoot: ctx.projectRoot,
|
|
@@ -3019,11 +3536,11 @@ async function runSetup(ctx, options = {}) {
|
|
|
3019
3536
|
}
|
|
3020
3537
|
|
|
3021
3538
|
// src/cli/commands/portal.ts
|
|
3022
|
-
import * as
|
|
3539
|
+
import * as p5 from "@clack/prompts";
|
|
3023
3540
|
|
|
3024
3541
|
// src/lib/hubspot-portals.ts
|
|
3025
|
-
import { existsSync as
|
|
3026
|
-
import { resolve as
|
|
3542
|
+
import { existsSync as existsSync18, readFileSync as readFileSync15 } from "fs";
|
|
3543
|
+
import { resolve as resolve23 } from "path";
|
|
3027
3544
|
function stripYamlScalar(value) {
|
|
3028
3545
|
const trimmed = value.trim();
|
|
3029
3546
|
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
@@ -3061,20 +3578,20 @@ function parseHubSpotConfigPortals(raw) {
|
|
|
3061
3578
|
return { defaultPortal, portals };
|
|
3062
3579
|
}
|
|
3063
3580
|
function readHubSpotPortals(projectRoot) {
|
|
3064
|
-
const configPath =
|
|
3065
|
-
if (!
|
|
3581
|
+
const configPath = resolve23(projectRoot, HUBSPOT_CONFIG_FILE);
|
|
3582
|
+
if (!existsSync18(configPath)) {
|
|
3066
3583
|
throw new WpHsError(`${HUBSPOT_CONFIG_FILE} not found in project root.`, {
|
|
3067
3584
|
exitCode: ExitCode.USER_ERROR,
|
|
3068
3585
|
remediation: "Run wp setup, then add your portal + PAT."
|
|
3069
3586
|
});
|
|
3070
3587
|
}
|
|
3071
|
-
const raw =
|
|
3588
|
+
const raw = readFileSync15(configPath, "utf8");
|
|
3072
3589
|
const { defaultPortal, portals } = parseHubSpotConfigPortals(raw);
|
|
3073
3590
|
return { configPath, defaultPortal, portals };
|
|
3074
3591
|
}
|
|
3075
3592
|
function resolvePortalName(portals, nameOrId) {
|
|
3076
3593
|
const needle = nameOrId.trim();
|
|
3077
|
-
return portals.find((
|
|
3594
|
+
return portals.find((p6) => p6.name === needle) ?? portals.find((p6) => p6.portalId === needle) ?? null;
|
|
3078
3595
|
}
|
|
3079
3596
|
|
|
3080
3597
|
// src/cli/commands/portal.ts
|
|
@@ -3129,7 +3646,7 @@ async function runPortalUse(ctx, options = {}) {
|
|
|
3129
3646
|
remediation: "wp portal use <name> \xB7 wp portal list"
|
|
3130
3647
|
});
|
|
3131
3648
|
}
|
|
3132
|
-
const answer = await
|
|
3649
|
+
const answer = await p5.select({
|
|
3133
3650
|
message: "Set default HubSpot portal",
|
|
3134
3651
|
options: data.portals.map((portal2) => ({
|
|
3135
3652
|
value: portal2.name,
|
|
@@ -3137,7 +3654,7 @@ async function runPortalUse(ctx, options = {}) {
|
|
|
3137
3654
|
hint: portal2.name === data.defaultPortal ? `current default \xB7 ${portal2.portalId ?? "no id"}` : portal2.portalId ?? void 0
|
|
3138
3655
|
}))
|
|
3139
3656
|
});
|
|
3140
|
-
if (
|
|
3657
|
+
if (p5.isCancel(answer)) {
|
|
3141
3658
|
throw new WpHsError("Portal selection cancelled", { exitCode: ExitCode.USER_ERROR });
|
|
3142
3659
|
}
|
|
3143
3660
|
targetName = answer;
|
|
@@ -3198,7 +3715,7 @@ async function runWatch(ctx, options) {
|
|
|
3198
3715
|
// src/cli/program.ts
|
|
3199
3716
|
function readVersion() {
|
|
3200
3717
|
try {
|
|
3201
|
-
const pkg = JSON.parse(
|
|
3718
|
+
const pkg = JSON.parse(readFileSync16(resolve24(getKitRoot(), "package.json"), "utf8"));
|
|
3202
3719
|
return pkg.version ?? "0.0.0";
|
|
3203
3720
|
} catch {
|
|
3204
3721
|
return "0.0.0";
|
|
@@ -3216,7 +3733,7 @@ function buildContext(global) {
|
|
|
3216
3733
|
return { projectRoot, config, logger, global };
|
|
3217
3734
|
}
|
|
3218
3735
|
function attachGlobals(command) {
|
|
3219
|
-
command.option("--project <path>", "Project root (woodscli.json)").option("--ci", "Non-interactive CI mode", false).option("--json", "Machine-readable JSON output", false).option("--verbose", "Step-by-step logs and child-process output (disables spinners)", false).option("--quiet", "Errors only", false).option("--dry-run", "Print planned actions without executing", false).option("-y, --yes", "Assume yes (non-interactive)", false);
|
|
3736
|
+
command.option("--project <path>", "Project root (woodscli.json)").option("--ci", "Non-interactive CI mode", false).option("--json", "Machine-readable JSON output", false).option("--verbose", "Step-by-step logs and child-process output (disables spinners)", false).option("--quiet", "Errors only", false).option("--dry-run", "Print planned actions without executing", false).option("-y, --yes", "Assume yes (non-interactive)", false).option("--no-bump", "Do not auto-increment module build id before CDN build", false);
|
|
3220
3737
|
}
|
|
3221
3738
|
async function handle(fn) {
|
|
3222
3739
|
try {
|