@citolab/tspci 2.7.1 → 2.8.4

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/CHANGELOG.md CHANGED
@@ -3,6 +3,50 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [2.8.4](/compare/v2.8.3...v2.8.4) (2026-02-25)
7
+
8
+ **Note:** Version bump only for package @citolab/tspci
9
+
10
+
11
+
12
+
13
+
14
+ ## [2.8.3](/compare/v2.8.2...v2.8.3) (2026-02-25)
15
+
16
+ **Note:** Version bump only for package @citolab/tspci
17
+
18
+
19
+
20
+
21
+
22
+ ## [2.8.2](/compare/v2.8.1...v2.8.2) (2026-02-24)
23
+
24
+ **Note:** Version bump only for package @citolab/tspci
25
+
26
+
27
+
28
+
29
+
30
+ ## [2.8.1](/compare/v2.8.0...v2.8.1) (2026-02-24)
31
+
32
+ **Note:** Version bump only for package @citolab/tspci
33
+
34
+
35
+
36
+
37
+
38
+ # [2.8.0](/compare/v2.7.1...v2.8.0) (2026-02-24)
39
+
40
+
41
+ ### Features
42
+
43
+ * add local test initialization scripts and rollup plugins 582f4d8
44
+ * enhance CLI options handling and project path resolution f9b51c6
45
+
46
+
47
+
48
+
49
+
6
50
  ## [2.7.1](/compare/v2.7.0...v2.7.1) (2026-02-09)
7
51
 
8
52
  **Note:** Version bump only for package @citolab/tspci
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@citolab/tspci",
3
- "version": "2.7.1",
3
+ "version": "2.8.4",
4
4
  "description": "an opiniated typescript ims-pci builder with tao integration",
5
5
  "author": "patrick <patrick.deklein@cito.nl>",
6
6
  "repository": "@citolab/tspci",
@@ -9,7 +9,10 @@
9
9
  },
10
10
  "scripts": {
11
11
  "help": "tspci --help",
12
- "init": "tspci init --force --path=../../pcis"
12
+ "init": "tspci init --force --path=../../pcis",
13
+ "test:init:local": "bash ./scripts/test-init-local.sh",
14
+ "test:init:smoke": "bash ./scripts/test-init-local.sh",
15
+ "test:init:matrix": "bash ./scripts/test-init-matrix.sh"
13
16
  },
14
17
  "type": "module",
15
18
  "types": "types/index.d.ts",
@@ -25,22 +28,29 @@
25
28
  "@rollup/plugin-typescript": "^12.3.0",
26
29
  "@tailwindcss/nesting": "0.0.0-insiders.565cd3e",
27
30
  "@tailwindcss/postcss": "^4.1.18",
28
- "@types/node": "^25.1.0",
29
- "@typescript-eslint/eslint-plugin": "^8.54.0",
30
- "@typescript-eslint/parser": "^8.54.0",
31
31
  "autoprefixer": "10.4.23",
32
32
  "chalk": "5.6.2",
33
33
  "chokidar": "^5.0.0",
34
34
  "clear": "0.1.0",
35
+ "deepmerge": "^4.3.1",
36
+ "fast-glob": "^3.3.3",
35
37
  "figlet": "1.10.0",
36
38
  "inquirer": "13.2.2",
37
39
  "postcss": "^8.5.6",
38
40
  "postcss-import": "^16.1.1",
41
+ "postcss-load-config": "^6.0.1",
39
42
  "rollup": "^4.57.1",
40
- "rollup-plugin-copy": "^3.5.0",
41
43
  "rollup-plugin-livereload": "^2.0.5",
42
- "rollup-plugin-postcss": "^4.0.2",
43
44
  "tailwindcss": "^4.1.18"
44
45
  },
