@wix/agent-skills 1.7.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -7
- package/package.json +1 -1
- package/skills/wix-app/SKILL.md +2 -0
- package/skills/wix-app/references/STORES_VERSIONING.md +1 -0
- package/skills/wix-design-system/SKILL.md +4 -0
- package/skills/wix-headless/SKILL.md +55 -32
- package/skills/wix-headless/references/BUILD-astro.md +246 -0
- package/skills/wix-headless/references/BUILD-own-build.md +167 -0
- package/skills/wix-headless/references/BUILD.md +50 -252
- package/skills/wix-headless/references/DESIGN_SYSTEM.md +94 -34
- package/skills/wix-headless/references/DISCOVERY-connect.md +55 -0
- package/skills/wix-headless/references/DISCOVERY-create.md +238 -0
- package/skills/wix-headless/references/DISCOVERY.md +41 -422
- package/skills/wix-headless/references/PLAN-connect.md +19 -0
- package/skills/wix-headless/references/PLAN-create.md +17 -0
- package/skills/wix-headless/references/PLAN.md +37 -27
- package/skills/wix-headless/references/SEED.md +27 -18
- package/skills/wix-headless/references/SETUP.md +32 -213
- package/skills/wix-headless/references/astro/blog/BLOG_PAGES.md +5 -4
- package/skills/wix-headless/references/astro/bookings/COMPONENTS.md +337 -0
- package/skills/wix-headless/references/astro/bookings/SERVICES_PAGES.md +428 -0
- package/skills/wix-headless/references/astro/cms/CMS_FOUNDATIONS.md +5 -5
- package/skills/wix-headless/references/astro/cms/FAQ_KNOWLEDGE_BASE.md +10 -10
- package/skills/wix-headless/references/astro/cms/PORTFOLIO.md +12 -13
- package/skills/wix-headless/references/astro/cms/RESOURCE_LIBRARY.md +10 -12
- package/skills/wix-headless/references/astro/cms/TEAM_DIRECTORY.md +8 -8
- package/skills/wix-headless/references/astro/designer/INSTRUCTIONS.md +50 -27
- package/skills/wix-headless/references/astro/ecom/CART_PAGES.md +7 -8
- package/skills/wix-headless/references/astro/ecom/CART_WIRING.md +5 -5
- package/skills/wix-headless/references/astro/ecom/COMPONENTS_CSS.md +3 -3
- package/skills/wix-headless/references/astro/forms/CONTACT_FORM.md +5 -19
- package/skills/wix-headless/references/astro/gift-cards/COMPONENTS.md +2 -2
- package/skills/wix-headless/references/astro/gift-cards/PAGES.md +1 -1
- package/skills/wix-headless/references/astro/stores/BACK_IN_STOCK.md +6 -6
- package/skills/wix-headless/references/astro/stores/CATEGORY_PAGES.md +4 -4
- package/skills/wix-headless/references/astro/stores/COMPONENTS_CSS.md +3 -3
- package/skills/wix-headless/references/astro/stores/HOME_AND_NAV.md +3 -3
- package/skills/wix-headless/references/astro/stores/PRODUCT_PAGES.md +9 -9
- package/skills/wix-headless/references/astro/stores/SHARED_WIRING.md +5 -4
- package/skills/wix-headless/references/astro/templates/Footer.astro +1 -1
- package/skills/wix-headless/references/astro/templates/Layout.astro +1 -1
- package/skills/wix-headless/references/astro/templates/Navigation.astro +1 -1
- package/skills/wix-headless/references/astro/templates/astro.config.mjs +1 -1
- package/skills/wix-headless/references/astro/templates/bookings/AvailabilityCalendar.tsx +218 -0
- package/skills/wix-headless/references/astro/templates/bookings/BookingForm.tsx +194 -0
- package/skills/wix-headless/references/astro/templates/bookings/ManageBooking.tsx +84 -0
- package/skills/wix-headless/references/astro/templates/bookings/SeoTags.astro +25 -0
- package/skills/wix-headless/references/astro/templates/bookings/ServiceBookingFlow.tsx +50 -0
- package/skills/wix-headless/references/astro/templates/bookings/ServiceCard.astro +45 -0
- package/skills/wix-headless/references/astro/templates/bookings/api/confirm-booking.ts +34 -0
- package/skills/wix-headless/references/astro/templates/bookings/api/waitlist.ts +74 -0
- package/skills/wix-headless/references/astro/templates/bookings/booking-confirmation.astro +111 -0
- package/skills/wix-headless/references/astro/templates/bookings/components-bookings.css +413 -0
- package/skills/wix-headless/references/astro/templates/bookings/manage-booking.astro +76 -0
- package/skills/wix-headless/references/astro/templates/bookings/services/[slug].astro +81 -0
- package/skills/wix-headless/references/astro/templates/bookings/services/index.astro +65 -0
- package/skills/wix-headless/references/astro/templates/global.css +2 -2
- package/skills/wix-headless/references/astro/templates/index.astro +1 -1
- package/skills/wix-headless/references/blog/BLOG_CONTENT.md +19 -9
- package/skills/wix-headless/references/blog/INSTRUCTIONS.md +3 -3
- package/skills/wix-headless/references/bookings/INSTRUCTIONS.md +106 -0
- package/skills/wix-headless/references/bookings/SERVICES_DATA.md +295 -0
- package/skills/wix-headless/references/cms/CMS_FOUNDATIONS.md +4 -2
- package/skills/wix-headless/references/cms/INSTRUCTIONS.md +27 -1
- package/skills/wix-headless/references/commands/known-apps.json +2 -1
- package/skills/wix-headless/references/custom/CONNECTION_PLAN.md +142 -0
- package/skills/wix-headless/references/custom/INSTRUCTIONS.md +71 -21
- package/skills/wix-headless/references/custom/blog/WIRING.md +52 -0
- package/skills/wix-headless/references/custom/bookings/WIRING.md +88 -0
- package/skills/wix-headless/references/custom/cms/WIRING.md +97 -0
- package/skills/wix-headless/references/custom/ecom/WIRING.md +67 -0
- package/skills/wix-headless/references/custom/forms/WIRING.md +101 -0
- package/skills/wix-headless/references/custom/stores/WIRING.md +80 -0
- package/skills/wix-headless/references/ecom/INSTRUCTIONS.md +5 -5
- package/skills/wix-headless/references/forms/INSTRUCTIONS.md +2 -2
- package/skills/wix-headless/references/gift-cards/INSTRUCTIONS.md +3 -3
- package/skills/wix-headless/references/images/INSTRUCTIONS.md +16 -15
- package/skills/wix-headless/references/seed-recipes.md +2 -2
- package/skills/wix-headless/references/shared/AGENTS_MD.md +47 -0
- package/skills/wix-headless/references/shared/AUTHENTICATION.md +3 -5
- package/skills/wix-headless/references/shared/DESIGN_MD.md +85 -0
- package/skills/wix-headless/references/shared/IMAGE_GENERATION.md +29 -28
- package/skills/wix-headless/references/shared/IMPLEMENTER.md +87 -27
- package/skills/wix-headless/references/shared/PRODUCTION_SHARP_EDGES.md +2 -2
- package/skills/wix-headless/references/shared/RETURN_CONTRACT.md +24 -313
- package/skills/wix-headless/references/shared/STYLING.md +8 -18
- package/skills/wix-headless/references/stores/INSTRUCTIONS.md +39 -9
- package/skills/wix-headless/references/stores/PRODUCT_CATALOG_DATA.md +17 -14
- package/skills/wix-headless/references/verticals/_schema.json +1 -1
- package/skills/wix-headless/references/verticals/_schema.md +3 -3
- package/skills/wix-headless/references/verticals/blog.md +1 -1
- package/skills/wix-headless/references/verticals/bookings.md +41 -0
- package/skills/wix-headless/references/verticals/cms.md +1 -1
- package/skills/wix-headless/references/verticals/ecom.md +2 -2
- package/skills/wix-headless/references/verticals/gift-cards.md +1 -1
- package/skills/wix-headless/references/verticals/stores.md +1 -1
- package/skills/wix-headless/scripts/check-manifest.mjs +143 -6
- package/skills/wix-headless/scripts/compose.mjs +540 -0
- package/skills/wix-headless/scripts/emit-design-tokens.mjs +128 -51
- package/skills/wix-headless/scripts/flatten.sh +61 -0
- package/skills/wix-headless/scripts/generate-images.mjs +242 -0
- package/skills/wix-headless/scripts/scaffold.sh +47 -16
- package/skills/wix-headless/scripts/write-decorative-json.mjs +62 -0
- package/skills/wix-manage/SKILL.md +1 -1
- package/skills/wix-manage/references/blog/how-to-create-blog-posts.md +1 -1
- package/skills/wix-manage/references/cms/cms-data-items-crud.md +1 -1
- package/skills/wix-manage/references/cms/cms-references-and-relationships.md +1 -1
- package/skills/wix-manage/references/cms/cms-schema-management.md +1 -1
- package/skills/wix-manage/references/domains/domain-search-and-purchase.md +10 -7
- package/skills/wix-manage/references/forms/create-form.md +49 -1
- package/skills/wix-manage/references/stores/bulk-create-products-with-options.md +1 -1
- package/skills/wix-manage/references/stores/setup-online-store-catalog-v3.md +1 -1
- package/skills/wix-headless/references/astro/COMPOSE.md +0 -142
- package/skills/wix-headless/scripts/finalize-run-json.mjs +0 -141
- package/skills/wix-headless/scripts/init-site-json.mjs +0 -101
- package/skills/wix-headless/scripts/preview.sh +0 -45
- package/skills/wix-headless/scripts/release.sh +0 -73
|
@@ -1,83 +1,160 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
3
|
-
// designTokens JSON payload piped in on stdin. Invoked by the wix-headless
|
|
4
|
-
// skill orchestrator immediately after the Phase 2 Design System agent
|
|
5
|
-
// returns — the orchestrator pipes designerReturn.data.designTokens directly,
|
|
6
|
-
// no site.json round-trip.
|
|
2
|
+
// Project .wix/design-tokens.css + .wix/site.d.ts from the Designer's DESIGN.md.
|
|
7
3
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
4
|
+
// The Designer authors DESIGN.md directly (the run is single-folder — CWD is the
|
|
5
|
+
// project — so the Designer can write it during Phase 2). This script does NOT
|
|
6
|
+
// write DESIGN.md; it reads the frontmatter the Designer wrote and projects the
|
|
7
|
+
// two MECHANICAL artifacts an LLM must not hand-write (a malformed :root block or
|
|
8
|
+
// divergent type union is a silent build/drift bug):
|
|
9
|
+
// .wix/design-tokens.css — :root custom properties (build-consumed; any frontend imports it).
|
|
10
|
+
// .wix/site.d.ts — typed token-name unions.
|
|
11
|
+
//
|
|
12
|
+
// DESIGN.md frontmatter is the single design source — compose.mjs (astro) reads
|
|
13
|
+
// the same file. Runs in the Setup-window bridge, after the Designer returns. On
|
|
14
|
+
// the own-build (SPA) path it runs too (the SPA imports design-tokens.css);
|
|
15
|
+
// compose.mjs does NOT run there (it is astro-only).
|
|
12
16
|
//
|
|
13
17
|
// Usage:
|
|
14
|
-
//
|
|
18
|
+
// node emit-design-tokens.mjs <project-dir> # reads <project-dir>/DESIGN.md
|
|
19
|
+
// node emit-design-tokens.mjs <project-dir> --design-md <path> # explicit DESIGN.md path
|
|
15
20
|
|
|
16
|
-
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
21
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
17
22
|
import { join } from "node:path";
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
// ── args ─────────────────────────────────────────────────────────────────────
|
|
25
|
+
const argv = process.argv.slice(2);
|
|
26
|
+
let designMdPath = null;
|
|
27
|
+
const positional = [];
|
|
28
|
+
for (let i = 0; i < argv.length; i++) {
|
|
29
|
+
if (argv[i] === "--design-md") designMdPath = argv[++i];
|
|
30
|
+
else if (argv[i].startsWith("--design-md=")) designMdPath = argv[i].slice("--design-md=".length);
|
|
31
|
+
else positional.push(argv[i]);
|
|
24
32
|
}
|
|
33
|
+
const projectDir = positional[0] ?? process.cwd();
|
|
34
|
+
const designMd = designMdPath ?? join(projectDir, "DESIGN.md");
|
|
25
35
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
// ── DESIGN.md frontmatter parser ──────────────────────────────────────────────
|
|
37
|
+
// Mirrors compose.mjs's parser for the restricted DESIGN.md shape (groups of
|
|
38
|
+
// `key: scalar`, typography tokens with an indented or flow-style fontFamily).
|
|
39
|
+
// The format contract is references/shared/DESIGN_MD.md; both readers must agree.
|
|
40
|
+
// Color values are quoted because an unquoted `#hex` after `: ` is a YAML comment.
|
|
41
|
+
function unquote(v) {
|
|
42
|
+
v = v.trim();
|
|
43
|
+
const qc = v[0];
|
|
44
|
+
if (qc === '"' || qc === "'") {
|
|
45
|
+
let out = "";
|
|
46
|
+
for (let i = 1; i < v.length; i++) {
|
|
47
|
+
const c = v[i];
|
|
48
|
+
if (qc === '"' && c === "\\" && i + 1 < v.length) { out += v[++i]; continue; }
|
|
49
|
+
if (c === qc) break;
|
|
50
|
+
out += c;
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
const hash = v.search(/\s#/); // strip trailing inline comment on bare scalars
|
|
55
|
+
if (hash !== -1) v = v.slice(0, hash).trim();
|
|
56
|
+
return v;
|
|
57
|
+
}
|
|
58
|
+
function parseInlineObject(s) {
|
|
59
|
+
const inner = s.trim().replace(/^\{/, "").replace(/\}$/, "");
|
|
60
|
+
const obj = {};
|
|
61
|
+
let buf = "", inq = null;
|
|
62
|
+
const parts = [];
|
|
63
|
+
for (const ch of inner) {
|
|
64
|
+
if (inq) { if (ch === inq) inq = null; buf += ch; continue; }
|
|
65
|
+
if (ch === '"' || ch === "'") { inq = ch; buf += ch; continue; }
|
|
66
|
+
if (ch === ",") { parts.push(buf); buf = ""; continue; }
|
|
67
|
+
buf += ch;
|
|
32
68
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
69
|
+
if (buf.trim()) parts.push(buf);
|
|
70
|
+
for (const p of parts) {
|
|
71
|
+
const ci = p.indexOf(":");
|
|
72
|
+
if (ci === -1) continue;
|
|
73
|
+
obj[p.slice(0, ci).trim()] = unquote(p.slice(ci + 1));
|
|
74
|
+
}
|
|
75
|
+
return obj;
|
|
76
|
+
}
|
|
77
|
+
function parseFrontmatter(text) {
|
|
78
|
+
const m = text.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
79
|
+
if (!m) return null;
|
|
80
|
+
const root = {};
|
|
81
|
+
let group = null, sub = null;
|
|
82
|
+
for (const raw of m[1].split(/\r?\n/)) {
|
|
83
|
+
if (!raw.trim() || /^\s*#/.test(raw)) continue;
|
|
84
|
+
const indent = raw.match(/^ */)[0].length;
|
|
85
|
+
const ci = raw.indexOf(":");
|
|
86
|
+
if (ci === -1) continue;
|
|
87
|
+
const key = raw.slice(0, ci).trim();
|
|
88
|
+
const rest = raw.slice(ci + 1).trim();
|
|
89
|
+
if (indent === 0) {
|
|
90
|
+
if (rest === "") { root[key] = {}; group = key; sub = null; }
|
|
91
|
+
else if (rest.startsWith("{")) { root[key] = parseInlineObject(rest); group = null; sub = null; }
|
|
92
|
+
else { root[key] = unquote(rest); group = null; sub = null; }
|
|
93
|
+
} else if (indent === 2 && group) {
|
|
94
|
+
if (rest === "") { root[group][key] = {}; sub = key; }
|
|
95
|
+
else if (rest.startsWith("{")) { root[group][key] = parseInlineObject(rest); sub = null; }
|
|
96
|
+
else { root[group][key] = unquote(rest); sub = null; }
|
|
97
|
+
} else if (indent >= 4 && group && sub) {
|
|
98
|
+
if (typeof root[group][sub] !== "object") root[group][sub] = {};
|
|
99
|
+
root[group][sub][key] = unquote(rest);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return root;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ── read + validate the Designer's DESIGN.md ──────────────────────────────────
|
|
106
|
+
if (!existsSync(designMd)) {
|
|
107
|
+
console.error(`emit-design-tokens: no DESIGN.md at ${designMd} — the Designer must author it first`);
|
|
108
|
+
process.exit(2);
|
|
109
|
+
}
|
|
110
|
+
const design = parseFrontmatter(readFileSync(designMd, "utf8"));
|
|
111
|
+
if (!design || typeof design !== "object" || Object.keys(design).length === 0) {
|
|
112
|
+
console.error(`emit-design-tokens: could not parse YAML frontmatter from ${designMd} (check it starts with '---', values are quoted, 2-space indent)`);
|
|
36
113
|
process.exit(2);
|
|
37
114
|
}
|
|
38
115
|
|
|
39
|
-
|
|
40
|
-
|
|
116
|
+
const colors = design.colors ?? {};
|
|
117
|
+
const typography = design.typography ?? {};
|
|
118
|
+
const spacing = design.spacing ?? {};
|
|
119
|
+
const rounded = design.rounded ?? {};
|
|
120
|
+
const containers = design.containers ?? {};
|
|
121
|
+
const fontFamilyOf = (lvl) => (typography[lvl] && typeof typography[lvl] === "object" ? typography[lvl].fontFamily : typography[lvl]);
|
|
122
|
+
|
|
123
|
+
if (Object.keys(colors).length === 0) {
|
|
124
|
+
console.error(`emit-design-tokens: DESIGN.md frontmatter has no 'colors' group — cannot project tokens (${designMd})`);
|
|
41
125
|
process.exit(2);
|
|
42
126
|
}
|
|
43
127
|
|
|
44
128
|
const wixDir = join(projectDir, ".wix");
|
|
45
129
|
mkdirSync(wixDir, { recursive: true });
|
|
46
130
|
|
|
47
|
-
|
|
48
|
-
colors: "--color-",
|
|
49
|
-
fonts: "--font-",
|
|
50
|
-
radii: "--radius-",
|
|
51
|
-
spacing: "--spacing-",
|
|
52
|
-
containers: "--container-",
|
|
53
|
-
};
|
|
54
|
-
|
|
131
|
+
// ── .wix/design-tokens.css ────────────────────────────────────────────────────
|
|
55
132
|
const cssLines = ["/* Generated by emit-design-tokens.mjs. Do not edit. */", ":root {"];
|
|
56
|
-
for (const [
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
133
|
+
for (const [k, v] of Object.entries(colors)) cssLines.push(` --color-${k}: ${v};`);
|
|
134
|
+
for (const lvl of Object.keys(typography)) {
|
|
135
|
+
const fam = fontFamilyOf(lvl);
|
|
136
|
+
if (fam) cssLines.push(` --font-${lvl}: ${fam};`);
|
|
61
137
|
}
|
|
138
|
+
for (const [k, v] of Object.entries(rounded)) cssLines.push(` --radius-${k}: ${v};`);
|
|
139
|
+
for (const [k, v] of Object.entries(spacing)) cssLines.push(` --spacing-${k}: ${v};`);
|
|
140
|
+
for (const [k, v] of Object.entries(containers)) cssLines.push(` --container-${k}: ${v};`);
|
|
62
141
|
cssLines.push("}", "");
|
|
63
142
|
writeFileSync(join(wixDir, "design-tokens.css"), cssLines.join("\n"));
|
|
64
143
|
|
|
65
|
-
|
|
66
|
-
|
|
144
|
+
// ── .wix/site.d.ts ────────────────────────────────────────────────────────────
|
|
145
|
+
const recordType = (keys) => {
|
|
67
146
|
if (keys.length === 0) return "Record<string, string>";
|
|
68
|
-
|
|
69
|
-
return `Record<${literal}, string>`;
|
|
147
|
+
return `Record<${keys.map((k) => JSON.stringify(k)).join(" | ")}, string>`;
|
|
70
148
|
};
|
|
71
|
-
|
|
72
149
|
const dts = `// Generated by emit-design-tokens.mjs. Do not edit.
|
|
73
150
|
export type DesignTokens = {
|
|
74
|
-
colors: ${
|
|
75
|
-
fonts: ${
|
|
76
|
-
radii: ${
|
|
77
|
-
spacing: ${
|
|
78
|
-
containers: ${
|
|
151
|
+
colors: ${recordType(Object.keys(colors))};
|
|
152
|
+
fonts: ${recordType(Object.keys(typography))};
|
|
153
|
+
radii: ${recordType(Object.keys(rounded))};
|
|
154
|
+
spacing: ${recordType(Object.keys(spacing))};
|
|
155
|
+
containers: ${recordType(Object.keys(containers))};
|
|
79
156
|
};
|
|
80
157
|
`;
|
|
81
158
|
writeFileSync(join(wixDir, "site.d.ts"), dts);
|
|
82
159
|
|
|
83
|
-
console.log(`emit-design-tokens:
|
|
160
|
+
console.log(`emit-design-tokens: projected ${join(wixDir, "design-tokens.css")} and ${join(wixDir, "site.d.ts")} from ${designMd}`);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Flatten a scaffolded subdir up into the current directory — the framework-
|
|
3
|
+
# agnostic half of single-folder bootstrap for the own-build path.
|
|
4
|
+
#
|
|
5
|
+
# WHY THIS EXISTS: every framework scaffolder (Vite, Vue, SvelteKit, …) insists
|
|
6
|
+
# on creating a *named subdir* and offers no reliable in-place option. The skill
|
|
7
|
+
# wants the project in CWD (one `.wix`, CWD == project == site-root — same layout
|
|
8
|
+
# astro's scaffold.sh produces). The flatten is identical regardless of framework,
|
|
9
|
+
# so it lives here as ONE tested script instead of being re-derived in prose every
|
|
10
|
+
# run (the orchestrator otherwise burns a long turn agonizing over mv -n vs rsync
|
|
11
|
+
# vs cp --no-clobber and BSD-vs-GNU portability). Scaffold into a temp subdir with
|
|
12
|
+
# the framework's own command, then call this.
|
|
13
|
+
#
|
|
14
|
+
# astro's scaffold.sh ALSO delegates its in-place flatten here (same logic), so
|
|
15
|
+
# this is the single source for the flatten. Remove the flatten entirely when the
|
|
16
|
+
# CLI gains `--folder-name .` (or --cwd) — the in-place flag obviates it.
|
|
17
|
+
#
|
|
18
|
+
# Usage:
|
|
19
|
+
# bash <SKILL_ROOT>/scripts/flatten.sh <scaffold-subdir>
|
|
20
|
+
#
|
|
21
|
+
# <scaffold-subdir>: the transient directory the framework scaffolder created
|
|
22
|
+
# (e.g. "_scaffold_tmp"). Everything inside it — including
|
|
23
|
+
# dotfiles — is moved up into CWD; its `.wix/*` is merged into
|
|
24
|
+
# the existing `.wix/` WITHOUT overwriting any pre-existing
|
|
25
|
+
# `.wix/` entries (mv -n); then the subdir is removed.
|
|
26
|
+
#
|
|
27
|
+
# Exit codes:
|
|
28
|
+
# 0 — ok (or subdir absent / already flat — no-op)
|
|
29
|
+
# 2 — argument missing
|
|
30
|
+
# 5 — could not fully flatten (name collision left files behind)
|
|
31
|
+
|
|
32
|
+
set -euo pipefail
|
|
33
|
+
|
|
34
|
+
SUBDIR="${1:-}"
|
|
35
|
+
if [[ -z "$SUBDIR" ]]; then
|
|
36
|
+
echo "flatten.sh: scaffold-subdir argument required." >&2
|
|
37
|
+
echo "Usage: bash flatten.sh <scaffold-subdir>" >&2
|
|
38
|
+
exit 2
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# Absent or not a dir → nothing to flatten (scaffolder may have gone in-place).
|
|
42
|
+
[[ -d "$SUBDIR" ]] || { echo "flatten.sh: '$SUBDIR' is not a directory — nothing to flatten."; exit 0; }
|
|
43
|
+
|
|
44
|
+
shopt -s dotglob nullglob
|
|
45
|
+
mkdir -p .wix
|
|
46
|
+
if [[ -d "$SUBDIR/.wix" ]]; then
|
|
47
|
+
for f in "$SUBDIR"/.wix/*; do
|
|
48
|
+
mv -n "$f" .wix/ # -n: never overwrite pre-existing `.wix/` entries
|
|
49
|
+
done
|
|
50
|
+
rmdir "$SUBDIR/.wix" 2>/dev/null || rm -rf "$SUBDIR/.wix"
|
|
51
|
+
fi
|
|
52
|
+
for f in "$SUBDIR"/*; do
|
|
53
|
+
mv -n "$f" ./
|
|
54
|
+
done
|
|
55
|
+
shopt -u dotglob nullglob
|
|
56
|
+
|
|
57
|
+
if ! rmdir "$SUBDIR" 2>/dev/null; then
|
|
58
|
+
echo "flatten.sh: could not fully flatten '$SUBDIR' into CWD — leftover:" >&2
|
|
59
|
+
ls -A "$SUBDIR" >&2
|
|
60
|
+
exit 5
|
|
61
|
+
fi
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Parallel image generator + Wix Media importer.
|
|
3
|
+
//
|
|
4
|
+
// WHY THIS EXISTS. Image generation is N independent (generate → import) chains
|
|
5
|
+
// with no cross-image dependency, so it is embarrassingly parallel. When the
|
|
6
|
+
// agent drives it by hand the chains serialize into minutes of avoidable wall.
|
|
7
|
+
// The agent ALSO cannot use the
|
|
8
|
+
// single-array batch form on the default model (`google:4@2` 504s when one
|
|
9
|
+
// request carries N≥3 tasks). This script resolves both: it fires N *separate
|
|
10
|
+
// single-task* requests CONCURRENTLY in-process (Promise.all), so there is no
|
|
11
|
+
// per-turn serialization to lose and no N≥3-in-one-request 504 to hit. It is
|
|
12
|
+
// the deterministic analog of the "one concurrent batch" rule the prose docs
|
|
13
|
+
// ask the agent to follow by hand.
|
|
14
|
+
//
|
|
15
|
+
// SCOPE. Generation + Wix Media import ONLY — the parallelizable, recipe-free
|
|
16
|
+
// half. It returns a key→{url,fileId} map. Attachment stays where the recipe
|
|
17
|
+
// knowledge lives: Phase 1 decorative URLs flow into write-decorative-json.mjs;
|
|
18
|
+
// Phase 2 entity URLs flow into the per-entity PATCH/PUT/publish plan from
|
|
19
|
+
// plan-entity-image-waves.mjs. This script never touches frontend source or
|
|
20
|
+
// Wix entities.
|
|
21
|
+
//
|
|
22
|
+
// Phase-agnostic: `key` is a slot name for Phase 1 (`hero`, `about`) or an
|
|
23
|
+
// entityId for Phase 2. The caller interprets the returned map.
|
|
24
|
+
//
|
|
25
|
+
// AUTH (references/shared/AUTHENTICATION.md). Site-scoped REST token, minted
|
|
26
|
+
// once per run and reused. Pass it in via the WIX_TOKEN env var (preferred —
|
|
27
|
+
// honors mint-once) or the stdin `token` field. As a last resort the script
|
|
28
|
+
// mints one itself with `npx @wix/cli@latest token --site <siteId>` in
|
|
29
|
+
// <projectDir>; that costs a CLI spin-up, so prefer passing the cached token.
|
|
30
|
+
//
|
|
31
|
+
// USAGE
|
|
32
|
+
// echo '{
|
|
33
|
+
// "siteId": "ed57...",
|
|
34
|
+
// "model": "google:4@2",
|
|
35
|
+
// "projectDir": "/abs/path/frenchiesgoodies", // only needed if minting
|
|
36
|
+
// "images": [
|
|
37
|
+
// {"key":"hero","positivePrompt":"...","width":1376,"height":768},
|
|
38
|
+
// {"key":"about","positivePrompt":"...","width":1200,"height":896}
|
|
39
|
+
// ]
|
|
40
|
+
// }' | WIX_TOKEN="$TOKEN" node generate-images.mjs
|
|
41
|
+
//
|
|
42
|
+
// OUTPUT (stdout, JSON)
|
|
43
|
+
// {
|
|
44
|
+
// "status": "complete" | "partial" | "failed",
|
|
45
|
+
// "model": "google:4@2",
|
|
46
|
+
// "count": { "requested": 2, "ok": 2, "failed": 0 },
|
|
47
|
+
// "map": { "hero": {"url":"https://static.wixstatic.com/...","fileId":"..~mv2.png"} },
|
|
48
|
+
// "slots": { "hero": "https://static.wixstatic.com/..." }, // key→url, ready for write-decorative-json.mjs
|
|
49
|
+
// "results": [ {"key":"hero","taskUUID":"..","status":"ok","genReqId":"..","importReqId":".."} ],
|
|
50
|
+
// "errors": [ {"key":"about","stage":"generate","code":"504","message":".."} ]
|
|
51
|
+
// }
|
|
52
|
+
// Exit 0 on complete/partial, 1 on failed (zero images succeeded) or bad input.
|
|
53
|
+
// x-wix-request-id is captured per call into results[] for trace analysis — it
|
|
54
|
+
// is never printed to stderr or narrated (CLAUDE.md run policy).
|
|
55
|
+
|
|
56
|
+
import { readFileSync } from "node:fs";
|
|
57
|
+
import { execFileSync } from "node:child_process";
|
|
58
|
+
import { randomUUID } from "node:crypto";
|
|
59
|
+
|
|
60
|
+
const RUNWARE_URL = "https://www.wixapis.com/runwareschemaless/v1/request";
|
|
61
|
+
const MEDIA_IMPORT_URL = "https://www.wixapis.com/site-media/v1/files/import";
|
|
62
|
+
const GEN_TIMEOUT_MS = 120_000; // google:4@2 is slow; generous per-task cap
|
|
63
|
+
const IMPORT_TIMEOUT_MS = 60_000;
|
|
64
|
+
const ALLOWED_DIMS = [[1024, 1024], [1376, 768], [1200, 896]];
|
|
65
|
+
|
|
66
|
+
function fail(reason) {
|
|
67
|
+
console.log(JSON.stringify({ status: "failed", reason }, null, 2));
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ---- input ----------------------------------------------------------------
|
|
72
|
+
let input;
|
|
73
|
+
try {
|
|
74
|
+
input = JSON.parse(readFileSync(0, "utf8"));
|
|
75
|
+
} catch (e) {
|
|
76
|
+
fail(`stdin is not valid JSON (${e.message})`);
|
|
77
|
+
}
|
|
78
|
+
const siteId = input.siteId;
|
|
79
|
+
const model = input.model || "google:4@2";
|
|
80
|
+
const images = Array.isArray(input.images) ? input.images : [];
|
|
81
|
+
if (!siteId) fail("siteId is required");
|
|
82
|
+
if (images.length === 0) fail("no images in input — nothing to generate");
|
|
83
|
+
|
|
84
|
+
// ---- token (mint-once: prefer env/field, mint only as fallback) -----------
|
|
85
|
+
let token = process.env.WIX_TOKEN || input.token;
|
|
86
|
+
if (!token) {
|
|
87
|
+
try {
|
|
88
|
+
token = execFileSync("npx", ["@wix/cli@latest", "token", "--site", siteId], {
|
|
89
|
+
cwd: input.projectDir || process.cwd(),
|
|
90
|
+
encoding: "utf8",
|
|
91
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
92
|
+
}).trim();
|
|
93
|
+
} catch (e) {
|
|
94
|
+
fail(`no token: pass WIX_TOKEN env or stdin "token", or run where the CLI can mint (${e.message})`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (!token) fail("token minting returned empty");
|
|
98
|
+
|
|
99
|
+
const baseHeaders = {
|
|
100
|
+
Authorization: `Bearer ${token}`,
|
|
101
|
+
"wix-site-id": siteId,
|
|
102
|
+
"Content-Type": "application/json",
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// ---- one fetch with timeout + reqid capture -------------------------------
|
|
106
|
+
async function call(url, body, timeoutMs) {
|
|
107
|
+
const ctrl = new AbortController();
|
|
108
|
+
const t = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
109
|
+
try {
|
|
110
|
+
const res = await fetch(url, {
|
|
111
|
+
method: "POST",
|
|
112
|
+
headers: baseHeaders,
|
|
113
|
+
body: JSON.stringify(body),
|
|
114
|
+
signal: ctrl.signal,
|
|
115
|
+
});
|
|
116
|
+
const reqId = res.headers.get("x-wix-request-id") || null;
|
|
117
|
+
const text = await res.text();
|
|
118
|
+
let json = null;
|
|
119
|
+
try { json = text ? JSON.parse(text) : null; } catch { /* keep raw */ }
|
|
120
|
+
return { ok: res.ok, status: res.status, reqId, json, raw: text };
|
|
121
|
+
} finally {
|
|
122
|
+
clearTimeout(t);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function normalizeDims(w, h) {
|
|
127
|
+
if (ALLOWED_DIMS.some(([aw, ah]) => aw === w && ah === h)) return [w, h];
|
|
128
|
+
return [1024, 1024]; // safe default — Runware rejects free-form sizes (400)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Generate one image (single-task body — the form the default model accepts),
|
|
132
|
+
// then import it to Wix Media. Returns a per-image record; never throws.
|
|
133
|
+
async function genAndImport(img) {
|
|
134
|
+
const key = img.key;
|
|
135
|
+
const taskUUID = img.taskUUID || randomUUID();
|
|
136
|
+
const [width, height] = normalizeDims(img.width ?? 1024, img.height ?? 1024);
|
|
137
|
+
const outputFormat = (img.outputFormat || "PNG").toUpperCase();
|
|
138
|
+
const rec = { key, taskUUID, status: "failed", genReqId: null, importReqId: null };
|
|
139
|
+
|
|
140
|
+
if (!img.positivePrompt) {
|
|
141
|
+
return { ...rec, error: { key, stage: "input", code: "NO_PROMPT", message: "positivePrompt missing" } };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// --- generate (retry once on transient 5xx/504/timeout) ---
|
|
145
|
+
const task = {
|
|
146
|
+
taskType: "imageInference",
|
|
147
|
+
taskUUID,
|
|
148
|
+
outputType: "URL",
|
|
149
|
+
outputFormat,
|
|
150
|
+
positivePrompt: img.positivePrompt,
|
|
151
|
+
width,
|
|
152
|
+
height,
|
|
153
|
+
model,
|
|
154
|
+
numberResults: 1,
|
|
155
|
+
// NB: never send `steps`/`CFGScale` — google:4@2 rejects them (400).
|
|
156
|
+
};
|
|
157
|
+
let gen, imageURL;
|
|
158
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
159
|
+
try {
|
|
160
|
+
gen = await call(RUNWARE_URL, [task], GEN_TIMEOUT_MS);
|
|
161
|
+
} catch (e) {
|
|
162
|
+
gen = { ok: false, status: e.name === "AbortError" ? 504 : 0, reqId: null, json: null, raw: String(e) };
|
|
163
|
+
}
|
|
164
|
+
rec.genReqId = gen.reqId;
|
|
165
|
+
const data = Array.isArray(gen.json?.data) ? gen.json.data : null;
|
|
166
|
+
imageURL = data?.[0]?.imageURL;
|
|
167
|
+
if (gen.ok && imageURL) break;
|
|
168
|
+
// transient → retry once; hard 4xx (except 504-shaped) → give up
|
|
169
|
+
const transient = gen.status >= 500 || gen.status === 504 || gen.status === 0;
|
|
170
|
+
if (!transient || attempt === 1) {
|
|
171
|
+
return {
|
|
172
|
+
...rec,
|
|
173
|
+
error: {
|
|
174
|
+
key, stage: "generate", code: String(gen.status),
|
|
175
|
+
message: gen.json?.errors?.[0]?.message || gen.json?.errorMessage || gen.raw?.slice(0, 200) || "no imageURL in response",
|
|
176
|
+
reqId: gen.reqId,
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// --- import to Wix Media ---
|
|
183
|
+
let imp;
|
|
184
|
+
try {
|
|
185
|
+
imp = await call(MEDIA_IMPORT_URL, {
|
|
186
|
+
url: imageURL,
|
|
187
|
+
mimeType: outputFormat === "JPG" ? "image/jpeg" : "image/png",
|
|
188
|
+
displayName: img.displayName || `${key}.${outputFormat.toLowerCase()}`,
|
|
189
|
+
}, IMPORT_TIMEOUT_MS);
|
|
190
|
+
} catch (e) {
|
|
191
|
+
imp = { ok: false, status: 0, reqId: null, json: null, raw: String(e) };
|
|
192
|
+
}
|
|
193
|
+
rec.importReqId = imp.reqId;
|
|
194
|
+
const file = imp.json?.file;
|
|
195
|
+
if (!imp.ok || !file?.url) {
|
|
196
|
+
return {
|
|
197
|
+
...rec,
|
|
198
|
+
error: {
|
|
199
|
+
key, stage: "import", code: String(imp.status),
|
|
200
|
+
message: imp.json?.message || imp.raw?.slice(0, 200) || "no file.url in import response",
|
|
201
|
+
reqId: imp.reqId,
|
|
202
|
+
},
|
|
203
|
+
// generation succeeded — surface the raw URL so the caller can decide
|
|
204
|
+
genURL: imageURL,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return { ...rec, status: "ok", url: file.url, fileId: file.fileUrl }; // Wix returns the file ID in .fileUrl (not .url)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ---- run all in parallel --------------------------------------------------
|
|
212
|
+
const settled = await Promise.allSettled(images.map(genAndImport));
|
|
213
|
+
const results = settled.map((s, i) =>
|
|
214
|
+
s.status === "fulfilled" ? s.value
|
|
215
|
+
: { key: images[i].key, status: "failed", error: { key: images[i].key, stage: "internal", message: String(s.reason) } });
|
|
216
|
+
|
|
217
|
+
const map = {};
|
|
218
|
+
const slots = {};
|
|
219
|
+
const errors = [];
|
|
220
|
+
let okCount = 0;
|
|
221
|
+
for (const r of results) {
|
|
222
|
+
if (r.status === "ok") {
|
|
223
|
+
okCount++;
|
|
224
|
+
map[r.key] = { url: r.url, fileId: r.fileId };
|
|
225
|
+
slots[r.key] = r.url;
|
|
226
|
+
} else if (r.error) {
|
|
227
|
+
errors.push(r.error);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const status = okCount === images.length ? "complete" : okCount === 0 ? "failed" : "partial";
|
|
232
|
+
console.log(JSON.stringify({
|
|
233
|
+
status,
|
|
234
|
+
model,
|
|
235
|
+
count: { requested: images.length, ok: okCount, failed: images.length - okCount },
|
|
236
|
+
map,
|
|
237
|
+
slots,
|
|
238
|
+
results: results.map(({ url, fileId, genURL, ...keep }) => keep), // keep slim; urls live in map
|
|
239
|
+
errors,
|
|
240
|
+
}, null, 2));
|
|
241
|
+
|
|
242
|
+
process.exit(status === "failed" ? 1 : 0);
|
|
@@ -2,11 +2,17 @@
|
|
|
2
2
|
# Scaffold a new Wix Managed Headless project using the CLI's preset blank template.
|
|
3
3
|
#
|
|
4
4
|
# Usage:
|
|
5
|
-
# bash <SKILL_ROOT>/scripts/scaffold.sh <
|
|
5
|
+
# bash <SKILL_ROOT>/scripts/scaffold.sh <folder-name> "<Brand Name>" [--frontend astro|custom]
|
|
6
6
|
#
|
|
7
|
-
# <
|
|
8
|
-
#
|
|
9
|
-
#
|
|
7
|
+
# <folder-name>: lowercase letters, numbers, and hyphens only; must start with a
|
|
8
|
+
# letter or number. The CLI scaffolds into a subdir of this name,
|
|
9
|
+
# which this script then FLATTENS into the current directory (see
|
|
10
|
+
# "In-place flatten" below) — so the project ends up *in CWD*, not
|
|
11
|
+
# in a nested folder. The name is still required (the CLI mandates
|
|
12
|
+
# it) but the local folder is transient.
|
|
13
|
+
# <Brand Name>: human-readable business name; quote if it contains spaces. The
|
|
14
|
+
# CLI derives the Wix project display-name and URL slug from it, so
|
|
15
|
+
# it must include at least one English letter or number.
|
|
10
16
|
# --frontend: the frontend axis. Defaults to "astro" — the only supported (and
|
|
11
17
|
# only scaffolded) frontend. "custom" (any non-astro frontend) is
|
|
12
18
|
# NOT scaffolded yet: Discovery routes custom to the not-available
|
|
@@ -14,12 +20,13 @@
|
|
|
14
20
|
# so this script should only ever be invoked with "astro". If it is
|
|
15
21
|
# invoked with "custom" it exits 4 (recognized, not staged).
|
|
16
22
|
#
|
|
17
|
-
# After scaffold succeeds,
|
|
18
|
-
#
|
|
19
|
-
#
|
|
23
|
+
# After scaffold succeeds, the project lives in the CURRENT directory (the subdir
|
|
24
|
+
# was flattened away). Read ./wix.config.json to extract appId (project's appId)
|
|
25
|
+
# and siteId (used as --site for `wix token` and in REST call bodies). The
|
|
26
|
+
# orchestrator does that read; this script runs the npm create + the flatten.
|
|
20
27
|
#
|
|
21
28
|
# Behavior:
|
|
22
|
-
# - Pre-flight validates the
|
|
29
|
+
# - Pre-flight validates the folder name syntax (regex ^[a-z0-9][a-z0-9-]*$).
|
|
23
30
|
# - Pre-flight requires both positional args.
|
|
24
31
|
# - Runs `npm create @wix/new@latest headless` with --no-publish + --skip-install
|
|
25
32
|
# so the orchestrator can deferred-install with its own package set.
|
|
@@ -27,12 +34,15 @@
|
|
|
27
34
|
# blank starter. Without it, @wix/create-new (>=0.0.72) prompts for a template
|
|
28
35
|
# choice and aborts in a non-TTY agent shell ("not supported in non-interactive
|
|
29
36
|
# terminals").
|
|
37
|
+
# - FLATTENS the scaffolded subdir into CWD (single-folder layout: one `.wix`,
|
|
38
|
+
# CWD == project == site-root). Interim until the CLI supports in-place create.
|
|
30
39
|
#
|
|
31
40
|
# Exit codes:
|
|
32
41
|
# 0 — ok
|
|
33
|
-
# 2 — argument validation failed (bad
|
|
42
|
+
# 2 — argument validation failed (bad folder name, missing args, unknown --frontend value)
|
|
34
43
|
# 3 — Wix CLI not logged in (defensive; the Discovery pre-flight is the primary check)
|
|
35
44
|
# 4 — frontend value recognized but not scaffolded yet (custom today)
|
|
45
|
+
# 5 — scaffold succeeded but the subdir could not be flattened into CWD (name collision)
|
|
36
46
|
# <other> — npm create failed; stderr surfaced to caller for orchestrator-side
|
|
37
47
|
# recovery (auth / other scaffold failures live in the orchestrator).
|
|
38
48
|
|
|
@@ -62,8 +72,8 @@ while [[ $# -gt 0 ]]; do
|
|
|
62
72
|
done
|
|
63
73
|
|
|
64
74
|
if [[ ${#POSITIONAL[@]} -lt 2 || -z "${POSITIONAL[0]:-}" || -z "${POSITIONAL[1]:-}" ]]; then
|
|
65
|
-
echo "scaffold.sh: both positional args required. Got
|
|
66
|
-
echo "Usage: bash scaffold.sh <
|
|
75
|
+
echo "scaffold.sh: both positional args required. Got folder-name='${POSITIONAL[0]:-}' brand-name='${POSITIONAL[1]:-}'." >&2
|
|
76
|
+
echo "Usage: bash scaffold.sh <folder-name> \"<Brand Name>\" [--frontend astro|custom] — folder name first, brand quoted." >&2
|
|
67
77
|
exit 2
|
|
68
78
|
fi
|
|
69
79
|
|
|
@@ -81,10 +91,10 @@ case "$FRONTEND" in
|
|
|
81
91
|
;;
|
|
82
92
|
esac
|
|
83
93
|
|
|
84
|
-
if [[ ! "${POSITIONAL[0]}" =~ ^[a-z0-9]
|
|
85
|
-
echo "scaffold.sh:
|
|
86
|
-
echo "
|
|
87
|
-
echo "Derive from the brand
|
|
94
|
+
if [[ ! "${POSITIONAL[0]}" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then
|
|
95
|
+
echo "scaffold.sh: folder-name='${POSITIONAL[0]}' is not valid." >&2
|
|
96
|
+
echo "Folder name must contain only lowercase letters, numbers, and hyphens, and start with a letter or number." >&2
|
|
97
|
+
echo "Derive it from the brand as a lowercase, npm-safe directory name." >&2
|
|
88
98
|
exit 2
|
|
89
99
|
fi
|
|
90
100
|
|
|
@@ -100,9 +110,30 @@ if ! npx @wix/cli@latest whoami >/dev/null 2>&1; then
|
|
|
100
110
|
exit 3
|
|
101
111
|
fi
|
|
102
112
|
|
|
113
|
+
FOLDER="${POSITIONAL[0]}"
|
|
114
|
+
|
|
103
115
|
npm create @wix/new@latest headless -- \
|
|
104
116
|
--business-name "${POSITIONAL[1]}" \
|
|
105
|
-
--
|
|
117
|
+
--folder-name "$FOLDER" \
|
|
106
118
|
--site-template \
|
|
107
119
|
--no-publish \
|
|
108
120
|
--skip-install
|
|
121
|
+
|
|
122
|
+
# --- In-place flatten (interim hack) -----------------------------------------
|
|
123
|
+
# The CLI always scaffolds into a named subdir; there is NO in-place option —
|
|
124
|
+
# `headless init` regex-rejects `--folder-name .` ("must contain only lowercase
|
|
125
|
+
# letters, numbers, and hyphens") and offers no --cwd/--here flag. To keep the
|
|
126
|
+
# whole run single-folder (one `.wix`, CWD == project == site-root, no nested
|
|
127
|
+
# `.wix` to reason about), fold the scaffolded subdir up into the current dir.
|
|
128
|
+
#
|
|
129
|
+
# The .wix-merge + mv -n + rmdir logic is identical to flatten.sh (the own-build
|
|
130
|
+
# path's framework-agnostic flatten), so we delegate to it rather than duplicate.
|
|
131
|
+
# flatten.sh lives next to this script; resolve its path relative to our own dir
|
|
132
|
+
# so the call works regardless of CWD. (Both exit 5 on the same name-collision.)
|
|
133
|
+
# When the CLI gains `--folder-name .` (or --cwd), the TODO to drop the flatten
|
|
134
|
+
# lives in flatten.sh.
|
|
135
|
+
if [[ -d "$FOLDER" ]]; then
|
|
136
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
137
|
+
bash "$SCRIPT_DIR/flatten.sh" "$FOLDER"
|
|
138
|
+
fi
|
|
139
|
+
# -----------------------------------------------------------------------------
|