create-warlock 5.29.0 → 5.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/README.md +66 -15
- package/esm/commands/create-new-app/get-app-path.mjs +1 -1
- package/esm/commands/create-new-app/get-app-path.mjs.map +1 -1
- package/esm/commands/create-new-app/index.mjs +20 -14
- package/esm/commands/create-new-app/index.mjs.map +1 -1
- package/esm/commands/create-new-app/types.d.mts +3 -2
- package/esm/commands/create-warlock-app/index.mjs +2 -1
- package/esm/commands/create-warlock-app/index.mjs.map +1 -1
- package/esm/features/agent-kit-targets.mjs +1 -1
- package/esm/features/agent-kit-targets.mjs.map +1 -1
- package/esm/features/database-drivers.mjs +1 -1
- package/esm/features/database-drivers.mjs.map +1 -1
- package/esm/features/features-map.mjs +13 -13
- package/esm/features/features-map.mjs.map +1 -1
- package/esm/flags/assert-flag-combinations.mjs +1 -1
- package/esm/flags/assert-flag-combinations.mjs.map +1 -1
- package/esm/flags/assert-package-manager.mjs +1 -1
- package/esm/flags/assert-package-manager.mjs.map +1 -1
- package/esm/flags/definitions.mjs +6 -1
- package/esm/flags/definitions.mjs.map +1 -1
- package/esm/flags/seed-from-flags.mjs +2 -1
- package/esm/flags/seed-from-flags.mjs.map +1 -1
- package/esm/helpers/app.mjs +72 -6
- package/esm/helpers/app.mjs.map +1 -1
- package/esm/helpers/exec.mjs +1 -1
- package/esm/helpers/exec.mjs.map +1 -1
- package/esm/helpers/insert-import.mjs +1 -1
- package/esm/helpers/insert-import.mjs.map +1 -1
- package/esm/helpers/package-manager.mjs +22 -2
- package/esm/helpers/package-manager.mjs.map +1 -1
- package/esm/helpers/paths.mjs +1 -1
- package/esm/helpers/paths.mjs.map +1 -1
- package/esm/helpers/pnpm-build-approvals.mjs +58 -3
- package/esm/helpers/pnpm-build-approvals.mjs.map +1 -1
- package/esm/helpers/project-builder-helpers.mjs +1 -1
- package/esm/helpers/project-builder-helpers.mjs.map +1 -1
- package/esm/helpers/tty.mjs +1 -1
- package/esm/helpers/tty.mjs.map +1 -1
- package/esm/helpers/warlock-versions.mjs +1 -1
- package/esm/helpers/warlock-versions.mjs.map +1 -1
- package/esm/index.d.mts +1 -1
- package/esm/index.mjs +6 -2
- package/esm/index.mjs.map +1 -1
- package/esm/prompts/ask-agent-targets.mjs +4 -2
- package/esm/prompts/ask-agent-targets.mjs.map +1 -1
- package/esm/prompts/ask-ai-features.mjs +5 -3
- package/esm/prompts/ask-ai-features.mjs.map +1 -1
- package/esm/prompts/ask-features.mjs +53 -0
- package/esm/prompts/ask-features.mjs.map +1 -0
- package/esm/prompts/ask-lint-toolchain.mjs +21 -0
- package/esm/prompts/ask-lint-toolchain.mjs.map +1 -0
- package/esm/prompts/ask-project-name.mjs +1 -1
- package/esm/prompts/ask-project-name.mjs.map +1 -1
- package/esm/prompts/ask-stack.mjs +1 -1
- package/esm/prompts/ask-stack.mjs.map +1 -1
- package/esm/prompts/fit-to-terminal.mjs +46 -0
- package/esm/prompts/fit-to-terminal.mjs.map +1 -0
- package/esm/summary.mjs +12 -2
- package/esm/summary.mjs.map +1 -1
- package/esm/ui/banner.mjs +115 -47
- package/esm/ui/banner.mjs.map +1 -1
- package/esm/ui/report.mjs +1 -1
- package/esm/ui/report.mjs.map +1 -1
- package/esm/ui/spinner.mjs +1 -1
- package/esm/ui/spinner.mjs.map +1 -1
- package/esm/ui/spinners.mjs +1 -1
- package/esm/ui/spinners.mjs.map +1 -1
- package/esm/ui/wrap-text.mjs +55 -0
- package/esm/ui/wrap-text.mjs.map +1 -0
- package/llms-full.txt +79 -29
- package/package.json +6 -5
- package/skills/create-a-warlock-project/SKILL.md +79 -29
- package/templates/toolchains/oxc/.oxfmtrc.json +12 -0
- package/templates/toolchains/oxc/.oxlintrc.json +24 -0
- package/templates/toolchains/oxc/.vscode/extensions.json +3 -0
- package/templates/toolchains/oxc/.vscode/settings.json +35 -0
- package/templates/warlock/AGENTS.md +22 -2
- package/templates/warlock/README.md +5 -3
- package/templates/warlock/package.json +6 -4
- package/templates/warlock/skills/api-design/SKILL.md +2 -2
- package/templates/warlock/skills/code-standards/SKILL.md +18 -4
- package/templates/warlock/skills/security-baseline/SKILL.md +12 -1
- package/templates/warlock/skills/testing-strategy/SKILL.md +17 -0
- package/templates/warlock/src/app/auth/routes.ts +25 -5
- package/templates/warlock/src/app/auth/utils/types.ts +6 -1
- package/templates/warlock/src/app/contact/routes.ts +1 -1
- package/templates/warlock/src/app/locale/routes.ts +1 -1
- package/templates/warlock/src/app/users/controllers/list-users.controller.ts +10 -2
- package/templates/warlock/src/app/users/schema/list-users.schema.ts +15 -0
- package/templates/warlock/src/config/tests.ts +8 -0
- package/templates/warlock/src/web/404.css +2 -6
- package/templates/warlock/src/web/root.tsx +5 -0
|
@@ -2,7 +2,7 @@ import { exec, execSync } from "child_process";
|
|
|
2
2
|
import { promisify } from "util";
|
|
3
3
|
import detectPackageManager from "which-pm-runs";
|
|
4
4
|
|
|
5
|
-
//#region
|
|
5
|
+
//#region ../../packages/create-warlock/src/helpers/package-manager.ts
|
|
6
6
|
const execAsync = promisify(exec);
|
|
7
7
|
let detectedPackageManager;
|
|
8
8
|
let cachedSystemManagers;
|
|
@@ -100,6 +100,26 @@ function getPreferredPackageManager() {
|
|
|
100
100
|
function setPackageManager(packageManager) {
|
|
101
101
|
detectedPackageManager = packageManager;
|
|
102
102
|
}
|
|
103
|
+
let cachedPnpmVersion;
|
|
104
|
+
/**
|
|
105
|
+
* The version `pnpm --version` reports, or undefined when pnpm cannot be run.
|
|
106
|
+
* The scaffold's pnpm settings depend on it (see pnpmBuildApprovalLayout).
|
|
107
|
+
*/
|
|
108
|
+
function getPnpmVersion() {
|
|
109
|
+
if (cachedPnpmVersion === void 0) try {
|
|
110
|
+
cachedPnpmVersion = execSync("pnpm --version", {
|
|
111
|
+
encoding: "utf8",
|
|
112
|
+
stdio: [
|
|
113
|
+
"ignore",
|
|
114
|
+
"pipe",
|
|
115
|
+
"ignore"
|
|
116
|
+
]
|
|
117
|
+
}).trim();
|
|
118
|
+
} catch {
|
|
119
|
+
cachedPnpmVersion = null;
|
|
120
|
+
}
|
|
121
|
+
return cachedPnpmVersion ?? void 0;
|
|
122
|
+
}
|
|
103
123
|
function runPackageManagerCommand(command) {
|
|
104
124
|
const packageManager = getPackageManager();
|
|
105
125
|
if (packageManager === "npm") return `npm run ${command}`;
|
|
@@ -107,5 +127,5 @@ function runPackageManagerCommand(command) {
|
|
|
107
127
|
}
|
|
108
128
|
|
|
109
129
|
//#endregion
|
|
110
|
-
export { ALLOWED_PACKAGE_MANAGERS, detectPackageManagers, getPackageManager, getPreferredPackageManager, getSystemPackageManagers, isValidPackageManager, runPackageManagerCommand, setPackageManager };
|
|
130
|
+
export { ALLOWED_PACKAGE_MANAGERS, detectPackageManagers, getPackageManager, getPnpmVersion, getPreferredPackageManager, getSystemPackageManagers, isValidPackageManager, runPackageManagerCommand, setPackageManager };
|
|
111
131
|
//# sourceMappingURL=package-manager.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"package-manager.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"package-manager.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/helpers/package-manager.ts"],"sourcesContent":["import { exec, execSync } from \"child_process\";\nimport { promisify } from \"util\";\nimport detectPackageManager from \"which-pm-runs\";\n\nconst execAsync = promisify(exec);\n\nlet detectedPackageManager: string | undefined;\nlet cachedSystemManagers: string[] | undefined;\nlet cachedPreferredManager: string | undefined;\n\n/**\n * The only package managers `--pm` may select. This value reaches `spawn()`\n * as the executable to run and is spliced into the generated\n * `package.json`'s scripts — an allow-list here is a hard security\n * boundary, not just input hygiene, so it stays a fixed literal list\n * rather than anything derived from user input or the running environment.\n */\nexport const ALLOWED_PACKAGE_MANAGERS = [\"npm\", \"yarn\", \"pnpm\", \"bun\"] as const;\n\nexport type AllowedPackageManager = (typeof ALLOWED_PACKAGE_MANAGERS)[number];\n\n/** Whether `value` is one of the allow-listed package managers. */\nexport function isValidPackageManager(value: string): value is AllowedPackageManager {\n return (ALLOWED_PACKAGE_MANAGERS as readonly string[]).includes(value);\n}\n\nexport function getPackageManager() {\n if (detectedPackageManager) {\n return detectedPackageManager;\n }\n\n return getPreferredPackageManager();\n}\n\n/**\n * Check if a package manager is installed\n */\nfunction isInstalled(manager: string): boolean {\n try {\n execSync(`${manager} --version`, { stdio: \"ignore\" });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Check if a package manager is installed (async)\n */\nasync function checkManager(manager: string): Promise<boolean> {\n try {\n await execAsync(`${manager} --version`);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Every allow-listed manager that has to be PROBED. npm is excluded because\n * it is assumed present.\n *\n * Both detectors read this one list. They used to hold their own literal\n * copies, and the copies drifted: `bun` reached the allow-list and `--help`\n * without reaching either probe, so `--pm=bun` passed the spelling check and\n * then could never appear among the options that would honour it.\n */\nconst PROBED_PACKAGE_MANAGERS = ALLOWED_PACKAGE_MANAGERS.filter((manager) => manager !== \"npm\");\n\nconst PREFERRED_PACKAGE_MANAGERS = [\"pnpm\", \"yarn\", \"npm\"] as const;\n\n/**\n * A running non-npm manager is already usable. Every fallback must be present\n * in the detector's installed set, so both sync and async paths agree.\n */\nfunction preferredPackageManager(isAvailable: (manager: AllowedPackageManager) => boolean): string {\n const runningPm = detectPackageManager()?.name;\n if (runningPm && runningPm !== \"npm\" && isValidPackageManager(runningPm)) {\n return runningPm;\n }\n\n return PREFERRED_PACKAGE_MANAGERS.find(isAvailable) ?? \"npm\";\n}\n\n/**\n * Detect available package managers asynchronously and cache results\n */\nexport async function detectPackageManagers() {\n const found = await Promise.all(PROBED_PACKAGE_MANAGERS.map(checkManager));\n const available = PROBED_PACKAGE_MANAGERS.filter((_, index) => found[index]);\n const managers = [\"npm\", ...available];\n\n cachedSystemManagers = managers;\n cachedPreferredManager = preferredPackageManager((manager) => managers.includes(manager));\n}\n\n/**\n * Get available package managers on the system\n */\nexport function getSystemPackageManagers(): string[] {\n if (cachedSystemManagers) return cachedSystemManagers;\n\n // Same list the async detector probes, so the two can no longer disagree\n // about which managers exist. npm is assumed to be always available.\n return [\"npm\", ...PROBED_PACKAGE_MANAGERS.filter(isInstalled)];\n}\n\n/**\n * Get the preferred package manager based on priority\n */\nexport function getPreferredPackageManager(): string {\n if (cachedPreferredManager) return cachedPreferredManager;\n return preferredPackageManager((manager) => manager === \"npm\" || isInstalled(manager));\n}\n\nexport function setPackageManager(packageManager: string) {\n detectedPackageManager = packageManager;\n}\n\nlet cachedPnpmVersion: string | null | undefined;\n\n/**\n * The version `pnpm --version` reports, or undefined when pnpm cannot be run.\n * The scaffold's pnpm settings depend on it (see pnpmBuildApprovalLayout).\n */\nexport function getPnpmVersion(): string | undefined {\n if (cachedPnpmVersion === undefined) {\n try {\n cachedPnpmVersion = execSync(\"pnpm --version\", {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim();\n } catch {\n cachedPnpmVersion = null;\n }\n }\n\n return cachedPnpmVersion ?? undefined;\n}\n\nexport function installCommand() {\n return `${getPackageManager()} install`;\n}\n\nexport function startCommand() {\n if (getPackageManager() === \"npm\") return \"npm run dev\";\n\n return `${getPackageManager()} dev`;\n}\n\nexport function runPackageManagerCommand(command: string) {\n const packageManager = getPackageManager();\n\n if (packageManager === \"npm\") return `npm run ${command}`;\n\n return `${packageManager} ${command}`;\n}\n"],"mappings":";;;;;AAIA,MAAM,YAAY,UAAU,IAAI;AAEhC,IAAI;AACJ,IAAI;AACJ,IAAI;;;;;;;;AASJ,MAAa,2BAA2B;CAAC;CAAO;CAAQ;CAAQ;AAAK;;AAKrE,SAAgB,sBAAsB,OAA+C;CACnF,OAAQ,yBAA+C,SAAS,KAAK;AACvE;AAEA,SAAgB,oBAAoB;CAClC,IAAI,wBACF,OAAO;CAGT,OAAO,2BAA2B;AACpC;;;;AAKA,SAAS,YAAY,SAA0B;CAC7C,IAAI;EACF,SAAS,GAAG,QAAQ,aAAa,EAAE,OAAO,SAAS,CAAC;EACpD,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;;;;AAKA,eAAe,aAAa,SAAmC;CAC7D,IAAI;EACF,MAAM,UAAU,GAAG,QAAQ,WAAW;EACtC,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;;;AAWA,MAAM,0BAA0B,yBAAyB,QAAQ,YAAY,YAAY,KAAK;AAE9F,MAAM,6BAA6B;CAAC;CAAQ;CAAQ;AAAK;;;;;AAMzD,SAAS,wBAAwB,aAAkE;CACjG,MAAM,YAAY,qBAAqB,CAAC,EAAE;CAC1C,IAAI,aAAa,cAAc,SAAS,sBAAsB,SAAS,GACrE,OAAO;CAGT,OAAO,2BAA2B,KAAK,WAAW,KAAK;AACzD;;;;AAKA,eAAsB,wBAAwB;CAC5C,MAAM,QAAQ,MAAM,QAAQ,IAAI,wBAAwB,IAAI,YAAY,CAAC;CAEzE,MAAM,WAAW,CAAC,OAAO,GADP,wBAAwB,QAAQ,GAAG,UAAU,MAAM,MACjC,CAAC;CAErC,uBAAuB;CACvB,yBAAyB,yBAAyB,YAAY,SAAS,SAAS,OAAO,CAAC;AAC1F;;;;AAKA,SAAgB,2BAAqC;CACnD,IAAI,sBAAsB,OAAO;CAIjC,OAAO,CAAC,OAAO,GAAG,wBAAwB,OAAO,WAAW,CAAC;AAC/D;;;;AAKA,SAAgB,6BAAqC;CACnD,IAAI,wBAAwB,OAAO;CACnC,OAAO,yBAAyB,YAAY,YAAY,SAAS,YAAY,OAAO,CAAC;AACvF;AAEA,SAAgB,kBAAkB,gBAAwB;CACxD,yBAAyB;AAC3B;AAEA,IAAI;;;;;AAMJ,SAAgB,iBAAqC;CACnD,IAAI,sBAAsB,QACxB,IAAI;EACF,oBAAoB,SAAS,kBAAkB;GAC7C,UAAU;GACV,OAAO;IAAC;IAAU;IAAQ;GAAQ;EACpC,CAAC,CAAC,CAAC,KAAK;CACV,QAAQ;EACN,oBAAoB;CACtB;CAGF,OAAO,qBAAqB;AAC9B;AAYA,SAAgB,yBAAyB,SAAiB;CACxD,MAAM,iBAAiB,kBAAkB;CAEzC,IAAI,mBAAmB,OAAO,OAAO,WAAW;CAEhD,OAAO,GAAG,eAAe,GAAG;AAC9B"}
|
package/esm/helpers/paths.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import { fileURLToPath } from "url";
|
|
3
3
|
|
|
4
|
-
//#region
|
|
4
|
+
//#region ../../packages/create-warlock/src/helpers/paths.ts
|
|
5
5
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
6
|
const __dirname = path.dirname(__filename);
|
|
7
7
|
function template(templateName) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"paths.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/helpers/paths.ts"],"sourcesContent":["import path from \"path\";\nimport { fileURLToPath } from \"url\";\n\nconst __filename = fileURLToPath(import.meta.url);\n\nconst __dirname = path.dirname(__filename);\n\nexport type Template = \"warlock\";\n\nexport function template(templateName: Template): string {\n return path.resolve(__dirname, \"../\", \"../\", \"templates\", templateName);\n}\n\nexport function packageRoot(...paths: string[]): string {\n return path.resolve(__dirname, \"../\", \"../\", ...paths);\n}\n"],"mappings":";;;;AAGA,MAAM,aAAa,cAAc,OAAO,KAAK,GAAG;AAEhD,MAAM,YAAY,KAAK,QAAQ,UAAU;AAIzC,SAAgB,SAAS,cAAgC;CACvD,OAAO,KAAK,QAAQ,WAAW,OAAO,OAAO,aAAa,YAAY;AACxE;AAEA,SAAgB,YAAY,GAAG,OAAyB;CACtD,OAAO,KAAK,QAAQ,WAAW,OAAO,OAAO,GAAG,KAAK;AACvD"}
|
|
@@ -1,11 +1,66 @@
|
|
|
1
|
-
//#region
|
|
1
|
+
//#region ../../packages/create-warlock/src/helpers/pnpm-build-approvals.ts
|
|
2
|
+
/**
|
|
3
|
+
* Install scripts the template itself needs, mirrored from the `allowBuilds`
|
|
4
|
+
* map in templates/warlock/pnpm-workspace.yaml (a spec keeps the two equal).
|
|
5
|
+
*/
|
|
6
|
+
const BASE_PNPM_BUILD_APPROVALS = ["esbuild"];
|
|
7
|
+
function pnpmBuildApprovalLayout(version) {
|
|
8
|
+
const match = /^(\d+)\.(\d+)\./.exec(version?.trim() ?? "");
|
|
9
|
+
if (!match) return "allowBuilds";
|
|
10
|
+
const major = Number(match[1]);
|
|
11
|
+
const minor = Number(match[2]);
|
|
12
|
+
if (major < 10 || major === 10 && minor < 5) return "packageJson";
|
|
13
|
+
if (major === 10 && minor < 26) return "onlyBuiltDependencies";
|
|
14
|
+
return "allowBuilds";
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Add packages to a block-style `onlyBuiltDependencies:` list, creating it when
|
|
18
|
+
* absent. A flow list or an alias is left alone rather than rewritten.
|
|
19
|
+
*/
|
|
20
|
+
function mergeOnlyBuiltDependencies(yaml, packages) {
|
|
21
|
+
if (packages.length === 0) return yaml;
|
|
22
|
+
const block = /^onlyBuiltDependencies:\s*(?:#.*)?(?:\r?\n|$)/m.exec(yaml);
|
|
23
|
+
if (!block) {
|
|
24
|
+
if (/^(?:onlyBuiltDependencies|"onlyBuiltDependencies"|'onlyBuiltDependencies')\s*:/m.test(yaml)) return yaml;
|
|
25
|
+
const items = packages.map((name) => ` - ${name}`).join("\n");
|
|
26
|
+
return `${yaml.replace(/\s*$/, "")}\n\n# pnpm 10.5-10.25 read install-script approvals from this list.\nonlyBuiltDependencies:\n${items}\n`;
|
|
27
|
+
}
|
|
28
|
+
const start = block.index + block[0].length;
|
|
29
|
+
const tail = yaml.slice(start);
|
|
30
|
+
const nextTopLevel = /^(?!\s|#|$)[^\r\n]+/m.exec(tail);
|
|
31
|
+
const end = nextTopLevel ? start + nextTopLevel.index : yaml.length;
|
|
32
|
+
const list = yaml.slice(start, end);
|
|
33
|
+
const listed = new Set([...list.matchAll(/^\s*-\s*["']?([^"'\s#]+)["']?/gm)].map((entry) => entry[1]));
|
|
34
|
+
const missing = packages.filter((name) => !listed.has(name));
|
|
35
|
+
if (missing.length === 0) return yaml;
|
|
36
|
+
const indentation = /^([ \t]*)-/m.exec(list)?.[1] ?? " ";
|
|
37
|
+
const additions = missing.map((name) => `${indentation}- ${name}\n`).join("");
|
|
38
|
+
return `${yaml.slice(0, start)}${additions}${list}${yaml.slice(end)}`;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Add packages to `pnpm.onlyBuiltDependencies` in a parsed package.json,
|
|
42
|
+
* keeping existing entries and their order.
|
|
43
|
+
*/
|
|
44
|
+
function mergePackageJsonBuildApprovals(packageJson, packages) {
|
|
45
|
+
const pnpm = packageJson.pnpm ?? {};
|
|
46
|
+
const current = Array.isArray(pnpm.onlyBuiltDependencies) ? pnpm.onlyBuiltDependencies : [];
|
|
47
|
+
const merged = [...current, ...packages.filter((name) => !current.includes(name))];
|
|
48
|
+
return {
|
|
49
|
+
...packageJson,
|
|
50
|
+
pnpm: {
|
|
51
|
+
...pnpm,
|
|
52
|
+
onlyBuiltDependencies: merged
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
2
56
|
/** Native dependency build scripts required by features the scaffold explicitly selected. */
|
|
3
57
|
const FEATURE_BUILD_APPROVALS = { image: ["sharp"] };
|
|
4
58
|
function requiredPnpmBuildApprovals(features) {
|
|
5
59
|
return [...new Set(features.flatMap((feature) => FEATURE_BUILD_APPROVALS[feature] ?? []))];
|
|
6
60
|
}
|
|
7
61
|
/**
|
|
8
|
-
* Add only known feature-required builds to
|
|
62
|
+
* Add only known feature-required builds to the workspace `allowBuilds` policy
|
|
63
|
+
* (pnpm 10.26+, 11, 12).
|
|
9
64
|
* Existing `false` values are user decisions and are never overridden.
|
|
10
65
|
*/
|
|
11
66
|
function mergePnpmBuildApprovals(yaml, packages) {
|
|
@@ -42,5 +97,5 @@ function mergeOneApproval(yaml, packageName) {
|
|
|
42
97
|
}
|
|
43
98
|
|
|
44
99
|
//#endregion
|
|
45
|
-
export { mergePnpmBuildApprovals, requiredPnpmBuildApprovals };
|
|
100
|
+
export { BASE_PNPM_BUILD_APPROVALS, mergeOnlyBuiltDependencies, mergePackageJsonBuildApprovals, mergePnpmBuildApprovals, pnpmBuildApprovalLayout, requiredPnpmBuildApprovals };
|
|
46
101
|
//# sourceMappingURL=pnpm-build-approvals.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pnpm-build-approvals.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"pnpm-build-approvals.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/helpers/pnpm-build-approvals.ts"],"sourcesContent":["/**\n * Install scripts the template itself needs, mirrored from the `allowBuilds`\n * map in templates/warlock/pnpm-workspace.yaml (a spec keeps the two equal).\n */\nexport const BASE_PNPM_BUILD_APPROVALS: readonly string[] = [\"esbuild\"];\n\n/**\n * Where a pnpm version reads build approvals from, measured by real installs\n * of an esbuild dependency (create-warlock 5.30.0, FW / macOS report):\n *\n * - `packageJson`: pnpm 7–9 and 10.0–10.4. They reject a pnpm-workspace.yaml\n * without `packages:` (\"packages field missing or empty\"), and adding\n * `packages: [\".\"]` makes every later `pnpm add` fail with\n * ERR_PNPM_ADDING_TO_ROOT. So the app gets no workspace file and lists the\n * approvals under `pnpm.onlyBuiltDependencies` in package.json, which these\n * versions honour.\n * - `onlyBuiltDependencies`: pnpm 10.5–10.25 accept the settings-only file but\n * ignore `allowBuilds`; they read `onlyBuiltDependencies` from it.\n * - `allowBuilds`: pnpm 10.26 and later, 11 and 12. pnpm 11+ fails the install\n * (ERR_PNPM_IGNORED_BUILDS) when only `onlyBuiltDependencies` is present.\n *\n * An unknown version gets the current layout, `allowBuilds`.\n */\nexport type PnpmBuildApprovalLayout = \"packageJson\" | \"onlyBuiltDependencies\" | \"allowBuilds\";\n\nexport function pnpmBuildApprovalLayout(version: string | undefined): PnpmBuildApprovalLayout {\n const match = /^(\\d+)\\.(\\d+)\\./.exec(version?.trim() ?? \"\");\n\n if (!match) return \"allowBuilds\";\n\n const major = Number(match[1]);\n const minor = Number(match[2]);\n\n if (major < 10 || (major === 10 && minor < 5)) return \"packageJson\";\n if (major === 10 && minor < 26) return \"onlyBuiltDependencies\";\n\n return \"allowBuilds\";\n}\n\n/**\n * Add packages to a block-style `onlyBuiltDependencies:` list, creating it when\n * absent. A flow list or an alias is left alone rather than rewritten.\n */\nexport function mergeOnlyBuiltDependencies(yaml: string, packages: readonly string[]): string {\n if (packages.length === 0) return yaml;\n\n const block = /^onlyBuiltDependencies:\\s*(?:#.*)?(?:\\r?\\n|$)/m.exec(yaml);\n\n if (!block) {\n if (\n /^(?:onlyBuiltDependencies|\"onlyBuiltDependencies\"|'onlyBuiltDependencies')\\s*:/m.test(yaml)\n ) {\n return yaml;\n }\n\n const items = packages.map((name) => ` - ${name}`).join(\"\\n\");\n\n return `${yaml.replace(/\\s*$/, \"\")}\\n\\n# pnpm 10.5-10.25 read install-script approvals from this list.\\nonlyBuiltDependencies:\\n${items}\\n`;\n }\n\n const start = block.index + block[0].length;\n const tail = yaml.slice(start);\n const nextTopLevel = /^(?!\\s|#|$)[^\\r\\n]+/m.exec(tail);\n const end = nextTopLevel ? start + nextTopLevel.index : yaml.length;\n const list = yaml.slice(start, end);\n const listed = new Set(\n [...list.matchAll(/^\\s*-\\s*[\"']?([^\"'\\s#]+)[\"']?/gm)].map((entry) => entry[1]),\n );\n const missing = packages.filter((name) => !listed.has(name));\n\n if (missing.length === 0) return yaml;\n\n const indentation = /^([ \\t]*)-/m.exec(list)?.[1] ?? \" \";\n const additions = missing.map((name) => `${indentation}- ${name}\\n`).join(\"\");\n\n return `${yaml.slice(0, start)}${additions}${list}${yaml.slice(end)}`;\n}\n\n/**\n * Add packages to `pnpm.onlyBuiltDependencies` in a parsed package.json,\n * keeping existing entries and their order.\n */\nexport function mergePackageJsonBuildApprovals(\n packageJson: Record<string, unknown>,\n packages: readonly string[],\n): Record<string, unknown> {\n const pnpm = (packageJson.pnpm ?? {}) as Record<string, unknown>;\n const current = Array.isArray(pnpm.onlyBuiltDependencies)\n ? (pnpm.onlyBuiltDependencies as string[])\n : [];\n const merged = [...current, ...packages.filter((name) => !current.includes(name))];\n\n return { ...packageJson, pnpm: { ...pnpm, onlyBuiltDependencies: merged } };\n}\n\n/** Native dependency build scripts required by features the scaffold explicitly selected. */\nconst FEATURE_BUILD_APPROVALS: Readonly<Record<string, readonly string[]>> = {\n image: [\"sharp\"],\n};\n\nexport function requiredPnpmBuildApprovals(features: readonly string[]): readonly string[] {\n return [...new Set(features.flatMap((feature) => FEATURE_BUILD_APPROVALS[feature] ?? []))];\n}\n\n/**\n * Add only known feature-required builds to the workspace `allowBuilds` policy\n * (pnpm 10.26+, 11, 12).\n * Existing `false` values are user decisions and are never overridden.\n */\nexport function mergePnpmBuildApprovals(yaml: string, packages: readonly string[]): string {\n let result = yaml;\n for (const packageName of packages) {\n result = mergeOneApproval(result, packageName);\n }\n return result;\n}\n\nconst quotedKey = (name: string) => `(?:${name}|\"${name}\"|'${name}')`;\n\n/** Edit one ordinary YAML mapping while preserving every untouched byte. */\nfunction mergeOneApproval(yaml: string, packageName: string): string {\n const escaped = packageName.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const block = /^allowBuilds:\\s*(?:#.*)?(?:\\r?\\n|$)/m.exec(yaml);\n\n if (!block) {\n // Flow mappings are deliberately not rewritten. A hand parser must never\n // turn a decided flow entry into a duplicate block key.\n if (/^(?:allowBuilds|\"allowBuilds\"|'allowBuilds')\\s*:/m.test(yaml)) return yaml;\n return `${yaml.replace(/\\s*$/, \"\")}\\nallowBuilds:\\n ${packageName}: true\\n`;\n }\n\n const start = block.index + block[0].length;\n const tail = yaml.slice(start);\n const nextTopLevel = /^(?!\\s|#|$)[^\\r\\n]+/m.exec(tail);\n const end = nextTopLevel ? start + nextTopLevel.index : yaml.length;\n const mapping = yaml.slice(start, end);\n const key = quotedKey(escaped);\n const entry = new RegExp(`^(\\\\s*)${key}\\\\s*:\\\\s*([^#\\\\r\\\\n]*)(.*)$`, \"m\").exec(mapping);\n\n if (entry) {\n const value = entry[2]!.trim();\n if (value === \"true\" || value === \"false\") return yaml;\n if (\n !/^(?:set this to true or false|\"set this to true or false\"|'set this to true or false')$/.test(\n value,\n )\n )\n return yaml;\n // pnpm's undecided placeholder belongs to this generated mapping: decide it\n // in place rather than emitting a duplicate YAML key.\n const comment = entry[3] ?? \"\";\n const replacement = `${entry[1]}${packageName}: true${comment.startsWith(\"#\") ? \" \" : \"\"}${comment}`;\n return `${yaml.slice(0, start)}${mapping.slice(0, entry.index)}${replacement}${mapping.slice(entry.index + entry[0].length)}${yaml.slice(end)}`;\n }\n\n const indentation = /^([ \\t]+)\\S/m.exec(mapping)?.[1] ?? \" \";\n return `${yaml.slice(0, start)}${indentation}${packageName}: true\\n${mapping}${yaml.slice(end)}`;\n}\n"],"mappings":"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"}
|
|
@@ -6,7 +6,7 @@ import "@clack/prompts";
|
|
|
6
6
|
import { colors } from "@mongez/copper";
|
|
7
7
|
import path from "path";
|
|
8
8
|
|
|
9
|
-
//#region
|
|
9
|
+
//#region ../../packages/create-warlock/src/helpers/project-builder-helpers.ts
|
|
10
10
|
/**
|
|
11
11
|
* Bootstrap the project's git repository.
|
|
12
12
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project-builder-helpers.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"project-builder-helpers.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/helpers/project-builder-helpers.ts"],"sourcesContent":["import { outro } from \"@clack/prompts\";\nimport { colors } from \"@mongez/copper\";\nimport {\n copyDirectory,\n getFile,\n getJsonFile,\n putFile,\n putJsonFile,\n renameFile,\n} from \"@warlock.js/fs\";\nimport path from \"path\";\nimport { executeCommand } from \"./exec\";\nimport { startCommand } from \"./package-manager\";\nimport { Template, template } from \"./paths\";\n\n/**\n * Bootstrap the project's git repository.\n *\n * Returns `false` as soon as a step fails, and leaves that step's captured\n * output where the caller can claim it with `takeLastCommandOutput()`. It used\n * to `return true` unconditionally, which meant a machine without git printed\n * \"Grimoire initialized!\" over a directory that was not a repository.\n */\nexport async function initializeGitRepository(appPath: string) {\n const steps: string[][] = [\n // initialize git repository\n [\"init\"],\n // switching to `main` branch\n [\"checkout\", \"-b\", \"main\"],\n // add files\n [\"add\", \".\"],\n // commit files\n [\"commit\", \"-m\", \"Initial commit\"],\n ];\n\n for (const args of steps) {\n const succeeded = await executeCommand(`git`, args, appPath);\n\n if (!succeeded) return false;\n }\n\n return true;\n}\n\nexport async function updateEnvFile(appPath: string, appName: string) {\n // update package.json file\n const packageJson: any = getJsonFile(path.resolve(appPath, \"package.json\"));\n\n packageJson.name = appName;\n\n putJsonFile(path.resolve(appPath, \"package.json\"), packageJson);\n\n // update env file\n const dotEnv = getFile(path.resolve(appPath, \".env\"))\n .replace(\"AppName\", appName)\n .replace(\n \"AppCodeName\",\n appName\n .split(/-|_/g)\n .map((word) => word[0])\n .join(\"\"),\n );\n\n putFile(path.resolve(appPath, \".env\"), dotEnv);\n\n // update .env.production file\n let dotEnvProduction = getFile(path.resolve(appPath, \".env.shared\"));\n\n dotEnvProduction = dotEnvProduction.replace(\"AppName\", appName).replace(\n \"AppCodeName\",\n appName\n .split(/-|_/g)\n .map((word) => word[0])\n .join(\"\"),\n );\n\n putFile(path.resolve(appPath, \".env.shared\"), dotEnvProduction);\n}\n\nexport async function copyTemplateFiles(templateName: Template, appPath: string, _appName: string) {\n // copy project files\n copyDirectory(template(templateName), appPath);\n\n // replace _.gitignore to\n renameFile(path.resolve(appPath, \"_.gitignore\"), path.resolve(appPath, \".gitignore\"));\n}\n\nexport async function allDone(appName: string) {\n outro(\n \"Awesome! Your project is ready to rock! \" + \"Run the following command to start development:\",\n );\n\n console.log(colors.cyan(`cd ${appName} && ${startCommand()}`));\n\n console.log();\n\n console.log(\n `Pro tip: Install the ${colors.yellow(\n \"Generator Z\",\n )} extension in VSCode for helpful code snippets and productivity boosters!`,\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAuBA,eAAsB,wBAAwB,SAAiB;CAY7D,KAAK,MAAM,QAAQ;EATjB,CAAC,MAAM;EAEP;GAAC;GAAY;GAAM;EAAM;EAEzB,CAAC,OAAO,GAAG;EAEX;GAAC;GAAU;GAAM;EAAgB;CAGZ,GAGrB,IAAI,CAAC,MAFmB,eAAe,OAAO,MAAM,OAAO,GAE3C,OAAO;CAGzB,OAAO;AACT"}
|
package/esm/helpers/tty.mjs
CHANGED
package/esm/helpers/tty.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tty.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"tty.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/helpers/tty.ts"],"sourcesContent":["/**\n * Whether stdin is attached to an interactive terminal.\n *\n * A scaffolder spawned by CI, a script, or an agent harness has no TTY on\n * stdin — `process.stdin.isTTY` is `undefined` there. Prompting anyway does\n * not fail cleanly: `@clack/prompts` tries to put a non-existent terminal\n * into raw mode and Node's libuv layer throws `TTY initialization failed:\n * uv_tty_init returned EBADF (bad file descriptor)` before a single file is\n * written. That is an internal implementation detail leaking to a developer\n * whose only mistake was not being at a keyboard.\n *\n * Isolated in its own module (rather than read inline) so tests can mock the\n * non-interactive case without spawning a real detached process every time.\n */\nexport function hasInteractiveStdin(): boolean {\n return Boolean(process.stdin.isTTY);\n}\n"],"mappings":";;;;;;;;;;;;;;;AAcA,SAAgB,sBAA+B;CAC7C,OAAO,QAAQ,QAAQ,MAAM,KAAK;AACpC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { packageRoot, template } from "./paths.mjs";
|
|
2
2
|
import { getJsonFile } from "@warlock.js/fs";
|
|
3
3
|
|
|
4
|
-
//#region
|
|
4
|
+
//#region ../../packages/create-warlock/src/helpers/warlock-versions.ts
|
|
5
5
|
/**
|
|
6
6
|
* Resolving the version to stamp onto the generated project's `@warlock.js/*`
|
|
7
7
|
* dependencies.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"warlock-versions.mjs","names":[],"sources":["../../../../../../create-warlock/src/helpers/warlock-versions.ts"],"sourcesContent":["/**\n * Resolving the version to stamp onto the generated project's `@warlock.js/*`\n * dependencies.\n *\n * ## Why this file exists\n *\n * The scaffolder used to stamp its OWN version onto every sibling package\n * (`\"@warlock.js/core\": \"4.16.2\"`). That is only correct while the scaffolder's\n * version is published — and it usually is not: the release tooling bumps the\n * source version on every build, including `--no-publish` builds, so between\n * publishes the working tree carries a version that exists nowhere on the\n * registry. Every project scaffolded in that window pinned eight dependencies\n * to a version npm cannot resolve, and the install died with `ETARGET`.\n *\n * ## The rule\n *\n * Never write a dependency version we have not established exists. Resolution\n * order, per package:\n *\n * 1. the scaffolder's own version, IF the registry has it published — this\n * preserves the lockstep guarantee the pin was introduced for;\n * 2. otherwise the registry's `latest` — the newest thing that actually\n * exists, with a note explaining the substitution;\n * 3. otherwise (registry unreachable) a caret range floored to the major,\n * e.g. `^4.0.0` — always satisfiable by any published 4.x, and the\n * scaffold-time install writes a lockfile that freezes the result anyway.\n *\n * A package that resolves to nothing (404 — never published) is reported, not\n * papered over: it is the difference between \"your install failed\" and \"the\n * feature you asked for does not exist yet\".\n */\n\nimport { getJsonFile } from \"@warlock.js/fs\";\nimport { packageRoot, template } from \"./paths\";\n\nexport type VersionSource =\n \"own-version\" | \"registry-latest\" | \"range-fallback\";\n\nexport type ResolvedVersion = {\n package: string;\n /** The exact version or range to write into the generated package.json. */\n version: string;\n source: VersionSource;\n};\n\nexport type VersionResolution = {\n /** package name -> version/range to stamp. */\n versions: Record<string, string>;\n /** Human-readable notes worth showing before the install runs. */\n notes: string[];\n /** Packages the registry does not know about at all. */\n unpublished: string[];\n /** True when the registry could not be reached and ranges were guessed. */\n offline: boolean;\n};\n\nconst REGISTRY_TIMEOUT_MS = 6_000;\n\n/**\n * Registry to query. `npm_config_registry` is set by npm/npx when the\n * scaffolder runs through them, so a private mirror is honoured for free.\n */\nfunction registryUrl(): string {\n const registry =\n process.env.npm_config_registry?.trim() || \"https://registry.npmjs.org\";\n\n return registry.replace(/\\/+$/, \"\");\n}\n\n/** `4.16.2` -> `^4.0.0`; anything unparseable -> `latest`. */\nexport function fallbackRange(version: string): string {\n const major = /^\\s*v?(\\d+)\\./.exec(version)?.[1];\n\n return major ? `^${major}.0.0` : \"latest\";\n}\n\ntype Packument = {\n \"dist-tags\"?: Record<string, string>;\n versions?: Record<string, unknown>;\n};\n\n/**\n * Fetch the abbreviated packument for a package. Returns `undefined` when the\n * registry cannot be reached (network / timeout) and `null` when the registry\n * answers that the package does not exist.\n */\nasync function fetchPackument(\n packageName: string,\n): Promise<Packument | null | undefined> {\n const url = `${registryUrl()}/${packageName.replace(\"/\", \"%2F\")}`;\n\n try {\n const response = await fetch(url, {\n headers: { accept: \"application/vnd.npm.install-v1+json\" },\n signal: AbortSignal.timeout(REGISTRY_TIMEOUT_MS),\n });\n\n // 404 (public) and 401 (scoped package the registry hides) both mean the\n // same thing to us: there is nothing here to install.\n if (response.status === 404 || response.status === 401) return null;\n\n if (!response.ok) return undefined;\n\n return (await response.json()) as Packument;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Resolve one package against the registry. Pure aside from the fetch, so the\n * decision table above is readable in one place.\n */\nasync function resolvePackage(\n packageName: string,\n ownVersion: string,\n): Promise<ResolvedVersion & { published: boolean; reachable: boolean }> {\n const packument = await fetchPackument(packageName);\n\n if (packument === undefined) {\n return {\n package: packageName,\n version: fallbackRange(ownVersion),\n source: \"range-fallback\",\n published: true,\n reachable: false,\n };\n }\n\n if (packument === null) {\n return {\n package: packageName,\n version: fallbackRange(ownVersion),\n source: \"range-fallback\",\n published: false,\n reachable: true,\n };\n }\n\n if (packument.versions?.[ownVersion]) {\n return {\n package: packageName,\n version: ownVersion,\n source: \"own-version\",\n published: true,\n reachable: true,\n };\n }\n\n const latest = packument[\"dist-tags\"]?.latest;\n\n if (latest) {\n return {\n package: packageName,\n version: latest,\n source: \"registry-latest\",\n published: true,\n reachable: true,\n };\n }\n\n return {\n package: packageName,\n version: fallbackRange(ownVersion),\n source: \"range-fallback\",\n published: true,\n reachable: true,\n };\n}\n\n/**\n * Every `@warlock.js/*` dependency the template declares. Read from the\n * template rather than the copied project so resolution can start before (or\n * in parallel with) the copy.\n */\nexport function templateWarlockDependencies(): string[] {\n const templatePackageJson = getJsonFile(\n `${template(\"warlock\")}/package.json`,\n ) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n\n const names = new Set<string>();\n\n for (const field of [\"dependencies\", \"devDependencies\"] as const) {\n for (const name of Object.keys(templatePackageJson[field] ?? {})) {\n if (name.startsWith(\"@warlock.js/\")) names.add(name);\n }\n }\n\n return [...names];\n}\n\n/** The scaffolder's own published version, i.e. the lockstep candidate. */\nexport function scaffolderVersion(): string {\n return (getJsonFile(packageRoot(\"package.json\")) as { version: string })\n .version;\n}\n\nlet cached: Promise<VersionResolution> | undefined;\n\n/**\n * Resolve the versions to stamp, once per process.\n *\n * Never throws and never blocks a scaffold: the worst case is the caret\n * fallback plus a note saying so.\n */\nexport function resolveWarlockVersions(\n packages: string[] = templateWarlockDependencies(),\n ownVersion: string = scaffolderVersion(),\n): Promise<VersionResolution> {\n if (cached) return cached;\n\n cached = (async (): Promise<VersionResolution> => {\n const resolutions = await Promise.all(\n packages.map(name => resolvePackage(name, ownVersion)),\n );\n\n const versions: Record<string, string> = {};\n const notes: string[] = [];\n const unpublished: string[] = [];\n const substituted: ResolvedVersion[] = [];\n\n let offline = false;\n\n for (const resolution of resolutions) {\n versions[resolution.package] = resolution.version;\n\n if (!resolution.reachable) offline = true;\n if (!resolution.published) unpublished.push(resolution.package);\n if (resolution.source === \"registry-latest\") substituted.push(resolution);\n }\n\n if (offline) {\n notes.push(\n `Could not reach the npm registry — pinning @warlock.js/* to ${fallbackRange(ownVersion)} instead of an exact version.`,\n );\n }\n\n if (substituted.length > 0) {\n const latest = substituted[0].version;\n\n notes.push(\n `create-warlock ${ownVersion} is not published yet — pinning @warlock.js/* to the latest published version (${latest}).`,\n );\n }\n\n if (unpublished.length > 0) {\n notes.push(\n `Not published on the registry: ${unpublished.join(\", \")} — the install will fail until they are released.`,\n );\n }\n\n return { versions, notes, unpublished, offline };\n })();\n\n return cached;\n}\n\n/** Test/CLI seam: forget the memoized resolution. */\nexport function resetWarlockVersionsCache() {\n cached = undefined;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"warlock-versions.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/helpers/warlock-versions.ts"],"sourcesContent":["/**\n * Resolving the version to stamp onto the generated project's `@warlock.js/*`\n * dependencies.\n *\n * ## Why this file exists\n *\n * The scaffolder used to stamp its OWN version onto every sibling package\n * (`\"@warlock.js/core\": \"4.16.2\"`). That is only correct while the scaffolder's\n * version is published — and it usually is not: the release tooling bumps the\n * source version on every build, including `--no-publish` builds, so between\n * publishes the working tree carries a version that exists nowhere on the\n * registry. Every project scaffolded in that window pinned eight dependencies\n * to a version npm cannot resolve, and the install died with `ETARGET`.\n *\n * ## The rule\n *\n * Never write a dependency version we have not established exists. Resolution\n * order, per package:\n *\n * 1. the scaffolder's own version, IF the registry has it published — this\n * preserves the lockstep guarantee the pin was introduced for;\n * 2. otherwise the registry's `latest` — the newest thing that actually\n * exists, with a note explaining the substitution;\n * 3. otherwise (registry unreachable) a caret range floored to the major,\n * e.g. `^4.0.0` — always satisfiable by any published 4.x, and the\n * scaffold-time install writes a lockfile that freezes the result anyway.\n *\n * A package that resolves to nothing (404 — never published) is reported, not\n * papered over: it is the difference between \"your install failed\" and \"the\n * feature you asked for does not exist yet\".\n */\n\nimport { getJsonFile } from \"@warlock.js/fs\";\nimport { packageRoot, template } from \"./paths\";\n\nexport type VersionSource = \"own-version\" | \"registry-latest\" | \"range-fallback\";\n\nexport type ResolvedVersion = {\n package: string;\n /** The exact version or range to write into the generated package.json. */\n version: string;\n source: VersionSource;\n};\n\nexport type VersionResolution = {\n /** package name -> version/range to stamp. */\n versions: Record<string, string>;\n /** Human-readable notes worth showing before the install runs. */\n notes: string[];\n /** Packages the registry does not know about at all. */\n unpublished: string[];\n /** True when the registry could not be reached and ranges were guessed. */\n offline: boolean;\n};\n\nconst REGISTRY_TIMEOUT_MS = 6_000;\n\n/**\n * Registry to query. `npm_config_registry` is set by npm/npx when the\n * scaffolder runs through them, so a private mirror is honoured for free.\n */\nfunction registryUrl(): string {\n const registry = process.env.npm_config_registry?.trim() || \"https://registry.npmjs.org\";\n\n return registry.replace(/\\/+$/, \"\");\n}\n\n/** `4.16.2` -> `^4.0.0`; anything unparseable -> `latest`. */\nexport function fallbackRange(version: string): string {\n const major = /^\\s*v?(\\d+)\\./.exec(version)?.[1];\n\n return major ? `^${major}.0.0` : \"latest\";\n}\n\ntype Packument = {\n \"dist-tags\"?: Record<string, string>;\n versions?: Record<string, unknown>;\n};\n\n/**\n * Fetch the abbreviated packument for a package. Returns `undefined` when the\n * registry cannot be reached (network / timeout) and `null` when the registry\n * answers that the package does not exist.\n */\nasync function fetchPackument(packageName: string): Promise<Packument | null | undefined> {\n const url = `${registryUrl()}/${packageName.replace(\"/\", \"%2F\")}`;\n\n try {\n const response = await fetch(url, {\n headers: { accept: \"application/vnd.npm.install-v1+json\" },\n signal: AbortSignal.timeout(REGISTRY_TIMEOUT_MS),\n });\n\n // 404 (public) and 401 (scoped package the registry hides) both mean the\n // same thing to us: there is nothing here to install.\n if (response.status === 404 || response.status === 401) return null;\n\n if (!response.ok) return undefined;\n\n return (await response.json()) as Packument;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Resolve one package against the registry. Pure aside from the fetch, so the\n * decision table above is readable in one place.\n */\nasync function resolvePackage(\n packageName: string,\n ownVersion: string,\n): Promise<ResolvedVersion & { published: boolean; reachable: boolean }> {\n const packument = await fetchPackument(packageName);\n\n if (packument === undefined) {\n return {\n package: packageName,\n version: fallbackRange(ownVersion),\n source: \"range-fallback\",\n published: true,\n reachable: false,\n };\n }\n\n if (packument === null) {\n return {\n package: packageName,\n version: fallbackRange(ownVersion),\n source: \"range-fallback\",\n published: false,\n reachable: true,\n };\n }\n\n if (packument.versions?.[ownVersion]) {\n return {\n package: packageName,\n version: ownVersion,\n source: \"own-version\",\n published: true,\n reachable: true,\n };\n }\n\n const latest = packument[\"dist-tags\"]?.latest;\n\n if (latest) {\n return {\n package: packageName,\n version: latest,\n source: \"registry-latest\",\n published: true,\n reachable: true,\n };\n }\n\n return {\n package: packageName,\n version: fallbackRange(ownVersion),\n source: \"range-fallback\",\n published: true,\n reachable: true,\n };\n}\n\n/**\n * Every `@warlock.js/*` dependency the template declares. Read from the\n * template rather than the copied project so resolution can start before (or\n * in parallel with) the copy.\n */\nexport function templateWarlockDependencies(): string[] {\n const templatePackageJson = getJsonFile(`${template(\"warlock\")}/package.json`) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n\n const names = new Set<string>();\n\n for (const field of [\"dependencies\", \"devDependencies\"] as const) {\n for (const name of Object.keys(templatePackageJson[field] ?? {})) {\n if (name.startsWith(\"@warlock.js/\")) names.add(name);\n }\n }\n\n return [...names];\n}\n\n/** The scaffolder's own published version, i.e. the lockstep candidate. */\nexport function scaffolderVersion(): string {\n return (getJsonFile(packageRoot(\"package.json\")) as { version: string }).version;\n}\n\nlet cached: Promise<VersionResolution> | undefined;\n\n/**\n * Resolve the versions to stamp, once per process.\n *\n * Never throws and never blocks a scaffold: the worst case is the caret\n * fallback plus a note saying so.\n */\nexport function resolveWarlockVersions(\n packages: string[] = templateWarlockDependencies(),\n ownVersion: string = scaffolderVersion(),\n): Promise<VersionResolution> {\n if (cached) return cached;\n\n cached = (async (): Promise<VersionResolution> => {\n const resolutions = await Promise.all(packages.map((name) => resolvePackage(name, ownVersion)));\n\n const versions: Record<string, string> = {};\n const notes: string[] = [];\n const unpublished: string[] = [];\n const substituted: ResolvedVersion[] = [];\n\n let offline = false;\n\n for (const resolution of resolutions) {\n versions[resolution.package] = resolution.version;\n\n if (!resolution.reachable) offline = true;\n if (!resolution.published) unpublished.push(resolution.package);\n if (resolution.source === \"registry-latest\") substituted.push(resolution);\n }\n\n if (offline) {\n notes.push(\n `Could not reach the npm registry — pinning @warlock.js/* to ${fallbackRange(ownVersion)} instead of an exact version.`,\n );\n }\n\n if (substituted.length > 0) {\n const latest = substituted[0].version;\n\n notes.push(\n `create-warlock ${ownVersion} is not published yet — pinning @warlock.js/* to the latest published version (${latest}).`,\n );\n }\n\n if (unpublished.length > 0) {\n notes.push(\n `Not published on the registry: ${unpublished.join(\", \")} — the install will fail until they are released.`,\n );\n }\n\n return { versions, notes, unpublished, offline };\n })();\n\n return cached;\n}\n\n/** Test/CLI seam: forget the memoized resolution. */\nexport function resetWarlockVersionsCache() {\n cached = undefined;\n}\n"],"mappings":"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"}
|
package/esm/index.d.mts
CHANGED
package/esm/index.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import createNewApp from "./commands/create-new-app/index.mjs";
|
|
|
4
4
|
import { buildHelpText } from "./flags/definitions.mjs";
|
|
5
5
|
import { getJsonFile } from "@warlock.js/fs";
|
|
6
6
|
|
|
7
|
-
//#region
|
|
7
|
+
//#region ../../packages/create-warlock/src/index.ts
|
|
8
8
|
const valueFlags = [
|
|
9
9
|
"name",
|
|
10
10
|
"db",
|
|
@@ -12,7 +12,8 @@ const valueFlags = [
|
|
|
12
12
|
"features",
|
|
13
13
|
"ai",
|
|
14
14
|
"agents",
|
|
15
|
-
"stack"
|
|
15
|
+
"stack",
|
|
16
|
+
"lint"
|
|
16
17
|
];
|
|
17
18
|
const HELP_TEXT = buildHelpText();
|
|
18
19
|
/**
|
|
@@ -77,6 +78,9 @@ function parseFlags(argv) {
|
|
|
77
78
|
case "pm":
|
|
78
79
|
flags.pm = value ?? "";
|
|
79
80
|
break;
|
|
81
|
+
case "lint":
|
|
82
|
+
flags.lint = value ?? "";
|
|
83
|
+
break;
|
|
80
84
|
case "features":
|
|
81
85
|
flags.features = splitList(value);
|
|
82
86
|
break;
|
package/esm/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../../../packages/create-warlock/src/index.ts"],"sourcesContent":["import { getJsonFile } from \"@warlock.js/fs\";\nimport createNewApp from \"./commands/create-new-app\";\nimport { CliFlags, Stack } from \"./commands/create-new-app/types\";\nimport { NO_DATABASE } from \"./features/database-drivers\";\nimport { buildHelpText } from \"./flags/definitions\";\nimport { packageRoot } from \"./helpers/paths\";\n\nconst valueFlags = [\"name\", \"db\", \"pm\", \"features\", \"ai\", \"agents\", \"stack\", \"lint\"];\n\nconst HELP_TEXT = buildHelpText();\n\n/**\n * Parse the scaffolder's own CLI flags for non-interactive mode.\n *\n * @example\n * create-warlock my-app --db=postgres --features=test,herald --ai=ai-openai,ai-anthropic --yes\n */\nexport function parseFlags(argv: string[]): CliFlags {\n const flags: CliFlags = {};\n const positionals: string[] = [];\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n\n if (!arg.startsWith(\"-\")) {\n positionals.push(arg);\n continue;\n }\n\n const equalIndex = arg.indexOf(\"=\");\n const key = (equalIndex === -1 ? arg : arg.slice(0, equalIndex)).replace(/^-+/, \"\");\n let value: string | undefined = equalIndex === -1 ? undefined : arg.slice(equalIndex + 1);\n\n // Value-taking flags may use either `--key=value` or `--key value`.\n if (valueFlags.includes(key) && value === undefined) {\n const next = argv[i + 1];\n\n if (next && !next.startsWith(\"-\")) {\n value = next;\n i++;\n }\n }\n\n switch (key) {\n case \"help\":\n case \"h\":\n flags.help = true;\n break;\n case \"version\":\n case \"v\":\n flags.version = true;\n break;\n case \"yes\":\n case \"y\":\n flags.yes = true;\n break;\n case \"git\":\n flags.git = true;\n break;\n case \"no-git\":\n flags.git = false;\n break;\n case \"jwt\":\n flags.jwt = true;\n break;\n case \"no-jwt\":\n flags.jwt = false;\n break;\n case \"name\":\n flags.name = value;\n break;\n case \"db\":\n flags.db = value;\n break;\n case \"no-db\":\n // Opt out of a database entirely — equivalent to `--db=none`.\n flags.db = NO_DATABASE;\n break;\n case \"pm\":\n // Keep an explicitly supplied but valueless `--pm` distinguishable\n // from omitting the flag. The command validator rejects this empty\n // value instead of silently selecting the preferred manager.\n flags.pm = value ?? \"\";\n break;\n case \"lint\":\n flags.lint = value ?? \"\";\n break;\n case \"features\":\n flags.features = splitList(value);\n break;\n case \"ai\":\n flags.ai = splitList(value);\n break;\n case \"agents\":\n flags.agents = splitList(value);\n break;\n case \"stack\":\n flags.stack = value as Stack;\n break;\n case \"interactive\":\n case \"customize\":\n flags.interactive = true;\n break;\n }\n }\n\n if (!flags.name && positionals.length > 0) {\n flags.name = positionals[0];\n }\n\n return flags;\n}\n\nfunction splitList(value: string | undefined): string[] {\n if (!value) return [];\n\n return value\n .split(\",\")\n .map((item) => item.trim())\n .filter(Boolean);\n}\n\n/**\n * Read this package's own `version` field. A plain JSON read — no network\n * call, no write, nothing else touched — so `--version` stays a pure,\n * side-effect-free exit.\n */\nexport function packageVersion(): string {\n return (getJsonFile(packageRoot(\"package.json\")) as { version: string }).version;\n}\n\nexport default function createApp() {\n const flags = parseFlags(process.argv.slice(2));\n\n // `--help` and `--version` must exit before anything that touches the\n // filesystem, the network, or a prompt — they win over every other flag,\n // including a positional project name.\n if (flags.help) {\n console.log(HELP_TEXT);\n process.exit(0);\n return;\n }\n\n if (flags.version) {\n console.log(packageVersion());\n process.exit(0);\n return;\n }\n\n // An unexpected throw must surface as a readable error AND a non-zero exit\n // code — never as a stack trace the user scrolls past on the way to a green\n // banner (there is no banner after this point).\n Promise.resolve(createNewApp(flags)).catch((error: unknown) => {\n console.error();\n console.error(` create-warlock failed: ${(error as Error)?.message ?? String(error)}`);\n console.error();\n\n process.exit(1);\n });\n}\n"],"mappings":";;;;;;;AAOA,MAAM,aAAa;CAAC;CAAQ;CAAM;CAAM;CAAY;CAAM;CAAU;CAAS;AAAM;AAEnF,MAAM,YAAY,cAAc;;;;;;;AAQhC,SAAgB,WAAW,MAA0B;CACnD,MAAM,QAAkB,CAAC;CACzB,MAAM,cAAwB,CAAC;CAE/B,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,MAAM,KAAK;EAEjB,IAAI,CAAC,IAAI,WAAW,GAAG,GAAG;GACxB,YAAY,KAAK,GAAG;GACpB;EACF;EAEA,MAAM,aAAa,IAAI,QAAQ,GAAG;EAClC,MAAM,OAAO,eAAe,KAAK,MAAM,IAAI,MAAM,GAAG,UAAU,EAAC,CAAE,QAAQ,OAAO,EAAE;EAClF,IAAI,QAA4B,eAAe,KAAK,SAAY,IAAI,MAAM,aAAa,CAAC;EAGxF,IAAI,WAAW,SAAS,GAAG,KAAK,UAAU,QAAW;GACnD,MAAM,OAAO,KAAK,IAAI;GAEtB,IAAI,QAAQ,CAAC,KAAK,WAAW,GAAG,GAAG;IACjC,QAAQ;IACR;GACF;EACF;EAEA,QAAQ,KAAR;GACE,KAAK;GACL,KAAK;IACH,MAAM,OAAO;IACb;GACF,KAAK;GACL,KAAK;IACH,MAAM,UAAU;IAChB;GACF,KAAK;GACL,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,OAAO;IACb;GACF,KAAK;IACH,MAAM,KAAK;IACX;GACF,KAAK;IAEH,MAAM,KAAK;IACX;GACF,KAAK;IAIH,MAAM,KAAK,SAAS;IACpB;GACF,KAAK;IACH,MAAM,OAAO,SAAS;IACtB;GACF,KAAK;IACH,MAAM,WAAW,UAAU,KAAK;IAChC;GACF,KAAK;IACH,MAAM,KAAK,UAAU,KAAK;IAC1B;GACF,KAAK;IACH,MAAM,SAAS,UAAU,KAAK;IAC9B;GACF,KAAK;IACH,MAAM,QAAQ;IACd;GACF,KAAK;GACL,KAAK;IACH,MAAM,cAAc;IACpB;EACJ;CACF;CAEA,IAAI,CAAC,MAAM,QAAQ,YAAY,SAAS,GACtC,MAAM,OAAO,YAAY;CAG3B,OAAO;AACT;AAEA,SAAS,UAAU,OAAqC;CACtD,IAAI,CAAC,OAAO,OAAO,CAAC;CAEpB,OAAO,MACJ,MAAM,GAAG,CAAC,CACV,KAAK,SAAS,KAAK,KAAK,CAAC,CAAC,CAC1B,OAAO,OAAO;AACnB;;;;;;AAOA,SAAgB,iBAAyB;CACvC,OAAQ,YAAY,YAAY,cAAc,CAAC,CAAC,CAAyB;AAC3E;AAEA,SAAwB,YAAY;CAClC,MAAM,QAAQ,WAAW,QAAQ,KAAK,MAAM,CAAC,CAAC;CAK9C,IAAI,MAAM,MAAM;EACd,QAAQ,IAAI,SAAS;EACrB,QAAQ,KAAK,CAAC;EACd;CACF;CAEA,IAAI,MAAM,SAAS;EACjB,QAAQ,IAAI,eAAe,CAAC;EAC5B,QAAQ,KAAK,CAAC;EACd;CACF;CAKA,QAAQ,QAAQ,aAAa,KAAK,CAAC,CAAC,CAAC,OAAO,UAAmB;EAC7D,QAAQ,MAAM;EACd,QAAQ,MAAM,4BAA6B,OAAiB,WAAW,OAAO,KAAK,GAAG;EACtF,QAAQ,MAAM;EAEd,QAAQ,KAAK,CAAC;CAChB,CAAC;AACH"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { BUILTIN_AGENT_KIT_TARGETS, DEFAULT_AGENT_KIT_TARGET, getValidAgentKitTargets } from "../features/agent-kit-targets.mjs";
|
|
2
|
+
import { multiselectMaxItems } from "./fit-to-terminal.mjs";
|
|
2
3
|
import { cancel, isCancel, multiselect } from "@clack/prompts";
|
|
3
4
|
|
|
4
|
-
//#region
|
|
5
|
+
//#region ../../packages/create-warlock/src/prompts/ask-agent-targets.ts
|
|
5
6
|
/**
|
|
6
7
|
* Which `agent-kit` targets to derive per-agent docs and skills for.
|
|
7
8
|
*
|
|
@@ -27,7 +28,8 @@ async function askAgentTargets(requested) {
|
|
|
27
28
|
label: target
|
|
28
29
|
})),
|
|
29
30
|
initialValues: initialSelection(requested, targets),
|
|
30
|
-
required: false
|
|
31
|
+
required: false,
|
|
32
|
+
maxItems: multiselectMaxItems(targets.length)
|
|
31
33
|
});
|
|
32
34
|
if (isCancel(answer)) {
|
|
33
35
|
cancel("Agent selection cancelled");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ask-agent-targets.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"ask-agent-targets.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/prompts/ask-agent-targets.ts"],"sourcesContent":["import { cancel, isCancel, multiselect } from \"@clack/prompts\";\nimport {\n BUILTIN_AGENT_KIT_TARGETS,\n DEFAULT_AGENT_KIT_TARGET,\n getValidAgentKitTargets,\n} from \"../features/agent-kit-targets\";\nimport { multiselectMaxItems } from \"./fit-to-terminal\";\n\n/**\n * Which `agent-kit` targets to derive per-agent docs and skills for.\n *\n * The valid target list is not a literal we own — it comes from the installed\n * `@mongez/agent-kit`, or a cached network fetch, and\n * {@link getValidAgentKitTargets} THROWS when neither is available. That is\n * the right behaviour for `--agents=something-unusual`, where guessing would\n * be worse than failing. It is the wrong behaviour here: this prompt sits\n * five questions into the wizard, and an offline laptop must not lose four\n * already-given answers to a failed HTTP request.\n *\n * So an unreachable list is not fatal — it narrows the menu to the built-in\n * targets and SAYS SO, rather than either crashing or silently pretending\n * that is the whole list.\n */\nexport async function askAgentTargets(requested: string[] | undefined): Promise<string[]> {\n const { targets: known, complete } = await availableTargets();\n const targets = reconcileRequested(requested, known, complete);\n\n const answer = await multiselect({\n message: complete\n ? \"Which coding agents should this project be set up for?\"\n : \"Which coding agents should this project be set up for? (offline — showing built-in targets only)\",\n options: targets.map((target) => ({ value: target, label: target })),\n initialValues: initialSelection(requested, targets),\n required: false,\n maxItems: multiselectMaxItems(targets.length),\n });\n\n if (isCancel(answer)) {\n cancel(\"Agent selection cancelled\");\n process.exit(0);\n }\n\n const selected = answer as string[];\n\n return selected.length > 0 ? selected : [DEFAULT_AGENT_KIT_TARGET];\n}\n\n/**\n * The target list, plus whether it is the REAL one. `complete: false` means\n * the lookup failed and these are only the offline built-ins.\n */\nasync function availableTargets(): Promise<{\n targets: string[];\n complete: boolean;\n}> {\n try {\n return { targets: await getValidAgentKitTargets(), complete: true };\n } catch {\n return { targets: [...BUILTIN_AGENT_KIT_TARGETS], complete: false };\n }\n}\n\n/**\n * What `--agents` asked for has to end up among the OPTIONS, or it would be\n * pre-ticked invisibly and then silently dropped — the same defect this\n * wizard's other flags already had.\n *\n * With a complete list, an unrecognised target is refused, naming the valid\n * ones, exactly as `resolveAgentTargets` does for the non-interactive path.\n * With an incomplete one, we have no authority to call anything invalid, so\n * the requested targets are offered alongside the built-ins instead of being\n * judged.\n */\nfunction reconcileRequested(\n requested: string[] | undefined,\n known: string[],\n complete: boolean,\n): string[] {\n if (!requested || requested.length === 0) return known;\n\n const unknown = requested.filter((target) => !known.includes(target));\n\n if (unknown.length === 0) return known;\n\n if (complete) {\n cancel(\n `Unknown agent-kit target(s): ${unknown.join(\", \")} — valid targets: ${known\n .slice()\n .sort()\n .join(\", \")}`,\n );\n process.exit(1);\n }\n\n return [...known, ...unknown];\n}\n\n/**\n * Pre-tick what `--agents` asked for. Everything it names is among the\n * options by now, so nothing is filtered away here.\n */\nfunction initialSelection(requested: string[] | undefined, targets: string[]): string[] {\n const wanted = requested && requested.length > 0 ? requested : [DEFAULT_AGENT_KIT_TARGET];\n\n return wanted.filter((target) => targets.includes(target));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAuBA,eAAsB,gBAAgB,WAAoD;CACxF,MAAM,EAAE,SAAS,OAAO,aAAa,MAAM,iBAAiB;CAC5D,MAAM,UAAU,mBAAmB,WAAW,OAAO,QAAQ;CAE7D,MAAM,SAAS,MAAM,YAAY;EAC/B,SAAS,WACL,2DACA;EACJ,SAAS,QAAQ,KAAK,YAAY;GAAE,OAAO;GAAQ,OAAO;EAAO,EAAE;EACnE,eAAe,iBAAiB,WAAW,OAAO;EAClD,UAAU;EACV,UAAU,oBAAoB,QAAQ,MAAM;CAC9C,CAAC;CAED,IAAI,SAAS,MAAM,GAAG;EACpB,OAAO,2BAA2B;EAClC,QAAQ,KAAK,CAAC;CAChB;CAEA,MAAM,WAAW;CAEjB,OAAO,SAAS,SAAS,IAAI,WAAW,CAAC,wBAAwB;AACnE;;;;;AAMA,eAAe,mBAGZ;CACD,IAAI;EACF,OAAO;GAAE,SAAS,MAAM,wBAAwB;GAAG,UAAU;EAAK;CACpE,QAAQ;EACN,OAAO;GAAE,SAAS,CAAC,GAAG,yBAAyB;GAAG,UAAU;EAAM;CACpE;AACF;;;;;;;;;;;;AAaA,SAAS,mBACP,WACA,OACA,UACU;CACV,IAAI,CAAC,aAAa,UAAU,WAAW,GAAG,OAAO;CAEjD,MAAM,UAAU,UAAU,QAAQ,WAAW,CAAC,MAAM,SAAS,MAAM,CAAC;CAEpE,IAAI,QAAQ,WAAW,GAAG,OAAO;CAEjC,IAAI,UAAU;EACZ,OACE,gCAAgC,QAAQ,KAAK,IAAI,EAAE,oBAAoB,MACpE,MAAM,CAAC,CACP,KAAK,CAAC,CACN,KAAK,IAAI,GACd;EACA,QAAQ,KAAK,CAAC;CAChB;CAEA,OAAO,CAAC,GAAG,OAAO,GAAG,OAAO;AAC9B;;;;;AAMA,SAAS,iBAAiB,WAAiC,SAA6B;CAGtF,QAFe,aAAa,UAAU,SAAS,IAAI,YAAY,CAAC,wBAAwB,EAE3E,CAAC,QAAQ,WAAW,QAAQ,SAAS,MAAM,CAAC;AAC3D"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { getAiPackageOptions, getAiProviderOptions } from "../features/features-map.mjs";
|
|
2
|
+
import { fitOptionsToWidth, multiselectMaxItems } from "./fit-to-terminal.mjs";
|
|
2
3
|
import { isCancel, log, multiselect } from "@clack/prompts";
|
|
3
4
|
|
|
4
|
-
//#region
|
|
5
|
+
//#region ../../packages/create-warlock/src/prompts/ask-ai-features.ts
|
|
5
6
|
const NO_AI = "none";
|
|
6
7
|
const aiOptions = [
|
|
7
8
|
...getAiProviderOptions(),
|
|
@@ -18,9 +19,10 @@ async function askAiFeatures(initialValues = []) {
|
|
|
18
19
|
while (true) {
|
|
19
20
|
const answer = await multiselect({
|
|
20
21
|
message: "Add AI packages? Providers + capabilities — the core AI package is included automatically",
|
|
21
|
-
options: aiOptions,
|
|
22
|
+
options: fitOptionsToWidth(aiOptions),
|
|
22
23
|
initialValues: selectedValues,
|
|
23
|
-
required: false
|
|
24
|
+
required: false,
|
|
25
|
+
maxItems: multiselectMaxItems(aiOptions.length)
|
|
24
26
|
});
|
|
25
27
|
if (isCancel(answer)) return answer;
|
|
26
28
|
const selected = answer;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ask-ai-features.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"ask-ai-features.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/prompts/ask-ai-features.ts"],"sourcesContent":["import { isCancel, log, multiselect } from \"@clack/prompts\";\nimport { getAiPackageOptions, getAiProviderOptions } from \"../features/features-map\";\nimport { fitOptionsToWidth, multiselectMaxItems } from \"./fit-to-terminal\";\n\nconst NO_AI = \"none\";\n\nconst aiOptions = [\n ...getAiProviderOptions(),\n ...getAiPackageOptions(),\n {\n value: NO_AI,\n label: \"None\",\n hint: \"Do not add AI providers or capability packages\",\n },\n];\n\n/** Ask for optional AI packages without leaking the UI-only None sentinel. */\nexport async function askAiFeatures(initialValues: string[] = []): Promise<string[] | symbol> {\n let selectedValues = initialValues.filter((value) => value !== NO_AI);\n\n while (true) {\n const answer = await multiselect({\n message:\n \"Add AI packages? Providers + capabilities — the core AI package is included automatically\",\n options: fitOptionsToWidth(aiOptions),\n initialValues: selectedValues,\n required: false,\n maxItems: multiselectMaxItems(aiOptions.length),\n });\n\n if (isCancel(answer)) return answer;\n\n const selected = answer as string[];\n\n if (!selected.includes(NO_AI)) return selected;\n\n if (selected.length === 1) return [];\n\n log.warn(\"Choose None by itself, or select one or more AI packages.\");\n\n selectedValues = selected.filter((value) => value !== NO_AI);\n }\n}\n"],"mappings":";;;;;AAIA,MAAM,QAAQ;AAEd,MAAM,YAAY;CAChB,GAAG,qBAAqB;CACxB,GAAG,oBAAoB;CACvB;EACE,OAAO;EACP,OAAO;EACP,MAAM;CACR;AACF;;AAGA,eAAsB,cAAc,gBAA0B,CAAC,GAA+B;CAC5F,IAAI,iBAAiB,cAAc,QAAQ,UAAU,UAAU,KAAK;CAEpE,OAAO,MAAM;EACX,MAAM,SAAS,MAAM,YAAY;GAC/B,SACE;GACF,SAAS,kBAAkB,SAAS;GACpC,eAAe;GACf,UAAU;GACV,UAAU,oBAAoB,UAAU,MAAM;EAChD,CAAC;EAED,IAAI,SAAS,MAAM,GAAG,OAAO;EAE7B,MAAM,WAAW;EAEjB,IAAI,CAAC,SAAS,SAAS,KAAK,GAAG,OAAO;EAEtC,IAAI,SAAS,WAAW,GAAG,OAAO,CAAC;EAEnC,IAAI,KAAK,2DAA2D;EAEpE,iBAAiB,SAAS,QAAQ,UAAU,UAAU,KAAK;CAC7D;AACF"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { getFeatureGroups } from "../features/features-map.mjs";
|
|
2
|
+
import { fitOptionsToWidth, multiselectMaxItems } from "./fit-to-terminal.mjs";
|
|
3
|
+
import { isCancel, multiselect, select } from "@clack/prompts";
|
|
4
|
+
|
|
5
|
+
//#region ../../packages/create-warlock/src/prompts/ask-features.ts
|
|
6
|
+
const CONTINUE = "@continue";
|
|
7
|
+
const SELECT_ALL = "@all";
|
|
8
|
+
/** Edit one short feature group at a time, preserving terminal paging. */
|
|
9
|
+
async function askFeatures(initialValues = []) {
|
|
10
|
+
const groups = getFeatureGroups();
|
|
11
|
+
const selected = new Set(initialValues);
|
|
12
|
+
let lastGroup;
|
|
13
|
+
while (true) {
|
|
14
|
+
const options = [...groups.map((group) => ({
|
|
15
|
+
value: group.label,
|
|
16
|
+
label: `${group.label} (${group.options.filter((option) => selected.has(option.value)).length}/${group.options.length})`,
|
|
17
|
+
hint: group.options.map((option) => option.label).join(", ")
|
|
18
|
+
})), {
|
|
19
|
+
value: CONTINUE,
|
|
20
|
+
label: `Continue (${selected.size} selected)`
|
|
21
|
+
}];
|
|
22
|
+
const groupName = await select({
|
|
23
|
+
message: "Optional features by group",
|
|
24
|
+
initialValue: lastGroup,
|
|
25
|
+
options: fitOptionsToWidth(options),
|
|
26
|
+
maxItems: multiselectMaxItems(options.length)
|
|
27
|
+
});
|
|
28
|
+
if (isCancel(groupName)) return groupName;
|
|
29
|
+
if (groupName === CONTINUE) return [...selected];
|
|
30
|
+
lastGroup = groupName;
|
|
31
|
+
const group = groups.find((candidate) => candidate.label === groupName);
|
|
32
|
+
const previouslySelected = new Set(group.options.filter((option) => selected.has(option.value)).map((option) => option.value));
|
|
33
|
+
const groupOptions = [{
|
|
34
|
+
value: SELECT_ALL,
|
|
35
|
+
label: "Select all",
|
|
36
|
+
hint: "Include every feature in this group"
|
|
37
|
+
}, ...group.options];
|
|
38
|
+
const answer = await multiselect({
|
|
39
|
+
message: group.label,
|
|
40
|
+
options: fitOptionsToWidth(groupOptions),
|
|
41
|
+
initialValues: group.options.filter((option) => selected.has(option.value)).map((option) => option.value),
|
|
42
|
+
required: false,
|
|
43
|
+
maxItems: multiselectMaxItems(groupOptions.length)
|
|
44
|
+
});
|
|
45
|
+
if (isCancel(answer)) return answer;
|
|
46
|
+
for (const option of group.options) if (answer.includes(option.value) || answer.includes(SELECT_ALL) && !previouslySelected.has(option.value)) selected.add(option.value);
|
|
47
|
+
else selected.delete(option.value);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
//#endregion
|
|
52
|
+
export { askFeatures };
|
|
53
|
+
//# sourceMappingURL=ask-features.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ask-features.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/prompts/ask-features.ts"],"sourcesContent":["import { isCancel, multiselect, select } from \"@clack/prompts\";\nimport { getFeatureGroups } from \"../features/features-map\";\nimport { fitOptionsToWidth, multiselectMaxItems } from \"./fit-to-terminal\";\n\nconst CONTINUE = \"@continue\";\nconst SELECT_ALL = \"@all\";\n\n/** Edit one short feature group at a time, preserving terminal paging. */\nexport async function askFeatures(initialValues: string[] = []): Promise<string[] | symbol> {\n const groups = getFeatureGroups();\n const selected = new Set(initialValues);\n let lastGroup: string | undefined;\n\n while (true) {\n const options = [\n ...groups.map((group) => ({\n value: group.label,\n label: `${group.label} (${group.options.filter((option) => selected.has(option.value)).length}/${group.options.length})`,\n hint: group.options.map((option) => option.label).join(\", \"),\n })),\n { value: CONTINUE, label: `Continue (${selected.size} selected)` },\n ];\n const groupName = await select({\n message: \"Optional features by group\",\n initialValue: lastGroup,\n options: fitOptionsToWidth(options),\n maxItems: multiselectMaxItems(options.length),\n });\n\n if (isCancel(groupName)) return groupName;\n if (groupName === CONTINUE) return [...selected];\n\n lastGroup = groupName;\n const group = groups.find((candidate) => candidate.label === groupName)!;\n const previouslySelected = new Set(\n group.options.filter((option) => selected.has(option.value)).map((option) => option.value),\n );\n const groupOptions = [\n {\n value: SELECT_ALL,\n label: \"Select all\",\n hint: \"Include every feature in this group\",\n },\n ...group.options,\n ];\n const answer = await multiselect({\n message: group.label,\n options: fitOptionsToWidth(groupOptions),\n initialValues: group.options\n .filter((option) => selected.has(option.value))\n .map((option) => option.value),\n required: false,\n maxItems: multiselectMaxItems(groupOptions.length),\n });\n if (isCancel(answer)) return answer;\n\n for (const option of group.options) {\n if (\n answer.includes(option.value) ||\n (answer.includes(SELECT_ALL) && !previouslySelected.has(option.value))\n )\n selected.add(option.value);\n else selected.delete(option.value);\n }\n }\n}\n"],"mappings":";;;;;AAIA,MAAM,WAAW;AACjB,MAAM,aAAa;;AAGnB,eAAsB,YAAY,gBAA0B,CAAC,GAA+B;CAC1F,MAAM,SAAS,iBAAiB;CAChC,MAAM,WAAW,IAAI,IAAI,aAAa;CACtC,IAAI;CAEJ,OAAO,MAAM;EACX,MAAM,UAAU,CACd,GAAG,OAAO,KAAK,WAAW;GACxB,OAAO,MAAM;GACb,OAAO,GAAG,MAAM,MAAM,IAAI,MAAM,QAAQ,QAAQ,WAAW,SAAS,IAAI,OAAO,KAAK,CAAC,CAAC,CAAC,OAAO,GAAG,MAAM,QAAQ,OAAO;GACtH,MAAM,MAAM,QAAQ,KAAK,WAAW,OAAO,KAAK,CAAC,CAAC,KAAK,IAAI;EAC7D,EAAE,GACF;GAAE,OAAO;GAAU,OAAO,aAAa,SAAS,KAAK;EAAY,CACnE;EACA,MAAM,YAAY,MAAM,OAAO;GAC7B,SAAS;GACT,cAAc;GACd,SAAS,kBAAkB,OAAO;GAClC,UAAU,oBAAoB,QAAQ,MAAM;EAC9C,CAAC;EAED,IAAI,SAAS,SAAS,GAAG,OAAO;EAChC,IAAI,cAAc,UAAU,OAAO,CAAC,GAAG,QAAQ;EAE/C,YAAY;EACZ,MAAM,QAAQ,OAAO,MAAM,cAAc,UAAU,UAAU,SAAS;EACtE,MAAM,qBAAqB,IAAI,IAC7B,MAAM,QAAQ,QAAQ,WAAW,SAAS,IAAI,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,WAAW,OAAO,KAAK,CAC3F;EACA,MAAM,eAAe,CACnB;GACE,OAAO;GACP,OAAO;GACP,MAAM;EACR,GACA,GAAG,MAAM,OACX;EACA,MAAM,SAAS,MAAM,YAAY;GAC/B,SAAS,MAAM;GACf,SAAS,kBAAkB,YAAY;GACvC,eAAe,MAAM,QAClB,QAAQ,WAAW,SAAS,IAAI,OAAO,KAAK,CAAC,CAAC,CAC9C,KAAK,WAAW,OAAO,KAAK;GAC/B,UAAU;GACV,UAAU,oBAAoB,aAAa,MAAM;EACnD,CAAC;EACD,IAAI,SAAS,MAAM,GAAG,OAAO;EAE7B,KAAK,MAAM,UAAU,MAAM,SACzB,IACE,OAAO,SAAS,OAAO,KAAK,KAC3B,OAAO,SAAS,UAAU,KAAK,CAAC,mBAAmB,IAAI,OAAO,KAAK,GAEpE,SAAS,IAAI,OAAO,KAAK;OACtB,SAAS,OAAO,OAAO,KAAK;CAErC;AACF"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { select } from "@clack/prompts";
|
|
2
|
+
|
|
3
|
+
//#region ../../packages/create-warlock/src/prompts/ask-lint-toolchain.ts
|
|
4
|
+
/** The full wizard offers the same toolchain choice as --lint. */
|
|
5
|
+
function askLintToolchain(initialValue = "eslint") {
|
|
6
|
+
return select({
|
|
7
|
+
message: "Choose lint and formatting tools",
|
|
8
|
+
options: [{
|
|
9
|
+
value: "eslint",
|
|
10
|
+
label: "ESLint + Prettier"
|
|
11
|
+
}, {
|
|
12
|
+
value: "oxc",
|
|
13
|
+
label: "Oxlint + Oxfmt"
|
|
14
|
+
}],
|
|
15
|
+
initialValue
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
//#endregion
|
|
20
|
+
export { askLintToolchain };
|
|
21
|
+
//# sourceMappingURL=ask-lint-toolchain.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ask-lint-toolchain.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/prompts/ask-lint-toolchain.ts"],"sourcesContent":["import { select } from \"@clack/prompts\";\nimport type { LintToolchain } from \"../commands/create-new-app/types\";\n\n/** The full wizard offers the same toolchain choice as --lint. */\nexport function askLintToolchain(initialValue: LintToolchain = \"eslint\") {\n return select<LintToolchain>({\n message: \"Choose lint and formatting tools\",\n options: [\n { value: \"eslint\", label: \"ESLint + Prettier\" },\n { value: \"oxc\", label: \"Oxlint + Oxfmt\" },\n ],\n initialValue,\n });\n}\n"],"mappings":";;;;AAIA,SAAgB,iBAAiB,eAA8B,UAAU;CACvE,OAAO,OAAsB;EAC3B,SAAS;EACT,SAAS,CACP;GAAE,OAAO;GAAU,OAAO;EAAoB,GAC9C;GAAE,OAAO;GAAO,OAAO;EAAiB,CAC1C;EACA;CACF,CAAC;AACH"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cancel, isCancel, text } from "@clack/prompts";
|
|
2
2
|
|
|
3
|
-
//#region
|
|
3
|
+
//#region ../../packages/create-warlock/src/prompts/ask-project-name.ts
|
|
4
4
|
/**
|
|
5
5
|
* The project name is the one answer with no sane default, so every
|
|
6
6
|
* interactive path has to ask it. It lives here so the default path and the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ask-project-name.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"ask-project-name.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/prompts/ask-project-name.ts"],"sourcesContent":["import { cancel, isCancel, text } from \"@clack/prompts\";\n\n/**\n * The project name is the one answer with no sane default, so every\n * interactive path has to ask it. It lives here so the default path and the\n * full wizard ask it with the SAME wording and the same cancel handling —\n * and so a name already collected by one path can simply be handed to the\n * other instead of being asked a second time.\n */\nexport async function askProjectName(): Promise<string> {\n const answer = await text({\n message: \"What shall we call your project?\",\n placeholder: \"my-warlock-app\",\n });\n\n if (isCancel(answer) || !answer.trim()) {\n cancel(\"A project name is required to continue\");\n process.exit(0);\n }\n\n return answer;\n}\n"],"mappings":";;;;;;;;;;AASA,eAAsB,iBAAkC;CACtD,MAAM,SAAS,MAAM,KAAK;EACxB,SAAS;EACT,aAAa;CACf,CAAC;CAED,IAAI,SAAS,MAAM,KAAK,CAAC,OAAO,KAAK,GAAG;EACtC,OAAO,wCAAwC;EAC/C,QAAQ,KAAK,CAAC;CAChB;CAEA,OAAO;AACT"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cancel, isCancel, select } from "@clack/prompts";
|
|
2
2
|
|
|
3
|
-
//#region
|
|
3
|
+
//#region ../../packages/create-warlock/src/prompts/ask-stack.ts
|
|
4
4
|
/**
|
|
5
5
|
* The structural question in the default (non-`--interactive`) interactive
|
|
6
6
|
* path: does the generated app need server-rendered web pages, or is it an
|