@wp-typia/project-tools 0.24.12 → 0.24.13

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.
@@ -1,6 +1,7 @@
1
1
  import { promises as fsp } from "node:fs";
2
2
  import path from "node:path";
3
- import { assertValidHookAnchor, assertValidHookedBlockPosition, getMutableBlockHooks, normalizeBlockSlug, readWorkspaceBlockJson, resolveWorkspaceBlock, rollbackWorkspaceMutation, snapshotWorkspaceFiles, } from "./cli-add-shared.js";
3
+ import { executeWorkspaceMutationPlan } from "./cli-add-workspace-mutation.js";
4
+ import { assertValidHookAnchor, assertValidHookedBlockPosition, getMutableBlockHooks, normalizeBlockSlug, readWorkspaceBlockJson, resolveWorkspaceBlock, } from "./cli-add-shared.js";
4
5
  import { readWorkspaceInventoryAsync } from "../workspace/workspace-inventory.js";
5
6
  import { resolveWorkspaceProject } from "../workspace/workspace-project.js";
6
7
  /**
@@ -35,23 +36,17 @@ export async function runAddHookedBlockCommand({ anchorBlockName, blockName, cwd
35
36
  if (Object.prototype.hasOwnProperty.call(blockHooks, resolvedAnchorBlockName)) {
36
37
  throw new Error(`${blockJsonRelativePath} already defines a blockHooks entry for "${resolvedAnchorBlockName}".`);
37
38
  }
38
- const mutationSnapshot = {
39
- fileSources: await snapshotWorkspaceFiles([blockJsonPath]),
40
- snapshotDirs: [],
41
- targetPaths: [],
42
- };
43
- try {
44
- blockHooks[resolvedAnchorBlockName] = resolvedPosition;
45
- await fsp.writeFile(blockJsonPath, JSON.stringify(blockJson, null, "\t"), "utf8");
46
- return {
47
- anchorBlockName: resolvedAnchorBlockName,
48
- blockSlug,
49
- position: resolvedPosition,
50
- projectDir: workspace.projectDir,
51
- };
52
- }
53
- catch (error) {
54
- await rollbackWorkspaceMutation(mutationSnapshot);
55
- throw error;
56
- }
39
+ return executeWorkspaceMutationPlan({
40
+ filePaths: [blockJsonPath],
41
+ run: async () => {
42
+ blockHooks[resolvedAnchorBlockName] = resolvedPosition;
43
+ await fsp.writeFile(blockJsonPath, JSON.stringify(blockJson, null, "\t"), "utf8");
44
+ return {
45
+ anchorBlockName: resolvedAnchorBlockName,
46
+ blockSlug,
47
+ position: resolvedPosition,
48
+ projectDir: workspace.projectDir,
49
+ };
50
+ },
51
+ });
57
52
  }
@@ -1,10 +1,19 @@
1
- export interface WorkspaceMutationPlan<TResult> {
1
+ import { type WorkspaceMutationSnapshot } from "./cli-add-shared.js";
2
+ /**
3
+ * Paths captured before a workspace mutation so its filesystem changes can be rolled back.
4
+ */
5
+ export interface WorkspaceMutationSnapshotPlan {
2
6
  /** Files to capture before the mutation starts. Missing files are restored as absent. */
3
7
  filePaths: string[];
4
8
  /** Snapshot directories created by the mutation, usually migration fixtures. */
5
9
  snapshotDirs?: string[];
6
10
  /** Created files or directories to remove if the mutation fails. */
7
11
  targetPaths?: string[];
12
+ }
13
+ /**
14
+ * A workspace mutation snapshot plan paired with the operation to execute.
15
+ */
16
+ export interface WorkspaceMutationPlan<TResult> extends WorkspaceMutationSnapshotPlan {
8
17
  /** Mutating work to execute after the snapshot is captured. */
9
18
  run: () => Promise<TResult>;
10
19
  }
@@ -16,10 +25,14 @@ export declare class WorkspaceMutationRollbackError extends Error {
16
25
  readonly rollbackError: unknown;
17
26
  constructor(mutationError: unknown, rollbackError: unknown);
18
27
  }
28
+ /**
29
+ * Capture the files and paths needed to roll back a workspace add mutation.
30
+ */
31
+ export declare function createWorkspaceMutationSnapshot({ filePaths, snapshotDirs, targetPaths, }: WorkspaceMutationSnapshotPlan): Promise<WorkspaceMutationSnapshot>;
19
32
  /**
20
33
  * Execute a workspace add mutation with rollback on any failure.
21
34
  */
