@smi-digital/create-smi-app 1.0.5 → 2.0.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/dist/index.js CHANGED
@@ -530,18 +530,40 @@ async function createIntegrations(options, projectRoot, templatesDir) {
530
530
  integrationKey
531
531
  );
532
532
  const integrationRoot = join4(templatesDir, "integrations", integrationKey);
533
- const filesToApply = [integrationConfig.workflows.prValidation];
534
- if (options.configureCd && integrationConfig.workflows.deploy) {
535
- filesToApply.push(integrationConfig.workflows.deploy);
533
+ const filesToApply = [];
534
+ if (integrationConfig.workflows) {
535
+ if (integrationConfig.workflows.prValidation)
536
+ filesToApply.push(integrationConfig.workflows.prValidation);
537
+ if (options.configureCd && integrationConfig.workflows.deploy)
538
+ filesToApply.push(integrationConfig.workflows.deploy);
536
539
  }
537
- const applyFile = async (file) => runStep(
538
- `Adding ${file.target}`,
539
- async () => copyTemplateFile(
540
+ if (integrationConfig.files) {
541
+ for (const file of integrationConfig.files) {
542
+ if (!file.condition || file.condition === options.integrationInputs?.astro_mode) {
543
+ filesToApply.push(file);
544
+ }
545
+ }
546
+ }
547
+ if (options.configureCd && integrationConfig.cdFiles) {
548
+ for (const file of integrationConfig.cdFiles) {
549
+ if (!file.condition || file.condition === options.integrationInputs?.astro_mode) {
550
+ filesToApply.push(file);
551
+ }
552
+ }
553
+ }
554
+ const applyFile = async (file) => runStep(`Adding ${file.target}`, async () => {
555
+ const templateValues = {
556
+ ...options.integrationInputs,
557
+ // eslint-disable-next-line camelcase
558
+ astro_port: "4321"
559
+ // Hardcoded to SSR port
560
+ };
561
+ return copyTemplateFile(
540
562
  join4(integrationRoot, file.template),
541
563
  join4(projectRoot, file.target),
542
- options.integrationInputs
543
- )
544
- );
564
+ templateValues
565
+ );
566
+ });
545
567
  const applySequentially = async (index) => {
546
568
  const file = filesToApply[index];
547
569
  if (!file) {
@@ -580,6 +602,10 @@ function getAppTargets(options) {
580
602
  ];
581
603
  }
582
604
  async function createProjectStructure(options, projectRoot) {
605
+ if (options.projectType === "singleProject" && "framework" in options && asFramework(options.framework) === "none") {
606
+ await mkdir2(join5(projectRoot, "src"), { recursive: true });
607
+ return;
608
+ }
583
609
  if (options.projectType !== "monorepo") {
584
610
  return;
585
611
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smi-digital/create-smi-app",
3
- "version": "1.0.5",
3
+ "version": "2.0.0",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -1,6 +1,3 @@
1
- # ==============================================================================
2
- # COPY THIS FILE TO: .github/workflows/deploy.yml in your project repository
3
- # ==============================================================================
4
1
  name: Deploy Production
5
2
 
6
3
  on:
@@ -11,16 +8,17 @@ on:
11
8
 
12
9
  jobs:
13
10
  call-central-deployer:
14
- uses: smi-digital/cd-library/.github/workflows/deploy-strapi-astro.yml@main
11
+ uses: smi-digital/cd-library/.github/workflows/deploy-ansible.yml@main
15
12
  with:
16
- # Replace the following Variables with the actual ones
17
13
  project_domain: __PROJECT_DOMAIN__
18
- frontend_domain: __FRONTEND_DOMAIN__
19
14
  backend_domain: __BACKEND_DOMAIN__
20
15
  app_name: __APP_NAME__
21
- include_www: __INCLUDE_WWW__
16
+ image_tag: latest
22
17
  secrets:
23
- # These secrets MUST be added to your repository settings!
24
- # Go to Settings > Secrets and variables > Actions
25
- ASTRO_FRONTEND_ENV: ${{ secrets.ASTRO_FRONTEND_ENV }}
26
- STRAPI_BACKEND_ENV: ${{ secrets.STRAPI_BACKEND_ENV }}
18
+ # These secrets must be configured at the Organization or Repository level:
19
+ SERVER_SSH_KEY: ${{ secrets.SERVER_SSH_KEY }}
20
+ SERVER_IP: ${{ secrets.SERVER_IP }}
21
+ SERVER_DEPLOY_USER: ${{ secrets.SERVER_DEPLOY_USER }}
22
+ VAULT_PASSWORD: ${{ secrets.VAULT_PASSWORD }}
23
+ ZOT_USERNAME: ${{ secrets.ZOT_USERNAME }}
24
+ ZOT_PASSWORD: ${{ secrets.ZOT_PASSWORD }}
@@ -0,0 +1,23 @@
1
+ # ========================================================
2
+ # EXAMPLE: Dockerfile for Strapi Backend
3
+ # Place this in your /backend folder
4
+ # ========================================================
5
+
6
+ FROM node:18-alpine
7
+ # Installing libvips-dev for sharp Compatibility
8
+ RUN apk update && apk add --no-cache build-base gcc autoconf automake zlib-dev libpng-dev vips-dev > /dev/null 2>&1
9
+ ARG NODE_ENV=production
10
+ ENV NODE_ENV=${NODE_ENV}
11
+
12
+ WORKDIR /opt/
13
+ COPY package.json ./
14
+ RUN npm install -g node-gyp
15
+ RUN npm config set fetch-retry-maxtimeout 600000 -g && npm install --only=production
16
+ ENV PATH /opt/node_modules/.bin:$PATH
17
+
18
+ WORKDIR /opt/app
19
+ COPY . .
20
+ RUN npm run build
21
+
22
+ EXPOSE 1337
23
+ CMD ["npm", "run", "start"]
@@ -0,0 +1,31 @@
1
+ # ========================================================
2
+ # EXAMPLE: Dockerfile for Astro SSR (Server-Side Rendering)
3
+ # Place this in your /frontend folder and name it 'Dockerfile'
4
+ # ========================================================
5
+
6
+ # Stage 1: Build with Bun
7
+ FROM oven/bun:1 as builder
8
+ WORKDIR /app
9
+ COPY package.json bun.lock ./
10
+ RUN bun install
11
+ COPY . .
12
+ RUN bun run build
13
+
14
+ # Stage 2: Serve dynamic application using Bun
15
+ FROM oven/bun:1-alpine
16
+ WORKDIR /app
17
+
18
+ # Copy the built SSR server bundle and node_modules
19
+ COPY --from=builder /app/node_modules ./node_modules
20
+ COPY --from=builder /app/dist ./dist
21
+ COPY --from=builder /app/package.json ./
22
+
23
+ # Expose port 4321 (Matches the 'frontend_port' logic in Ansible for SSR)
24
+ EXPOSE 4321
25
+
26
+ # Default environment variables
27
+ ENV HOST=0.0.0.0
28
+ ENV PORT=4321
29
+
30
+ # Start the server (Adjust entrypoint depending on your Astro Node/Bun adapter)
31
+ CMD ["bun", "run", "./dist/server/entry.mjs"]
@@ -0,0 +1,41 @@
1
+ export default {
2
+ register(/*{ strapi }*/) {},
3
+
4
+ async bootstrap({ strapi }) {
5
+ // 1. Find the Public Role
6
+ const publicRole = await strapi
7
+ .query('plugin::users-permissions.role')
8
+ .findOne({ where: { type: 'public' } });
9
+
10
+ if (!publicRole) return;
11
+
12
+ // 2. Dynamically find all Custom APIs created by the developer
13
+ // strapi.api contains all user-generated content types (ignoring system plugins)
14
+ const customApis = Object.keys(strapi.api);
15
+ const permissionsToOpen: string[] = [];
16
+
17
+ // 3. Build the permission strings (find and findOne) for every custom API
18
+ for (const apiName of customApis) {
19
+ permissionsToOpen.push(`api::${apiName}.${apiName}.find`);
20
+ permissionsToOpen.push(`api::${apiName}.${apiName}.findOne`);
21
+ }
22
+
23
+ // 4. Grant the permissions programmatically
24
+ for (const action of permissionsToOpen) {
25
+ const existingPermission = await strapi
26
+ .query('plugin::users-permissions.permission')
27
+ .findOne({ where: { role: publicRole.id, action } });
28
+
29
+ // If the permission doesn't exist yet, create it
30
+ if (!existingPermission) {
31
+ await strapi.query('plugin::users-permissions.permission').create({
32
+ data: {
33
+ action: action,
34
+ role: publicRole.id,
35
+ },
36
+ });
37
+ console.log(`[BOOTSTRAP] Automatically granted public access to: ${action}`);
38
+ }
39
+ }
40
+ },
41
+ };
@@ -0,0 +1,52 @@
1
+ services:
2
+ __APP_NAME__-strapi:
3
+ container_name: __APP_NAME__-strapi
4
+ # The image is built in CI and pushed to Zot
5
+ image: registry.smi-digital.com/__APP_NAME__-strapi:${IMAGE_TAG:-latest}
6
+ restart: unless-stopped
7
+ env_file:
8
+ - ./backend.env
9
+ volumes:
10
+ - ./data/uploads:/opt/app/public/uploads
11
+ - ./data/database:/opt/app/.tmp
12
+ networks:
13
+ - agency_shared_network
14
+ healthcheck:
15
+ test: ["CMD", "curl", "-f", "http://localhost:1337/admin"]
16
+ interval: 10s
17
+ timeout: 5s
18
+ retries: 15
19
+ labels:
20
+ - "traefik.enable=true"
21
+ # Route traffic from your backend domain to this container
22
+ - "traefik.http.routers.__APP_NAME__-strapi.rule=Host(`__BACKEND_DOMAIN__`)"
23
+ # Request an SSL certificate from the global Let's Encrypt resolver
24
+ - "traefik.http.routers.__APP_NAME__-strapi.tls.certresolver=letsencrypt"
25
+ # Tell Traefik which port to send traffic to inside the container
26
+ - "traefik.http.services.__APP_NAME__-strapi.loadbalancer.server.port=1337"
27
+ # Strapi requires larger body sizes for media uploads (25MB)
28
+ - "traefik.http.middlewares.__APP_NAME__-strapi-limit.buffering.maxRequestBodyBytes=25000000"
29
+ - "traefik.http.routers.__APP_NAME__-strapi.middlewares=__APP_NAME__-strapi-limit"
30
+
31
+ __APP_NAME__-astro:
32
+ container_name: __APP_NAME__-astro
33
+ image: registry.smi-digital.com/__APP_NAME__-astro:${IMAGE_TAG:-latest}
34
+ restart: unless-stopped
35
+ env_file:
36
+ - ./frontend.env
37
+ networks:
38
+ - agency_shared_network
39
+ depends_on:
40
+ __APP_NAME__-strapi:
41
+ condition: service_healthy
42
+ labels:
43
+ - "traefik.enable=true"
44
+ # Listen on both www and non-www (If include_www is selected during setup, you can manually adjust this)
45
+ - "traefik.http.routers.__APP_NAME__-astro.rule=Host(`__PROJECT_DOMAIN__`) || Host(`www.__PROJECT_DOMAIN__`)"
46
+ - "traefik.http.routers.__APP_NAME__-astro.tls.certresolver=letsencrypt"
47
+ # Port 4321 for Bun/SSR, Port 80 if pure SSG Nginx container
48
+ - "traefik.http.services.__APP_NAME__-astro.loadbalancer.server.port=__ASTRO_PORT__"
49
+
50
+ networks:
51
+ agency_shared_network:
52
+ external: true
@@ -0,0 +1,16 @@
1
+ // This middleware enforces Stale-While-Revalidate (SWR) caching for SSR pages.
2
+ // It ensures pages are fast for users while updating in the background.
3
+ import { defineMiddleware } from 'astro:middleware';
4
+
5
+ export const onRequest = defineMiddleware(async (context, next) => {
6
+ const response = await next();
7
+
8
+ // Only apply SWR caching to HTML responses (SSR pages)
9
+ // We do not want to aggressively cache API routes or dynamic forms
10
+ if (response.headers.get('content-type')?.includes('text/html')) {
11
+ // Cache for 10 seconds. For the next 1 hour (3600s), serve stale cache while fetching new data.
12
+ response.headers.set('Cache-Control', 'public, s-maxage=10, stale-while-revalidate=3600');
13
+ }
14
+
15
+ return response;
16
+ });
@@ -15,13 +15,6 @@
15
15
  "default": "my-new-client.com",
16
16
  "required": true
17
17
  },
18
- {
19
- "key": "frontend_domain",
20
- "message": "What is the frontend domain?",
21
- "type": "input",
22
- "default": "my-new-client.com",
23
- "required": true
24
- },
25
18
  {
26
19
  "key": "backend_domain",
27
20
  "message": "What is the backend domain?",
@@ -53,5 +46,29 @@
53
46
  "template": ".github/workflows/deploy.yml.template",
54
47
  "target": ".github/workflows/deploy.yml"
55
48
  }
56
- }
49
+ },
50
+ "files": [
51
+ {
52
+ "template": "backend/src/index.ts.template",
53
+ "target": "backend/src/index.ts"
54
+ }
55
+ ],
56
+ "cdFiles": [
57
+ {
58
+ "template": "docker-compose.yml.template",
59
+ "target": "docker-compose.yml"
60
+ },
61
+ {
62
+ "template": "Dockerfile.backend.template",
63
+ "target": "backend/Dockerfile"
64
+ },
65
+ {
66
+ "template": "Dockerfile.frontend.ssr.template",
67
+ "target": "frontend/Dockerfile"
68
+ },
69
+ {
70
+ "template": "frontend/src/middleware.ts.template",
71
+ "target": "frontend/src/middleware.ts"
72
+ }
73
+ ]
57
74
  }