45
- "gitHead": "1481d1ed65ab57b07bf6a12ae9f7e402105d2964"
46
+ "devDependencies": {
47
+ "@types/node": "^25.1.0",
48
+ "@typescript-eslint/eslint-plugin": "^8.54.0",
49
+ "@typescript-eslint/parser": "^8.54.0"
50
+ },
51
+ "publishConfig": {
52
+ "access": "public",
53
+ "registry": "https://registry.npmjs.org/"
54
+ },
55
+ "gitHead": "2ad2236ae56f3927ef2d836c987b86a94ba5c070"
46
56
  }
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ PKG_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
6
+ TEST_ROOT="${PKG_DIR}/tmp/init-local-test"
7
+ NPM_CACHE_DIR="${PKG_DIR}/tmp/npm-cache"
8
+
9
+ PCI_NAME="${1:-LocalTsPci}"
10
+ PROJECT_TYPE="${2:-typescript}"
11
+ DESCRIPTION="${3:-Local init test generated by script}"
12
+
13
+ TARGET_DIR="${TEST_ROOT}/${PCI_NAME}"
14
+ rm -rf "${TARGET_DIR}"
15
+ mkdir -p "${NPM_CACHE_DIR}"
16
+ rm -rf "${PKG_DIR}/tmp/init-local-test" "${PKG_DIR}/tmp/init-matrix-test"
17
+ rm -f "${PKG_DIR}"/citolab-*.tgz
18
+
19
+ echo "Packing local @citolab/tspci package..."
20
+ PACK_TGZ="$(cd "${PKG_DIR}" && npm pack --silent --ignore-scripts --cache "${NPM_CACHE_DIR}" | tail -n 1)"
21
+ PACK_PATH="${PKG_DIR}/${PACK_TGZ}"
22
+ cleanup() {
23
+ rm -f "${PACK_PATH}" 2>/dev/null || true
24
+ }
25
+ trap cleanup EXIT
26
+
27
+ echo "Generating project in: ${TARGET_DIR}"
28
+ node "${PKG_DIR}/src/cli.js" init \
29
+ --no-install \
30
+ --force \
31
+ --path "${TEST_ROOT}" \
32
+ --name "${PCI_NAME}" \
33
+ --description "${DESCRIPTION}" \
34
+ --project-type "${PROJECT_TYPE}"
35
+
36
+ echo ""
37
+ echo "Pointing generated project to local @citolab/tspci package..."
38
+ node -e "
39
+ const fs = require('fs');
40
+ const path = require('path');
41
+ const pkgPath = process.argv[1];
42
+ const localDep = process.argv[2];
43
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
44
+ pkg.devDependencies = pkg.devDependencies || {};
45
+ pkg.devDependencies['@citolab/tspci'] = localDep;
46
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8');
47
+ " "${TARGET_DIR}/package.json" "file:${PACK_PATH}"
48
+
49
+ echo ""
50
+ echo "Generated files:"
51
+ find "${TARGET_DIR}" -maxdepth 3 -type f | sort
52
+
53
+ EXPECTED_FILE="${TARGET_DIR}/src/index.ts"
54
+ if [[ "${PROJECT_TYPE}" == "javascript" ]]; then
55
+ EXPECTED_FILE="${TARGET_DIR}/src/index.js"
56
+ fi
57
+ if [[ "${PROJECT_TYPE}" == "preact+tailwind" ]]; then
58
+ EXPECTED_FILE="${TARGET_DIR}/src/index.tsx"
59
+ fi
60
+
61
+ if [[ ! -f "${EXPECTED_FILE}" ]]; then
62
+ echo ""
63
+ echo "ERROR: expected entry file not found: ${EXPECTED_FILE}" >&2
64
+ exit 1
65
+ fi
66
+
67
+ echo ""
68
+ echo "Installing dependencies in generated project..."
69
+ (cd "${TARGET_DIR}" && npm install --cache "${NPM_CACHE_DIR}" --workspaces=false)
70
+
71
+ echo ""
72
+ echo "Running production build smoke test..."
73
+ node -e "
74
+ const { spawnSync } = require('child_process');
75
+ const targetDir = process.argv[1];
76
+ const result = spawnSync('npm', ['run', 'prod'], {
77
+ cwd: targetDir,
78
+ stdio: 'inherit',
79
+ timeout: 180000
80
+ });
81
+ if (result.error) {
82
+ console.error(result.error.message);
83
+ process.exit(1);
84
+ }
85
+ process.exit(result.status ?? 1);
86
+ " "${TARGET_DIR}"
87
+
88
+ DIST_JS="${TARGET_DIR}/dist/index.js"
89
+ if [[ ! -f "${DIST_JS}" ]]; then
90
+ echo ""
91
+ echo "ERROR: expected build output not found: ${DIST_JS}" >&2
92
+ exit 1
93
+ fi
94
+
95
+ DIST_TYPES="${TARGET_DIR}/dist/index.d.ts"
96
+ if [[ ! -f "${DIST_TYPES}" ]]; then
97
+ echo ""
98
+ echo "ERROR: expected type output not found: ${DIST_TYPES}" >&2
99
+ exit 1
100
+ fi
101
+
102
+ echo ""
103
+ echo "OK: found expected entry file: ${EXPECTED_FILE}"
104
+ echo "OK: production build output exists: ${DIST_JS}"
105
+ echo "OK: production type output exists: ${DIST_TYPES}"
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ TSPCI_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
6
+ REPO_ROOT="$(cd "${TSPCI_DIR}/../.." && pwd)"
7
+ TEST_ROOT="${TSPCI_DIR}/tmp/init-matrix-test"
8
+ NPM_CACHE_DIR="${TSPCI_DIR}/tmp/npm-cache"
9
+
10
+ clean_dir_contents() {
11
+ local dir_path="$1"
12
+ mkdir -p "${dir_path}"
13
+ find "${dir_path}" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
14
+ }
15
+
16
+ mkdir -p "${NPM_CACHE_DIR}"
17
+ clean_dir_contents "${TEST_ROOT}"
18
+
19
+ # On Windows (Git Bash), convert Unix-style paths to native Windows paths so
20
+ # npm can resolve file: references correctly. No-op on Linux/macOS.
21
+ npm_path() {
22
+ if command -v cygpath &>/dev/null; then
23
+ cygpath -m "$1"
24
+ else
25
+ echo "$1"
26
+ fi
27
+ }
28
+
29
+ TARBALLS=()
30
+ cleanup() {
31
+ for file in "${TARBALLS[@]:-}"; do
32
+ rm -f "${file}" 2>/dev/null || true
33
+ done
34
+ }
35
+ trap cleanup EXIT
36
+
37
+ prepare_pkg_for_pack() {
38
+ local pkg_dir="$1"
39
+ clean_dir_contents "${pkg_dir}/tmp/init-local-test"
40
+ clean_dir_contents "${pkg_dir}/tmp/init-matrix-test"
41
+ rm -f "${pkg_dir}"/citolab-*.tgz
42
+ }
43
+
44
+ pack_local_pkg() {
45
+ local pkg_dir="$1"
46
+ local pkg_name="$2"
47
+ local tgz
48
+ prepare_pkg_for_pack "${pkg_dir}"
49
+ echo " - packing ${pkg_name}" >&2
50
+ tgz="$(cd "${pkg_dir}" && npm pack --silent --ignore-scripts --cache "${NPM_CACHE_DIR}" | tail -n 1 | tr -d '\r')"
51
+ local full_path="${pkg_dir}/${tgz}"
52
+ TARBALLS+=("${full_path}")
53
+ echo "${full_path}"
54
+ }
55
+
56
+ echo "Packing local packages..."
57
+ TSPCI_TGZ="$(pack_local_pkg "${TSPCI_DIR}" "@citolab/tspci")"
58
+ QBCI_TGZ="$(pack_local_pkg "${REPO_ROOT}/lib/tspci-qbci" "@citolab/tspci-qbci")"
59
+ QTI3_TGZ="$(pack_local_pkg "${REPO_ROOT}/lib/tspci-qti3" "@citolab/tspci-qti3")"
60
+ TAO_TGZ="$(pack_local_pkg "${REPO_ROOT}/lib/tspci-tao" "@citolab/tspci-tao")"
61
+ PREACT_STORE_TGZ="$(pack_local_pkg "${REPO_ROOT}/lib/preact-store" "@citolab/preact-store")"
62
+
63
+ create_project() {
64
+ local project_name="$1"
65
+ local project_type="$2"
66
+ local description="$3"
67
+ local project_dir="${TEST_ROOT}/${project_name}"
68
+
69
+ node "${TSPCI_DIR}/src/cli.js" init \
70
+ --no-install \
71
+ --force \
72
+ --path "${TEST_ROOT}" \
73
+ --name "${project_name}" \
74
+ --description "${description}" \
75
+ --project-type "${project_type}"
76
+
77
+ node -e '
78
+ const fs = require("fs");
79
+ const pkgPath = process.argv[1];
80
+ const tspciTar = process.argv[2];
81
+ const preactStoreTar = process.argv[3];
82
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
83
+ pkg.devDependencies = pkg.devDependencies || {};
84
+ pkg.devDependencies["@citolab/tspci"] = `file:${tspciTar}`;
85
+ if (pkg.dependencies && pkg.dependencies["@citolab/preact-store"]) {
86
+ pkg.dependencies["@citolab/preact-store"] = `file:${preactStoreTar}`;
87
+ }
88
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
89
+ ' "${project_dir}/package.json" "$(npm_path "${TSPCI_TGZ}")" "$(npm_path "${PREACT_STORE_TGZ}")"
90
+
91
+ (cd "${project_dir}" && npm install --cache "${NPM_CACHE_DIR}" --workspaces=false)
92
+ }
93
+
94
+ assert_file() {
95
+ local file_path="$1"
96
+ if [[ ! -f "${file_path}" ]]; then
97
+ echo "ERROR: expected file not found: ${file_path}" >&2
98
+ exit 1
99
+ fi
100
+ }
101
+
102
+ run_base_build() {
103
+ local project_dir="$1"
104
+ (cd "${project_dir}" && npm run prod)
105
+ assert_file "${project_dir}/dist/index.js"
106
+ }
107
+
108
+ echo "Scenario 1: vanilla PCI creates and builds"
109
+ create_project "VanillaPci" "javascript" "Vanilla PCI matrix test"
110
+ assert_file "${TEST_ROOT}/VanillaPci/src/index.js"
111
+ run_base_build "${TEST_ROOT}/VanillaPci"
112
+
113
+ echo "Scenario 2: TypeScript PCI (without tailwind/preact) creates and builds"
114
+ create_project "TypeScriptPci" "typescript" "TypeScript PCI matrix test"
115
+ assert_file "${TEST_ROOT}/TypeScriptPci/src/index.ts"
116
+ run_base_build "${TEST_ROOT}/TypeScriptPci"
117
+
118
+ echo "Scenario 3: TypeScript + tailwind PCI creates and builds"
119
+ create_project "TypeScriptTailwindPci" "preact+tailwind" "TypeScript + tailwind PCI matrix test"
120
+ assert_file "${TEST_ROOT}/TypeScriptTailwindPci/src/index.tsx"
121
+ run_base_build "${TEST_ROOT}/TypeScriptTailwindPci"
122
+
123
+ echo "Scenario 4: add tspci-qbci and export .ci package"
124
+ create_project "QbciPci" "typescript" "QBCI export matrix test"
125
+ (cd "${TEST_ROOT}/QbciPci" && npm install --save-dev "file:$(npm_path "${QBCI_TGZ}")" --cache "${NPM_CACHE_DIR}" --workspaces=false)
126
+ (cd "${TEST_ROOT}/QbciPci" && npm run tspci -- add --target qbci)
127
+ (cd "${TEST_ROOT}/QbciPci" && npm run prod -- --target qbci)
128
+ ls "${TEST_ROOT}/QbciPci/ci-dist/"*.ci >/dev/null
129
+
130
+ echo "Scenario 5: add tspci-qti3 and export qti3 zip"
131
+ create_project "Qti3Pci" "typescript" "QTI3 export matrix test"
132
+ (cd "${TEST_ROOT}/Qti3Pci" && npm install --save-dev "file:$(npm_path "${QTI3_TGZ}")" --cache "${NPM_CACHE_DIR}" --workspaces=false)
133
+ (cd "${TEST_ROOT}/Qti3Pci" && npm run tspci -- add --target qti3)
134
+ (cd "${TEST_ROOT}/Qti3Pci" && npm run prod -- --target qti3)
135
+ ls "${TEST_ROOT}/Qti3Pci/qti3-dist/"*.zip >/dev/null
136
+
137
+ echo "Scenario 6: add tspci-tao and export tao zip"
138
+ create_project "TaoPci" "typescript" "TAO export matrix test"
139
+ (cd "${TEST_ROOT}/TaoPci" && npm install --save-dev "file:$(npm_path "${TAO_TGZ}")" --cache "${NPM_CACHE_DIR}" --workspaces=false)
140
+ (cd "${TEST_ROOT}/TaoPci" && npm run tspci -- add --target tao --ci)
141
+ node -e '
142
+ const fs = require("fs");
143
+ const pkgPath = process.argv[1];
144
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
145
+ pkg.config = pkg.config || {};
146
+ pkg.config.tspci = pkg.config.tspci || {};
147
+ if (!pkg.config.tspci.label) pkg.config.tspci.label = pkg.config.tspci.typeIdentifier || "tao";
148
+ if (!pkg.config.tspci.short) pkg.config.tspci.short = String(pkg.config.tspci.label).slice(0, 5);
149
+ if (!Array.isArray(pkg.config.tspci.score) || pkg.config.tspci.score.length === 0) {
150
+ pkg.config.tspci.score = ["MATCH_CORRECT", "NONE"];
151
+ }
152
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
153
+ ' "${TEST_ROOT}/TaoPci/package.json"
154
+ (cd "${TEST_ROOT}/TaoPci" && npm run prod -- --target tao)
155
+ ls "${TEST_ROOT}/TaoPci/dist/tao-pci-"*.zip >/dev/null
156
+
157
+ echo "Scenario 7: preact-store with TypeScript + tailwind builds"
158
+ create_project "TailwindPreactStorePci" "preact+tailwind" "Tailwind + preact-store matrix test"
159
+ (cd "${TEST_ROOT}/TailwindPreactStorePci" && npm install --save "file:$(npm_path "${PREACT_STORE_TGZ}")" --cache "${NPM_CACHE_DIR}" --workspaces=false)
160
+ run_base_build "${TEST_ROOT}/TailwindPreactStorePci"
161
+
162
+ echo ""
163
+ echo "All matrix scenarios passed."
package/src/cli.js CHANGED
@@ -14,7 +14,7 @@ import inquirer from "inquirer";
14
14
  const args = process.argv.slice(2);
