create-qpq-app 0.1.5 → 0.1.7

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.
@@ -8,17 +8,120 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
11
14
  Object.defineProperty(exports, "__esModule", { value: true });
12
15
  exports.transpileToJavaScript = void 0;
16
+ const fs_1 = __importDefault(require("fs"));
17
+ const path_1 = __importDefault(require("path"));
18
+ const files_1 = require("../lib/files");
13
19
  const types_1 = require("../types");
14
- // Placeholder for the JavaScript flavour a later pass will transpile the
15
- // scaffolded TypeScript sources here. For now it's an honest no-op so the
16
- // language answer already threads through the pipeline.
20
+ // Convert the scaffolded TypeScript app to JavaScript: per-file type
21
+ // stripping (JSX preserved, comments kept), then remove the TypeScript
22
+ // build plumbing the app no longer needs.
23
+ //
24
+ // The qpq toolchain runs the result as-is: the CLI's ts-node require hook
25
+ // compiles the app's ESM-syntax .js at load time (allowJs, added below), and
26
+ // rspack bundles .js/.jsx natively.
17
27
  exports.transpileToJavaScript = {
18
28
  name: 'Converting to JavaScript',
19
29
  shouldRun: (answers) => answers.language === types_1.AppLanguage.javascript,
20
- run: () => __awaiter(void 0, void 0, void 0, function* () {
21
- console.log(' JavaScript output is not available yet — generating TypeScript for now.');
30
+ run: (_a) => __awaiter(void 0, [_a], void 0, function* ({ targetDirectory }) {
31
+ var _b, _c, _d;
32
+ // Lazy so TypeScript scaffolds never load the compiler.
33
+ /* eslint-disable @typescript-eslint/no-require-imports */
34
+ const ts = require('typescript');
35
+ const prettier = require('prettier');
36
+ /* eslint-enable @typescript-eslint/no-require-imports */
37
+ // The transpiler re-prints files in its own style; format the output with
38
+ // the app's own prettier settings so generated code matches the repo.
39
+ const prettierRcPath = path_1.default.join(targetDirectory, '.prettierrc');
40
+ const prettierOptions = fs_1.default.existsSync(prettierRcPath) ? (0, files_1.readJsonFile)(prettierRcPath) : {};
41
+ const compilerOptions = {
42
+ target: ts.ScriptTarget.ESNext,
43
+ module: ts.ModuleKind.ESNext, // keep import/export statements as written
44
+ jsx: ts.JsxEmit.Preserve,
45
+ removeComments: false,
46
+ };
47
+ let converted = 0;
48
+ for (const filePath of (0, files_1.listFilesRecursive)(targetDirectory)) {
49
+ if (filePath.endsWith('.d.ts')) {
50
+ fs_1.default.rmSync(filePath);
51
+ continue;
52
+ }
53
+ const isTsx = filePath.endsWith('.tsx');
54
+ if (!isTsx && !filePath.endsWith('.ts')) {
55
+ continue;
56
+ }
57
+ const source = fs_1.default.readFileSync(filePath, 'utf8');
58
+ const { outputText } = ts.transpileModule(source, { compilerOptions, fileName: filePath });
59
+ // Type-only statements leave blank gaps behind — collapse runs of them.
60
+ const collapsed = outputText.replace(/\n{3,}/g, '\n\n');
61
+ const outputPath = filePath.replace(/\.tsx?$/, isTsx ? '.jsx' : '.js');
62
+ const output = yield prettier.format(collapsed, Object.assign(Object.assign({}, prettierOptions), { filepath: outputPath }));
63
+ fs_1.default.writeFileSync(outputPath, output);
64
+ fs_1.default.rmSync(filePath);
65
+ converted += 1;
66
+ }
67
+ // TypeScript build plumbing: tsconfigs, the ambient types/ dir, and the
68
+ // per-package tsc scripts all go. Lib packages no longer build — their
69
+ // package.json main points straight at src.
70
+ let libsRepointed = 0;
71
+ for (const filePath of (0, files_1.listFilesRecursive)(targetDirectory)) {
72
+ const baseName = path_1.default.basename(filePath);
73
+ if (baseName.startsWith('tsconfig') && baseName.endsWith('.json') && filePath !== path_1.default.join(targetDirectory, 'tsconfig.json')) {
74
+ fs_1.default.rmSync(filePath);
75
+ continue;
76
+ }
77
+ if (baseName === 'package.json' && filePath !== path_1.default.join(targetDirectory, 'package.json')) {
78
+ const packageJson = (0, files_1.readJsonFile)(filePath);
79
+ let changed = false;
80
+ if ((_b = packageJson.scripts) === null || _b === void 0 ? void 0 : _b['validate-ts']) {
81
+ delete packageJson.scripts['validate-ts'];
82
+ changed = true;
83
+ }
84
+ // Lib packages (identified by their tsc build) get served from
85
+ // source. A tsc build with an unexpected main means the template's
86
+ // conventions moved — fail loudly rather than scaffold a broken app.
87
+ if (((_c = packageJson.scripts) === null || _c === void 0 ? void 0 : _c.build) === 'tsc -b') {
88
+ if (packageJson.main !== './dist/src/index.js') {
89
+ throw new Error(`${filePath} has a tsc build but main is "${packageJson.main}" (expected "./dist/src/index.js") — update transpileToJavaScript for the new template convention.`);
90
+ }
91
+ packageJson.main = './src/index.js';
92
+ delete packageJson.typings;
93
+ delete packageJson.scripts.build;
94
+ packageJson.exports = { '.': { default: './src/index.js' } };
95
+ packageJson.files = ['src'];
96
+ changed = true;
97
+ libsRepointed += 1;
98
+ }
99
+ if (changed) {
100
+ (0, files_1.writeJsonFile)(filePath, packageJson);
101
+ }
102
+ }
103
+ }
104
+ if (converted === 0 || libsRepointed === 0) {
105
+ throw new Error(`JavaScript conversion looks wrong: ${converted} files transpiled, ${libsRepointed} lib packages repointed — has the template changed shape?`);
106
+ }
107
+ // The ROOT tsconfig.json stays — the qpq CLI's ts-node require hook reads
108
+ // its ts-node block, and allowJs makes that hook compile the app's
109
+ // ESM-syntax .js files (imports, __dirname) at load time.
110
+ fs_1.default.rmSync(path_1.default.join(targetDirectory, 'types'), { recursive: true, force: true });
111
+ fs_1.default.rmSync(path_1.default.join(targetDirectory, 'tsconfig.base.json'), { force: true });
112
+ const rootTsconfigPath = path_1.default.join(targetDirectory, 'tsconfig.json');
113
+ const rootTsconfig = (0, files_1.readJsonFile)(rootTsconfigPath);
114
+ rootTsconfig['ts-node'].compilerOptions.allowJs = true;
115
+ // Scope the hook to this repo — allowJs must never recompile quidproquo
116
+ // libs living outside node_modules (file:-linked dev setups).
117
+ rootTsconfig['ts-node'].scope = true;
118
+ rootTsconfig['ts-node'].scopeDir = '.';
119
+ (0, files_1.writeJsonFile)(rootTsconfigPath, rootTsconfig);
120
+ const rootPackageJsonPath = path_1.default.join(targetDirectory, 'package.json');
121
+ const rootPackageJson = (0, files_1.readJsonFile)(rootPackageJsonPath);
122
+ (_d = rootPackageJson.scripts) === null || _d === void 0 ? true : delete _d['validate-ts'];
123
+ (0, files_1.writeJsonFile)(rootPackageJsonPath, rootPackageJson);
124
+ console.log(` Converted ${converted} files to JavaScript.`);
22
125
  }),
23
126
  };
24
127
  //# sourceMappingURL=008_transpileToJavaScript.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"008_transpileToJavaScript.js","sourceRoot":"","sources":["../../../src/steps/008_transpileToJavaScript.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,oCAAyD;AAEzD,2EAA2E;AAC3E,0EAA0E;AAC1E,wDAAwD;AAC3C,QAAA,qBAAqB,GAAqB;IACrD,IAAI,EAAE,0BAA0B;IAEhC,SAAS,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,KAAK,mBAAW,CAAC,UAAU;IAEnE,GAAG,EAAE,GAAS,EAAE;QACd,OAAO,CAAC,GAAG,CAAC,2EAA2E,CAAC,CAAC;IAC3F,CAAC,CAAA;CACF,CAAC","sourcesContent":["import { AppLanguage, CreateQpqAppStep } from '../types';\n\n// Placeholder for the JavaScript flavour — a later pass will transpile the\n// scaffolded TypeScript sources here. For now it's an honest no-op so the\n// language answer already threads through the pipeline.\nexport const transpileToJavaScript: CreateQpqAppStep = {\n name: 'Converting to JavaScript',\n\n shouldRun: (answers) => answers.language === AppLanguage.javascript,\n\n run: async () => {\n console.log(' JavaScript output is not available yet — generating TypeScript for now.');\n },\n};\n"]}
1
+ {"version":3,"file":"008_transpileToJavaScript.js","sourceRoot":"","sources":["../../../src/steps/008_transpileToJavaScript.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'fs';\nimport path from 'path';\n\nimport { listFilesRecursive, readJsonFile, writeJsonFile } from '../lib/files';\nimport { AppLanguage, CreateQpqAppStep } from '../types';\n\n// Convert the scaffolded TypeScript app to JavaScript: per-file type\n// stripping (JSX preserved, comments kept), then remove the TypeScript\n// build plumbing the app no longer needs.\n//\n// The qpq toolchain runs the result as-is: the CLI's ts-node require hook\n// compiles the app's ESM-syntax .js at load time (allowJs, added below), and\n// rspack bundles .js/.jsx natively.\nexport const transpileToJavaScript: CreateQpqAppStep = {\n name: 'Converting to JavaScript',\n\n shouldRun: (answers) => answers.language === AppLanguage.javascript,\n\n run: async ({ targetDirectory }) => {\n // Lazy so TypeScript scaffolds never load the compiler.\n /* eslint-disable @typescript-eslint/no-require-imports */\n const ts = require('typescript') as typeof import('typescript');\n const prettier = require('prettier') as typeof import('prettier');\n /* eslint-enable @typescript-eslint/no-require-imports */\n\n // The transpiler re-prints files in its own style; format the output with\n // the app's own prettier settings so generated code matches the repo.\n const prettierRcPath = path.join(targetDirectory, '.prettierrc');\n const prettierOptions = fs.existsSync(prettierRcPath) ? readJsonFile(prettierRcPath) : {};\n\n const compilerOptions: import('typescript').CompilerOptions = {\n target: ts.ScriptTarget.ESNext,\n module: ts.ModuleKind.ESNext, // keep import/export statements as written\n jsx: ts.JsxEmit.Preserve,\n removeComments: false,\n };\n\n let converted = 0;\n for (const filePath of listFilesRecursive(targetDirectory)) {\n if (filePath.endsWith('.d.ts')) {\n fs.rmSync(filePath);\n continue;\n }\n\n const isTsx = filePath.endsWith('.tsx');\n if (!isTsx && !filePath.endsWith('.ts')) {\n continue;\n }\n\n const source = fs.readFileSync(filePath, 'utf8');\n const { outputText } = ts.transpileModule(source, { compilerOptions, fileName: filePath });\n\n // Type-only statements leave blank gaps behind — collapse runs of them.\n const collapsed = outputText.replace(/\\n{3,}/g, '\\n\\n');\n\n const outputPath = filePath.replace(/\\.tsx?$/, isTsx ? '.jsx' : '.js');\n const output = await prettier.format(collapsed, { ...prettierOptions, filepath: outputPath });\n\n fs.writeFileSync(outputPath, output);\n fs.rmSync(filePath);\n converted += 1;\n }\n\n // TypeScript build plumbing: tsconfigs, the ambient types/ dir, and the\n // per-package tsc scripts all go. Lib packages no longer build — their\n // package.json main points straight at src.\n let libsRepointed = 0;\n for (const filePath of listFilesRecursive(targetDirectory)) {\n const baseName = path.basename(filePath);\n\n if (baseName.startsWith('tsconfig') && baseName.endsWith('.json') && filePath !== path.join(targetDirectory, 'tsconfig.json')) {\n fs.rmSync(filePath);\n continue;\n }\n\n if (baseName === 'package.json' && filePath !== path.join(targetDirectory, 'package.json')) {\n const packageJson = readJsonFile(filePath);\n let changed = false;\n\n if (packageJson.scripts?.['validate-ts']) {\n delete packageJson.scripts['validate-ts'];\n changed = true;\n }\n\n // Lib packages (identified by their tsc build) get served from\n // source. A tsc build with an unexpected main means the template's\n // conventions moved — fail loudly rather than scaffold a broken app.\n if (packageJson.scripts?.build === 'tsc -b') {\n if (packageJson.main !== './dist/src/index.js') {\n throw new Error(\n `${filePath} has a tsc build but main is \"${packageJson.main}\" (expected \"./dist/src/index.js\") — update transpileToJavaScript for the new template convention.`,\n );\n }\n\n packageJson.main = './src/index.js';\n delete packageJson.typings;\n delete packageJson.scripts.build;\n packageJson.exports = { '.': { default: './src/index.js' } };\n packageJson.files = ['src'];\n changed = true;\n libsRepointed += 1;\n }\n\n if (changed) {\n writeJsonFile(filePath, packageJson);\n }\n }\n }\n\n if (converted === 0 || libsRepointed === 0) {\n throw new Error(\n `JavaScript conversion looks wrong: ${converted} files transpiled, ${libsRepointed} lib packages repointed — has the template changed shape?`,\n );\n }\n\n // The ROOT tsconfig.json stays — the qpq CLI's ts-node require hook reads\n // its ts-node block, and allowJs makes that hook compile the app's\n // ESM-syntax .js files (imports, __dirname) at load time.\n fs.rmSync(path.join(targetDirectory, 'types'), { recursive: true, force: true });\n fs.rmSync(path.join(targetDirectory, 'tsconfig.base.json'), { force: true });\n\n const rootTsconfigPath = path.join(targetDirectory, 'tsconfig.json');\n const rootTsconfig = readJsonFile(rootTsconfigPath);\n rootTsconfig['ts-node'].compilerOptions.allowJs = true;\n // Scope the hook to this repo — allowJs must never recompile quidproquo\n // libs living outside node_modules (file:-linked dev setups).\n rootTsconfig['ts-node'].scope = true;\n rootTsconfig['ts-node'].scopeDir = '.';\n writeJsonFile(rootTsconfigPath, rootTsconfig);\n\n const rootPackageJsonPath = path.join(targetDirectory, 'package.json');\n const rootPackageJson = readJsonFile(rootPackageJsonPath);\n delete rootPackageJson.scripts?.['validate-ts'];\n writeJsonFile(rootPackageJsonPath, rootPackageJson);\n\n console.log(` Converted ${converted} files to JavaScript.`);\n },\n};\n"]}
@@ -23,7 +23,7 @@ deploys as a single docker image.
23
23
  Next steps:
24
24
 
25
25
  cd ${answers.appName}
26
- ${install} npm run go:dev # local dev server on http://localhost:8080
26
+ ${install} npm run go:dev # api on http://localhost:8080, web on http://localhost:3080
27
27
  npm run go # build the docker image (then run the printed docker command)
28
28
 
29
29
  Deploy config lives in apps/${answers.appName}/deploy.config.json (domain: ${answers.domain}).
@@ -1 +1 @@
1
- {"version":3,"file":"013_printNextSteps.js","sourceRoot":"","sources":["../../../src/steps/013_printNextSteps.ts"],"names":[],"mappings":";;;;;;;;;;;;AAEa,QAAA,cAAc,GAAqB;IAC9C,IAAI,EAAE,MAAM;IAEZ,GAAG,EAAE,KAAoB,EAAE,4CAAf,EAAE,OAAO,EAAE;QACrB,MAAM,OAAO,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,kCAAkC,CAAC;QAEtF,OAAO,CAAC,GAAG,CAAC;UACN,OAAO,CAAC,OAAO;;;;;;;OAOlB,OAAO,CAAC,OAAO;EACpB,OAAO;;;8BAGqB,OAAO,CAAC,OAAO,gCAAgC,OAAO,CAAC,MAAM;CAC1F,CAAC,CAAC;IACD,CAAC,CAAA;CACF,CAAC","sourcesContent":["import { CreateQpqAppStep } from '../types';\n\nexport const printNextSteps: CreateQpqAppStep = {\n name: 'Done',\n\n run: async ({ answers }) => {\n const install = answers.installDependencies ? '' : ' npm install\\n npm run build\\n';\n\n console.log(`\nCreated ${answers.appName}!\n\nYour app has five services — admin, auth, design, shell and todo — and\ndeploys as a single docker image.\n\nNext steps:\n\n cd ${answers.appName}\n${install} npm run go:dev # local dev server on http://localhost:8080\n npm run go # build the docker image (then run the printed docker command)\n\nDeploy config lives in apps/${answers.appName}/deploy.config.json (domain: ${answers.domain}).\n`);\n },\n};\n"]}
1
+ {"version":3,"file":"013_printNextSteps.js","sourceRoot":"","sources":["../../../src/steps/013_printNextSteps.ts"],"names":[],"mappings":";;;;;;;;;;;;AAEa,QAAA,cAAc,GAAqB;IAC9C,IAAI,EAAE,MAAM;IAEZ,GAAG,EAAE,KAAoB,EAAE,4CAAf,EAAE,OAAO,EAAE;QACrB,MAAM,OAAO,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,kCAAkC,CAAC;QAEtF,OAAO,CAAC,GAAG,CAAC;UACN,OAAO,CAAC,OAAO;;;;;;;OAOlB,OAAO,CAAC,OAAO;EACpB,OAAO;;;8BAGqB,OAAO,CAAC,OAAO,gCAAgC,OAAO,CAAC,MAAM;CAC1F,CAAC,CAAC;IACD,CAAC,CAAA;CACF,CAAC","sourcesContent":["import { CreateQpqAppStep } from '../types';\n\nexport const printNextSteps: CreateQpqAppStep = {\n name: 'Done',\n\n run: async ({ answers }) => {\n const install = answers.installDependencies ? '' : ' npm install\\n npm run build\\n';\n\n console.log(`\nCreated ${answers.appName}!\n\nYour app has five services — admin, auth, design, shell and todo — and\ndeploys as a single docker image.\n\nNext steps:\n\n cd ${answers.appName}\n${install} npm run go:dev # api on http://localhost:8080, web on http://localhost:3080\n npm run go # build the docker image (then run the printed docker command)\n\nDeploy config lives in apps/${answers.appName}/deploy.config.json (domain: ${answers.domain}).\n`);\n },\n};\n"]}
@@ -1,12 +1,111 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { listFilesRecursive, readJsonFile, writeJsonFile } from '../lib/files';
1
4
  import { AppLanguage } from '../types';
2
- // Placeholder for the JavaScript flavour a later pass will transpile the
3
- // scaffolded TypeScript sources here. For now it's an honest no-op so the
4
- // language answer already threads through the pipeline.
5
+ // Convert the scaffolded TypeScript app to JavaScript: per-file type
6
+ // stripping (JSX preserved, comments kept), then remove the TypeScript
7
+ // build plumbing the app no longer needs.
8
+ //
9
+ // The qpq toolchain runs the result as-is: the CLI's ts-node require hook
10
+ // compiles the app's ESM-syntax .js at load time (allowJs, added below), and
11
+ // rspack bundles .js/.jsx natively.
5
12
  export const transpileToJavaScript = {
6
13
  name: 'Converting to JavaScript',
7
14
  shouldRun: (answers) => answers.language === AppLanguage.javascript,
8
- run: async () => {
9
- console.log(' JavaScript output is not available yet generating TypeScript for now.');
15
+ run: async ({ targetDirectory }) => {
16
+ // Lazy so TypeScript scaffolds never load the compiler.
17
+ /* eslint-disable @typescript-eslint/no-require-imports */
18
+ const ts = require('typescript');
19
+ const prettier = require('prettier');
20
+ /* eslint-enable @typescript-eslint/no-require-imports */
21
+ // The transpiler re-prints files in its own style; format the output with
22
+ // the app's own prettier settings so generated code matches the repo.
23
+ const prettierRcPath = path.join(targetDirectory, '.prettierrc');
24
+ const prettierOptions = fs.existsSync(prettierRcPath) ? readJsonFile(prettierRcPath) : {};
25
+ const compilerOptions = {
26
+ target: ts.ScriptTarget.ESNext,
27
+ module: ts.ModuleKind.ESNext, // keep import/export statements as written
28
+ jsx: ts.JsxEmit.Preserve,
29
+ removeComments: false,
30
+ };
31
+ let converted = 0;
32
+ for (const filePath of listFilesRecursive(targetDirectory)) {
33
+ if (filePath.endsWith('.d.ts')) {
34
+ fs.rmSync(filePath);
35
+ continue;
36
+ }
37
+ const isTsx = filePath.endsWith('.tsx');
38
+ if (!isTsx && !filePath.endsWith('.ts')) {
39
+ continue;
40
+ }
41
+ const source = fs.readFileSync(filePath, 'utf8');
42
+ const { outputText } = ts.transpileModule(source, { compilerOptions, fileName: filePath });
43
+ // Type-only statements leave blank gaps behind — collapse runs of them.
44
+ const collapsed = outputText.replace(/\n{3,}/g, '\n\n');
45
+ const outputPath = filePath.replace(/\.tsx?$/, isTsx ? '.jsx' : '.js');
46
+ const output = await prettier.format(collapsed, { ...prettierOptions, filepath: outputPath });
47
+ fs.writeFileSync(outputPath, output);
48
+ fs.rmSync(filePath);
49
+ converted += 1;
50
+ }
51
+ // TypeScript build plumbing: tsconfigs, the ambient types/ dir, and the
52
+ // per-package tsc scripts all go. Lib packages no longer build — their
53
+ // package.json main points straight at src.
54
+ let libsRepointed = 0;
55
+ for (const filePath of listFilesRecursive(targetDirectory)) {
56
+ const baseName = path.basename(filePath);
57
+ if (baseName.startsWith('tsconfig') && baseName.endsWith('.json') && filePath !== path.join(targetDirectory, 'tsconfig.json')) {
58
+ fs.rmSync(filePath);
59
+ continue;
60
+ }
61
+ if (baseName === 'package.json' && filePath !== path.join(targetDirectory, 'package.json')) {
62
+ const packageJson = readJsonFile(filePath);
63
+ let changed = false;
64
+ if (packageJson.scripts?.['validate-ts']) {
65
+ delete packageJson.scripts['validate-ts'];
66
+ changed = true;
67
+ }
68
+ // Lib packages (identified by their tsc build) get served from
69
+ // source. A tsc build with an unexpected main means the template's
70
+ // conventions moved — fail loudly rather than scaffold a broken app.
71
+ if (packageJson.scripts?.build === 'tsc -b') {
72
+ if (packageJson.main !== './dist/src/index.js') {
73
+ throw new Error(`${filePath} has a tsc build but main is "${packageJson.main}" (expected "./dist/src/index.js") — update transpileToJavaScript for the new template convention.`);
74
+ }
75
+ packageJson.main = './src/index.js';
76
+ delete packageJson.typings;
77
+ delete packageJson.scripts.build;
78
+ packageJson.exports = { '.': { default: './src/index.js' } };
79
+ packageJson.files = ['src'];
80
+ changed = true;
81
+ libsRepointed += 1;
82
+ }
83
+ if (changed) {
84
+ writeJsonFile(filePath, packageJson);
85
+ }
86
+ }
87
+ }
88
+ if (converted === 0 || libsRepointed === 0) {
89
+ throw new Error(`JavaScript conversion looks wrong: ${converted} files transpiled, ${libsRepointed} lib packages repointed — has the template changed shape?`);
90
+ }
91
+ // The ROOT tsconfig.json stays — the qpq CLI's ts-node require hook reads
92
+ // its ts-node block, and allowJs makes that hook compile the app's
93
+ // ESM-syntax .js files (imports, __dirname) at load time.
94
+ fs.rmSync(path.join(targetDirectory, 'types'), { recursive: true, force: true });
95
+ fs.rmSync(path.join(targetDirectory, 'tsconfig.base.json'), { force: true });
96
+ const rootTsconfigPath = path.join(targetDirectory, 'tsconfig.json');
97
+ const rootTsconfig = readJsonFile(rootTsconfigPath);
98
+ rootTsconfig['ts-node'].compilerOptions.allowJs = true;
99
+ // Scope the hook to this repo — allowJs must never recompile quidproquo
100
+ // libs living outside node_modules (file:-linked dev setups).
101
+ rootTsconfig['ts-node'].scope = true;
102
+ rootTsconfig['ts-node'].scopeDir = '.';
103
+ writeJsonFile(rootTsconfigPath, rootTsconfig);
104
+ const rootPackageJsonPath = path.join(targetDirectory, 'package.json');
105
+ const rootPackageJson = readJsonFile(rootPackageJsonPath);
106
+ delete rootPackageJson.scripts?.['validate-ts'];
107
+ writeJsonFile(rootPackageJsonPath, rootPackageJson);
108
+ console.log(` Converted ${converted} files to JavaScript.`);
10
109
  },
11
110
  };
12
111
  //# sourceMappingURL=008_transpileToJavaScript.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"008_transpileToJavaScript.js","sourceRoot":"","sources":["../../../src/steps/008_transpileToJavaScript.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAoB,MAAM,UAAU,CAAC;AAEzD,2EAA2E;AAC3E,0EAA0E;AAC1E,wDAAwD;AACxD,MAAM,CAAC,MAAM,qBAAqB,GAAqB;IACrD,IAAI,EAAE,0BAA0B;IAEhC,SAAS,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,KAAK,WAAW,CAAC,UAAU;IAEnE,GAAG,EAAE,KAAK,IAAI,EAAE;QACd,OAAO,CAAC,GAAG,CAAC,2EAA2E,CAAC,CAAC;IAC3F,CAAC;CACF,CAAC","sourcesContent":["import { AppLanguage, CreateQpqAppStep } from '../types';\n\n// Placeholder for the JavaScript flavour — a later pass will transpile the\n// scaffolded TypeScript sources here. For now it's an honest no-op so the\n// language answer already threads through the pipeline.\nexport const transpileToJavaScript: CreateQpqAppStep = {\n name: 'Converting to JavaScript',\n\n shouldRun: (answers) => answers.language === AppLanguage.javascript,\n\n run: async () => {\n console.log(' JavaScript output is not available yet — generating TypeScript for now.');\n },\n};\n"]}
1
+ {"version":3,"file":"008_transpileToJavaScript.js","sourceRoot":"","sources":["../../../src/steps/008_transpileToJavaScript.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'fs';\nimport path from 'path';\n\nimport { listFilesRecursive, readJsonFile, writeJsonFile } from '../lib/files';\nimport { AppLanguage, CreateQpqAppStep } from '../types';\n\n// Convert the scaffolded TypeScript app to JavaScript: per-file type\n// stripping (JSX preserved, comments kept), then remove the TypeScript\n// build plumbing the app no longer needs.\n//\n// The qpq toolchain runs the result as-is: the CLI's ts-node require hook\n// compiles the app's ESM-syntax .js at load time (allowJs, added below), and\n// rspack bundles .js/.jsx natively.\nexport const transpileToJavaScript: CreateQpqAppStep = {\n name: 'Converting to JavaScript',\n\n shouldRun: (answers) => answers.language === AppLanguage.javascript,\n\n run: async ({ targetDirectory }) => {\n // Lazy so TypeScript scaffolds never load the compiler.\n /* eslint-disable @typescript-eslint/no-require-imports */\n const ts = require('typescript') as typeof import('typescript');\n const prettier = require('prettier') as typeof import('prettier');\n /* eslint-enable @typescript-eslint/no-require-imports */\n\n // The transpiler re-prints files in its own style; format the output with\n // the app's own prettier settings so generated code matches the repo.\n const prettierRcPath = path.join(targetDirectory, '.prettierrc');\n const prettierOptions = fs.existsSync(prettierRcPath) ? readJsonFile(prettierRcPath) : {};\n\n const compilerOptions: import('typescript').CompilerOptions = {\n target: ts.ScriptTarget.ESNext,\n module: ts.ModuleKind.ESNext, // keep import/export statements as written\n jsx: ts.JsxEmit.Preserve,\n removeComments: false,\n };\n\n let converted = 0;\n for (const filePath of listFilesRecursive(targetDirectory)) {\n if (filePath.endsWith('.d.ts')) {\n fs.rmSync(filePath);\n continue;\n }\n\n const isTsx = filePath.endsWith('.tsx');\n if (!isTsx && !filePath.endsWith('.ts')) {\n continue;\n }\n\n const source = fs.readFileSync(filePath, 'utf8');\n const { outputText } = ts.transpileModule(source, { compilerOptions, fileName: filePath });\n\n // Type-only statements leave blank gaps behind — collapse runs of them.\n const collapsed = outputText.replace(/\\n{3,}/g, '\\n\\n');\n\n const outputPath = filePath.replace(/\\.tsx?$/, isTsx ? '.jsx' : '.js');\n const output = await prettier.format(collapsed, { ...prettierOptions, filepath: outputPath });\n\n fs.writeFileSync(outputPath, output);\n fs.rmSync(filePath);\n converted += 1;\n }\n\n // TypeScript build plumbing: tsconfigs, the ambient types/ dir, and the\n // per-package tsc scripts all go. Lib packages no longer build — their\n // package.json main points straight at src.\n let libsRepointed = 0;\n for (const filePath of listFilesRecursive(targetDirectory)) {\n const baseName = path.basename(filePath);\n\n if (baseName.startsWith('tsconfig') && baseName.endsWith('.json') && filePath !== path.join(targetDirectory, 'tsconfig.json')) {\n fs.rmSync(filePath);\n continue;\n }\n\n if (baseName === 'package.json' && filePath !== path.join(targetDirectory, 'package.json')) {\n const packageJson = readJsonFile(filePath);\n let changed = false;\n\n if (packageJson.scripts?.['validate-ts']) {\n delete packageJson.scripts['validate-ts'];\n changed = true;\n }\n\n // Lib packages (identified by their tsc build) get served from\n // source. A tsc build with an unexpected main means the template's\n // conventions moved — fail loudly rather than scaffold a broken app.\n if (packageJson.scripts?.build === 'tsc -b') {\n if (packageJson.main !== './dist/src/index.js') {\n throw new Error(\n `${filePath} has a tsc build but main is \"${packageJson.main}\" (expected \"./dist/src/index.js\") — update transpileToJavaScript for the new template convention.`,\n );\n }\n\n packageJson.main = './src/index.js';\n delete packageJson.typings;\n delete packageJson.scripts.build;\n packageJson.exports = { '.': { default: './src/index.js' } };\n packageJson.files = ['src'];\n changed = true;\n libsRepointed += 1;\n }\n\n if (changed) {\n writeJsonFile(filePath, packageJson);\n }\n }\n }\n\n if (converted === 0 || libsRepointed === 0) {\n throw new Error(\n `JavaScript conversion looks wrong: ${converted} files transpiled, ${libsRepointed} lib packages repointed — has the template changed shape?`,\n );\n }\n\n // The ROOT tsconfig.json stays — the qpq CLI's ts-node require hook reads\n // its ts-node block, and allowJs makes that hook compile the app's\n // ESM-syntax .js files (imports, __dirname) at load time.\n fs.rmSync(path.join(targetDirectory, 'types'), { recursive: true, force: true });\n fs.rmSync(path.join(targetDirectory, 'tsconfig.base.json'), { force: true });\n\n const rootTsconfigPath = path.join(targetDirectory, 'tsconfig.json');\n const rootTsconfig = readJsonFile(rootTsconfigPath);\n rootTsconfig['ts-node'].compilerOptions.allowJs = true;\n // Scope the hook to this repo — allowJs must never recompile quidproquo\n // libs living outside node_modules (file:-linked dev setups).\n rootTsconfig['ts-node'].scope = true;\n rootTsconfig['ts-node'].scopeDir = '.';\n writeJsonFile(rootTsconfigPath, rootTsconfig);\n\n const rootPackageJsonPath = path.join(targetDirectory, 'package.json');\n const rootPackageJson = readJsonFile(rootPackageJsonPath);\n delete rootPackageJson.scripts?.['validate-ts'];\n writeJsonFile(rootPackageJsonPath, rootPackageJson);\n\n console.log(` Converted ${converted} files to JavaScript.`);\n },\n};\n"]}
@@ -11,7 +11,7 @@ deploys as a single docker image.
11
11
  Next steps:
12
12
 
13
13
  cd ${answers.appName}
14
- ${install} npm run go:dev # local dev server on http://localhost:8080
14
+ ${install} npm run go:dev # api on http://localhost:8080, web on http://localhost:3080
15
15
  npm run go # build the docker image (then run the printed docker command)
16
16
 
17
17
  Deploy config lives in apps/${answers.appName}/deploy.config.json (domain: ${answers.domain}).
@@ -1 +1 @@
1
- {"version":3,"file":"013_printNextSteps.js","sourceRoot":"","sources":["../../../src/steps/013_printNextSteps.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,cAAc,GAAqB;IAC9C,IAAI,EAAE,MAAM;IAEZ,GAAG,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;QACzB,MAAM,OAAO,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,kCAAkC,CAAC;QAEtF,OAAO,CAAC,GAAG,CAAC;UACN,OAAO,CAAC,OAAO;;;;;;;OAOlB,OAAO,CAAC,OAAO;EACpB,OAAO;;;8BAGqB,OAAO,CAAC,OAAO,gCAAgC,OAAO,CAAC,MAAM;CAC1F,CAAC,CAAC;IACD,CAAC;CACF,CAAC","sourcesContent":["import { CreateQpqAppStep } from '../types';\n\nexport const printNextSteps: CreateQpqAppStep = {\n name: 'Done',\n\n run: async ({ answers }) => {\n const install = answers.installDependencies ? '' : ' npm install\\n npm run build\\n';\n\n console.log(`\nCreated ${answers.appName}!\n\nYour app has five services — admin, auth, design, shell and todo — and\ndeploys as a single docker image.\n\nNext steps:\n\n cd ${answers.appName}\n${install} npm run go:dev # local dev server on http://localhost:8080\n npm run go # build the docker image (then run the printed docker command)\n\nDeploy config lives in apps/${answers.appName}/deploy.config.json (domain: ${answers.domain}).\n`);\n },\n};\n"]}
1
+ {"version":3,"file":"013_printNextSteps.js","sourceRoot":"","sources":["../../../src/steps/013_printNextSteps.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,cAAc,GAAqB;IAC9C,IAAI,EAAE,MAAM;IAEZ,GAAG,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;QACzB,MAAM,OAAO,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,kCAAkC,CAAC;QAEtF,OAAO,CAAC,GAAG,CAAC;UACN,OAAO,CAAC,OAAO;;;;;;;OAOlB,OAAO,CAAC,OAAO;EACpB,OAAO;;;8BAGqB,OAAO,CAAC,OAAO,gCAAgC,OAAO,CAAC,MAAM;CAC1F,CAAC,CAAC;IACD,CAAC;CACF,CAAC","sourcesContent":["import { CreateQpqAppStep } from '../types';\n\nexport const printNextSteps: CreateQpqAppStep = {\n name: 'Done',\n\n run: async ({ answers }) => {\n const install = answers.installDependencies ? '' : ' npm install\\n npm run build\\n';\n\n console.log(`\nCreated ${answers.appName}!\n\nYour app has five services — admin, auth, design, shell and todo — and\ndeploys as a single docker image.\n\nNext steps:\n\n cd ${answers.appName}\n${install} npm run go:dev # api on http://localhost:8080, web on http://localhost:3080\n npm run go # build the docker image (then run the printed docker command)\n\nDeploy config lives in apps/${answers.appName}/deploy.config.json (domain: ${answers.domain}).\n`);\n },\n};\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-qpq-app",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Scaffold a new quidproquo app — npx create-qpq-app my-app",
5
5
  "main": "./lib/commonjs/index.js",
6
6
  "module": "./lib/esm/index.js",
@@ -22,6 +22,7 @@
22
22
  "build:esm": "tsc -p tsconfig.esm.json",
23
23
  "build:bin-perms": "node -e \"require('fs').chmodSync('lib/commonjs/bin/createQpqApp.js', 0o755)\"",
24
24
  "snapshot-template": "node ./scripts/snapshotTemplate.mjs",
25
+ "smoke-test": "node ./scripts/smokeTest.mjs",
25
26
  "prepack": "npm run snapshot-template",
26
27
  "lint": "npx eslint .",
27
28
  "lint:fix": "npx eslint . --fix",
@@ -44,12 +45,13 @@
44
45
  },
45
46
  "homepage": "https://github.com/joe-coady/quidproquo#readme",
46
47
  "dependencies": {
47
- "@inquirer/prompts": "^8.5.2"
48
+ "@inquirer/prompts": "^8.5.2",
49
+ "prettier": "^3.2.5",
50
+ "typescript": "^5.8.2"
48
51
  },
49
52
  "devDependencies": {
50
53
  "@types/node": "^22.13.13",
51
- "quidproquo-tsconfig": "0.1.5",
52
- "typescript": "^5.8.2"
54
+ "quidproquo-tsconfig": "0.1.7"
53
55
  },
54
56
  "bin": {
55
57
  "create-qpq-app": "./lib/commonjs/bin/createQpqApp.js"
@@ -21,8 +21,7 @@ always runs against the in-repo HEAD. Build the framework first
21
21
  nvm use # ALWAYS FIRST — switches to the Node version in .nvmrc
22
22
  npm install # installs deps + symlinks the sibling quidproquo-* packages (file: refs)
23
23
  npm run build # build every workspace (libs -> tsc -b to dist; services -> type-check)
24
- npm run go:dev # backend dev server on :8080 (all services of the selected app)
25
- npm run go:dev:web # views dev servers (host + one Rspack server per remote)
24
+ npm run go:dev # full local dev stack: api dev server (:8080) + all views dev servers
26
25
  ```
27
26
 
28
27
  Smoke test once `go:dev` is up:
@@ -74,7 +73,7 @@ generic CDK app in `quidproquo-deploy-awscdk`.
74
73
  `defineDevServerOptions({ port })` in its `infrastructure.ts`). The
75
74
  only generated artifact is `apps/<app>/tsconfig.federated.json` — regenerate
76
75
  with `npm run prep` after adding or removing a marker.
77
- - **The dev server** (`qpq go:dev`, powered by `quidproquo-dev-server`) loads each service's
76
+ - **The dev server** (`qpq go:dev:api`, powered by `quidproquo-dev-server`) loads each service's
78
77
  `infrastructure.ts` directly — no synth needed, just built libs. Its bundle
79
78
  externalizes `node_modules` (an externals function in its rspack config) so native modules like
80
79
  `sqlite3` load at runtime from their install location.
@@ -90,7 +89,8 @@ generic CDK app in `quidproquo-deploy-awscdk`.
90
89
  npm run build # build only the libs (services/views are bundled, not built); scope with --workspace=@qpqjs/constants
91
90
  npm run validate-ts # TS check: build libs, then tsc-typecheck every service + views
92
91
  npm run prep # regenerate tsconfig.federated.json from // federated.export markers
93
- npm run go:dev # backend dev server on :8080, every service at /api/<svc>
92
+ npm run go:dev # api + web dev servers in one process (one ctrl+c stops the lot)
93
+ npm run go:dev:api # backend dev server only, on :8080, every service at /api/<svc>
94
94
  npm run go:dev:web # boot ALL views Rspack dev servers (host shell :3080 + remotes on their
95
95
  # ports); remotes resolve dynamically at runtime via mf-manifest.json.
96
96
  # Subset: npm run go:dev:web -- --only host,shell,design
@@ -99,7 +99,7 @@ npm run go:dev:web # boot ALL views Rspack dev servers (host shell :3080 + rem
99
99
  ### App selection (multi-app)
100
100
 
101
101
  Every app-facing script (`prep`, `synth`, `go`, `go:docker`, `go:dev`,
102
- `go:dev:web`) resolves its target app the same way:
102
+ `go:dev:api`, `go:dev:web`) resolves its target app the same way:
103
103
 
104
104
  1. `--app <name>` — **must come after `--`**: `npm run go:dev -- --app qpqjs`
105
105
  (this npm silently swallows flags before `--`)
@@ -18,8 +18,9 @@ export function Footer() {
18
18
  <span className="hero__title-glow">your first story?</span>
19
19
  </h2>
20
20
  <p className="footer__cta-sub">
21
- Install the core, yield an action, and let the runtime handle the
22
- rest.
21
+ One command scaffolds a full app five services, a local dev
22
+ server, a one-image docker deploy. Yield an action; the runtime
23
+ handles the rest.
23
24
  </p>
24
25
  <div className="footer__cta-actions">
25
26
  <InstallChip />
@@ -1,6 +1,6 @@
1
1
  import { useState } from 'react';
2
2
 
3
- const INSTALL_COMMAND = 'npm install quidproquo-core';
3
+ const INSTALL_COMMAND = 'npx create-qpq-app myapp';
4
4
 
5
5
  export function InstallChip() {
6
6
  const [copied, setCopied] = useState(false);
@@ -30,6 +30,20 @@ import {
30
30
 
31
31
  const modulePrefix = 'todo';
32
32
 
33
+ // The deployed version tag — git sha when available, so a fresh checkout
34
+ // (or a scaffold that skipped git init) still loads.
35
+ const getVersionTag = (): string => {
36
+ try {
37
+ return execSync('git rev-parse --short HEAD', {
38
+ stdio: ['ignore', 'pipe', 'ignore'],
39
+ })
40
+ .toString()
41
+ .trim();
42
+ } catch {
43
+ return 'no-git';
44
+ }
45
+ };
46
+
33
47
  // The shared config every todo service starts from — app identity, dns,
34
48
  // auth, admin, api and caching. Each service's infrastructure.ts layers its
35
49
  // own resources on top of this.
@@ -50,11 +64,7 @@ export const defineTodoService = (
50
64
  process.env.ACTOR_NAME
51
65
  ),
52
66
 
53
- defineApplicationVersion(
54
- `${execSync('git rev-parse --short HEAD')
55
- .toString()
56
- .trim()}-${new Date().toISOString()}`
57
- ),
67
+ defineApplicationVersion(`${getVersionTag()}-${new Date().toISOString()}`),
58
68
 
59
69
  defineDns(domainName),
60
70
 
@@ -42,7 +42,7 @@ The parameters are identical to [askAiPrompt](./ask-ai-prompt.md) — see there
42
42
  | --- | --- | --- |
43
43
  | `model` | `AiModel` | Which model to prompt. |
44
44
  | `prompt` | `string` | The user prompt. Ignored when `options.messages` is set. |
45
- | `options` | `AskAiPromptStreamOptions` | `{ system?, aiName?, messages?, reasoning? }` — same shape and meaning as [`AskAiPromptOptions`](./ask-ai-prompt.md#askaipromptoptions). |
45
+ | `options` | `AskAiPromptStreamOptions` | `{ system?, aiName?, messages?, reasoning?, caching? }` — same shape and meaning as [`AskAiPromptOptions`](./ask-ai-prompt.md#askaipromptoptions). |
46
46
 
47
47
  ## Returns
48
48
 
@@ -49,6 +49,7 @@ function* askAiPrompt(
49
49
  | `aiName` | `string` | – | Name of a [defineAi](../../../config/core/ai.md) config to bind. This is what wires up tool definitions (and their executors) for the model to call. Omit for a plain, tool-less prompt. |
50
50
  | `messages` | [`AiMessage[]`](#aimessage) | – | A full conversation history. When present, this is sent instead of `prompt`, letting you carry a multi-turn dialogue (including prior assistant turns and tool results). |
51
51
  | `reasoning` | [`AiReasoningConfig`](#aireasoningconfig) | – | Enables extended thinking. Its presence turns reasoning on; `budgetTokens` caps how many tokens the model may spend thinking before it answers (defaults to `4096` on AWS). |
52
+ | `caching` | `boolean` | – | Marks the system prompt and the last message (or the last `messages` entry) with a Bedrock cache point, so a following call in the same conversation can read everything up to there from cache instead of reprocessing it. |
52
53
 
53
54
  ### `AiModel`
54
55