create-cwa 0.1.0 → 2.0.0-alpha.1

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/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ dist/
2
+ node_modules/
package/dist/index.js CHANGED
@@ -3,22 +3,51 @@ import { downloadTemplate } from 'giget';
3
3
  import fse from 'fs-extra';
4
4
  import fg from 'fast-glob';
5
5
  import { readFile, writeFile } from 'node:fs/promises';
6
- import { existsSync } from 'node:fs';
6
+ import { existsSync, readFileSync } from 'node:fs';
7
7
  import { join, resolve } from 'node:path';
8
8
  import { tmpdir } from 'node:os';
9
9
  import { randomBytes } from 'node:crypto';
10
10
  import { spawnSync } from 'node:child_process';
11
- const MANIFEST_URL = 'https://raw.githubusercontent.com/components-web-app/components-web-app/main/cwa-manifest.json';
12
- async function fetchManifest() {
11
+ const REPO = 'components-web-app/components-web-app';
12
+ // The CLI and the template share a version: create-cwa X downloads the template at
13
+ // tag vX, so an install is reproducible. `--ref <branch|tag>` overrides it, for
14
+ // example `--ref main` for the latest unreleased template.
15
+ const CLI_VERSION = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
16
+ function parseArgs(argv) {
17
+ const args = argv.slice(2);
18
+ let ref = `v${CLI_VERSION}`;
19
+ const positional = [];
20
+ for (let i = 0; i < args.length; i++) {
21
+ const arg = args[i];
22
+ if (arg === '--ref' && args[i + 1])
23
+ ref = args[++i];
24
+ else if (arg.startsWith('--ref='))
25
+ ref = arg.slice('--ref='.length);
26
+ else
27
+ positional.push(arg);
28
+ }
29
+ return { projectName: positional[0], ref };
30
+ }
31
+ async function fetchManifest(ref) {
32
+ let res;
13
33
  try {
14
- const res = await fetch(MANIFEST_URL);
15
- if (!res.ok)
16
- throw new Error(`HTTP ${res.status}`);
17
- return (await res.json());
34
+ res = await fetch(`https://raw.githubusercontent.com/${REPO}/${ref}/cwa-manifest.json`);
18
35
  }
19
36
  catch {
20
37
  throw new Error('Could not fetch the CWA manifest. Check your internet connection and try again.');
21
38
  }
39
+ if (res.status === 404) {
40
+ throw new Error(`No CWA template found at "${ref}". Check the --ref value.`);
41
+ }
42
+ if (!res.ok) {
43
+ throw new Error(`Could not fetch the CWA manifest (HTTP ${res.status}). Try again shortly.`);
44
+ }
45
+ try {
46
+ return (await res.json());
47
+ }
48
+ catch {
49
+ throw new Error(`The CWA manifest at "${ref}" is not valid JSON.`);
50
+ }
22
51
  }
23
52
  function checkCancel(value) {
24
53
  if (isCancel(value)) {
@@ -37,8 +66,7 @@ function run(cmd, args, cwd) {
37
66
  const result = spawnSync(cmd, args, { stdio: 'inherit', cwd });
38
67
  return result.status === 0;
39
68
  }
40
- async function askQuestions(manifest, argv) {
41
- const targetArg = argv[2];
69
+ async function askQuestions(manifest, targetArg) {
42
70
  const projectName = checkCancel(await text({
43
71
  message: 'Project name?',
44
72
  placeholder: 'my-cwa-app',
@@ -79,11 +107,11 @@ async function askQuestions(manifest, argv) {
79
107
  }));
80
108
  return { projectName: projectName.trim(), ci, features: [...resolved], fixtures };
81
109
  }
82
- async function downloadRepo(manifest, tempDir) {
110
+ async function downloadRepo(manifest, ref, tempDir) {
83
111
  const s = spinner();
84
- s.start('Downloading template...');
112
+ s.start(`Downloading template (${ref})...`);
85
113
  try {
86
- await downloadTemplate(`github:${manifest.repo}#${manifest.branch}`, {
114
+ await downloadTemplate(`github:${manifest.repo}#${ref}`, {
87
115
  dir: tempDir,
88
116
  force: true,
89
117
  });
@@ -151,7 +179,7 @@ async function generateReadme(tempDir, answers) {
151
179
  Sample content is bundled. Load it after the API container is healthy:
152
180
 
153
181
  \`\`\`bash
154
- docker compose exec php bin/console doctrine:fixtures:load
182
+ docker compose exec php bin/console doctrine:fixtures:load --append
155
183
  \`\`\`
156
184
  `
157
185
  : '';
@@ -166,25 +194,27 @@ ${featureList}
166
194
  ## Getting started
167
195
 
168
196
  \`\`\`bash
169
- # 1. Start the stack
170
- # composer install, database wait, and migrations all run automatically
197
+ # Start the stack. The php container runs composer install, waits for the
198
+ # database and migrates; the app container installs the Nuxt dependencies and
199
+ # runs the dev server. There is nothing to start by hand.
171
200
  docker compose up -d
172
201
 
173
- # 2. Install Nuxt app dependencies (run locally, not inside Docker)
174
- cd app
175
- pnpm install
176
-
177
- # 3. Start the dev server
178
- pnpm dev
202
+ # Follow the Nuxt dev server
203
+ docker compose logs -f app
179
204
  \`\`\`
180
205
 
206
+ \`app/\` is mounted into the app container, so its \`node_modules\` is shared. Run
207
+ \`pnpm install\` in \`app/\` yourself only if your editor needs the types locally.
208
+
181
209
  | URL | Description |
182
210
  |---|---|
183
211
  | https://localhost | Nuxt app |
184
212
  | https://localhost/_api | API (JSON-LD / HAL) |
185
- | https://localhost/admin | CWA admin panel |
213
+ | https://localhost/login | Sign in to the CWA admin |
186
214
 
187
- > **SSL:** The dev stack uses self-signed certs. Accept the browser warning or trust the CA at \`api/frankenphp/caddy/certs/\`.
215
+ > **SSL:** Caddy issues local certificates from its own CA. Accept the browser warning, or trust the CA once:
216
+ > \`docker compose cp php:/data/caddy/pki/authorities/local/root.crt caddy-local-root.crt\`,
217
+ > then add \`caddy-local-root.crt\` to your system or browser trust store.
188
218
 
189
219
  ${fixturesSection}
190
220
  ${ciSection}## Learn more
@@ -206,21 +236,22 @@ async function moveToTarget(tempDir, targetDir) {
206
236
  }
207
237
  async function main() {
208
238
  intro('create-cwa — Components Web App scaffolder');
239
+ const args = parseArgs(process.argv);
209
240
  const s = spinner();
210
241
  s.start('Fetching manifest...');
211
242
  let manifest;
212
243
  try {
213
- manifest = await fetchManifest();
244
+ manifest = await fetchManifest(args.ref);
214
245
  s.stop('Manifest loaded.');
215
246
  }
216
247
  catch (err) {
217
248
  s.stop(err.message);
218
249
  process.exit(1);
219
250
  }
220
- const answers = await askQuestions(manifest, process.argv);
251
+ const answers = await askQuestions(manifest, args.projectName);
221
252
  const targetDir = resolve(process.cwd(), answers.projectName);
222
253
  const tempDir = join(tmpdir(), `cwa-${randomBytes(6).toString('hex')}`);
223
- await downloadRepo(manifest, tempDir);
254
+ await downloadRepo(manifest, args.ref, tempDir);
224
255
  const s2 = spinner();
225
256
  s2.start('Configuring project...');
226
257
  try {
@@ -252,7 +283,8 @@ async function main() {
252
283
  ' • waits for PostgreSQL to be ready',
253
284
  ' • runs database migrations',
254
285
  '',
255
- 'You only need to run pnpm install and pnpm dev locally.',
286
+ 'The app container installs the Nuxt dependencies and runs the dev server,',
287
+ 'so there is nothing to start by hand.',
256
288
  answers.fixtures
257
289
  ? '\nFixtures are NOT loaded automatically — you will be reminded below.'
258
290
  : '',
@@ -267,7 +299,7 @@ async function main() {
267
299
  log.step('Running: docker compose up -d');
268
300
  const ok = run('docker', ['compose', 'up', '-d'], targetDir);
269
301
  if (!ok) {
270
- log.warn('docker compose up -d failed. Start it manually before running pnpm dev.');
302
+ log.warn('docker compose up -d failed. Run it manually from the project directory.');
271
303
  }
272
304
  }
273
305
  }
@@ -278,8 +310,8 @@ async function main() {
278
310
  const hasPnpm = commandExists('pnpm');
279
311
  if (hasPnpm) {
280
312
  const installDeps = checkCancel(await confirm({
281
- message: 'Install Nuxt app dependencies now? (pnpm install in app/)',
282
- initialValue: true,
313
+ message: 'Also install app/ dependencies on this machine, for editor type support? (the app container installs them either way)',
314
+ initialValue: false,
283
315
  }));
284
316
  if (installDeps) {
285
317
  log.step('Running: pnpm install');
@@ -293,18 +325,18 @@ async function main() {
293
325
  log.warn('pnpm not found — run: npm i -g pnpm, then pnpm install inside app/');
294
326
  }
295
327
  const outroLines = [
296
- `Start the dev server:`,
297
- ` cd ${answers.projectName}/app && pnpm dev`,
328
+ `The dev server runs in the app container. Follow it with:`,
329
+ ` cd ${answers.projectName} && docker compose logs -f app`,
298
330
  '',
299
331
  'Then visit:',
300
332
  ' https://localhost — app',
301
333
  ' https://localhost/_api — API',
302
- ' https://localhost/admin — admin panel',
334
+ ' https://localhost/login — sign in to the admin',
303
335
  ];
304
336
  if (answers.fixtures) {
305
337
  outroLines.push('');
306
338
  outroLines.push('Load sample content (once the API container is healthy):');
307
- outroLines.push(' docker compose exec php bin/console doctrine:fixtures:load');
339
+ outroLines.push(' docker compose exec php bin/console doctrine:fixtures:load --append');
308
340
  }
309
341
  outro(outroLines.join('\n'));
310
342
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-cwa",
3
- "version": "0.1.0",
3
+ "version": "2.0.0-alpha.1",
4
4
  "description": "Create a new CWA (Components Web App) project",
5
5
  "type": "module",
6
6
  "bin": {
@@ -21,7 +21,7 @@
21
21
  "directory": "packages/create-cwa"
22
22
  },
23
23
  "engines": {
24
- "node": ">=18.0.0"
24
+ "node": ">=22.13.0"
25
25
  },
26
26
  "dependencies": {
27
27
  "@clack/prompts": "^0.9.0",
package/src/index.ts CHANGED
@@ -15,26 +15,55 @@ import { downloadTemplate } from 'giget'
15
15
  import fse from 'fs-extra'
16
16
  import fg from 'fast-glob'
17
17
  import { readFile, writeFile } from 'node:fs/promises'
18
- import { existsSync } from 'node:fs'
18
+ import { existsSync, readFileSync } from 'node:fs'
19
19
  import { join, resolve } from 'node:path'
20
20
  import { tmpdir } from 'node:os'
21
21
  import { randomBytes } from 'node:crypto'
22
22
  import { spawnSync } from 'node:child_process'
23
23
  import type { Manifest, Answers } from './types.js'
24
24
 
25
- const MANIFEST_URL =
26
- 'https://raw.githubusercontent.com/components-web-app/components-web-app/main/cwa-manifest.json'
25
+ const REPO = 'components-web-app/components-web-app'
26
+
27
+ // The CLI and the template share a version: create-cwa X downloads the template at
28
+ // tag vX, so an install is reproducible. `--ref <branch|tag>` overrides it, for
29
+ // example `--ref main` for the latest unreleased template.
30
+ const CLI_VERSION: string = JSON.parse(
31
+ readFileSync(new URL('../package.json', import.meta.url), 'utf8')
32
+ ).version
33
+
34
+ function parseArgs(argv: string[]): { projectName?: string; ref: string } {
35
+ const args = argv.slice(2)
36
+ let ref = `v${CLI_VERSION}`
37
+ const positional: string[] = []
38
+ for (let i = 0; i < args.length; i++) {
39
+ const arg = args[i]
40
+ if (arg === '--ref' && args[i + 1]) ref = args[++i]
41
+ else if (arg.startsWith('--ref=')) ref = arg.slice('--ref='.length)
42
+ else positional.push(arg)
43
+ }
44
+ return { projectName: positional[0], ref }
45
+ }
27
46
 
28
- async function fetchManifest(): Promise<Manifest> {
47
+ async function fetchManifest(ref: string): Promise<Manifest> {
48
+ let res: Response
29
49
  try {
30
- const res = await fetch(MANIFEST_URL)
31
- if (!res.ok) throw new Error(`HTTP ${res.status}`)
32
- return (await res.json()) as Manifest
50
+ res = await fetch(`https://raw.githubusercontent.com/${REPO}/${ref}/cwa-manifest.json`)
33
51
  } catch {
34
52
  throw new Error(
35
53
  'Could not fetch the CWA manifest. Check your internet connection and try again.'
36
54
  )
37
55
  }
56
+ if (res.status === 404) {
57
+ throw new Error(`No CWA template found at "${ref}". Check the --ref value.`)
58
+ }
59
+ if (!res.ok) {
60
+ throw new Error(`Could not fetch the CWA manifest (HTTP ${res.status}). Try again shortly.`)
61
+ }
62
+ try {
63
+ return (await res.json()) as Manifest
64
+ } catch {
65
+ throw new Error(`The CWA manifest at "${ref}" is not valid JSON.`)
66
+ }
38
67
  }
39
68
 
40
69
  function checkCancel<T>(value: T | symbol): T {
@@ -57,9 +86,7 @@ function run(cmd: string, args: string[], cwd: string): boolean {
57
86
  return result.status === 0
58
87
  }
59
88
 
60
- async function askQuestions(manifest: Manifest, argv: string[]): Promise<Answers> {
61
- const targetArg = argv[2]
62
-
89
+ async function askQuestions(manifest: Manifest, targetArg?: string): Promise<Answers> {
63
90
  const projectName = checkCancel(
64
91
  await text({
65
92
  message: 'Project name?',
@@ -118,11 +145,11 @@ async function askQuestions(manifest: Manifest, argv: string[]): Promise<Answers
118
145
  return { projectName: projectName.trim(), ci, features: [...resolved], fixtures }
119
146
  }
120
147
 
121
- async function downloadRepo(manifest: Manifest, tempDir: string): Promise<void> {
148
+ async function downloadRepo(manifest: Manifest, ref: string, tempDir: string): Promise<void> {
122
149
  const s = spinner()
123
- s.start('Downloading template...')
150
+ s.start(`Downloading template (${ref})...`)
124
151
  try {
125
- await downloadTemplate(`github:${manifest.repo}#${manifest.branch}`, {
152
+ await downloadTemplate(`github:${manifest.repo}#${ref}`, {
126
153
  dir: tempDir,
127
154
  force: true,
128
155
  })
@@ -206,7 +233,7 @@ async function generateReadme(tempDir: string, answers: Answers): Promise<void>
206
233
  Sample content is bundled. Load it after the API container is healthy:
207
234
 
208
235
  \`\`\`bash
209
- docker compose exec php bin/console doctrine:fixtures:load
236
+ docker compose exec php bin/console doctrine:fixtures:load --append
210
237
  \`\`\`
211
238
  `
212
239
  : ''
@@ -222,25 +249,27 @@ ${featureList}
222
249
  ## Getting started
223
250
 
224
251
  \`\`\`bash
225
- # 1. Start the stack
226
- # composer install, database wait, and migrations all run automatically
252
+ # Start the stack. The php container runs composer install, waits for the
253
+ # database and migrates; the app container installs the Nuxt dependencies and
254
+ # runs the dev server. There is nothing to start by hand.
227
255
  docker compose up -d
228
256
 
229
- # 2. Install Nuxt app dependencies (run locally, not inside Docker)
230
- cd app
231
- pnpm install
232
-
233
- # 3. Start the dev server
234
- pnpm dev
257
+ # Follow the Nuxt dev server
258
+ docker compose logs -f app
235
259
  \`\`\`
236
260
 
261
+ \`app/\` is mounted into the app container, so its \`node_modules\` is shared. Run
262
+ \`pnpm install\` in \`app/\` yourself only if your editor needs the types locally.
263
+
237
264
  | URL | Description |
238
265
  |---|---|
239
266
  | https://localhost | Nuxt app |
240
267
  | https://localhost/_api | API (JSON-LD / HAL) |
241
- | https://localhost/admin | CWA admin panel |
268
+ | https://localhost/login | Sign in to the CWA admin |
242
269
 
243
- > **SSL:** The dev stack uses self-signed certs. Accept the browser warning or trust the CA at \`api/frankenphp/caddy/certs/\`.
270
+ > **SSL:** Caddy issues local certificates from its own CA. Accept the browser warning, or trust the CA once:
271
+ > \`docker compose cp php:/data/caddy/pki/authorities/local/root.crt caddy-local-root.crt\`,
272
+ > then add \`caddy-local-root.crt\` to your system or browser trust store.
244
273
 
245
274
  ${fixturesSection}
246
275
  ${ciSection}## Learn more
@@ -266,22 +295,23 @@ async function moveToTarget(tempDir: string, targetDir: string): Promise<void> {
266
295
  async function main(): Promise<void> {
267
296
  intro('create-cwa — Components Web App scaffolder')
268
297
 
298
+ const args = parseArgs(process.argv)
269
299
  const s = spinner()
270
300
  s.start('Fetching manifest...')
271
301
  let manifest: Manifest
272
302
  try {
273
- manifest = await fetchManifest()
303
+ manifest = await fetchManifest(args.ref)
274
304
  s.stop('Manifest loaded.')
275
305
  } catch (err) {
276
306
  s.stop((err as Error).message)
277
307
  process.exit(1)
278
308
  }
279
309
 
280
- const answers = await askQuestions(manifest, process.argv)
310
+ const answers = await askQuestions(manifest, args.projectName)
281
311
  const targetDir = resolve(process.cwd(), answers.projectName)
282
312
  const tempDir = join(tmpdir(), `cwa-${randomBytes(6).toString('hex')}`)
283
313
 
284
- await downloadRepo(manifest, tempDir)
314
+ await downloadRepo(manifest, args.ref, tempDir)
285
315
 
286
316
  const s2 = spinner()
287
317
  s2.start('Configuring project...')
@@ -321,7 +351,8 @@ async function main(): Promise<void> {
321
351
  ' • waits for PostgreSQL to be ready',
322
352
  ' • runs database migrations',
323
353
  '',
324
- 'You only need to run pnpm install and pnpm dev locally.',
354
+ 'The app container installs the Nuxt dependencies and runs the dev server,',
355
+ 'so there is nothing to start by hand.',
325
356
  answers.fixtures
326
357
  ? '\nFixtures are NOT loaded automatically — you will be reminded below.'
327
358
  : '',
@@ -342,7 +373,7 @@ async function main(): Promise<void> {
342
373
  log.step('Running: docker compose up -d')
343
374
  const ok = run('docker', ['compose', 'up', '-d'], targetDir)
344
375
  if (!ok) {
345
- log.warn('docker compose up -d failed. Start it manually before running pnpm dev.')
376
+ log.warn('docker compose up -d failed. Run it manually from the project directory.')
346
377
  }
347
378
  }
348
379
  } else {
@@ -354,8 +385,8 @@ async function main(): Promise<void> {
354
385
  if (hasPnpm) {
355
386
  const installDeps = checkCancel(
356
387
  await confirm({
357
- message: 'Install Nuxt app dependencies now? (pnpm install in app/)',
358
- initialValue: true,
388
+ message: 'Also install app/ dependencies on this machine, for editor type support? (the app container installs them either way)',
389
+ initialValue: false,
359
390
  })
360
391
  ) as boolean
361
392
 
@@ -371,19 +402,19 @@ async function main(): Promise<void> {
371
402
  }
372
403
 
373
404
  const outroLines = [
374
- `Start the dev server:`,
375
- ` cd ${answers.projectName}/app && pnpm dev`,
405
+ `The dev server runs in the app container. Follow it with:`,
406
+ ` cd ${answers.projectName} && docker compose logs -f app`,
376
407
  '',
377
408
  'Then visit:',
378
409
  ' https://localhost — app',
379
410
  ' https://localhost/_api — API',
380
- ' https://localhost/admin — admin panel',
411
+ ' https://localhost/login — sign in to the admin',
381
412
  ]
382
413
 
383
414
  if (answers.fixtures) {
384
415
  outroLines.push('')
385
416
  outroLines.push('Load sample content (once the API container is healthy):')
386
- outroLines.push(' docker compose exec php bin/console doctrine:fixtures:load')
417
+ outroLines.push(' docker compose exec php bin/console doctrine:fixtures:load --append')
387
418
  }
388
419
 
389
420
  outro(outroLines.join('\n'))
package/src/types.ts CHANGED
@@ -26,6 +26,8 @@ export interface ManifestCi {
26
26
  export interface Manifest {
27
27
  version: string
28
28
  repo: string
29
+ // Read only by create-cwa 0.x, which downloads this branch. Newer versions download
30
+ // the tag matching their own version, or --ref.
29
31
  branch: string
30
32
  alwaysExclude: string[]
31
33
  questions: ManifestQuestion[]
package/dist/index.d.ts DELETED
@@ -1 +0,0 @@
1
- export {};
package/dist/types.d.ts DELETED
@@ -1,36 +0,0 @@
1
- export interface ManifestChoice {
2
- value: string;
3
- label: string;
4
- requires?: string[];
5
- }
6
- export interface ManifestQuestion {
7
- id: string;
8
- type: 'text' | 'select' | 'multiselect' | 'confirm';
9
- message: string;
10
- placeholder?: string;
11
- hint?: string;
12
- default?: boolean;
13
- choices?: ManifestChoice[];
14
- }
15
- export interface ManifestFeature {
16
- requires?: string[];
17
- exclude: string[];
18
- }
19
- export interface ManifestCi {
20
- exclude: string[];
21
- }
22
- export interface Manifest {
23
- version: string;
24
- repo: string;
25
- branch: string;
26
- alwaysExclude: string[];
27
- questions: ManifestQuestion[];
28
- features: Record<string, ManifestFeature>;
29
- ci: Record<string, ManifestCi>;
30
- }
31
- export interface Answers {
32
- projectName: string;
33
- ci: string;
34
- features: string[];
35
- fixtures: boolean;
36
- }
package/dist/types.js DELETED
@@ -1 +0,0 @@
1
- export {};