22
- export declare function executeWorkspaceMutationPlan<TResult>({ filePaths, run, snapshotDirs, targetPaths, }: WorkspaceMutationPlan<TResult>): Promise<TResult>;
35
+ export declare function executeWorkspaceMutationPlan<TResult>(plan: WorkspaceMutationPlan<TResult>): Promise<TResult>;
23
36
  /**
24
37
  * Insert a PHP snippet before the workspace textdomain hook or closing tag.
25
38
  */
@@ -15,16 +15,22 @@ export class WorkspaceMutationRollbackError extends Error {
15
15
  }
16
16
  }
17
17
  /**
18
- * Execute a workspace add mutation with rollback on any failure.
18
+ * Capture the files and paths needed to roll back a workspace add mutation.
19
19
  */
20
- export async function executeWorkspaceMutationPlan({ filePaths, run, snapshotDirs = [], targetPaths = [], }) {
21
- const mutationSnapshot = {
20
+ export async function createWorkspaceMutationSnapshot({ filePaths, snapshotDirs = [], targetPaths = [], }) {
21
+ return {
22
22
  fileSources: await snapshotWorkspaceFiles(filePaths),
23
23
  snapshotDirs: [...snapshotDirs],
24
24
  targetPaths: [...targetPaths],
25
25
  };
26
+ }
27
+ /**
28
+ * Execute a workspace add mutation with rollback on any failure.
29
+ */
30
+ export async function executeWorkspaceMutationPlan(plan) {
31
+ const mutationSnapshot = await createWorkspaceMutationSnapshot(plan);
26
32
  try {
27
- return await run();
33
+ return await plan.run();
28
34
  }
29
35
  catch (error) {
30
36
  try {
@@ -2,6 +2,7 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import ts from 'typescript';
4
4
  import { containsCompletion, countOuterParserContinues, hasCanonicalRuntimeImports, hasCanonicalUnknownFlagThrow, hasEarlierAbruptCompletion, hasOnlyCanonicalParserEffects, isAllowedSyncHelperTopLevelStatement, isCanonicalSyncMainCatchHandler, isSafeErrorConstruction, unwrapStaticExpression, } from './cli-doctor-standalone-control-flow.js';
5
+ import { hasTypeScriptSyntaxErrors, isSafeProjectRelativePath, } from './cli-doctor-standalone-shared.js';
5
6
  const STANDALONE_SYNC_REST_SCRIPT = path.join('scripts', 'sync-rest-contracts.ts');
6
7
  const STANDALONE_REST_OPEN_API_FILE = path.join('src', 'api.openapi.json');
7
8
  const STANDALONE_REST_CLIENT_FILE = path.join('src', 'api-client.ts');
@@ -99,26 +100,6 @@ const REST_SYNC_RUNTIME_IMPORTS = new Map([
99
100
  ]);
100
101
  const WORDPRESS_NAMED_CAPTURE_PREFIX = '(?P<';
101
102
  const WORDPRESS_CAPTURE_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/u;
102
- function hasTypeScriptSyntaxErrors(source, fileName) {
103
- const result = ts.transpileModule(source, {
104
- compilerOptions: { target: ts.ScriptTarget.Latest },
105
- fileName,
106
- reportDiagnostics: true,
107
- });
108
- return (result.diagnostics ?? []).some((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error);
109
- }
110
- function isProjectLocalRelativePath(relativePath) {
111
- return (relativePath.length > 0 &&
112
- !relativePath.startsWith(`..${path.sep}`) &&
113
- relativePath !== '..' &&
114
- !path.isAbsolute(relativePath));
115
- }
116
- function isSafeProjectRelativePath(projectDir, filePath) {
117
- if (path.isAbsolute(filePath)) {
118
- return false;
119
- }
120
- return isProjectLocalRelativePath(path.relative(projectDir, path.resolve(projectDir, filePath)));
121
- }
122
103
  function getStandaloneRestContractArtifactPaths(baseName) {
123
104
  return {
124
105
  jsonSchemaFile: path.join('src', 'api-schemas', `${baseName}.schema.json`),
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Detect TypeScript errors reported during isolated transpilation.
3
+ * This includes syntax errors and does not perform a full type check.
4
+ *
5
+ * @param source TypeScript source text to parse.
6
+ * @param fileName File name to associate with parser diagnostics.
7
+ * @returns Whether the source produces at least one error diagnostic.
8
+ */
9
+ export declare function hasTypeScriptSyntaxErrors(source: string, fileName: string): boolean;
10
+ /**
11
+ * Check whether a relative path stays within its project boundary.
12
+ *
13
+ * @param relativePath Relative path to validate.
14
+ * @returns Whether the path is non-empty, rootless, and does not escape upward.
15
+ */
16
+ export declare function isProjectLocalRelativePath(relativePath: string): boolean;
17
+ /**
18
+ * Check whether a file path resolves inside a project directory.
19
+ *
20
+ * @param projectDir Absolute project directory used as the resolution base.
21
+ * @param filePath Project-relative file path to validate.
22
+ * @returns Whether the resolved file path remains inside the project directory.
23
+ */
24
+ export declare function isSafeProjectRelativePath(projectDir: string, filePath: string): boolean;
@@ -0,0 +1,50 @@
1
+ import path from 'node:path';
2
+ import ts from 'typescript';
3
+ function hasFilesystemRoot(filePath) {
4
+ return (path.isAbsolute(filePath) || path.win32.parse(filePath).root.length > 0);
5
+ }
6
+ /**
7
+ * Detect TypeScript errors reported during isolated transpilation.
8
+ * This includes syntax errors and does not perform a full type check.
9
+ *
10
+ * @param source TypeScript source text to parse.
11
+ * @param fileName File name to associate with parser diagnostics.
12
+ * @returns Whether the source produces at least one error diagnostic.
13
+ */
14
+ export function hasTypeScriptSyntaxErrors(source, fileName) {
15
+ const result = ts.transpileModule(source, {
16
+ compilerOptions: { target: ts.ScriptTarget.Latest },
17
+ fileName,
18
+ reportDiagnostics: true,
19
+ });
20
+ return (result.diagnostics ?? []).some((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error);
21
+ }
22
+ /**
23
+ * Check whether a relative path stays within its project boundary.
24
+ *
25
+ * @param relativePath Relative path to validate.
26
+ * @returns Whether the path is non-empty, rootless, and does not escape upward.
27
+ */
28
+ export function isProjectLocalRelativePath(relativePath) {
29
+ if (relativePath.length === 0 || hasFilesystemRoot(relativePath)) {
30
+ return false;
31
+ }
32
+ return [path.posix, path.win32].every((pathApi) => {
33
+ const normalizedPath = pathApi.normalize(relativePath);
34
+ return (normalizedPath !== '..' &&
35
+ !normalizedPath.startsWith(`..${pathApi.sep}`));
36
+ });
37
+ }
38
+ /**
39
+ * Check whether a file path resolves inside a project directory.
40
+ *
41
+ * @param projectDir Absolute project directory used as the resolution base.
42
+ * @param filePath Project-relative file path to validate.
43
+ * @returns Whether the resolved file path remains inside the project directory.
44
+ */
45
+ export function isSafeProjectRelativePath(projectDir, filePath) {
46
+ if (!path.isAbsolute(projectDir) || hasFilesystemRoot(filePath)) {
47
+ return false;
48
+ }
49
+ return isProjectLocalRelativePath(path.relative(projectDir, path.resolve(projectDir, filePath)));
50
+ }
@@ -9,6 +9,7 @@ import { findPhpFunctionCallEnd, findPhpFunctionRange, getPhpCodeBraceDepth, has
9
9
  import { readJsonFileSync } from '../shared/json-utils.js';
10
10
  import { createDoctorCheck, createDoctorScopeCheck, } from './cli-doctor-workspace-shared.js';
11
11
  import { containsCompletion, countOuterParserContinues, getSafeErrorConstruction, hasCanonicalRuntimeImports, hasCanonicalUnknownFlagThrow, hasEarlierAbruptCompletion, hasOnlyCanonicalParserEffects, isAllowedSyncHelperTopLevelStatement, isCanonicalSyncMainCatchHandler, unwrapStaticExpression, } from './cli-doctor-standalone-control-flow.js';
12
+ import { hasTypeScriptSyntaxErrors, isProjectLocalRelativePath, isSafeProjectRelativePath, } from './cli-doctor-standalone-shared.js';
12
13
  import { checkStandaloneRestArtifacts, parseStandaloneRestConfig, STANDALONE_PERSISTENCE_BLOCK_SCHEMA_ARTIFACTS, standaloneProjectRequiresRest, } from './cli-doctor-standalone-rest.js';
13
14
  const STANDALONE_SYNC_SCRIPT = path.join('scripts', 'sync-types-to-block-json.ts');
14
15
  const STANDALONE_SYNC_PROJECT_SCRIPT = path.join('scripts', 'sync-project.ts');
@@ -496,26 +497,6 @@ function findSyncOptionsObject(sourceFile, syncImportBindings) {
496
497
  ? call.options
497
498
  : null;
498
499
  }
499
- function hasTypeScriptSyntaxErrors(source, fileName) {
500
- const result = ts.transpileModule(source, {
501
- compilerOptions: { target: ts.ScriptTarget.Latest },
502
- fileName,
503
- reportDiagnostics: true,
504
- });
505
- return (result.diagnostics ?? []).some((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error);
506
- }
507
- function isProjectLocalRelativePath(relativePath) {
508
- return (relativePath.length > 0 &&
509
- !relativePath.startsWith(`..${path.sep}`) &&
510
- relativePath !== '..' &&
511
- !path.isAbsolute(relativePath));
512
- }
513
- function isSafeProjectRelativePath(projectDir, filePath) {
514
- if (path.isAbsolute(filePath)) {
515
- return false;
516
- }
517
- return isProjectLocalRelativePath(path.relative(projectDir, path.resolve(projectDir, filePath)));
518
- }
519
500
  function parseStandaloneSyncConfig(project) {
520
501
  const syncScriptPath = path.join(project.projectDir, STANDALONE_SYNC_SCRIPT);
521
502
  let source;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wp-typia/project-tools",
3
- "version": "0.24.12",
3
+ "version": "0.24.13",
4
4
  "description": "Project orchestration and programmatic tooling for wp-typia",
5
5
  "packageManager": "bun@1.3.11",
6
6
  "type": "module",
@@ -126,8 +126,8 @@
126
126
  "postpack": "node ./scripts/publish-manifest.mjs restore",
127
127
  "test": "bun run build && bun test tests/*.test.ts",
128
128
  "test:quick": "bun run build && bun test tests/workspace-add.test.ts tests/cli-add-workspace-ability.test.ts tests/cli-add-workspace-ai.test.ts tests/workspace-doctor.test.ts tests/scaffold-compound.test.ts",
129
- "test:scaffold-core": "bun run build && bun test tests/block-generator-service.test.ts tests/built-in-block-artifacts.test.ts tests/scaffold-basic.test.ts tests/scaffold-persistence.test.ts tests/template-source.test.ts tests/init-command.test.ts tests/package-versions.test.ts tests/cli-entry.test.ts tests/cli-prompt.test.ts tests/import-policy.test.ts tests/wordpress-ai-spec.test.ts tests/typia-llm.test.ts",
130
- "test:workspace": "bun run build && bun test tests/workspace-add.test.ts tests/cli-add-workspace-ability.test.ts tests/cli-add-workspace-ai.test.ts tests/workspace-doctor.test.ts",
129
+ "test:scaffold-core": "bun run build && bun test tests/block-generator-service.test.ts tests/built-in-block-artifacts.test.ts tests/cli-doctor-boundaries.test.ts tests/cli-doctor-standalone-shared.test.ts tests/scaffold-basic.test.ts tests/scaffold-persistence.test.ts tests/template-source.test.ts tests/init-command.test.ts tests/package-versions.test.ts tests/cli-entry.test.ts tests/cli-prompt.test.ts tests/import-policy.test.ts tests/wordpress-ai-spec.test.ts tests/typia-llm.test.ts",
130
+ "test:workspace": "bun run build && bun test tests/cli-add-workspace-mutation.test.ts tests/cli-add-workspace-boundaries.test.ts tests/workspace-add.test.ts tests/cli-add-workspace-ability.test.ts tests/cli-add-workspace-ai.test.ts tests/workspace-doctor.test.ts",
131
131
  "test:compound": "bun run build && bun test tests/scaffold-compound.test.ts",
132
132
  "test:migration-planning": "bun run build && bun test tests/migration-init.test.ts tests/migration-config.test.ts tests/migration-plan-wizard.test.ts",
133
133
  "test:migration-execution": "bun run build && bun test tests/migration-scaffold-diff.test.ts tests/migration-doctor.test.ts tests/migration-fixtures-fuzz.test.ts",