@better-openclaw/core 1.0.23 → 1.0.24
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/dist/bare-metal-partition.test.cjs +3 -4
- package/dist/bare-metal-partition.test.cjs.map +1 -1
- package/dist/bare-metal-partition.test.mjs +3 -4
- package/dist/bare-metal-partition.test.mjs.map +1 -1
- package/dist/composer.cjs +13 -1
- package/dist/composer.cjs.map +1 -1
- package/dist/composer.d.cts.map +1 -1
- package/dist/composer.d.mts.map +1 -1
- package/dist/composer.mjs +13 -1
- package/dist/composer.mjs.map +1 -1
- package/dist/composer.snapshot.test.cjs +1 -1
- package/dist/composer.snapshot.test.mjs +1 -1
- package/dist/composer.test.cjs +3 -2
- package/dist/composer.test.cjs.map +1 -1
- package/dist/composer.test.mjs +3 -2
- package/dist/composer.test.mjs.map +1 -1
- package/dist/deployers/strip-host-ports.test.cjs +1 -1
- package/dist/deployers/strip-host-ports.test.mjs +1 -1
- package/dist/generate.cjs +6 -2
- package/dist/generate.cjs.map +1 -1
- package/dist/generate.d.cts.map +1 -1
- package/dist/generate.d.mts.map +1 -1
- package/dist/generate.mjs +6 -2
- package/dist/generate.mjs.map +1 -1
- package/dist/generate.test.cjs +2 -2
- package/dist/generate.test.cjs.map +1 -1
- package/dist/generate.test.mjs +2 -2
- package/dist/generate.test.mjs.map +1 -1
- package/dist/generators/bare-metal-install.test.cjs +1 -1
- package/dist/generators/bare-metal-install.test.mjs +1 -1
- package/dist/generators/caddy.test.cjs +1 -1
- package/dist/generators/caddy.test.mjs +1 -1
- package/dist/generators/clone-repos.cjs +140 -0
- package/dist/generators/clone-repos.cjs.map +1 -0
- package/dist/generators/clone-repos.d.cts +11 -0
- package/dist/generators/clone-repos.d.cts.map +1 -0
- package/dist/generators/clone-repos.d.mts +11 -0
- package/dist/generators/clone-repos.d.mts.map +1 -0
- package/dist/generators/clone-repos.mjs +139 -0
- package/dist/generators/clone-repos.mjs.map +1 -0
- package/dist/generators/clone-repos.test.cjs +140 -0
- package/dist/generators/clone-repos.test.cjs.map +1 -0
- package/dist/generators/clone-repos.test.d.cts +1 -0
- package/dist/generators/clone-repos.test.d.mts +1 -0
- package/dist/generators/clone-repos.test.mjs +141 -0
- package/dist/generators/clone-repos.test.mjs.map +1 -0
- package/dist/generators/env.test.cjs +1 -1
- package/dist/generators/env.test.mjs +1 -1
- package/dist/generators/health-check.test.cjs +1 -1
- package/dist/generators/health-check.test.mjs +1 -1
- package/dist/generators/postgres-init.cjs +20 -0
- package/dist/generators/postgres-init.cjs.map +1 -1
- package/dist/generators/postgres-init.d.cts.map +1 -1
- package/dist/generators/postgres-init.d.mts.map +1 -1
- package/dist/generators/postgres-init.mjs +20 -0
- package/dist/generators/postgres-init.mjs.map +1 -1
- package/dist/generators/scripts.cjs +332 -3
- package/dist/generators/scripts.cjs.map +1 -1
- package/dist/generators/scripts.d.cts +3 -1
- package/dist/generators/scripts.d.cts.map +1 -1
- package/dist/generators/scripts.d.mts +3 -1
- package/dist/generators/scripts.d.mts.map +1 -1
- package/dist/generators/scripts.mjs +332 -3
- package/dist/generators/scripts.mjs.map +1 -1
- package/dist/generators/scripts.test.cjs +39 -5
- package/dist/generators/scripts.test.cjs.map +1 -1
- package/dist/generators/scripts.test.mjs +39 -5
- package/dist/generators/scripts.test.mjs.map +1 -1
- package/dist/generators/stack-manifest.cjs +1 -0
- package/dist/generators/stack-manifest.cjs.map +1 -1
- package/dist/generators/stack-manifest.d.cts +3 -2
- package/dist/generators/stack-manifest.d.cts.map +1 -1
- package/dist/generators/stack-manifest.d.mts +3 -2
- package/dist/generators/stack-manifest.d.mts.map +1 -1
- package/dist/generators/stack-manifest.mjs +1 -0
- package/dist/generators/stack-manifest.mjs.map +1 -1
- package/dist/generators/traefik.test.cjs +1 -1
- package/dist/generators/traefik.test.mjs +1 -1
- package/dist/index.cjs +8 -1
- package/dist/index.d.cts +5 -3
- package/dist/index.d.mts +5 -3
- package/dist/index.mjs +5 -3
- package/dist/migrations.test.cjs +1 -1
- package/dist/migrations.test.mjs +1 -1
- package/dist/presets/registry.cjs.map +1 -1
- package/dist/presets/registry.d.cts.map +1 -1
- package/dist/presets/registry.d.mts.map +1 -1
- package/dist/presets/registry.mjs.map +1 -1
- package/dist/presets/registry.test.cjs +1 -1
- package/dist/presets/registry.test.mjs +1 -1
- package/dist/resolver.cjs +8 -0
- package/dist/resolver.cjs.map +1 -1
- package/dist/resolver.mjs +9 -1
- package/dist/resolver.mjs.map +1 -1
- package/dist/resolver.test.cjs +47 -12
- package/dist/resolver.test.cjs.map +1 -1
- package/dist/resolver.test.mjs +47 -12
- package/dist/resolver.test.mjs.map +1 -1
- package/dist/{schema-B4c64P8N.d.cts → schema-eX44HhRp.d.mts} +62 -8
- package/dist/schema-eX44HhRp.d.mts.map +1 -0
- package/dist/{schema-CXNhYci1.d.mts → schema-tn5RK8CM.d.cts} +62 -8
- package/dist/schema-tn5RK8CM.d.cts.map +1 -0
- package/dist/schema.cjs +22 -4
- package/dist/schema.cjs.map +1 -1
- package/dist/schema.d.cts +2 -2
- package/dist/schema.d.mts +2 -2
- package/dist/schema.mjs +21 -5
- package/dist/schema.mjs.map +1 -1
- package/dist/schema.test.cjs +1 -1
- package/dist/schema.test.mjs +1 -1
- package/dist/services/definitions/apptension-saas.cjs +87 -0
- package/dist/services/definitions/apptension-saas.cjs.map +1 -0
- package/dist/services/definitions/apptension-saas.d.cts +7 -0
- package/dist/services/definitions/apptension-saas.d.cts.map +1 -0
- package/dist/services/definitions/apptension-saas.d.mts +7 -0
- package/dist/services/definitions/apptension-saas.d.mts.map +1 -0
- package/dist/services/definitions/apptension-saas.mjs +86 -0
- package/dist/services/definitions/apptension-saas.mjs.map +1 -0
- package/dist/services/definitions/boxyhq-saas.cjs +88 -0
- package/dist/services/definitions/boxyhq-saas.cjs.map +1 -0
- package/dist/services/definitions/boxyhq-saas.d.cts +7 -0
- package/dist/services/definitions/boxyhq-saas.d.cts.map +1 -0
- package/dist/services/definitions/boxyhq-saas.d.mts +7 -0
- package/dist/services/definitions/boxyhq-saas.d.mts.map +1 -0
- package/dist/services/definitions/boxyhq-saas.mjs +87 -0
- package/dist/services/definitions/boxyhq-saas.mjs.map +1 -0
- package/dist/services/definitions/cmsaas-starter.cjs +86 -0
- package/dist/services/definitions/cmsaas-starter.cjs.map +1 -0
- package/dist/services/definitions/cmsaas-starter.d.cts +7 -0
- package/dist/services/definitions/cmsaas-starter.d.cts.map +1 -0
- package/dist/services/definitions/cmsaas-starter.d.mts +7 -0
- package/dist/services/definitions/cmsaas-starter.d.mts.map +1 -0
- package/dist/services/definitions/cmsaas-starter.mjs +85 -0
- package/dist/services/definitions/cmsaas-starter.mjs.map +1 -0
- package/dist/services/definitions/index.cjs +51 -36
- package/dist/services/definitions/index.cjs.map +1 -1
- package/dist/services/definitions/index.d.cts +30 -25
- package/dist/services/definitions/index.d.cts.map +1 -1
- package/dist/services/definitions/index.d.mts +30 -25
- package/dist/services/definitions/index.d.mts.map +1 -1
- package/dist/services/definitions/index.mjs +47 -37
- package/dist/services/definitions/index.mjs.map +1 -1
- package/dist/services/definitions/ixartz-saas.cjs +88 -0
- package/dist/services/definitions/ixartz-saas.cjs.map +1 -0
- package/dist/services/definitions/ixartz-saas.d.cts +7 -0
- package/dist/services/definitions/ixartz-saas.d.cts.map +1 -0
- package/dist/services/definitions/ixartz-saas.d.mts +7 -0
- package/dist/services/definitions/ixartz-saas.d.mts.map +1 -0
- package/dist/services/definitions/ixartz-saas.mjs +87 -0
- package/dist/services/definitions/ixartz-saas.mjs.map +1 -0
- package/dist/services/definitions/mission-control.cjs +16 -2
- package/dist/services/definitions/mission-control.cjs.map +1 -1
- package/dist/services/definitions/mission-control.mjs +16 -2
- package/dist/services/definitions/mission-control.mjs.map +1 -1
- package/dist/services/definitions/open-saas.cjs +81 -0
- package/dist/services/definitions/open-saas.cjs.map +1 -0
- package/dist/services/definitions/open-saas.d.cts +7 -0
- package/dist/services/definitions/open-saas.d.cts.map +1 -0
- package/dist/services/definitions/open-saas.d.mts +7 -0
- package/dist/services/definitions/open-saas.d.mts.map +1 -0
- package/dist/services/definitions/open-saas.mjs +80 -0
- package/dist/services/definitions/open-saas.mjs.map +1 -0
- package/dist/services/registry.cjs +3 -0
- package/dist/services/registry.cjs.map +1 -1
- package/dist/services/registry.d.cts.map +1 -1
- package/dist/services/registry.d.mts.map +1 -1
- package/dist/services/registry.mjs +3 -0
- package/dist/services/registry.mjs.map +1 -1
- package/dist/services/registry.test.cjs +8 -1
- package/dist/services/registry.test.cjs.map +1 -1
- package/dist/services/registry.test.mjs +8 -1
- package/dist/services/registry.test.mjs.map +1 -1
- package/dist/{skill-manifest-BVUXU0__.mjs → skill-manifest-6XhrhWsG.mjs} +49 -1
- package/dist/{skill-manifest--IgY9REK.cjs.map → skill-manifest-6XhrhWsG.mjs.map} +1 -1
- package/dist/{skill-manifest--IgY9REK.cjs → skill-manifest-B8znSsym.cjs} +49 -1
- package/dist/{skill-manifest-BVUXU0__.mjs.map → skill-manifest-B8znSsym.cjs.map} +1 -1
- package/dist/skills/registry.cjs +3 -3
- package/dist/skills/registry.cjs.map +1 -1
- package/dist/skills/registry.mjs +3 -3
- package/dist/skills/registry.mjs.map +1 -1
- package/dist/skills/skill-manifest.cjs +1 -1
- package/dist/skills/skill-manifest.mjs +1 -1
- package/dist/track-analytics.cjs +50 -0
- package/dist/track-analytics.cjs.map +1 -0
- package/dist/track-analytics.d.cts +34 -0
- package/dist/track-analytics.d.cts.map +1 -0
- package/dist/track-analytics.d.mts +34 -0
- package/dist/track-analytics.d.mts.map +1 -0
- package/dist/track-analytics.mjs +48 -0
- package/dist/track-analytics.mjs.map +1 -0
- package/dist/track-analytics.test.cjs +91 -0
- package/dist/track-analytics.test.cjs.map +1 -0
- package/dist/track-analytics.test.d.cts +1 -0
- package/dist/track-analytics.test.d.mts +1 -0
- package/dist/track-analytics.test.mjs +92 -0
- package/dist/track-analytics.test.mjs.map +1 -0
- package/dist/types.cjs +7 -0
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +4 -2
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.mts +4 -2
- package/dist/types.d.mts.map +1 -1
- package/dist/types.mjs +7 -0
- package/dist/types.mjs.map +1 -1
- package/dist/validator.test.cjs +1 -1
- package/dist/validator.test.mjs +1 -1
- package/dist/version-manager.cjs +1 -1
- package/dist/version-manager.cjs.map +1 -1
- package/dist/version-manager.mjs +1 -1
- package/dist/version-manager.mjs.map +1 -1
- package/dist/version-manager.test.cjs +7 -5
- package/dist/version-manager.test.cjs.map +1 -1
- package/dist/version-manager.test.mjs +7 -5
- package/dist/version-manager.test.mjs.map +1 -1
- package/dist/{vi.2VT5v0um-DvC3SVNc.mjs → vi.2VT5v0um-C_jmO7m2.mjs} +5 -5
- package/dist/{vi.2VT5v0um-DvC3SVNc.mjs.map → vi.2VT5v0um-C_jmO7m2.mjs.map} +1 -1
- package/dist/{vi.2VT5v0um-CRqXre87.cjs → vi.2VT5v0um-iVBt6Fyq.cjs} +5 -5
- package/dist/{vi.2VT5v0um-CRqXre87.cjs.map → vi.2VT5v0um-iVBt6Fyq.cjs.map} +1 -1
- package/package.json +1 -1
- package/src/__snapshots__/composer.snapshot.test.ts.snap +155 -0
- package/src/bare-metal-partition.test.ts +4 -3
- package/src/composer.test.ts +4 -2
- package/src/composer.ts +20 -1
- package/src/generate.test.ts +2 -1
- package/src/generate.ts +10 -1
- package/src/generators/clone-repos.test.ts +154 -0
- package/src/generators/clone-repos.ts +159 -0
- package/src/generators/postgres-init.ts +17 -0
- package/src/generators/scripts.test.ts +52 -4
- package/src/generators/scripts.ts +351 -3
- package/src/generators/stack-manifest.ts +4 -2
- package/src/index.ts +8 -0
- package/src/presets/registry.ts +241 -329
- package/src/resolver.test.ts +53 -15
- package/src/resolver.ts +13 -1
- package/src/schema.ts +33 -4
- package/src/services/definitions/apptension-saas.ts +84 -0
- package/src/services/definitions/boxyhq-saas.ts +84 -0
- package/src/services/definitions/cmsaas-starter.ts +84 -0
- package/src/services/definitions/index.ts +90 -70
- package/src/services/definitions/ixartz-saas.ts +84 -0
- package/src/services/definitions/mission-control.ts +19 -2
- package/src/services/definitions/open-saas.ts +79 -0
- package/src/services/registry.test.ts +8 -0
- package/src/services/registry.ts +7 -0
- package/src/skills/manifest.json +64 -0
- package/src/skills/registry.ts +3 -3
- package/src/track-analytics.test.ts +82 -0
- package/src/track-analytics.ts +76 -0
- package/src/types.ts +11 -0
- package/src/version-manager.test.ts +10 -5
- package/src/version-manager.ts +1 -1
- package/dist/schema-B4c64P8N.d.cts.map +0 -1
- package/dist/schema-CXNhYci1.d.mts.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.test.mjs","names":[],"sources":["../src/generate.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { parse } from \"yaml\";\nimport { generate } from \"./generate.js\";\n\ndescribe(\"generate (end-to-end)\", () => {\n\tit(\"generates a minimal stack (redis only)\", () => {\n\t\tconst result = generate({\n\t\t\tprojectName: \"test-stack\",\n\t\t\tservices: [\"redis\"],\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t\tdeployment: \"local\",\n\t\t\tgenerateSecrets: true,\n\t\t\topenclawVersion: \"latest\",\n\t\t});\n\n\t\t// Core files must be present\n\t\texpect(result.files).toHaveProperty(\"docker-compose.yml\");\n\t\texpect(result.files).toHaveProperty(\".env.example\");\n\t\texpect(result.files).toHaveProperty(\".env\");\n\t\texpect(result.files).toHaveProperty(\"README.md\");\n\n\t\t// docker-compose.yml must be valid YAML\n\t\tconst composed = parse(result.files[\"docker-compose.yml\"]!);\n\t\texpect(composed).toHaveProperty(\"services\");\n\n\t\t// .env.example should reference REDIS_PASSWORD\n\t\texpect(result.files[\".env.example\"]).toContain(\"REDIS_PASSWORD\");\n\n\t\t// README should mention the project name\n\t\texpect(result.files[\"README.md\"]).toContain(\"test-stack\");\n\n\t\t// At least one service resolved\n\t\texpect(result.metadata.serviceCount).toBeGreaterThanOrEqual(1);\n\t});\n\n\tit(\"generates research-agent stack from skill pack\", () => {\n\t\tconst result = generate({\n\t\t\tprojectName: \"research-stack\",\n\t\t\tservices: [],\n\t\t\tskillPacks: [\"research-agent\"],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t\tdeployment: \"local\",\n\t\t\tgenerateSecrets: true,\n\t\t\topenclawVersion: \"latest\",\n\t\t});\n\n\t\t// Skill SKILL.md files for each skill in the research-agent pack\n\t\texpect(result.files).toHaveProperty(\"openclaw/workspace/skills/qdrant-memory/SKILL.md\");\n\t\texpect(result.files).toHaveProperty(\"openclaw/workspace/skills/searxng-search/SKILL.md\");\n\t\texpect(result.files).toHaveProperty(\"openclaw/workspace/skills/browserless-browse/SKILL.md\");\n\n\t\t// docker-compose.yml should contain the expected services\n\t\tconst composed = parse(result.files[\"docker-compose.yml\"]!);\n\t\texpect(composed.services).toHaveProperty(\"qdrant\");\n\t\texpect(composed.services).toHaveProperty(\"searxng\");\n\t\texpect(composed.services).toHaveProperty(\"browserless\");\n\t});\n\n\tit(\"generates full preset stack\", () => {\n\t\tconst fullServices = [\n\t\t\t\"redis\",\n\t\t\t\"postgresql\",\n\t\t\t\"qdrant\",\n\t\t\t\"n8n\",\n\t\t\t\"ffmpeg\",\n\t\t\t\"remotion\",\n\t\t\t\"minio\",\n\t\t\t\"caddy\",\n\t\t\t\"browserless\",\n\t\t\t\"searxng\",\n\t\t\t\"meilisearch\",\n\t\t\t\"uptime-kuma\",\n\t\t\t\"grafana\",\n\t\t\t\"prometheus\",\n\t\t\t\"ollama\",\n\t\t\t\"whisper\",\n\t\t\t\"gotify\",\n\t\t];\n\n\t\tconst result = generate({\n\t\t\tprojectName: \"full-stack\",\n\t\t\tservices: fullServices,\n\t\t\tskillPacks: [\n\t\t\t\t\"video-creator\",\n\t\t\t\t\"research-agent\",\n\t\t\t\t\"social-media\",\n\t\t\t\t\"dev-ops\",\n\t\t\t\t\"knowledge-base\",\n\t\t\t\t\"local-ai\",\n\t\t\t],\n\t\t\tproxy: \"caddy\",\n\t\t\tdomain: \"example.com\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t\tdeployment: \"local\",\n\t\t\tgenerateSecrets: true,\n\t\t\topenclawVersion: \"latest\",\n\t\t});\n\n\t\t// Should have many files\n\t\tconst fileCount = Object.keys(result.files).length;\n\t\texpect(fileCount).toBeGreaterThan(10);\n\n\t\t// Should include the start script\n\t\texpect(result.files).toHaveProperty(\"scripts/start.sh\");\n\n\t\t// Should not throw (it already didn't if we got here)\n\t\texpect(result.metadata.serviceCount).toBeGreaterThan(5);\n\t});\n\n\tit(\"generates caddy config when proxy is caddy\", () => {\n\t\tconst result = generate({\n\t\t\tprojectName: \"caddy-stack\",\n\t\t\tservices: [\"redis\"],\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"caddy\",\n\t\t\tdomain: \"example.com\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t\tdeployment: \"local\",\n\t\t\tgenerateSecrets: true,\n\t\t\topenclawVersion: \"latest\",\n\t\t});\n\n\t\texpect(result.files).toHaveProperty(\"caddy/Caddyfile\");\n\t\texpect(result.files[\"caddy/Caddyfile\"]!.length).toBeGreaterThan(0);\n\t});\n\n\tit(\"generates prometheus config when monitoring enabled\", () => {\n\t\tconst result = generate({\n\t\t\tprojectName: \"monitored-stack\",\n\t\t\tservices: [],\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t\tdeployment: \"local\",\n\t\t\tgenerateSecrets: true,\n\t\t\topenclawVersion: \"latest\",\n\t\t\tmonitoring: true,\n\t\t});\n\n\t\texpect(result.files).toHaveProperty(\"prometheus/prometheus.yml\");\n\t\t// Verify it's valid YAML\n\t\tconst promConfig = parse(result.files[\"prometheus/prometheus.yml\"]!);\n\t\texpect(promConfig).toBeDefined();\n\t});\n\n\tit(\"generates La Suite Meet stack with all expected services\", () => {\n\t\tconst lasuiteMeetServices = [\n\t\t\t\"postgresql\",\n\t\t\t\"redis\",\n\t\t\t\"livekit\",\n\t\t\t\"lasuite-meet-backend\",\n\t\t\t\"lasuite-meet-frontend\",\n\t\t\t\"lasuite-meet-agents\",\n\t\t];\n\t\tconst result = generate({\n\t\t\tprojectName: \"lasuite-meet-stack\",\n\t\t\tservices: lasuiteMeetServices,\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t\tdeployment: \"local\",\n\t\t\tgenerateSecrets: true,\n\t\t\topenclawVersion: \"latest\",\n\t\t});\n\n\t\t// Services may be split across main and profile compose files\n\t\tconst allServiceIds = new Set<string>();\n\t\tfor (const [filename, content] of Object.entries(result.files)) {\n\t\t\tif (filename.endsWith(\".yml\") && content) {\n\t\t\t\tconst doc = parse(content);\n\t\t\t\tif (doc?.services && typeof doc.services === \"object\") {\n\t\t\t\t\tfor (const id of Object.keys(doc.services)) {\n\t\t\t\t\t\tallServiceIds.add(id);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfor (const id of lasuiteMeetServices) {\n\t\t\texpect(allServiceIds.has(id), `missing service ${id}`).toBe(true);\n\t\t}\n\t\texpect(result.metadata.serviceCount).toBe(lasuiteMeetServices.length);\n\t});\n\n\tit(\"generates bare-metal installer for Windows (install.ps1)\", () => {\n\t\tconst result = generate({\n\t\t\tprojectName: \"win-stack\",\n\t\t\tservices: [\"redis\"],\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"windows/amd64\",\n\t\t\tdeployment: \"local\",\n\t\t\tdeploymentType: \"bare-metal\",\n\t\t\tgenerateSecrets: true,\n\t\t\topenclawVersion: \"latest\",\n\t\t});\n\t\texpect(result.files).toHaveProperty(\"install.ps1\");\n\t\texpect(result.files[\"install.ps1\"]).toContain(\"docker compose\");\n\t\texpect(result.files[\"install.ps1\"]).toContain(\"PowerShell\");\n\t});\n\n\tit(\"generates bare-metal installer for Linux/macOS (install.sh)\", () => {\n\t\tconst result = generate({\n\t\t\tprojectName: \"linux-stack\",\n\t\t\tservices: [\"redis\"],\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t\tdeployment: \"local\",\n\t\t\tdeploymentType: \"bare-metal\",\n\t\t\tgenerateSecrets: true,\n\t\t\topenclawVersion: \"latest\",\n\t\t});\n\t\texpect(result.files).toHaveProperty(\"install.sh\");\n\t\texpect(result.files[\"install.sh\"]).toContain(\"docker\");\n\t\texpect(result.files[\"install.sh\"]).toContain(\"compose\");\n\t});\n\n\tit(\"bare-metal with redis on Linux: native script, compose excludes redis, gateway has extra_hosts\", () => {\n\t\tconst result = generate({\n\t\t\tprojectName: \"bare-metal-redis\",\n\t\t\tservices: [\"redis\"],\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t\tdeployment: \"local\",\n\t\t\tdeploymentType: \"bare-metal\",\n\t\t\tgenerateSecrets: true,\n\t\t\topenclawVersion: \"latest\",\n\t\t});\n\n\t\t// Native install script for Linux\n\t\texpect(result.files).toHaveProperty(\"native/install-linux.sh\");\n\t\texpect(result.files[\"native/install-linux.sh\"]).toContain(\"redis\");\n\n\t\t// Docker compose must NOT include redis (native); only gateway/openclaw\n\t\tconst composed = parse(result.files[\"docker-compose.yml\"]!);\n\t\texpect(composed.services).not.toHaveProperty(\"redis\");\n\t\texpect(composed.services).toHaveProperty(\"openclaw-gateway\");\n\n\t\t// Gateway must have extra_hosts for host.docker.internal\n\t\tconst gateway = composed.services[\"openclaw-gateway\"];\n\t\texpect(gateway).toBeDefined();\n\t\texpect(gateway.extra_hosts).toBeDefined();\n\t\texpect(\n\t\t\t(gateway.extra_hosts as string[]).some(\n\t\t\t\t(h: string) => h.includes(\"host.docker.internal\") && h.includes(\"host-gateway\"),\n\t\t\t),\n\t\t).toBe(true);\n\n\t\t// .env should set REDIS_HOST to host.docker.internal for gateway to reach native Redis\n\t\texpect(result.files[\".env\"]).toContain(\"REDIS_HOST=host.docker.internal\");\n\t});\n\n\tit(\"throws on conflicting services\", () => {\n\t\texpect(() =>\n\t\t\tgenerate({\n\t\t\t\tprojectName: \"conflict-stack\",\n\t\t\t\tservices: [\"redis\", \"valkey\"],\n\t\t\t\tskillPacks: [],\n\t\t\t\tproxy: \"none\",\n\t\t\t\tgpu: false,\n\t\t\t\tplatform: \"linux/amd64\",\n\t\t\t\tdeployment: \"local\",\n\t\t\t\tgenerateSecrets: true,\n\t\t\t\topenclawVersion: \"latest\",\n\t\t\t}),\n\t\t).toThrow();\n\t});\n\n\tit(\"generates scripts directory\", () => {\n\t\tconst result = generate({\n\t\t\tprojectName: \"scripts-stack\",\n\t\t\tservices: [\"redis\"],\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t\tdeployment: \"local\",\n\t\t\tgenerateSecrets: true,\n\t\t\topenclawVersion: \"latest\",\n\t\t});\n\n\t\tconst expectedScripts = [\n\t\t\t\"scripts/start.sh\",\n\t\t\t\"scripts/stop.sh\",\n\t\t\t\"scripts/update.sh\",\n\t\t\t\"scripts/backup.sh\",\n\t\t\t\"scripts/status.sh\",\n\t\t];\n\n\t\tfor (const script of expectedScripts) {\n\t\t\texpect(result.files).toHaveProperty(script);\n\t\t\texpect(result.files[script]!.length).toBeGreaterThan(0);\n\t\t}\n\t});\n\n\tit(\"all generated .env.example vars have comments\", () => {\n\t\tconst result = generate({\n\t\t\tprojectName: \"env-comments-stack\",\n\t\t\tservices: [\"redis\", \"qdrant\", \"n8n\"],\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t\tdeployment: \"local\",\n\t\t\tgenerateSecrets: true,\n\t\t\topenclawVersion: \"latest\",\n\t\t});\n\n\t\tconst envExample = result.files[\".env.example\"]!;\n\t\tconst lines = envExample.split(\"\\n\");\n\n\t\t// Walk through lines: every non-empty, non-comment KEY=VALUE line should\n\t\t// have a preceding comment line (starting with #).\n\t\tfor (let i = 0; i < lines.length; i++) {\n\t\t\tconst line = lines[i]!.trim();\n\t\t\tif (line === \"\" || line.startsWith(\"#\")) continue;\n\n\t\t\t// This line looks like KEY=VALUE\n\t\t\tif (line.includes(\"=\")) {\n\t\t\t\t// There must be a comment somewhere before it (look backwards for a # line)\n\t\t\t\tlet foundComment = false;\n\t\t\t\tfor (let j = i - 1; j >= 0; j--) {\n\t\t\t\t\tconst prev = lines[j]!.trim();\n\t\t\t\t\tif (prev === \"\") continue; // skip blank lines\n\t\t\t\t\tif (prev.startsWith(\"#\")) {\n\t\t\t\t\t\tfoundComment = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\t// Hit another non-comment, non-empty line — no comment found\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\texpect(foundComment).toBe(true);\n\t\t\t}\n\t\t}\n\t});\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"generate.test.mjs","names":[],"sources":["../src/generate.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { parse } from \"yaml\";\nimport { generate } from \"./generate.js\";\n\ndescribe(\"generate (end-to-end)\", () => {\n\tit(\"generates a minimal stack (redis only)\", () => {\n\t\tconst result = generate({\n\t\t\tprojectName: \"test-stack\",\n\t\t\tservices: [\"redis\"],\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t\tdeployment: \"local\",\n\t\t\tgenerateSecrets: true,\n\t\t\topenclawVersion: \"latest\",\n\t\t});\n\n\t\t// Core files must be present\n\t\texpect(result.files).toHaveProperty(\"docker-compose.yml\");\n\t\texpect(result.files).toHaveProperty(\".env.example\");\n\t\texpect(result.files).toHaveProperty(\".env\");\n\t\texpect(result.files).toHaveProperty(\"README.md\");\n\n\t\t// docker-compose.yml must be valid YAML\n\t\tconst composed = parse(result.files[\"docker-compose.yml\"]!);\n\t\texpect(composed).toHaveProperty(\"services\");\n\n\t\t// .env.example should reference REDIS_PASSWORD\n\t\texpect(result.files[\".env.example\"]).toContain(\"REDIS_PASSWORD\");\n\n\t\t// README should mention the project name\n\t\texpect(result.files[\"README.md\"]).toContain(\"test-stack\");\n\n\t\t// At least one service resolved\n\t\texpect(result.metadata.serviceCount).toBeGreaterThanOrEqual(1);\n\t});\n\n\tit(\"generates research-agent stack from skill pack\", () => {\n\t\tconst result = generate({\n\t\t\tprojectName: \"research-stack\",\n\t\t\tservices: [],\n\t\t\tskillPacks: [\"research-agent\"],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t\tdeployment: \"local\",\n\t\t\tgenerateSecrets: true,\n\t\t\topenclawVersion: \"latest\",\n\t\t});\n\n\t\t// Skill SKILL.md files for each skill in the research-agent pack\n\t\texpect(result.files).toHaveProperty(\"openclaw/workspace/skills/qdrant-memory/SKILL.md\");\n\t\texpect(result.files).toHaveProperty(\"openclaw/workspace/skills/searxng-search/SKILL.md\");\n\t\texpect(result.files).toHaveProperty(\"openclaw/workspace/skills/browserless-browse/SKILL.md\");\n\n\t\t// docker-compose.yml should contain the expected services\n\t\tconst composed = parse(result.files[\"docker-compose.yml\"]!);\n\t\texpect(composed.services).toHaveProperty(\"qdrant\");\n\t\texpect(composed.services).toHaveProperty(\"searxng\");\n\t\texpect(composed.services).toHaveProperty(\"browserless\");\n\t});\n\n\tit(\"generates full preset stack\", () => {\n\t\tconst fullServices = [\n\t\t\t\"redis\",\n\t\t\t\"postgresql\",\n\t\t\t\"qdrant\",\n\t\t\t\"n8n\",\n\t\t\t\"ffmpeg\",\n\t\t\t\"remotion\",\n\t\t\t\"minio\",\n\t\t\t\"caddy\",\n\t\t\t\"browserless\",\n\t\t\t\"searxng\",\n\t\t\t\"meilisearch\",\n\t\t\t\"uptime-kuma\",\n\t\t\t\"grafana\",\n\t\t\t\"prometheus\",\n\t\t\t\"ollama\",\n\t\t\t\"whisper\",\n\t\t\t\"gotify\",\n\t\t];\n\n\t\tconst result = generate({\n\t\t\tprojectName: \"full-stack\",\n\t\t\tservices: fullServices,\n\t\t\tskillPacks: [\n\t\t\t\t\"video-creator\",\n\t\t\t\t\"research-agent\",\n\t\t\t\t\"social-media\",\n\t\t\t\t\"dev-ops\",\n\t\t\t\t\"knowledge-base\",\n\t\t\t\t\"local-ai\",\n\t\t\t],\n\t\t\tproxy: \"caddy\",\n\t\t\tdomain: \"example.com\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t\tdeployment: \"local\",\n\t\t\tgenerateSecrets: true,\n\t\t\topenclawVersion: \"latest\",\n\t\t});\n\n\t\t// Should have many files\n\t\tconst fileCount = Object.keys(result.files).length;\n\t\texpect(fileCount).toBeGreaterThan(10);\n\n\t\t// Should include the start script\n\t\texpect(result.files).toHaveProperty(\"scripts/start.sh\");\n\n\t\t// Should not throw (it already didn't if we got here)\n\t\texpect(result.metadata.serviceCount).toBeGreaterThan(5);\n\t});\n\n\tit(\"generates caddy config when proxy is caddy\", () => {\n\t\tconst result = generate({\n\t\t\tprojectName: \"caddy-stack\",\n\t\t\tservices: [\"redis\"],\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"caddy\",\n\t\t\tdomain: \"example.com\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t\tdeployment: \"local\",\n\t\t\tgenerateSecrets: true,\n\t\t\topenclawVersion: \"latest\",\n\t\t});\n\n\t\texpect(result.files).toHaveProperty(\"caddy/Caddyfile\");\n\t\texpect(result.files[\"caddy/Caddyfile\"]!.length).toBeGreaterThan(0);\n\t});\n\n\tit(\"generates prometheus config when monitoring enabled\", () => {\n\t\tconst result = generate({\n\t\t\tprojectName: \"monitored-stack\",\n\t\t\tservices: [],\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t\tdeployment: \"local\",\n\t\t\tgenerateSecrets: true,\n\t\t\topenclawVersion: \"latest\",\n\t\t\tmonitoring: true,\n\t\t});\n\n\t\texpect(result.files).toHaveProperty(\"prometheus/prometheus.yml\");\n\t\t// Verify it's valid YAML\n\t\tconst promConfig = parse(result.files[\"prometheus/prometheus.yml\"]!);\n\t\texpect(promConfig).toBeDefined();\n\t});\n\n\tit(\"generates La Suite Meet stack with all expected services\", () => {\n\t\tconst lasuiteMeetServices = [\n\t\t\t\"postgresql\",\n\t\t\t\"redis\",\n\t\t\t\"livekit\",\n\t\t\t\"lasuite-meet-backend\",\n\t\t\t\"lasuite-meet-frontend\",\n\t\t\t\"lasuite-meet-agents\",\n\t\t];\n\t\tconst result = generate({\n\t\t\tprojectName: \"lasuite-meet-stack\",\n\t\t\tservices: lasuiteMeetServices,\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t\tdeployment: \"local\",\n\t\t\tgenerateSecrets: true,\n\t\t\topenclawVersion: \"latest\",\n\t\t});\n\n\t\t// Services may be split across main and profile compose files\n\t\tconst allServiceIds = new Set<string>();\n\t\tfor (const [filename, content] of Object.entries(result.files)) {\n\t\t\tif (filename.endsWith(\".yml\") && content) {\n\t\t\t\tconst doc = parse(content);\n\t\t\t\tif (doc?.services && typeof doc.services === \"object\") {\n\t\t\t\t\tfor (const id of Object.keys(doc.services)) {\n\t\t\t\t\t\tallServiceIds.add(id);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfor (const id of lasuiteMeetServices) {\n\t\t\texpect(allServiceIds.has(id), `missing service ${id}`).toBe(true);\n\t\t}\n\t\t// Service count includes user services + mandatory platform services (convex, mission-control, tailscale)\n\t\texpect(result.metadata.serviceCount).toBeGreaterThanOrEqual(lasuiteMeetServices.length);\n\t});\n\n\tit(\"generates bare-metal installer for Windows (install.ps1)\", () => {\n\t\tconst result = generate({\n\t\t\tprojectName: \"win-stack\",\n\t\t\tservices: [\"redis\"],\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"windows/amd64\",\n\t\t\tdeployment: \"local\",\n\t\t\tdeploymentType: \"bare-metal\",\n\t\t\tgenerateSecrets: true,\n\t\t\topenclawVersion: \"latest\",\n\t\t});\n\t\texpect(result.files).toHaveProperty(\"install.ps1\");\n\t\texpect(result.files[\"install.ps1\"]).toContain(\"docker compose\");\n\t\texpect(result.files[\"install.ps1\"]).toContain(\"PowerShell\");\n\t});\n\n\tit(\"generates bare-metal installer for Linux/macOS (install.sh)\", () => {\n\t\tconst result = generate({\n\t\t\tprojectName: \"linux-stack\",\n\t\t\tservices: [\"redis\"],\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t\tdeployment: \"local\",\n\t\t\tdeploymentType: \"bare-metal\",\n\t\t\tgenerateSecrets: true,\n\t\t\topenclawVersion: \"latest\",\n\t\t});\n\t\texpect(result.files).toHaveProperty(\"install.sh\");\n\t\texpect(result.files[\"install.sh\"]).toContain(\"docker\");\n\t\texpect(result.files[\"install.sh\"]).toContain(\"compose\");\n\t});\n\n\tit(\"bare-metal with redis on Linux: native script, compose excludes redis, gateway has extra_hosts\", () => {\n\t\tconst result = generate({\n\t\t\tprojectName: \"bare-metal-redis\",\n\t\t\tservices: [\"redis\"],\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t\tdeployment: \"local\",\n\t\t\tdeploymentType: \"bare-metal\",\n\t\t\tgenerateSecrets: true,\n\t\t\topenclawVersion: \"latest\",\n\t\t});\n\n\t\t// Native install script for Linux\n\t\texpect(result.files).toHaveProperty(\"native/install-linux.sh\");\n\t\texpect(result.files[\"native/install-linux.sh\"]).toContain(\"redis\");\n\n\t\t// Docker compose must NOT include redis (native); only gateway/openclaw\n\t\tconst composed = parse(result.files[\"docker-compose.yml\"]!);\n\t\texpect(composed.services).not.toHaveProperty(\"redis\");\n\t\texpect(composed.services).toHaveProperty(\"openclaw-gateway\");\n\n\t\t// Gateway must have extra_hosts for host.docker.internal\n\t\tconst gateway = composed.services[\"openclaw-gateway\"];\n\t\texpect(gateway).toBeDefined();\n\t\texpect(gateway.extra_hosts).toBeDefined();\n\t\texpect(\n\t\t\t(gateway.extra_hosts as string[]).some(\n\t\t\t\t(h: string) => h.includes(\"host.docker.internal\") && h.includes(\"host-gateway\"),\n\t\t\t),\n\t\t).toBe(true);\n\n\t\t// .env should set REDIS_HOST to host.docker.internal for gateway to reach native Redis\n\t\texpect(result.files[\".env\"]).toContain(\"REDIS_HOST=host.docker.internal\");\n\t});\n\n\tit(\"throws on conflicting services\", () => {\n\t\texpect(() =>\n\t\t\tgenerate({\n\t\t\t\tprojectName: \"conflict-stack\",\n\t\t\t\tservices: [\"redis\", \"valkey\"],\n\t\t\t\tskillPacks: [],\n\t\t\t\tproxy: \"none\",\n\t\t\t\tgpu: false,\n\t\t\t\tplatform: \"linux/amd64\",\n\t\t\t\tdeployment: \"local\",\n\t\t\t\tgenerateSecrets: true,\n\t\t\t\topenclawVersion: \"latest\",\n\t\t\t}),\n\t\t).toThrow();\n\t});\n\n\tit(\"generates scripts directory\", () => {\n\t\tconst result = generate({\n\t\t\tprojectName: \"scripts-stack\",\n\t\t\tservices: [\"redis\"],\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t\tdeployment: \"local\",\n\t\t\tgenerateSecrets: true,\n\t\t\topenclawVersion: \"latest\",\n\t\t});\n\n\t\tconst expectedScripts = [\n\t\t\t\"scripts/start.sh\",\n\t\t\t\"scripts/stop.sh\",\n\t\t\t\"scripts/update.sh\",\n\t\t\t\"scripts/backup.sh\",\n\t\t\t\"scripts/status.sh\",\n\t\t];\n\n\t\tfor (const script of expectedScripts) {\n\t\t\texpect(result.files).toHaveProperty(script);\n\t\t\texpect(result.files[script]!.length).toBeGreaterThan(0);\n\t\t}\n\t});\n\n\tit(\"all generated .env.example vars have comments\", () => {\n\t\tconst result = generate({\n\t\t\tprojectName: \"env-comments-stack\",\n\t\t\tservices: [\"redis\", \"qdrant\", \"n8n\"],\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t\tdeployment: \"local\",\n\t\t\tgenerateSecrets: true,\n\t\t\topenclawVersion: \"latest\",\n\t\t});\n\n\t\tconst envExample = result.files[\".env.example\"]!;\n\t\tconst lines = envExample.split(\"\\n\");\n\n\t\t// Walk through lines: every non-empty, non-comment KEY=VALUE line should\n\t\t// have a preceding comment line (starting with #).\n\t\tfor (let i = 0; i < lines.length; i++) {\n\t\t\tconst line = lines[i]!.trim();\n\t\t\tif (line === \"\" || line.startsWith(\"#\")) continue;\n\n\t\t\t// This line looks like KEY=VALUE\n\t\t\tif (line.includes(\"=\")) {\n\t\t\t\t// There must be a comment somewhere before it (look backwards for a # line)\n\t\t\t\tlet foundComment = false;\n\t\t\t\tfor (let j = i - 1; j >= 0; j--) {\n\t\t\t\t\tconst prev = lines[j]!.trim();\n\t\t\t\t\tif (prev === \"\") continue; // skip blank lines\n\t\t\t\t\tif (prev.startsWith(\"#\")) {\n\t\t\t\t\t\tfoundComment = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\t// Hit another non-comment, non-empty line — no comment found\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\texpect(foundComment).toBe(true);\n\t\t\t}\n\t\t}\n\t});\n});\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_vi_2VT5v0um = require("../vi.2VT5v0um-
|
|
1
|
+
const require_vi_2VT5v0um = require("../vi.2VT5v0um-iVBt6Fyq.cjs");
|
|
2
2
|
const require_generators_bare_metal_install = require("./bare-metal-install.cjs");
|
|
3
3
|
//#region src/generators/bare-metal-install.test.ts
|
|
4
4
|
require_vi_2VT5v0um.describe("generateBareMetalInstall", () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as describe, r as it, t as globalExpect } from "../vi.2VT5v0um-
|
|
1
|
+
import { n as describe, r as it, t as globalExpect } from "../vi.2VT5v0um-C_jmO7m2.mjs";
|
|
2
2
|
import { generateBareMetalInstall } from "./bare-metal-install.mjs";
|
|
3
3
|
//#region src/generators/bare-metal-install.test.ts
|
|
4
4
|
describe("generateBareMetalInstall", () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_vi_2VT5v0um = require("../vi.2VT5v0um-
|
|
1
|
+
const require_vi_2VT5v0um = require("../vi.2VT5v0um-iVBt6Fyq.cjs");
|
|
2
2
|
const require_generate = require("../generate.cjs");
|
|
3
3
|
//#region src/generators/caddy.test.ts
|
|
4
4
|
require_vi_2VT5v0um.describe("generateCaddyfile (via generate)", () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as describe, r as it, t as globalExpect } from "../vi.2VT5v0um-
|
|
1
|
+
import { n as describe, r as it, t as globalExpect } from "../vi.2VT5v0um-C_jmO7m2.mjs";
|
|
2
2
|
import { generate } from "../generate.mjs";
|
|
3
3
|
//#region src/generators/caddy.test.ts
|
|
4
4
|
describe("generateCaddyfile (via generate)", () => {
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/generators/clone-repos.ts
|
|
3
|
+
/**
|
|
4
|
+
* Generates clone scripts for git-based services (SaaS boilerplates).
|
|
5
|
+
* Returns empty object if no git-based services exist in the resolved stack.
|
|
6
|
+
*/
|
|
7
|
+
function generateCloneScripts(resolved) {
|
|
8
|
+
const gitServices = resolved.services.filter((s) => s.definition.gitSource && s.definition.buildContext);
|
|
9
|
+
if (gitServices.length === 0) return {};
|
|
10
|
+
const files = {};
|
|
11
|
+
files["scripts/clone-repos.sh"] = `#!/usr/bin/env bash
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
# ─── Clone/Update Git-Based Service Repositories ────────────────────────────
|
|
15
|
+
# Idempotent: clones if missing, pulls if already present.
|
|
16
|
+
|
|
17
|
+
SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
|
|
18
|
+
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
19
|
+
REPOS_DIR="$PROJECT_DIR/repos"
|
|
20
|
+
|
|
21
|
+
# ── Colour helpers ──────────────────────────────────────────────────────────
|
|
22
|
+
if [ -t 1 ]; then
|
|
23
|
+
GREEN='\\033[0;32m'; YELLOW='\\033[1;33m'; CYAN='\\033[0;36m'; RED='\\033[0;31m'; NC='\\033[0m'
|
|
24
|
+
else
|
|
25
|
+
GREEN=''; YELLOW=''; CYAN=''; RED=''; NC=''
|
|
26
|
+
fi
|
|
27
|
+
info() { echo -e "\${CYAN}i $*\${NC}"; }
|
|
28
|
+
ok() { echo -e "\${GREEN}✓ $*\${NC}"; }
|
|
29
|
+
warn() { echo -e "\${YELLOW}⚠ $*\${NC}"; }
|
|
30
|
+
err() { echo -e "\${RED}✗ $*\${NC}" >&2; }
|
|
31
|
+
|
|
32
|
+
# ── Check git ───────────────────────────────────────────────────────────────
|
|
33
|
+
if ! command -v git &> /dev/null; then
|
|
34
|
+
err "git is not installed. Please install git first."
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
mkdir -p "$REPOS_DIR"
|
|
39
|
+
|
|
40
|
+
clone_or_update() {
|
|
41
|
+
local name="$1" url="$2" branch="\${3:-}"
|
|
42
|
+
local dir="$REPOS_DIR/$name"
|
|
43
|
+
|
|
44
|
+
if [ -d "$dir/.git" ]; then
|
|
45
|
+
info "Updating $name..."
|
|
46
|
+
git -C "$dir" pull --ff-only 2>/dev/null || warn "Could not fast-forward $name (you may have local changes)"
|
|
47
|
+
else
|
|
48
|
+
info "Cloning $name..."
|
|
49
|
+
if [ -n "$branch" ]; then
|
|
50
|
+
git clone --depth 1 --branch "$branch" "$url" "$dir"
|
|
51
|
+
else
|
|
52
|
+
git clone --depth 1 "$url" "$dir"
|
|
53
|
+
fi
|
|
54
|
+
fi
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
echo ""
|
|
58
|
+
info "Cloning/updating SaaS boilerplate repositories..."
|
|
59
|
+
echo ""
|
|
60
|
+
|
|
61
|
+
${gitServices.map((s) => {
|
|
62
|
+
const gs = s.definition.gitSource;
|
|
63
|
+
const branchArg = gs.branch ? `"${gs.branch}"` : "\"\"";
|
|
64
|
+
let block = `clone_or_update "${s.definition.id}" "${gs.repoUrl}" ${branchArg}`;
|
|
65
|
+
if (gs.postCloneCommands && gs.postCloneCommands.length > 0) {
|
|
66
|
+
const cmds = gs.postCloneCommands.map((cmd) => ` (cd "$REPOS_DIR/${s.definition.id}${gs.subdirectory ? `/${gs.subdirectory}` : ""}" && ${cmd})`).join("\n");
|
|
67
|
+
block += `\n${cmds}`;
|
|
68
|
+
}
|
|
69
|
+
return block;
|
|
70
|
+
}).join("\n\n")}
|
|
71
|
+
|
|
72
|
+
echo ""
|
|
73
|
+
ok "All repositories ready."
|
|
74
|
+
`;
|
|
75
|
+
files["scripts/clone-repos.ps1"] = `#Requires -Version 5.1
|
|
76
|
+
<#
|
|
77
|
+
.SYNOPSIS
|
|
78
|
+
Clone/update git-based service repositories.
|
|
79
|
+
Idempotent: clones if missing, pulls if already present.
|
|
80
|
+
#>
|
|
81
|
+
$ErrorActionPreference = "Stop"
|
|
82
|
+
|
|
83
|
+
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
84
|
+
$ProjectDir = Split-Path -Parent $ScriptDir
|
|
85
|
+
$ReposDir = Join-Path $ProjectDir "repos"
|
|
86
|
+
|
|
87
|
+
if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
|
|
88
|
+
Write-Error "git is not installed. Please install git first."
|
|
89
|
+
exit 1
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (-not (Test-Path $ReposDir)) { New-Item -ItemType Directory -Path $ReposDir -Force | Out-Null }
|
|
93
|
+
|
|
94
|
+
function Clone-OrUpdate {
|
|
95
|
+
param(
|
|
96
|
+
[string]$Name,
|
|
97
|
+
[string]$Url,
|
|
98
|
+
[string]$Branch = ""
|
|
99
|
+
)
|
|
100
|
+
$dir = Join-Path $ReposDir $Name
|
|
101
|
+
|
|
102
|
+
if (Test-Path (Join-Path $dir ".git")) {
|
|
103
|
+
Write-Host " Updating $Name..." -ForegroundColor Cyan
|
|
104
|
+
git -C $dir pull --ff-only 2>$null
|
|
105
|
+
if ($LASTEXITCODE -ne 0) { Write-Warning "Could not fast-forward $Name" }
|
|
106
|
+
} else {
|
|
107
|
+
Write-Host " Cloning $Name..." -ForegroundColor Cyan
|
|
108
|
+
if ($Branch) {
|
|
109
|
+
git clone --depth 1 --branch $Branch $Url $dir
|
|
110
|
+
} else {
|
|
111
|
+
git clone --depth 1 $Url $dir
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
Write-Host ""
|
|
117
|
+
Write-Host "Cloning/updating SaaS boilerplate repositories..." -ForegroundColor Cyan
|
|
118
|
+
Write-Host ""
|
|
119
|
+
|
|
120
|
+
${gitServices.map((s) => {
|
|
121
|
+
const gs = s.definition.gitSource;
|
|
122
|
+
const branchArg = gs.branch ? ` -Branch "${gs.branch}"` : "";
|
|
123
|
+
let block = `Clone-OrUpdate -Name "${s.definition.id}" -Url "${gs.repoUrl}"${branchArg}`;
|
|
124
|
+
if (gs.postCloneCommands && gs.postCloneCommands.length > 0) {
|
|
125
|
+
const subdir = gs.subdirectory ? `/${gs.subdirectory}` : "";
|
|
126
|
+
const cmds = gs.postCloneCommands.map((cmd) => `Push-Location "$ReposDir/${s.definition.id}${subdir}"; ${cmd}; Pop-Location`).join("\n");
|
|
127
|
+
block += `\n${cmds}`;
|
|
128
|
+
}
|
|
129
|
+
return block;
|
|
130
|
+
}).join("\n\n")}
|
|
131
|
+
|
|
132
|
+
Write-Host ""
|
|
133
|
+
Write-Host "All repositories ready." -ForegroundColor Green
|
|
134
|
+
`;
|
|
135
|
+
return files;
|
|
136
|
+
}
|
|
137
|
+
//#endregion
|
|
138
|
+
exports.generateCloneScripts = generateCloneScripts;
|
|
139
|
+
|
|
140
|
+
//# sourceMappingURL=clone-repos.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clone-repos.cjs","names":[],"sources":["../../src/generators/clone-repos.ts"],"sourcesContent":["import type { ResolverOutput } from \"../types.js\";\n\n/**\n * Generates clone scripts for git-based services (SaaS boilerplates).\n * Returns empty object if no git-based services exist in the resolved stack.\n */\nexport function generateCloneScripts(resolved: ResolverOutput): Record<string, string> {\n\tconst gitServices = resolved.services.filter(\n\t\t(s) => s.definition.gitSource && s.definition.buildContext,\n\t);\n\n\tif (gitServices.length === 0) return {};\n\n\tconst files: Record<string, string> = {};\n\n\t// ── scripts/clone-repos.sh ─────────────────────────────────────────────\n\n\tconst bashEntries = gitServices\n\t\t.map((s) => {\n\t\t\tconst gs = s.definition.gitSource!;\n\t\t\tconst branchArg = gs.branch ? `\"${gs.branch}\"` : '\"\"';\n\t\t\tlet block = `clone_or_update \"${s.definition.id}\" \"${gs.repoUrl}\" ${branchArg}`;\n\t\t\tif (gs.postCloneCommands && gs.postCloneCommands.length > 0) {\n\t\t\t\tconst cmds = gs.postCloneCommands\n\t\t\t\t\t.map((cmd) => ` (cd \"$REPOS_DIR/${s.definition.id}${gs.subdirectory ? `/${gs.subdirectory}` : \"\"}\" && ${cmd})`)\n\t\t\t\t\t.join(\"\\n\");\n\t\t\t\tblock += `\\n${cmds}`;\n\t\t\t}\n\t\t\treturn block;\n\t\t})\n\t\t.join(\"\\n\\n\");\n\n\tfiles[\"scripts/clone-repos.sh\"] = `#!/usr/bin/env bash\nset -euo pipefail\n\n# ─── Clone/Update Git-Based Service Repositories ────────────────────────────\n# Idempotent: clones if missing, pulls if already present.\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"\\${BASH_SOURCE[0]}\")\" && pwd)\"\nPROJECT_DIR=\"$(dirname \"$SCRIPT_DIR\")\"\nREPOS_DIR=\"$PROJECT_DIR/repos\"\n\n# ── Colour helpers ──────────────────────────────────────────────────────────\nif [ -t 1 ]; then\n GREEN='\\\\033[0;32m'; YELLOW='\\\\033[1;33m'; CYAN='\\\\033[0;36m'; RED='\\\\033[0;31m'; NC='\\\\033[0m'\nelse\n GREEN=''; YELLOW=''; CYAN=''; RED=''; NC=''\nfi\ninfo() { echo -e \"\\${CYAN}i $*\\${NC}\"; }\nok() { echo -e \"\\${GREEN}✓ $*\\${NC}\"; }\nwarn() { echo -e \"\\${YELLOW}⚠ $*\\${NC}\"; }\nerr() { echo -e \"\\${RED}✗ $*\\${NC}\" >&2; }\n\n# ── Check git ───────────────────────────────────────────────────────────────\nif ! command -v git &> /dev/null; then\n err \"git is not installed. Please install git first.\"\n exit 1\nfi\n\nmkdir -p \"$REPOS_DIR\"\n\nclone_or_update() {\n local name=\"$1\" url=\"$2\" branch=\"\\${3:-}\"\n local dir=\"$REPOS_DIR/$name\"\n\n if [ -d \"$dir/.git\" ]; then\n info \"Updating $name...\"\n git -C \"$dir\" pull --ff-only 2>/dev/null || warn \"Could not fast-forward $name (you may have local changes)\"\n else\n info \"Cloning $name...\"\n if [ -n \"$branch\" ]; then\n git clone --depth 1 --branch \"$branch\" \"$url\" \"$dir\"\n else\n git clone --depth 1 \"$url\" \"$dir\"\n fi\n fi\n}\n\necho \"\"\ninfo \"Cloning/updating SaaS boilerplate repositories...\"\necho \"\"\n\n${bashEntries}\n\necho \"\"\nok \"All repositories ready.\"\n`;\n\n\t// ── scripts/clone-repos.ps1 ────────────────────────────────────────────\n\n\tconst psEntries = gitServices\n\t\t.map((s) => {\n\t\t\tconst gs = s.definition.gitSource!;\n\t\t\tconst branchArg = gs.branch ? ` -Branch \"${gs.branch}\"` : \"\";\n\t\t\tlet block = `Clone-OrUpdate -Name \"${s.definition.id}\" -Url \"${gs.repoUrl}\"${branchArg}`;\n\t\t\tif (gs.postCloneCommands && gs.postCloneCommands.length > 0) {\n\t\t\t\tconst subdir = gs.subdirectory ? `/${gs.subdirectory}` : \"\";\n\t\t\t\tconst cmds = gs.postCloneCommands\n\t\t\t\t\t.map((cmd) => `Push-Location \"$ReposDir/${s.definition.id}${subdir}\"; ${cmd}; Pop-Location`)\n\t\t\t\t\t.join(\"\\n\");\n\t\t\t\tblock += `\\n${cmds}`;\n\t\t\t}\n\t\t\treturn block;\n\t\t})\n\t\t.join(\"\\n\\n\");\n\n\tfiles[\"scripts/clone-repos.ps1\"] = `#Requires -Version 5.1\n<#\n.SYNOPSIS\n Clone/update git-based service repositories.\n Idempotent: clones if missing, pulls if already present.\n#>\n$ErrorActionPreference = \"Stop\"\n\n$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path\n$ProjectDir = Split-Path -Parent $ScriptDir\n$ReposDir = Join-Path $ProjectDir \"repos\"\n\nif (-not (Get-Command git -ErrorAction SilentlyContinue)) {\n Write-Error \"git is not installed. Please install git first.\"\n exit 1\n}\n\nif (-not (Test-Path $ReposDir)) { New-Item -ItemType Directory -Path $ReposDir -Force | Out-Null }\n\nfunction Clone-OrUpdate {\n param(\n [string]$Name,\n [string]$Url,\n [string]$Branch = \"\"\n )\n $dir = Join-Path $ReposDir $Name\n\n if (Test-Path (Join-Path $dir \".git\")) {\n Write-Host \" Updating $Name...\" -ForegroundColor Cyan\n git -C $dir pull --ff-only 2>$null\n if ($LASTEXITCODE -ne 0) { Write-Warning \"Could not fast-forward $Name\" }\n } else {\n Write-Host \" Cloning $Name...\" -ForegroundColor Cyan\n if ($Branch) {\n git clone --depth 1 --branch $Branch $Url $dir\n } else {\n git clone --depth 1 $Url $dir\n }\n }\n}\n\nWrite-Host \"\"\nWrite-Host \"Cloning/updating SaaS boilerplate repositories...\" -ForegroundColor Cyan\nWrite-Host \"\"\n\n${psEntries}\n\nWrite-Host \"\"\nWrite-Host \"All repositories ready.\" -ForegroundColor Green\n`;\n\n\treturn files;\n}\n"],"mappings":";;;;;;AAMA,SAAgB,qBAAqB,UAAkD;CACtF,MAAM,cAAc,SAAS,SAAS,QACpC,MAAM,EAAE,WAAW,aAAa,EAAE,WAAW,aAC9C;AAED,KAAI,YAAY,WAAW,EAAG,QAAO,EAAE;CAEvC,MAAM,QAAgC,EAAE;AAmBxC,OAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAfd,YAClB,KAAK,MAAM;EACX,MAAM,KAAK,EAAE,WAAW;EACxB,MAAM,YAAY,GAAG,SAAS,IAAI,GAAG,OAAO,KAAK;EACjD,IAAI,QAAQ,oBAAoB,EAAE,WAAW,GAAG,KAAK,GAAG,QAAQ,IAAI;AACpE,MAAI,GAAG,qBAAqB,GAAG,kBAAkB,SAAS,GAAG;GAC5D,MAAM,OAAO,GAAG,kBACd,KAAK,QAAQ,qBAAqB,EAAE,WAAW,KAAK,GAAG,eAAe,IAAI,GAAG,iBAAiB,GAAG,OAAO,IAAI,GAAG,CAC/G,KAAK,KAAK;AACZ,YAAS,KAAK;;AAEf,SAAO;GACN,CACD,KAAK,OAAO,CAoDD;;;;;AAwBb,OAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAhBjB,YAChB,KAAK,MAAM;EACX,MAAM,KAAK,EAAE,WAAW;EACxB,MAAM,YAAY,GAAG,SAAS,aAAa,GAAG,OAAO,KAAK;EAC1D,IAAI,QAAQ,yBAAyB,EAAE,WAAW,GAAG,UAAU,GAAG,QAAQ,GAAG;AAC7E,MAAI,GAAG,qBAAqB,GAAG,kBAAkB,SAAS,GAAG;GAC5D,MAAM,SAAS,GAAG,eAAe,IAAI,GAAG,iBAAiB;GACzD,MAAM,OAAO,GAAG,kBACd,KAAK,QAAQ,4BAA4B,EAAE,WAAW,KAAK,OAAO,KAAK,IAAI,gBAAgB,CAC3F,KAAK,KAAK;AACZ,YAAS,KAAK;;AAEf,SAAO;GACN,CACD,KAAK,OAAO,CA+CH;;;;;AAMX,QAAO"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ResolverOutput } from "../types.cjs";
|
|
2
|
+
|
|
3
|
+
//#region src/generators/clone-repos.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Generates clone scripts for git-based services (SaaS boilerplates).
|
|
6
|
+
* Returns empty object if no git-based services exist in the resolved stack.
|
|
7
|
+
*/
|
|
8
|
+
declare function generateCloneScripts(resolved: ResolverOutput): Record<string, string>;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { generateCloneScripts };
|
|
11
|
+
//# sourceMappingURL=clone-repos.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clone-repos.d.cts","names":[],"sources":["../../src/generators/clone-repos.ts"],"mappings":";;;;;AAMA;;iBAAgB,oBAAA,CAAqB,QAAA,EAAU,cAAA,GAAiB,MAAA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ResolverOutput } from "../types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/generators/clone-repos.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Generates clone scripts for git-based services (SaaS boilerplates).
|
|
6
|
+
* Returns empty object if no git-based services exist in the resolved stack.
|
|
7
|
+
*/
|
|
8
|
+
declare function generateCloneScripts(resolved: ResolverOutput): Record<string, string>;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { generateCloneScripts };
|
|
11
|
+
//# sourceMappingURL=clone-repos.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clone-repos.d.mts","names":[],"sources":["../../src/generators/clone-repos.ts"],"mappings":";;;;;;AAMA;iBAAgB,oBAAA,CAAqB,QAAA,EAAU,cAAA,GAAiB,MAAA"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
//#region src/generators/clone-repos.ts
|
|
2
|
+
/**
|
|
3
|
+
* Generates clone scripts for git-based services (SaaS boilerplates).
|
|
4
|
+
* Returns empty object if no git-based services exist in the resolved stack.
|
|
5
|
+
*/
|
|
6
|
+
function generateCloneScripts(resolved) {
|
|
7
|
+
const gitServices = resolved.services.filter((s) => s.definition.gitSource && s.definition.buildContext);
|
|
8
|
+
if (gitServices.length === 0) return {};
|
|
9
|
+
const files = {};
|
|
10
|
+
files["scripts/clone-repos.sh"] = `#!/usr/bin/env bash
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
# ─── Clone/Update Git-Based Service Repositories ────────────────────────────
|
|
14
|
+
# Idempotent: clones if missing, pulls if already present.
|
|
15
|
+
|
|
16
|
+
SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
|
|
17
|
+
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
18
|
+
REPOS_DIR="$PROJECT_DIR/repos"
|
|
19
|
+
|
|
20
|
+
# ── Colour helpers ──────────────────────────────────────────────────────────
|
|
21
|
+
if [ -t 1 ]; then
|
|
22
|
+
GREEN='\\033[0;32m'; YELLOW='\\033[1;33m'; CYAN='\\033[0;36m'; RED='\\033[0;31m'; NC='\\033[0m'
|
|
23
|
+
else
|
|
24
|
+
GREEN=''; YELLOW=''; CYAN=''; RED=''; NC=''
|
|
25
|
+
fi
|
|
26
|
+
info() { echo -e "\${CYAN}i $*\${NC}"; }
|
|
27
|
+
ok() { echo -e "\${GREEN}✓ $*\${NC}"; }
|
|
28
|
+
warn() { echo -e "\${YELLOW}⚠ $*\${NC}"; }
|
|
29
|
+
err() { echo -e "\${RED}✗ $*\${NC}" >&2; }
|
|
30
|
+
|
|
31
|
+
# ── Check git ───────────────────────────────────────────────────────────────
|
|
32
|
+
if ! command -v git &> /dev/null; then
|
|
33
|
+
err "git is not installed. Please install git first."
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
mkdir -p "$REPOS_DIR"
|
|
38
|
+
|
|
39
|
+
clone_or_update() {
|
|
40
|
+
local name="$1" url="$2" branch="\${3:-}"
|
|
41
|
+
local dir="$REPOS_DIR/$name"
|
|
42
|
+
|
|
43
|
+
if [ -d "$dir/.git" ]; then
|
|
44
|
+
info "Updating $name..."
|
|
45
|
+
git -C "$dir" pull --ff-only 2>/dev/null || warn "Could not fast-forward $name (you may have local changes)"
|
|
46
|
+
else
|
|
47
|
+
info "Cloning $name..."
|
|
48
|
+
if [ -n "$branch" ]; then
|
|
49
|
+
git clone --depth 1 --branch "$branch" "$url" "$dir"
|
|
50
|
+
else
|
|
51
|
+
git clone --depth 1 "$url" "$dir"
|
|
52
|
+
fi
|
|
53
|
+
fi
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
echo ""
|
|
57
|
+
info "Cloning/updating SaaS boilerplate repositories..."
|
|
58
|
+
echo ""
|
|
59
|
+
|
|
60
|
+
${gitServices.map((s) => {
|
|
61
|
+
const gs = s.definition.gitSource;
|
|
62
|
+
const branchArg = gs.branch ? `"${gs.branch}"` : "\"\"";
|
|
63
|
+
let block = `clone_or_update "${s.definition.id}" "${gs.repoUrl}" ${branchArg}`;
|
|
64
|
+
if (gs.postCloneCommands && gs.postCloneCommands.length > 0) {
|
|
65
|
+
const cmds = gs.postCloneCommands.map((cmd) => ` (cd "$REPOS_DIR/${s.definition.id}${gs.subdirectory ? `/${gs.subdirectory}` : ""}" && ${cmd})`).join("\n");
|
|
66
|
+
block += `\n${cmds}`;
|
|
67
|
+
}
|
|
68
|
+
return block;
|
|
69
|
+
}).join("\n\n")}
|
|
70
|
+
|
|
71
|
+
echo ""
|
|
72
|
+
ok "All repositories ready."
|
|
73
|
+
`;
|
|
74
|
+
files["scripts/clone-repos.ps1"] = `#Requires -Version 5.1
|
|
75
|
+
<#
|
|
76
|
+
.SYNOPSIS
|
|
77
|
+
Clone/update git-based service repositories.
|
|
78
|
+
Idempotent: clones if missing, pulls if already present.
|
|
79
|
+
#>
|
|
80
|
+
$ErrorActionPreference = "Stop"
|
|
81
|
+
|
|
82
|
+
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
83
|
+
$ProjectDir = Split-Path -Parent $ScriptDir
|
|
84
|
+
$ReposDir = Join-Path $ProjectDir "repos"
|
|
85
|
+
|
|
86
|
+
if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
|
|
87
|
+
Write-Error "git is not installed. Please install git first."
|
|
88
|
+
exit 1
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (-not (Test-Path $ReposDir)) { New-Item -ItemType Directory -Path $ReposDir -Force | Out-Null }
|
|
92
|
+
|
|
93
|
+
function Clone-OrUpdate {
|
|
94
|
+
param(
|
|
95
|
+
[string]$Name,
|
|
96
|
+
[string]$Url,
|
|
97
|
+
[string]$Branch = ""
|
|
98
|
+
)
|
|
99
|
+
$dir = Join-Path $ReposDir $Name
|
|
100
|
+
|
|
101
|
+
if (Test-Path (Join-Path $dir ".git")) {
|
|
102
|
+
Write-Host " Updating $Name..." -ForegroundColor Cyan
|
|
103
|
+
git -C $dir pull --ff-only 2>$null
|
|
104
|
+
if ($LASTEXITCODE -ne 0) { Write-Warning "Could not fast-forward $Name" }
|
|
105
|
+
} else {
|
|
106
|
+
Write-Host " Cloning $Name..." -ForegroundColor Cyan
|
|
107
|
+
if ($Branch) {
|
|
108
|
+
git clone --depth 1 --branch $Branch $Url $dir
|
|
109
|
+
} else {
|
|
110
|
+
git clone --depth 1 $Url $dir
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
Write-Host ""
|
|
116
|
+
Write-Host "Cloning/updating SaaS boilerplate repositories..." -ForegroundColor Cyan
|
|
117
|
+
Write-Host ""
|
|
118
|
+
|
|
119
|
+
${gitServices.map((s) => {
|
|
120
|
+
const gs = s.definition.gitSource;
|
|
121
|
+
const branchArg = gs.branch ? ` -Branch "${gs.branch}"` : "";
|
|
122
|
+
let block = `Clone-OrUpdate -Name "${s.definition.id}" -Url "${gs.repoUrl}"${branchArg}`;
|
|
123
|
+
if (gs.postCloneCommands && gs.postCloneCommands.length > 0) {
|
|
124
|
+
const subdir = gs.subdirectory ? `/${gs.subdirectory}` : "";
|
|
125
|
+
const cmds = gs.postCloneCommands.map((cmd) => `Push-Location "$ReposDir/${s.definition.id}${subdir}"; ${cmd}; Pop-Location`).join("\n");
|
|
126
|
+
block += `\n${cmds}`;
|
|
127
|
+
}
|
|
128
|
+
return block;
|
|
129
|
+
}).join("\n\n")}
|
|
130
|
+
|
|
131
|
+
Write-Host ""
|
|
132
|
+
Write-Host "All repositories ready." -ForegroundColor Green
|
|
133
|
+
`;
|
|
134
|
+
return files;
|
|
135
|
+
}
|
|
136
|
+
//#endregion
|
|
137
|
+
export { generateCloneScripts };
|
|
138
|
+
|
|
139
|
+
//# sourceMappingURL=clone-repos.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clone-repos.mjs","names":[],"sources":["../../src/generators/clone-repos.ts"],"sourcesContent":["import type { ResolverOutput } from \"../types.js\";\n\n/**\n * Generates clone scripts for git-based services (SaaS boilerplates).\n * Returns empty object if no git-based services exist in the resolved stack.\n */\nexport function generateCloneScripts(resolved: ResolverOutput): Record<string, string> {\n\tconst gitServices = resolved.services.filter(\n\t\t(s) => s.definition.gitSource && s.definition.buildContext,\n\t);\n\n\tif (gitServices.length === 0) return {};\n\n\tconst files: Record<string, string> = {};\n\n\t// ── scripts/clone-repos.sh ─────────────────────────────────────────────\n\n\tconst bashEntries = gitServices\n\t\t.map((s) => {\n\t\t\tconst gs = s.definition.gitSource!;\n\t\t\tconst branchArg = gs.branch ? `\"${gs.branch}\"` : '\"\"';\n\t\t\tlet block = `clone_or_update \"${s.definition.id}\" \"${gs.repoUrl}\" ${branchArg}`;\n\t\t\tif (gs.postCloneCommands && gs.postCloneCommands.length > 0) {\n\t\t\t\tconst cmds = gs.postCloneCommands\n\t\t\t\t\t.map((cmd) => ` (cd \"$REPOS_DIR/${s.definition.id}${gs.subdirectory ? `/${gs.subdirectory}` : \"\"}\" && ${cmd})`)\n\t\t\t\t\t.join(\"\\n\");\n\t\t\t\tblock += `\\n${cmds}`;\n\t\t\t}\n\t\t\treturn block;\n\t\t})\n\t\t.join(\"\\n\\n\");\n\n\tfiles[\"scripts/clone-repos.sh\"] = `#!/usr/bin/env bash\nset -euo pipefail\n\n# ─── Clone/Update Git-Based Service Repositories ────────────────────────────\n# Idempotent: clones if missing, pulls if already present.\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"\\${BASH_SOURCE[0]}\")\" && pwd)\"\nPROJECT_DIR=\"$(dirname \"$SCRIPT_DIR\")\"\nREPOS_DIR=\"$PROJECT_DIR/repos\"\n\n# ── Colour helpers ──────────────────────────────────────────────────────────\nif [ -t 1 ]; then\n GREEN='\\\\033[0;32m'; YELLOW='\\\\033[1;33m'; CYAN='\\\\033[0;36m'; RED='\\\\033[0;31m'; NC='\\\\033[0m'\nelse\n GREEN=''; YELLOW=''; CYAN=''; RED=''; NC=''\nfi\ninfo() { echo -e \"\\${CYAN}i $*\\${NC}\"; }\nok() { echo -e \"\\${GREEN}✓ $*\\${NC}\"; }\nwarn() { echo -e \"\\${YELLOW}⚠ $*\\${NC}\"; }\nerr() { echo -e \"\\${RED}✗ $*\\${NC}\" >&2; }\n\n# ── Check git ───────────────────────────────────────────────────────────────\nif ! command -v git &> /dev/null; then\n err \"git is not installed. Please install git first.\"\n exit 1\nfi\n\nmkdir -p \"$REPOS_DIR\"\n\nclone_or_update() {\n local name=\"$1\" url=\"$2\" branch=\"\\${3:-}\"\n local dir=\"$REPOS_DIR/$name\"\n\n if [ -d \"$dir/.git\" ]; then\n info \"Updating $name...\"\n git -C \"$dir\" pull --ff-only 2>/dev/null || warn \"Could not fast-forward $name (you may have local changes)\"\n else\n info \"Cloning $name...\"\n if [ -n \"$branch\" ]; then\n git clone --depth 1 --branch \"$branch\" \"$url\" \"$dir\"\n else\n git clone --depth 1 \"$url\" \"$dir\"\n fi\n fi\n}\n\necho \"\"\ninfo \"Cloning/updating SaaS boilerplate repositories...\"\necho \"\"\n\n${bashEntries}\n\necho \"\"\nok \"All repositories ready.\"\n`;\n\n\t// ── scripts/clone-repos.ps1 ────────────────────────────────────────────\n\n\tconst psEntries = gitServices\n\t\t.map((s) => {\n\t\t\tconst gs = s.definition.gitSource!;\n\t\t\tconst branchArg = gs.branch ? ` -Branch \"${gs.branch}\"` : \"\";\n\t\t\tlet block = `Clone-OrUpdate -Name \"${s.definition.id}\" -Url \"${gs.repoUrl}\"${branchArg}`;\n\t\t\tif (gs.postCloneCommands && gs.postCloneCommands.length > 0) {\n\t\t\t\tconst subdir = gs.subdirectory ? `/${gs.subdirectory}` : \"\";\n\t\t\t\tconst cmds = gs.postCloneCommands\n\t\t\t\t\t.map((cmd) => `Push-Location \"$ReposDir/${s.definition.id}${subdir}\"; ${cmd}; Pop-Location`)\n\t\t\t\t\t.join(\"\\n\");\n\t\t\t\tblock += `\\n${cmds}`;\n\t\t\t}\n\t\t\treturn block;\n\t\t})\n\t\t.join(\"\\n\\n\");\n\n\tfiles[\"scripts/clone-repos.ps1\"] = `#Requires -Version 5.1\n<#\n.SYNOPSIS\n Clone/update git-based service repositories.\n Idempotent: clones if missing, pulls if already present.\n#>\n$ErrorActionPreference = \"Stop\"\n\n$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path\n$ProjectDir = Split-Path -Parent $ScriptDir\n$ReposDir = Join-Path $ProjectDir \"repos\"\n\nif (-not (Get-Command git -ErrorAction SilentlyContinue)) {\n Write-Error \"git is not installed. Please install git first.\"\n exit 1\n}\n\nif (-not (Test-Path $ReposDir)) { New-Item -ItemType Directory -Path $ReposDir -Force | Out-Null }\n\nfunction Clone-OrUpdate {\n param(\n [string]$Name,\n [string]$Url,\n [string]$Branch = \"\"\n )\n $dir = Join-Path $ReposDir $Name\n\n if (Test-Path (Join-Path $dir \".git\")) {\n Write-Host \" Updating $Name...\" -ForegroundColor Cyan\n git -C $dir pull --ff-only 2>$null\n if ($LASTEXITCODE -ne 0) { Write-Warning \"Could not fast-forward $Name\" }\n } else {\n Write-Host \" Cloning $Name...\" -ForegroundColor Cyan\n if ($Branch) {\n git clone --depth 1 --branch $Branch $Url $dir\n } else {\n git clone --depth 1 $Url $dir\n }\n }\n}\n\nWrite-Host \"\"\nWrite-Host \"Cloning/updating SaaS boilerplate repositories...\" -ForegroundColor Cyan\nWrite-Host \"\"\n\n${psEntries}\n\nWrite-Host \"\"\nWrite-Host \"All repositories ready.\" -ForegroundColor Green\n`;\n\n\treturn files;\n}\n"],"mappings":";;;;;AAMA,SAAgB,qBAAqB,UAAkD;CACtF,MAAM,cAAc,SAAS,SAAS,QACpC,MAAM,EAAE,WAAW,aAAa,EAAE,WAAW,aAC9C;AAED,KAAI,YAAY,WAAW,EAAG,QAAO,EAAE;CAEvC,MAAM,QAAgC,EAAE;AAmBxC,OAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAfd,YAClB,KAAK,MAAM;EACX,MAAM,KAAK,EAAE,WAAW;EACxB,MAAM,YAAY,GAAG,SAAS,IAAI,GAAG,OAAO,KAAK;EACjD,IAAI,QAAQ,oBAAoB,EAAE,WAAW,GAAG,KAAK,GAAG,QAAQ,IAAI;AACpE,MAAI,GAAG,qBAAqB,GAAG,kBAAkB,SAAS,GAAG;GAC5D,MAAM,OAAO,GAAG,kBACd,KAAK,QAAQ,qBAAqB,EAAE,WAAW,KAAK,GAAG,eAAe,IAAI,GAAG,iBAAiB,GAAG,OAAO,IAAI,GAAG,CAC/G,KAAK,KAAK;AACZ,YAAS,KAAK;;AAEf,SAAO;GACN,CACD,KAAK,OAAO,CAoDD;;;;;AAwBb,OAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAhBjB,YAChB,KAAK,MAAM;EACX,MAAM,KAAK,EAAE,WAAW;EACxB,MAAM,YAAY,GAAG,SAAS,aAAa,GAAG,OAAO,KAAK;EAC1D,IAAI,QAAQ,yBAAyB,EAAE,WAAW,GAAG,UAAU,GAAG,QAAQ,GAAG;AAC7E,MAAI,GAAG,qBAAqB,GAAG,kBAAkB,SAAS,GAAG;GAC5D,MAAM,SAAS,GAAG,eAAe,IAAI,GAAG,iBAAiB;GACzD,MAAM,OAAO,GAAG,kBACd,KAAK,QAAQ,4BAA4B,EAAE,WAAW,KAAK,OAAO,KAAK,IAAI,gBAAgB,CAC3F,KAAK,KAAK;AACZ,YAAS,KAAK;;AAEf,SAAO;GACN,CACD,KAAK,OAAO,CA+CH;;;;;AAMX,QAAO"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
const require_vi_2VT5v0um = require("../vi.2VT5v0um-iVBt6Fyq.cjs");
|
|
2
|
+
const require_generators_clone_repos = require("./clone-repos.cjs");
|
|
3
|
+
//#region src/generators/clone-repos.test.ts
|
|
4
|
+
/** Minimal resolved output with no services */
|
|
5
|
+
function emptyResolved() {
|
|
6
|
+
return {
|
|
7
|
+
services: [],
|
|
8
|
+
addedDependencies: [],
|
|
9
|
+
removedConflicts: [],
|
|
10
|
+
warnings: [],
|
|
11
|
+
errors: [],
|
|
12
|
+
isValid: true,
|
|
13
|
+
estimatedMemoryMB: 0,
|
|
14
|
+
aiProviders: [],
|
|
15
|
+
gsdRuntimes: []
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/** Create a resolved output with image-based services only */
|
|
19
|
+
function imageOnlyResolved() {
|
|
20
|
+
return {
|
|
21
|
+
...emptyResolved(),
|
|
22
|
+
services: [{
|
|
23
|
+
definition: {
|
|
24
|
+
id: "redis",
|
|
25
|
+
name: "Redis",
|
|
26
|
+
description: "In-memory cache",
|
|
27
|
+
category: "database",
|
|
28
|
+
icon: "🔴",
|
|
29
|
+
image: "redis",
|
|
30
|
+
imageTag: "7-alpine",
|
|
31
|
+
ports: [],
|
|
32
|
+
volumes: [],
|
|
33
|
+
environment: [],
|
|
34
|
+
dependsOn: [],
|
|
35
|
+
restartPolicy: "unless-stopped",
|
|
36
|
+
networks: ["openclaw-network"],
|
|
37
|
+
skills: [],
|
|
38
|
+
openclawEnvVars: [],
|
|
39
|
+
docsUrl: "https://redis.io",
|
|
40
|
+
tags: [],
|
|
41
|
+
maturity: "stable",
|
|
42
|
+
requires: [],
|
|
43
|
+
recommends: [],
|
|
44
|
+
conflictsWith: [],
|
|
45
|
+
gpuRequired: false
|
|
46
|
+
},
|
|
47
|
+
addedBy: "user"
|
|
48
|
+
}]
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** Create a resolved output with a git-based service */
|
|
52
|
+
function gitServiceResolved() {
|
|
53
|
+
return {
|
|
54
|
+
...emptyResolved(),
|
|
55
|
+
services: [{
|
|
56
|
+
definition: {
|
|
57
|
+
id: "open-saas",
|
|
58
|
+
name: "Open SaaS",
|
|
59
|
+
description: "SaaS boilerplate",
|
|
60
|
+
category: "saas-boilerplate",
|
|
61
|
+
icon: "🚀",
|
|
62
|
+
gitSource: {
|
|
63
|
+
repoUrl: "https://github.com/wasp-lang/open-saas.git",
|
|
64
|
+
branch: "main",
|
|
65
|
+
subdirectory: "template",
|
|
66
|
+
postCloneCommands: ["cp .env.example .env"]
|
|
67
|
+
},
|
|
68
|
+
buildContext: {
|
|
69
|
+
dockerfile: "Dockerfile",
|
|
70
|
+
context: "."
|
|
71
|
+
},
|
|
72
|
+
ports: [{
|
|
73
|
+
host: 3100,
|
|
74
|
+
container: 3e3,
|
|
75
|
+
description: "Web app",
|
|
76
|
+
exposed: true
|
|
77
|
+
}],
|
|
78
|
+
volumes: [],
|
|
79
|
+
environment: [],
|
|
80
|
+
dependsOn: [],
|
|
81
|
+
restartPolicy: "unless-stopped",
|
|
82
|
+
networks: ["openclaw-network"],
|
|
83
|
+
skills: [],
|
|
84
|
+
openclawEnvVars: [],
|
|
85
|
+
docsUrl: "https://opensaas.sh/docs",
|
|
86
|
+
tags: [],
|
|
87
|
+
maturity: "beta",
|
|
88
|
+
requires: [],
|
|
89
|
+
recommends: [],
|
|
90
|
+
conflictsWith: [],
|
|
91
|
+
gpuRequired: false
|
|
92
|
+
},
|
|
93
|
+
addedBy: "user"
|
|
94
|
+
}]
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
require_vi_2VT5v0um.describe("generateCloneScripts", () => {
|
|
98
|
+
require_vi_2VT5v0um.it("returns empty object when no git-based services exist", () => {
|
|
99
|
+
require_vi_2VT5v0um.globalExpect(require_generators_clone_repos.generateCloneScripts(emptyResolved())).toEqual({});
|
|
100
|
+
});
|
|
101
|
+
require_vi_2VT5v0um.it("returns empty object when only image-based services exist", () => {
|
|
102
|
+
require_vi_2VT5v0um.globalExpect(require_generators_clone_repos.generateCloneScripts(imageOnlyResolved())).toEqual({});
|
|
103
|
+
});
|
|
104
|
+
require_vi_2VT5v0um.it("generates bash and PowerShell scripts for git-based services", () => {
|
|
105
|
+
const result = require_generators_clone_repos.generateCloneScripts(gitServiceResolved());
|
|
106
|
+
require_vi_2VT5v0um.globalExpect(Object.keys(result)).toHaveLength(2);
|
|
107
|
+
require_vi_2VT5v0um.globalExpect(result).toHaveProperty("scripts/clone-repos.sh");
|
|
108
|
+
require_vi_2VT5v0um.globalExpect(result).toHaveProperty("scripts/clone-repos.ps1");
|
|
109
|
+
});
|
|
110
|
+
require_vi_2VT5v0um.it("bash script contains clone command with repo URL", () => {
|
|
111
|
+
const bash = require_generators_clone_repos.generateCloneScripts(gitServiceResolved())["scripts/clone-repos.sh"];
|
|
112
|
+
require_vi_2VT5v0um.globalExpect(bash).toContain("https://github.com/wasp-lang/open-saas.git");
|
|
113
|
+
require_vi_2VT5v0um.globalExpect(bash).toContain("\"open-saas\"");
|
|
114
|
+
require_vi_2VT5v0um.globalExpect(bash).toContain("\"main\"");
|
|
115
|
+
});
|
|
116
|
+
require_vi_2VT5v0um.it("bash script includes postCloneCommands", () => {
|
|
117
|
+
const bash = require_generators_clone_repos.generateCloneScripts(gitServiceResolved())["scripts/clone-repos.sh"];
|
|
118
|
+
require_vi_2VT5v0um.globalExpect(bash).toContain("cp .env.example .env");
|
|
119
|
+
});
|
|
120
|
+
require_vi_2VT5v0um.it("bash script includes git check and idempotency logic", () => {
|
|
121
|
+
const bash = require_generators_clone_repos.generateCloneScripts(gitServiceResolved())["scripts/clone-repos.sh"];
|
|
122
|
+
require_vi_2VT5v0um.globalExpect(bash).toContain("command -v git");
|
|
123
|
+
require_vi_2VT5v0um.globalExpect(bash).toContain("clone_or_update");
|
|
124
|
+
require_vi_2VT5v0um.globalExpect(bash).toContain("pull --ff-only");
|
|
125
|
+
require_vi_2VT5v0um.globalExpect(bash).toContain("git clone --depth 1");
|
|
126
|
+
});
|
|
127
|
+
require_vi_2VT5v0um.it("PowerShell script contains clone command with repo URL", () => {
|
|
128
|
+
const ps = require_generators_clone_repos.generateCloneScripts(gitServiceResolved())["scripts/clone-repos.ps1"];
|
|
129
|
+
require_vi_2VT5v0um.globalExpect(ps).toContain("https://github.com/wasp-lang/open-saas.git");
|
|
130
|
+
require_vi_2VT5v0um.globalExpect(ps).toContain("\"open-saas\"");
|
|
131
|
+
require_vi_2VT5v0um.globalExpect(ps).toContain("Clone-OrUpdate");
|
|
132
|
+
});
|
|
133
|
+
require_vi_2VT5v0um.it("bash script is executable (starts with shebang)", () => {
|
|
134
|
+
const bash = require_generators_clone_repos.generateCloneScripts(gitServiceResolved())["scripts/clone-repos.sh"];
|
|
135
|
+
require_vi_2VT5v0um.globalExpect(bash).toMatch(/^#!/);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
//#endregion
|
|
139
|
+
|
|
140
|
+
//# sourceMappingURL=clone-repos.test.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clone-repos.test.cjs","names":["describe","generateCloneScripts"],"sources":["../../src/generators/clone-repos.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { generateCloneScripts } from \"./clone-repos.js\";\nimport type { ResolverOutput } from \"../types.js\";\n\n/** Minimal resolved output with no services */\nfunction emptyResolved(): ResolverOutput {\n\treturn {\n\t\tservices: [],\n\t\taddedDependencies: [],\n\t\tremovedConflicts: [],\n\t\twarnings: [],\n\t\terrors: [],\n\t\tisValid: true,\n\t\testimatedMemoryMB: 0,\n\t\taiProviders: [],\n\t\tgsdRuntimes: [],\n\t};\n}\n\n/** Create a resolved output with image-based services only */\nfunction imageOnlyResolved(): ResolverOutput {\n\treturn {\n\t\t...emptyResolved(),\n\t\tservices: [\n\t\t\t{\n\t\t\t\tdefinition: {\n\t\t\t\t\tid: \"redis\",\n\t\t\t\t\tname: \"Redis\",\n\t\t\t\t\tdescription: \"In-memory cache\",\n\t\t\t\t\tcategory: \"database\",\n\t\t\t\t\ticon: \"🔴\",\n\t\t\t\t\timage: \"redis\",\n\t\t\t\t\timageTag: \"7-alpine\",\n\t\t\t\t\tports: [],\n\t\t\t\t\tvolumes: [],\n\t\t\t\t\tenvironment: [],\n\t\t\t\t\tdependsOn: [],\n\t\t\t\t\trestartPolicy: \"unless-stopped\",\n\t\t\t\t\tnetworks: [\"openclaw-network\"],\n\t\t\t\t\tskills: [],\n\t\t\t\t\topenclawEnvVars: [],\n\t\t\t\t\tdocsUrl: \"https://redis.io\",\n\t\t\t\t\ttags: [],\n\t\t\t\t\tmaturity: \"stable\",\n\t\t\t\t\trequires: [],\n\t\t\t\t\trecommends: [],\n\t\t\t\t\tconflictsWith: [],\n\t\t\t\t\tgpuRequired: false,\n\t\t\t\t},\n\t\t\t\taddedBy: \"user\",\n\t\t\t},\n\t\t],\n\t};\n}\n\n/** Create a resolved output with a git-based service */\nfunction gitServiceResolved(): ResolverOutput {\n\treturn {\n\t\t...emptyResolved(),\n\t\tservices: [\n\t\t\t{\n\t\t\t\tdefinition: {\n\t\t\t\t\tid: \"open-saas\",\n\t\t\t\t\tname: \"Open SaaS\",\n\t\t\t\t\tdescription: \"SaaS boilerplate\",\n\t\t\t\t\tcategory: \"saas-boilerplate\",\n\t\t\t\t\ticon: \"🚀\",\n\t\t\t\t\tgitSource: {\n\t\t\t\t\t\trepoUrl: \"https://github.com/wasp-lang/open-saas.git\",\n\t\t\t\t\t\tbranch: \"main\",\n\t\t\t\t\t\tsubdirectory: \"template\",\n\t\t\t\t\t\tpostCloneCommands: [\"cp .env.example .env\"],\n\t\t\t\t\t},\n\t\t\t\t\tbuildContext: {\n\t\t\t\t\t\tdockerfile: \"Dockerfile\",\n\t\t\t\t\t\tcontext: \".\",\n\t\t\t\t\t},\n\t\t\t\t\tports: [{ host: 3100, container: 3000, description: \"Web app\", exposed: true }],\n\t\t\t\t\tvolumes: [],\n\t\t\t\t\tenvironment: [],\n\t\t\t\t\tdependsOn: [],\n\t\t\t\t\trestartPolicy: \"unless-stopped\",\n\t\t\t\t\tnetworks: [\"openclaw-network\"],\n\t\t\t\t\tskills: [],\n\t\t\t\t\topenclawEnvVars: [],\n\t\t\t\t\tdocsUrl: \"https://opensaas.sh/docs\",\n\t\t\t\t\ttags: [],\n\t\t\t\t\tmaturity: \"beta\",\n\t\t\t\t\trequires: [],\n\t\t\t\t\trecommends: [],\n\t\t\t\t\tconflictsWith: [],\n\t\t\t\t\tgpuRequired: false,\n\t\t\t\t},\n\t\t\t\taddedBy: \"user\",\n\t\t\t},\n\t\t],\n\t};\n}\n\ndescribe(\"generateCloneScripts\", () => {\n\tit(\"returns empty object when no git-based services exist\", () => {\n\t\tconst result = generateCloneScripts(emptyResolved());\n\t\texpect(result).toEqual({});\n\t});\n\n\tit(\"returns empty object when only image-based services exist\", () => {\n\t\tconst result = generateCloneScripts(imageOnlyResolved());\n\t\texpect(result).toEqual({});\n\t});\n\n\tit(\"generates bash and PowerShell scripts for git-based services\", () => {\n\t\tconst result = generateCloneScripts(gitServiceResolved());\n\t\texpect(Object.keys(result)).toHaveLength(2);\n\t\texpect(result).toHaveProperty(\"scripts/clone-repos.sh\");\n\t\texpect(result).toHaveProperty(\"scripts/clone-repos.ps1\");\n\t});\n\n\tit(\"bash script contains clone command with repo URL\", () => {\n\t\tconst result = generateCloneScripts(gitServiceResolved());\n\t\tconst bash = result[\"scripts/clone-repos.sh\"];\n\t\texpect(bash).toContain(\"https://github.com/wasp-lang/open-saas.git\");\n\t\texpect(bash).toContain('\"open-saas\"');\n\t\texpect(bash).toContain('\"main\"');\n\t});\n\n\tit(\"bash script includes postCloneCommands\", () => {\n\t\tconst result = generateCloneScripts(gitServiceResolved());\n\t\tconst bash = result[\"scripts/clone-repos.sh\"];\n\t\texpect(bash).toContain(\"cp .env.example .env\");\n\t});\n\n\tit(\"bash script includes git check and idempotency logic\", () => {\n\t\tconst result = generateCloneScripts(gitServiceResolved());\n\t\tconst bash = result[\"scripts/clone-repos.sh\"];\n\t\texpect(bash).toContain(\"command -v git\");\n\t\texpect(bash).toContain(\"clone_or_update\");\n\t\texpect(bash).toContain(\"pull --ff-only\");\n\t\texpect(bash).toContain(\"git clone --depth 1\");\n\t});\n\n\tit(\"PowerShell script contains clone command with repo URL\", () => {\n\t\tconst result = generateCloneScripts(gitServiceResolved());\n\t\tconst ps = result[\"scripts/clone-repos.ps1\"];\n\t\texpect(ps).toContain(\"https://github.com/wasp-lang/open-saas.git\");\n\t\texpect(ps).toContain('\"open-saas\"');\n\t\texpect(ps).toContain(\"Clone-OrUpdate\");\n\t});\n\n\tit(\"bash script is executable (starts with shebang)\", () => {\n\t\tconst result = generateCloneScripts(gitServiceResolved());\n\t\tconst bash = result[\"scripts/clone-repos.sh\"];\n\t\texpect(bash).toMatch(/^#!/);\n\t});\n});\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|