@smi-digital/create-smi-app 2.7.4 → 2.7.6
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 +36 -12
- package/package.json +1 -1
- package/templates/integrations/strapi-astro/databases.yml.template +15 -0
- package/templates/integrations/strapi-astro/docker-compose.yml.template +8 -3
- package/templates/integrations/strapi-astro/integration.config.json +7 -2
- package/templates/integrations/strapi-astro/production.backend.env.template +11 -0
package/dist/index.js
CHANGED
|
@@ -156,6 +156,9 @@ var smiTheme = {
|
|
|
156
156
|
cursor: primary12("\u276F")
|
|
157
157
|
}
|
|
158
158
|
};
|
|
159
|
+
function toSlug(value) {
|
|
160
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/gv, "-").replace(/^-+|-+$/gv, "");
|
|
161
|
+
}
|
|
159
162
|
async function askIntegrationInputs(inputConfig, index = 0, answers = {}) {
|
|
160
163
|
const current = inputConfig[index];
|
|
161
164
|
if (!current) {
|
|
@@ -178,9 +181,17 @@ async function askIntegrationInputs(inputConfig, index = 0, answers = {}) {
|
|
|
178
181
|
const inputConfigData = {
|
|
179
182
|
message: current.message,
|
|
180
183
|
validate(value) {
|
|
181
|
-
|
|
184
|
+
const trimmed = value.trim();
|
|
185
|
+
if (current.required && trimmed.length === 0) {
|
|
182
186
|
return "This value is required.";
|
|
183
187
|
}
|
|
188
|
+
if (current.validate === "slug" && trimmed.length > 0) {
|
|
189
|
+
const isSlug = /^[a-z0-9]+(?:-[a-z0-9]+)*$/v.test(trimmed);
|
|
190
|
+
if (!isSlug) {
|
|
191
|
+
const suggestion = toSlug(trimmed);
|
|
192
|
+
return `Must be a lowercase slug \u2014 letters, digits and single hyphens only (no dots, uppercase or spaces).${suggestion ? ` Try: ${suggestion}` : ""}`;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
184
195
|
return true;
|
|
185
196
|
},
|
|
186
197
|
theme: smiTheme
|
|
@@ -458,7 +469,7 @@ ${stderrTail}` : "";
|
|
|
458
469
|
|
|
459
470
|
// src/modules/scaffoldActions/createApps.ts
|
|
460
471
|
import { join as join4 } from "path";
|
|
461
|
-
import {
|
|
472
|
+
import { writeFile as writeFile2 } from "fs/promises";
|
|
462
473
|
async function createFrameworkApp(projectRoot, target) {
|
|
463
474
|
const generator = FRAMEWORK_GENERATORS[target.framework];
|
|
464
475
|
if (!generator) {
|
|
@@ -487,14 +498,20 @@ async function createApps(projectRoot, targets) {
|
|
|
487
498
|
target.directory,
|
|
488
499
|
"config/plugins.ts"
|
|
489
500
|
);
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
501
|
+
await writeFile2(pluginsPath, "export default () => ({});\n");
|
|
502
|
+
} catch (error) {
|
|
503
|
+
console.warn(
|
|
504
|
+
`Could not normalize config/plugins.ts: ${error instanceof Error ? error.message : String(error)}`
|
|
494
505
|
);
|
|
495
|
-
await writeFile2(pluginsPath, pluginsContent);
|
|
496
|
-
} catch {
|
|
497
506
|
}
|
|
507
|
+
await runStep(
|
|
508
|
+
"Adding Postgres driver (pg) for production",
|
|
509
|
+
async () => runCommandQuiet(
|
|
510
|
+
"npm",
|
|
511
|
+
["install", "pg"],
|
|
512
|
+
join4(projectRoot, target.directory)
|
|
513
|
+
)
|
|
514
|
+
);
|
|
498
515
|
}
|
|
499
516
|
await runSequentially(index + 1);
|
|
500
517
|
};
|
|
@@ -502,7 +519,7 @@ async function createApps(projectRoot, targets) {
|
|
|
502
519
|
}
|
|
503
520
|
|
|
504
521
|
// src/modules/scaffoldActions/createIntegrations.ts
|
|
505
|
-
import { mkdir, readFile as
|
|
522
|
+
import { mkdir, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
506
523
|
import { dirname as dirname2, join as join5 } from "path";
|
|
507
524
|
import { randomBytes } from "crypto";
|
|
508
525
|
function toTemplateToken(key) {
|
|
@@ -517,7 +534,7 @@ function applyTemplateVariables(content, values) {
|
|
|
517
534
|
return result;
|
|
518
535
|
}
|
|
519
536
|
async function copyTemplateFile(sourcePath, targetPath, values) {
|
|
520
|
-
const rawContent = await
|
|
537
|
+
const rawContent = await readFile3(sourcePath, "utf8");
|
|
521
538
|
const content = applyTemplateVariables(rawContent, values);
|
|
522
539
|
await mkdir(dirname2(targetPath), { recursive: true });
|
|
523
540
|
await writeFile3(targetPath, content, "utf8");
|
|
@@ -581,7 +598,14 @@ async function createIntegrations(options, projectRoot, templatesDir) {
|
|
|
581
598
|
api_token_salt: generateSecret(),
|
|
582
599
|
admin_jwt_secret: generateSecret(),
|
|
583
600
|
transfer_token_salt: generateSecret(),
|
|
584
|
-
jwt_secret: generateSecret()
|
|
601
|
+
jwt_secret: generateSecret(),
|
|
602
|
+
// Postgres identifier derived from app_name (hyphens -> underscores), so
|
|
603
|
+
// the role/database name needs no quoting.
|
|
604
|
+
db_name: String(options.integrationInputs?.app_name ?? "").replaceAll(
|
|
605
|
+
"-",
|
|
606
|
+
"_"
|
|
607
|
+
),
|
|
608
|
+
db_password: generateSecret()
|
|
585
609
|
/* eslint-enable camelcase */
|
|
586
610
|
};
|
|
587
611
|
return copyTemplateFile(
|
|
@@ -604,7 +628,7 @@ async function createIntegrations(options, projectRoot, templatesDir) {
|
|
|
604
628
|
const frontendDir = join5(projectRoot, "frontend");
|
|
605
629
|
await runCommandQuiet("npx", ["astro", "add", "node", "-y"], frontendDir);
|
|
606
630
|
const astroConfigPath = join5(frontendDir, "astro.config.mjs");
|
|
607
|
-
let astroConfig = await
|
|
631
|
+
let astroConfig = await readFile3(astroConfigPath, "utf8");
|
|
608
632
|
astroConfig = astroConfig.replace(
|
|
609
633
|
/export default defineConfig\s*\(\s*\{/v,
|
|
610
634
|
"export default defineConfig({\n output: 'server',"
|
package/package.json
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Databases this project needs on the shared Postgres instance.
|
|
2
|
+
# The deploy provisions each one (login role + database) automatically — no
|
|
3
|
+
# manual step, no SSH. This file is the GitOps source of truth for the project's
|
|
4
|
+
# databases; passwords live (encrypted) in production.backend.env.
|
|
5
|
+
#
|
|
6
|
+
# - name: the database AND login-role name (Postgres-safe identifier)
|
|
7
|
+
# - password_env: the variable in production.backend.env holding its password
|
|
8
|
+
#
|
|
9
|
+
# To add an app-managed database (e.g. a separate dataset the site reads), add an
|
|
10
|
+
# entry here and its connection vars to production.backend.env:
|
|
11
|
+
# - name: my_dataset
|
|
12
|
+
# password_env: MY_DATASET_DATABASE_PASSWORD
|
|
13
|
+
databases:
|
|
14
|
+
- name: __DB_NAME__
|
|
15
|
+
password_env: DATABASE_PASSWORD
|
|
@@ -6,9 +6,9 @@ services:
|
|
|
6
6
|
restart: unless-stopped
|
|
7
7
|
env_file:
|
|
8
8
|
- ./backend.env
|
|
9
|
-
volumes:
|
|
9
|
+
volumes:
|
|
10
|
+
# Uploads stay on local disk for now; DB lives in Postgres (no sqlite volume).
|
|
10
11
|
- ./data/uploads:/opt/app/public/uploads
|
|
11
|
-
- ./data/database:/opt/app/.tmp
|
|
12
12
|
networks:
|
|
13
13
|
- agency_shared_network
|
|
14
14
|
healthcheck:
|
|
@@ -27,7 +27,10 @@ services:
|
|
|
27
27
|
- "traefik.http.services.__APP_NAME__-strapi.loadbalancer.server.port=1337"
|
|
28
28
|
# Strapi requires larger body sizes for media uploads (25MB)
|
|
29
29
|
- "traefik.http.middlewares.__APP_NAME__-strapi-limit.buffering.maxRequestBodyBytes=25000000"
|
|
30
|
-
|
|
30
|
+
# Global middlewares (defined in webhosting-infra/traefik/dynamic) + the local
|
|
31
|
+
# body-size limit. rate-limit first so abusive traffic is dropped early; this
|
|
32
|
+
# matters most on the public Strapi admin/API.
|
|
33
|
+
- "traefik.http.routers.__APP_NAME__-strapi.middlewares=rate-limit@file,secure-headers@file,__APP_NAME__-strapi-limit"
|
|
31
34
|
|
|
32
35
|
__APP_NAME__-astro:
|
|
33
36
|
container_name: __APP_NAME__-astro
|
|
@@ -75,6 +78,8 @@ services:
|
|
|
75
78
|
# Listen on both www and non-www
|
|
76
79
|
- "traefik.http.routers.__APP_NAME__-astro.rule=Host(`__PROJECT_DOMAIN__`) || Host(`www.__PROJECT_DOMAIN__`)"
|
|
77
80
|
- "traefik.http.routers.__APP_NAME__-astro.tls.certresolver=letsencrypt"
|
|
81
|
+
# Global middlewares (defined in webhosting-infra/traefik/dynamic)
|
|
82
|
+
- "traefik.http.routers.__APP_NAME__-astro.middlewares=secure-headers@file,rate-limit@file"
|
|
78
83
|
# Route traffic to the Nginx Cache (Port 80) instead of Astro directly
|
|
79
84
|
- "traefik.http.services.__APP_NAME__-astro.loadbalancer.server.port=80"
|
|
80
85
|
|
|
@@ -24,10 +24,11 @@
|
|
|
24
24
|
},
|
|
25
25
|
{
|
|
26
26
|
"key": "app_name",
|
|
27
|
-
"message": "What is the app name?",
|
|
27
|
+
"message": "What is the app name? (lowercase slug, e.g. my-new-client)",
|
|
28
28
|
"type": "input",
|
|
29
29
|
"default": "my-new-client",
|
|
30
|
-
"required": true
|
|
30
|
+
"required": true,
|
|
31
|
+
"validate": "slug"
|
|
31
32
|
},
|
|
32
33
|
{
|
|
33
34
|
"key": "include_www",
|
|
@@ -89,6 +90,10 @@
|
|
|
89
90
|
{
|
|
90
91
|
"template": "production.backend.env.template",
|
|
91
92
|
"target": "production.backend.env"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"template": "databases.yml.template",
|
|
96
|
+
"target": "databases.yml"
|
|
92
97
|
}
|
|
93
98
|
]
|
|
94
99
|
}
|
|
@@ -17,6 +17,17 @@ ADMIN_JWT_SECRET=__ADMIN_JWT_SECRET__
|
|
|
17
17
|
TRANSFER_TOKEN_SALT=__TRANSFER_TOKEN_SALT__
|
|
18
18
|
JWT_SECRET=__JWT_SECRET__
|
|
19
19
|
|
|
20
|
+
# Database — production uses Postgres (local dev defaults to SQLite).
|
|
21
|
+
# A matching role + database must exist on the shared Postgres instance,
|
|
22
|
+
# reachable as DATABASE_HOST on the agency_shared_network.
|
|
23
|
+
DATABASE_CLIENT=postgres
|
|
24
|
+
DATABASE_HOST=postgres
|
|
25
|
+
DATABASE_PORT=5432
|
|
26
|
+
DATABASE_NAME=__DB_NAME__
|
|
27
|
+
DATABASE_USERNAME=__DB_NAME__
|
|
28
|
+
DATABASE_PASSWORD=__DB_PASSWORD__
|
|
29
|
+
DATABASE_SSL=false
|
|
30
|
+
|
|
20
31
|
# Production Settings
|
|
21
32
|
HOST=0.0.0.0
|
|
22
33
|
PORT=1337
|