15
15
 
16
16
  // PK: retreive dirname, most of the time automaticly inserted in es modules
17
- import { fileURLToPath } from "url";
17
+ import { fileURLToPath, pathToFileURL } from "url";
18
18
  const __filename = fileURLToPath(import.meta.url);
19
19
  const __dirname = path.dirname(__filename);
20
20
 
@@ -115,8 +115,9 @@ function selectOptions(prompt, options) {
115
115
  {
116
116
  type: "list",
117
117
  message: prompt,
118
- name: "values",
119
- choices: options.map((o) => ({ name: o, checked: true })),
118
+ name: "value",
119
+ choices: options,
120
+ default: options[0],
120
121
  },
121
122
  ]);
122
123
  }
@@ -134,6 +135,32 @@ function askOption(prompt) {
134
135
  });
135
136
  }
136
137
 
138
+ function getArgValue(longName, shortName = null) {
139
+ const longEq = args.find((arg) => arg.startsWith(`${longName}=`));
140
+ if (longEq) {
141
+ return longEq.slice(longName.length + 1);
142
+ }
143
+
144
+ const shortEq = shortName ? args.find((arg) => arg.startsWith(`${shortName}=`)) : null;
145
+ if (shortEq) {
146
+ return shortEq.slice(shortName.length + 1);
147
+ }
148
+
149
+ const longIndex = args.findIndex((arg) => arg === longName);
150
+ if (longIndex !== -1 && args[longIndex + 1]) {
151
+ return args[longIndex + 1];
152
+ }
153
+
154
+ if (shortName) {
155
+ const shortIndex = args.findIndex((arg) => arg === shortName);
156
+ if (shortIndex !== -1 && args[shortIndex + 1]) {
157
+ return args[shortIndex + 1];
158
+ }
159
+ }
160
+
161
+ return null;
162
+ }
163
+
137
164
  function createDirectoryIfNotExists(file) {
138
165
  const dir = path.dirname(file);
139
166
  if (!fs.existsSync(dir)) {
@@ -216,14 +243,17 @@ const run = async () => {
216
243
 
217
244
  // prompt for name
218
245
  let pciName =
246
+ getArgValue("--name", "-n") ||
219
247
  (await askOption(
220
248
  "What your pci identifier (typeIdentifier). this can only contain alphanumeric characters (default: myPci) "
221
- )) || "myPci";
222
- let destinationFolder = path.join(process.cwd(), pciName);
223
- if (args.find((arg) => arg.startsWith("--path"))) {
224
- const pathIndex = args.findIndex((arg) => arg.startsWith("--path"));
225
- destinationFolder = path.join(process.cwd(), path.join(args[pathIndex].split("=")[1], pciName));
249
+ )) ||
250
+ "myPci";
251
+ let basePath = process.cwd();
252
+ const pathArg = getArgValue("--path", "-p");
253
+ if (pathArg) {
254
+ basePath = pathArg;
226
255
  }
256
+ let destinationFolder = path.resolve(process.cwd(), basePath, pciName);
227
257
  // if args contains --force then delete the directory if already exists
228
258
  if (args.indexOf("--force") >= 0 || args.indexOf("-f") >= 0) {
229
259
  if (fs.existsSync(destinationFolder)) {
@@ -249,16 +279,27 @@ const run = async () => {
249
279
  }
250
280
  console.log(chalk.green(`pci: ${pciName}`));
251
281
  const description =
282
+ getArgValue("--description", "-dsc") ||
252
283
  (await askOption("What is the description of your pci? (default: pci created with tspci)")) ||
253
284
  "pci created with tspci";
254
285
 
255
- const options = await selectOptions("Type of project?", ["preact+tailwind", "typescript", "javascript"]);
256
- console.log(chalk.green(`Options: ${options.values.length === 0 ? "none" : options.values}`));
286
+ const projectTypes = ["preact+tailwind", "typescript", "javascript"];
287
+ const projectTypeArg = getArgValue("--project-type", "-pt");
288
+ const selectedProjectType = projectTypeArg
289
+ ? projectTypes.find((projectType) => projectType === projectTypeArg)
290
+ : null;
291
+ if (projectTypeArg && !selectedProjectType) {
292
+ console.log(chalk.red(`Unknown project type "${projectTypeArg}". Use one of: ${projectTypes.join(", ")}`));
293
+ return;
294
+ }
295
+ const options = selectedProjectType ? { value: selectedProjectType } : await selectOptions("Type of project?", projectTypes);
296
+ const projectType = options?.value || "typescript";
297
+ console.log(chalk.green(`Option: ${projectType}`));
257
298
  // get relative path to executable
258
299
  const cliPath = __dirname;
259
300
 
260
301
  // create the project folder
261
- fs.mkdirSync(destinationFolder);
302
+ fs.mkdirSync(destinationFolder, { recursive: true });
262
303
 
263
304
  // copy the content of preset folder to the current folder
264
305
  const presetFolder = path.resolve(cliPath, "./../preset/base");
@@ -284,7 +325,7 @@ const run = async () => {
284
325
  }
285
326
  packageJsonContentJson.config.tspci.typeIdentifier = pciName;
286
327
 
287
- if (options.values.includes("preact+tailwind")) {
328
+ if (projectType === "preact+tailwind") {
288
329
  console.log(chalk.green("Adding tailwind css"));
289
330
 
290
331
  // read postcss.config.js
@@ -327,7 +368,7 @@ const run = async () => {
327
368
  copyDir(path.resolve(cliPath, "./../preset/tailwind"), destinationFolder);
328
369
  }
329
370
 
330
- if (options.values.includes("preact+tailwind")) {
371
+ if (projectType === "preact+tailwind") {
331
372
  console.log(chalk.green("Adding preact"));
332
373
  // delete index.ts
333
374
  // fs.rmSync(path.join(destinationFolder, "src/index.ts"), { force: true });
@@ -344,7 +385,7 @@ const run = async () => {
344
385
  fs.writeFileSync(packageJson, newPackageJsonContent);
345
386
  }
346
387
 
347
- if (options.values.includes("typescript")) {
388
+ if (projectType === "typescript") {
348
389
  copyDirAndReplaceInFiles(
349
390
  path.resolve(cliPath, "./../preset/typescript/src"),
350
391
  path.join(destinationFolder, "src"),
@@ -356,7 +397,7 @@ const run = async () => {
356
397
  fs.writeFileSync(packageJson, newPackageJsonContent);
357
398
  }
358
399
 
359
- if (options.values.includes("javascript")) {
400
+ if (projectType === "javascript") {
360
401
  copyDirAndReplaceInFiles(
361
402
  path.resolve(cliPath, "./../preset/javascript/src"),
362
403
  path.join(destinationFolder, "src"),
@@ -384,6 +425,7 @@ const run = async () => {
384
425
  return;
385
426
  } else {
386
427
  console.log(chalk.yellow("Skipping npm install, run npm install in the project folder to install dependencies"));
428
+ return;
387
429
  }
388
430
  }
389
431
 
@@ -394,7 +436,9 @@ const run = async () => {
394
436
  return;
395
437
  }
396
438
  const version = args.indexOf("--next") !== -1 ? "next" : "latest";
397
- const packageFileContent = fs.readFileSync(path.join(PACKAGE_ROOT_PATH, "package.json"), "utf8");
439
+ const passCiFlag = args.indexOf("--ci") !== -1;
440
+ const packageFilePath = path.join(PACKAGE_ROOT_PATH, "package.json");
441
+ const packageFileContent = fs.readFileSync(packageFilePath, "utf8");
398
442
  const packageJson = JSON.parse(packageFileContent);
399
443
 
400
444
  const modifyPci = () => {
@@ -402,10 +446,13 @@ const run = async () => {
402
446
  let init = false;
403
447
  if (fs.existsSync(scriptFile)) {
404
448
  // run init() script
405
- import(`file:///${scriptFile}`).then((script) => {
449
+ import(pathToFileURL(scriptFile).href).then((script) => {
406
450
  if (script.init) {
407
451
  init = true;
408
452
  console.log(chalk.green(`Running init script for ${targetName}`));
453
+ if (passCiFlag) {
454
+ process.argv.push("--ci");
455
+ }
409
456
  script.init();
410
457
  } else {
411
458
  if (!init) {
@@ -423,6 +470,19 @@ const run = async () => {
423
470
  stdio: [0, 1, 2],
424
471
  });
425
472
  }
473
+
474
+ // add a package script so users can export with a single command
475
+ const targetArg = args[targetIndex + 1];
476
+ if (targetArg) {
477
+ packageJson.scripts = packageJson.scripts || {};
478
+ const packageScriptName = `package:${targetArg}`;
479
+ if (!packageJson.scripts[packageScriptName]) {
480
+ packageJson.scripts[packageScriptName] = `tspci --target ${targetArg}`;
481
+ fs.writeFileSync(packageFilePath, JSON.stringify(packageJson, null, 2), "utf8");
482
+ console.log(chalk.green(`Added script "${packageScriptName}" to package.json`));
483
+ }
484
+ }
485
+
426
486
  modifyPci();
427
487
 
428
488
  return;
@@ -451,7 +511,7 @@ const run = async () => {
451
511
  return;
452
512
  }
453
513
  const scriptFile = path.join(path.join(path.join(NODE_MODULES, `${targetName}`), "src"), `createPackage.mjs`);
454
- import(`file:///${scriptFile}`).then((script) => {
514
+ import(pathToFileURL(scriptFile).href).then((script) => {
455
515
  script.bundle();
456
516
  });
457
517
  });
@@ -0,0 +1,91 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import fg from "fast-glob";
4
+
5
+ function hasGlobSyntax(value) {
6
+ return /[*?[\]{}()!+@]/.test(value);
7
+ }
8
+
9
+ function getGlobBase(pattern) {
10
+ const normalized = pattern.replace(/\\/g, "/");
11
+ const parts = normalized.split("/");
12
+ const baseParts = [];
13
+ for (const part of parts) {
14
+ if (hasGlobSyntax(part)) {
15
+ break;
16
+ }
17
+ baseParts.push(part);
18
+ }
19
+ return baseParts.length > 0 ? baseParts.join("/") : ".";
20
+ }
21
+
22
+ function copyDirectoryRecursive(sourceDir, destinationDir) {
23
+ fs.mkdirSync(destinationDir, { recursive: true });
24
+ for (const item of fs.readdirSync(sourceDir, { withFileTypes: true })) {
25
+ const sourcePath = path.join(sourceDir, item.name);
26
+ const destinationPath = path.join(destinationDir, item.name);
27
+ if (item.isDirectory()) {
28
+ copyDirectoryRecursive(sourcePath, destinationPath);
29
+ } else if (item.isFile()) {
30
+ fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
31
+ fs.copyFileSync(sourcePath, destinationPath);
32
+ }
33
+ }
34
+ }
35
+
36
+ async function copyTarget(target, flatten) {
37
+ const srcPattern = target?.src;
38
+ const destination = target?.dest;
39
+ if (!srcPattern || !destination) {
40
+ return;
41
+ }
42
+
43
+ const matches = await fg(srcPattern, { dot: true, onlyFiles: false });
44
+ if (matches.length === 0) {
45
+ return;
46
+ }
47
+
48
+ const basePath = hasGlobSyntax(srcPattern) ? getGlobBase(srcPattern) : null;
49
+
50
+ for (const match of matches) {
51
+ const sourcePath = path.resolve(match);
52
+ const sourceStats = fs.statSync(sourcePath);
53
+ const basename = path.basename(sourcePath);
54
+ const sourceDirname = path.dirname(sourcePath);
55
+
56
+ let destinationFolder = destination;
57
+ if (!flatten && basePath) {
58
+ const relativeDir = path.relative(path.resolve(basePath), sourceDirname);
59
+ destinationFolder = relativeDir ? path.join(destination, relativeDir) : destination;
60
+ }
61
+ const destinationPath = path.join(destinationFolder, basename);
62
+
63
+ if (sourceStats.isDirectory()) {
64
+ copyDirectoryRecursive(sourcePath, destinationPath);
65
+ } else if (sourceStats.isFile()) {
66
+ fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
67
+ fs.copyFileSync(sourcePath, destinationPath);
68
+ }
69
+ }
70
+ }
71
+
72
+ export function copyFiles(options = {}) {
73
+ const targets = Array.isArray(options.targets) ? options.targets : [];
74
+ const flatten = options.flatten !== false;
75
+
76
+ const copyAllTargets = async () => {
77
+ for (const target of targets) {
78
+ await copyTarget(target, flatten);
79
+ }
80
+ };
81
+
82
+ return {
83
+ name: "copy-files",
84
+ async buildStart() {
85
+ await copyAllTargets();
86
+ },
87
+ async writeBundle() {
88
+ await copyAllTargets();
89
+ },
90
+ };
91
+ }
@@ -0,0 +1,56 @@
1
+ import postcss from "postcss";
2
+ import loadPostCssConfig from "postcss-load-config";
3
+
4
+ export function postcssString(options = {}) {
5
+ const extensions = options.extensions || [".css"];
6
+ const configEnabled = options.config !== false;
7
+ const configCache = new Map();
8
+
9
+ const loadConfig = async (cwd) => {
10
+ if (configCache.has(cwd)) {
11
+ return configCache.get(cwd);
12
+ }
13
+
14
+ let loaded = { plugins: [], options: {} };
15
+ if (configEnabled) {
16
+ try {
17
+ const result = await loadPostCssConfig({}, cwd);
18
+ loaded = {
19
+ plugins: result?.plugins || [],
20
+ options: result?.options || {},
21
+ };
22
+ } catch (error) {
23
+ if (!String(error?.message || "").includes("No PostCSS Config found")) {
24
+ throw error;
25
+ }
26
+ }
27
+ }
28
+
29
+ configCache.set(cwd, loaded);
30
+ return loaded;
31
+ };
32
+
33
+ return {
34
+ name: "postcss-string",
35
+ async transform(code, id) {
36
+ const shouldProcess = extensions.some((ext) => id.endsWith(ext));
37
+ if (!shouldProcess) {
38
+ return null;
39
+ }
40
+
41
+ const cwd = process.cwd();
42
+ const { plugins, options: configOptions } = await loadConfig(cwd);
43
+ const result = await postcss(plugins).process(code, {
44
+ ...configOptions,
45
+ from: id,
46
+ to: id,
47
+ map: false,
48
+ });
49
+
50
+ return {
51
+ code: `export default ${JSON.stringify(result.css)};`,
52
+ map: { mappings: "" },
53
+ };
54
+ },
55
+ };
56
+ }
@@ -1,7 +1,7 @@
1
- import copy from "rollup-plugin-copy";
2
1
  import livereload from "rollup-plugin-livereload";
3
2
  // PK: not the official npm package, but a copy, needed watch functionality
4
3
  import { liveServer } from "./rollup-plugin-live-server.js";
4
+ import { copyFiles } from "./rollup-plugin-copy-files.js";
5
5
  import path from "path";
6
6
  import rollupConfig from "./rollup.config.js"; // Make sure this exports an object with `output` and `plugins`
7
7
 
@@ -18,13 +18,12 @@ const livesrvr = liveServer({
18
18
  });
19
19
 
20
20
  // Configure copy plugin to watch for changes
21
- const watchassets = copy({
21
+ const watchassets = copyFiles({
22
22
  targets: [
23
23
  { src: "./src/**/*.json", dest: "dist" },
24
24
  { src: "./src/**/*.svg", dest: "dist" },
25
25
  { src: "./src/**/*.png", dest: "dist" },
26
26
  ],
27
- watch: true, // Enable watching for changes
28
27
  });
29
28
 
30
29
  // Modify the existing Rollup config
@@ -3,12 +3,10 @@ import typescript from "@rollup/plugin-typescript";
3
3
  import alias from "@rollup/plugin-alias";
4
4
  import image from "@rollup/plugin-image";
5
5
  import replace from "@rollup/plugin-replace";
6
- import postcss from "rollup-plugin-postcss";
7
6
  import commonjs from "@rollup/plugin-commonjs"; // added this when added react-redux, gave an ' 'default' is not exported by node_modules/prop-types/index.js'
8
7
  import json from "@rollup/plugin-json";
9
-
10
- // copy, builtins, globals and litTailwind were added for use in simple bundle tool to make qti players
11
- import copy from "rollup-plugin-copy"; // copying assets to the assets folder in dist ( not for PCI, those assets are bundled )
8
+ import { postcssString } from "./rollup-plugin-postcss-string.js";
9
+ import { copyFiles } from "./rollup-plugin-copy-files.js";
12
10
 
13
11
  import fs from "fs";
14
12
  import path from "path";
@@ -59,13 +57,10 @@ export default {
59
57
  tsconfig: `${PACKAGE_ROOT_PATH}/tsconfig.json`,
60
58
  declarationDir: `${PACKAGE_ROOT_PATH}/dist`
61
59
  }),
62
- postcss({
63
- extract: false, // not extracting embeds the js in the build, which is what we need for shipping it
64
- modules: false, // tried modules at first, could work, but not with html in the qti file
65
- inject: false, // does not inject the imported styles in the head
60
+ postcssString({
66
61
  extensions: [".css"],
67
62
  }),
68
- copy({
63
+ copyFiles({
69
64
  targets: [
70
65
  { src: "src/assets", dest: "dist" }, // copy asses to dist folder
71
66
  ],